refactor code (wip) + fix bugs (wip) + tweak ui + save an existing page in ajax (HTML5 + FormData) + create a new page in ajax (wip)

This commit is contained in:
did 2011-11-25 02:04:42 +01:00
parent a50df5ad8e
commit 47625dfa17
30 changed files with 877 additions and 154 deletions

View File

@ -10,7 +10,6 @@
//= require underscore
//= require handlebars
//= require backbone
//= require backbone_rails_sync
//= require uploadify
//= require codemirror
//= require tinymce-jquery

View File

@ -1,8 +1,14 @@
class Locomotive.Models.EditableElement extends Backbone.Model
toJSONForSave: ->
_.tap {}, (hash) =>
for key, value of @.toJSON()
hash[key] = value if _.include(['id', 'source', 'content', 'remove_source'], key)
class Locomotive.Models.EditableElementsCollection extends Backbone.Collection
model: Locomotive.Models.EditableElement
blocks: ->
names = _.uniq(@map (editable, index) -> editable.get('block_name'))
_.tap [], (list) =>
@ -11,3 +17,6 @@ class Locomotive.Models.EditableElementsCollection extends Backbone.Collection
by_block: (name) ->
@filter (editable) -> editable.get('block_name') == name
toJSONForSave: ->
@map (model) => model.toJSONForSave()

View File

@ -1,7 +1,37 @@
class Locomotive.Models.Page extends Backbone.Model
paramRoot: 'page'
urlRoot: "#{Locomotive.mount_on}/pages"
initialize: ->
@_normalize()
_normalize: ->
@set
editable_elements: new Locomotive.Models.EditableElementsCollection(@get('editable_elements'))
# unless _.isArray @get('editable_elements')
# console.log('already a collection')
# return
#
# previous_collection = @previous('editable_elements')
#
# console.log(previous_collection)
#
# if _.isArray collection
# collection = new Locomotive.Models.EditableElementsCollection(@get('editable_elements'))
# else
# collection.fetch(@get('editable_elements'))
#
# @set(editable_elements: collection)
# @set
# editable_elements: new Locomotive.Models.EditableElementsCollection(@get('editable_elements'))
toJSON: ->
_.tap super, (hash) =>
hash.editable_elements = @get('editable_elements').toJSONForSave() if @get('editable_elements')
class Locomotive.Models.PagesCollection extends Backbone.Collection

View File

@ -9,6 +9,8 @@ class Locomotive.Views.EditableElements.EditAllView extends Backbone.View
_editable_elements_views: []
render: ->
window.bar = @
if @collection.isEmpty()
$(@el).hide()
else
@ -22,6 +24,16 @@ class Locomotive.Views.EditableElements.EditAllView extends Backbone.View
return @
after_render: ->
_.each @_editable_elements_views, (view) => view.after_render()
refresh: ->
_.each @_editable_elements_views, (view) =>
foo = @collection.get(view.model.get('id'))
console.log(foo.cid)
view.model = @collection.get(view.model.get('id'))
view.refresh()
render_elements: ->
index = 0
@ -60,4 +72,10 @@ class Locomotive.Views.EditableElements.EditAllView extends Backbone.View
_hide_last_separator: ->
_.each @$('fieldset'), (fieldset) =>
$(fieldset).find('li.last').removeClass('last')
$(_.last($(fieldset).find('li.input:visible'))).addClass('last')
$(_.last($(fieldset).find('li.input:visible'))).addClass('last')
remove: ->
_.each @_editable_elements_views, (view) => view.remove()
@_editable_elements_views.length = 0
super

View File

@ -17,8 +17,22 @@ class Locomotive.Views.EditableElements.FileView extends Backbone.View
render: ->
$(@el).html(ich.editable_file_input(@model.toJSON()))
# only in HTML 5
@$('input[type=file]').bind 'change', (event) =>
input = $(event.target)[0]
if input.files?
@model.set(source: input.files[0])
return @
after_render: ->
# do nothing
refresh: ->
@$('input[type=file]').unbind 'change'
@states = { 'change': false, 'delete': false }
@render()
toggle_change: (event) ->
@_toggle event, 'change',
on_change: =>
@ -31,9 +45,11 @@ class Locomotive.Views.EditableElements.FileView extends Backbone.View
on_change: =>
@$('a:first').addClass('deleted') & @$('a.change').hide()
@$('input[type=hidden].remove-flag').val('1')
@model.set('remove_source': true)
on_cancel: =>
@$('a:first').removeClass('deleted') & @$('a.change').show()
@$('input[type=hidden].remove-flag').val('0')
@model.set('remove_source': false)
_toggle: (event, state, options) ->
event.stopPropagation() & event.preventDefault()
@ -52,20 +68,6 @@ class Locomotive.Views.EditableElements.FileView extends Backbone.View
@states[state] = !@states[state]
# toggle_change: (event) ->
# event.stopPropagation() & event.preventDefault()
#
# button = $(event.target)
# label = button.attr('data-cancel-label')
#
# unless @changing
# @$('a:first').hide() & @$('input[type=file]').show() & @$('a.delete').hide()
# else
# @$('a:first').show() & @$('input[type=file]').hide() & @$('a.delete').show()
#
# button.attr('data-alt-label', button.html())
#
# button.html(label)
#
# @changing = !@changing
remove: ->
@$('input[type=file]').unbind 'change'
super

View File

@ -1,14 +1,8 @@
#= require ./short_text_view
Locomotive.Views.EditableElements ||= {}
class Locomotive.Views.EditableElements.LongTextView extends Backbone.View
class Locomotive.Views.EditableElements.LongTextView extends Locomotive.Views.EditableElements.ShortTextView
tagName: 'li'
className: 'text input html'
render: ->
$(@el).html(ich.editable_text_input(@model.toJSON()))
@$('textarea').tinymce window.Locomotive.tinyMCE.defaultSettings
return @
tinymce_settings: ->
window.Locomotive.tinyMCE.defaultSettings

View File

@ -9,6 +9,28 @@ class Locomotive.Views.EditableElements.ShortTextView extends Backbone.View
render: ->
$(@el).html(ich.editable_text_input(@model.toJSON()))
@$('textarea').tinymce window.Locomotive.tinyMCE.minimalSettings
return @
return @
after_render: ->
settings = _.extend {}, @tinymce_settings(),
onchange_callback: (editor) =>
console.log('content changed !!!! (' + @model.cid + '), ' + editor.getBody().innerHTML)
console.log(@model)
@model.set(content: editor.getBody().innerHTML)
console.log('here ?')
window.a = @$('textarea')
window.b = settings
@$('textarea').tinymce(settings)
tinymce_settings: ->
window.Locomotive.tinyMCE.minimalSettings
refresh: ->
# do nothing
remove: ->
@$('textarea').tinymce().destroy()
super

View File

@ -16,16 +16,18 @@ class Locomotive.Views.Pages.FormView extends Locomotive.Views.Shared.FormView
initialize: ->
_.bindAll(@, 'insert_image')
@page = new Locomotive.Models.Page(@options.page)
@model = new Locomotive.Models.Page(@options.page)
window.page = @page
window.foo = @model
@filled_slug = @touched_url = false
@image_picker_view = new Locomotive.Views.ThemeAssets.ImagePickerView
collection: new Locomotive.Models.ThemeAssetsCollection()
on_select: @insert_image
@editable_elements_view = new Locomotive.Views.EditableElements.EditAllView(collection: @page.get('editable_elements'))
Backbone.ModelBinding.bind @
@editable_elements_view = new Locomotive.Views.EditableElements.EditAllView(collection: @model.get('editable_elements'))
render: ->
super()
@ -45,7 +47,6 @@ class Locomotive.Views.Pages.FormView extends Locomotive.Views.Shared.FormView
# editable elements
@render_editable_elements()
# @enable_editable_elements_nav()
return @
@ -60,19 +61,41 @@ class Locomotive.Views.Pages.FormView extends Locomotive.Views.Shared.FormView
@image_picker_view.close()
enable_liquid_editing: ->
@editor = CodeMirror.fromTextArea @$('#page_raw_template').get()[0],
input = @$('#page_raw_template')
@editor = CodeMirror.fromTextArea input.get()[0],
mode: 'liquid'
autoMatchParens: false
lineNumbers: false
passDelay: 50
tabMode: 'shift'
theme: 'default'
onChange: (editor) => @model.set(raw_template: editor.getValue())
after_inputs_fold: ->
@editor.refresh()
show_error: (attribute, message, html) ->
switch attribute
when 'raw_template'
@$("#page_raw_template_input .CodeMirror").after(html)
else super
render_editable_elements: ->
@$('.formtastic fieldset.inputs:first').before(@editable_elements_view.render().el)
@editable_elements_view.after_render()
# Remove the editable elements and rebuild them
reset_editable_elements: ->
console.log('reset_editable_elements')
@editable_elements_view.remove()
# @editable_elements_view = new Locomotive.Views.EditableElements.EditAllView(collection: @model.get('editable_elements'))
@editable_elements_view.collection = @model.get('editable_elements')
@render_editable_elements()
# Just re-connect the model and the views (+ rebuild the files)
refresh_editable_elements: ->
@editable_elements_view.collection = @model.get('editable_elements')
@editable_elements_view.refresh()
fill_default_slug: (event) ->
unless @filled_slug

View File

@ -3,4 +3,37 @@ Locomotive.Views.Pages ||= {}
class Locomotive.Views.Pages.EditView extends Locomotive.Views.Pages.FormView
save: (event) ->
console.log('saving')
event.stopPropagation() & event.preventDefault()
@clear_errors()
console.log('saving')
@model.save {},
success: (model, response, xhr) =>
console.log('success')
model._normalize()
$.growl('success', xhr.getResponseHeader('X-Message'))
if model.get('template_changed') == true
@reset_editable_elements()
else
@refresh_editable_elements()
error: (model, xhr) =>
console.log('error')
# window.model = model
window.model = model
window.xhr = xhr
# probably restore the previous attributes
errors = JSON.parse(xhr.responseText)
@show_errors errors
$.growl('error', xhr.getResponseHeader('X-Message'))

View File

@ -35,8 +35,7 @@ class Locomotive.Views.Pages.ListView extends Backbone.View
# on_sort: (data) ->
on_successful_sort: (data, status, xhr) ->
window.foo = xhr
$.growl('success', xhr.getResponseHeader('Flash'));
$.growl('success', xhr.getResponseHeader('X-Message'))
on_failed_sort: (data, status, xhr) ->
$.growl('error', xhr.getResponseHeader('Flash'));
$.growl('error', xhr.getResponseHeader('X-Message'))

View File

@ -1,3 +1,33 @@
Locomotive.Views.Pages ||= {}
class Locomotive.Views.Pages.NewView extends Locomotive.Views.Pages.FormView
save: (event) ->
event.stopPropagation() & event.preventDefault()
@clear_errors()
console.log('saving')
@model.save {},
success: (model, response, xhr) =>
console.log('success')
model._normalize()
$.growl('success', xhr.getResponseHeader('X-Message'))
if model.get('template_changed') == true
@reset_editable_elements()
else
@refresh_editable_elements()
error: (model, xhr) =>
console.log('error')
window.xhr = xhr
errors = JSON.parse(xhr.responseText)
@show_errors errors
$.growl('error', xhr.getResponseHeader('X-Message'))

View File

@ -30,7 +30,7 @@ class Locomotive.Views.Shared.FormView extends Backbone.View
newValue = prompt(title.attr('title'), title.html());
if newValue && newValue != ''
title.html(newValue)
target.val(newValue)
target.val(newValue).trigger('change')
make_inputs_foldable: ->
self = @
@ -49,6 +49,18 @@ class Locomotive.Views.Shared.FormView extends Backbone.View
after_inputs_fold: ->
# overide this method if necessary
clear_errors: ->
@$('div.inline-errors').remove()
show_errors: (errors) ->
for attribute, message of errors
html = $("<div class=\"inline-errors\"><p>#{message[0]}</p></div>")
@show_error attribute, message[0], html
show_error: (attribute, message, html) ->
input = @$("##{@model.paramRoot}_#{attribute}")
input.after(html) if input.size() > 0
_enable_checkbox: (name, options) ->
@$('li#page_' + name + '_input input[type=checkbox]').checkToggle
on_callback: =>

View File

@ -15,8 +15,10 @@ ul.content-assets {
margin: 0 10px 10px 0;
border: 1px solid #ccced7;
@include border-radius(8px);
@include box-shadow(rgba(0, 0, 0, 0.2) 0px 1px 0px 0px);
@include box-shadow(rgba(0, 0, 0, 0.4) 0px 1px 0px 0px);
background: #ebedf4;
&.last {

View File

@ -9,10 +9,11 @@
.nav {
display: block;
width: 100%;
width: 918px;
height: 31px;
border-bottom: 1px solid #ccced7;
border: 1px solid #ccced7;
border-bottom: 0px;
@include border-top-radius(8px);
@include background-image(linear-gradient(#ebedf4, #d7dbe7));
@ -20,6 +21,7 @@
a {
float: left;
display: block;
position: relative;
line-height: 31px;
padding: 0px 20px 0 20px;
@ -35,30 +37,38 @@
&.on {
@include background-image(linear-gradient(#d9dde9, #ebedf4));
height: 32px;
height: 31px;
z-index: 999;
color: #1e1f26;
font-weight: bold;
border-right: 1px solid #f8f9fb;
border-left: 1px solid #f8f9fb;
border-right: 1px solid #ccced7;
border-left: 1px solid #ccced7;
@include box-shadow(rgba(193, 197, 207, 0.4) -1px 0px 0px 0px inset, rgba(193, 197, 207, 0.4) -1px 0px 0px 0px);
@include box-shadow(#EBEDF4 0 1px 0 0, rgba(255, 255, 255, 0.4) 0 1px 0 0 inset, rgba(255, 255, 255, 0.4) -1px 0 0 0 inset, rgba(255, 255, 255, 0.4) 1px 0 0 0 inset);
}
&:first-child {
&.on {
@include border-top-left-radius(8px);
border-left: none;
@include box-shadow(rgba(193, 197, 207, 0.4) -1px 0px 0px 0px inset);
@include box-shadow(#EBEDF4 0 1px 0 0, rgba(255, 255, 255, 0.4) 0 1px 0 0 inset, rgba(255, 255, 255, 0.4) -1px 0 0 0 inset);
}
}
} // a
} // .nav
.wrapper {
position: relative;
top: -1px;
}
fieldset {
clear: both;
ol {
border-top: none;
li.file {
span.file {

View File

@ -10,15 +10,18 @@ form.formtastic {
margin-bottom: 20px;
@include border-bottom-radius(8px);
@include box-shadow(rgba(0, 0, 0, 0.2) 0px 1px 0px 0px);
@include box-shadow(rgba(0, 0, 0, 0.4) 0px 1px 0px 0px);
background: #ebedf4;
legend {
display: block;
width: 100%;
width: 918px;
padding: 5px 0px;
border: 1px solid #ccced7;
border-bottom: 0px;
@include border-top-radius(8px);
@include background-image(linear-gradient(#ebedf4, #d7dbe7));
@ -58,7 +61,9 @@ form.formtastic {
margin-bottom: 0px;
padding: 0px;
border-top: 1px solid #ccced7;
border: 1px solid #ccced7;
border-bottom: 0px;
@include box-shadow(rgba(255, 255, 255, 0.5) 0 1px 0 0 inset);
> li.input {
margin: 0;

View File

@ -3,7 +3,7 @@ module Locomotive
sections 'contents'
respond_to :json, :only => [:show, :update, :sort, :get_path]
respond_to :json, :only => [:show, :create, :update, :sort, :get_path]
def index
@pages = current_site.all_pages_in_once
@ -33,17 +33,7 @@ module Locomotive
def update
@page = current_site.pages.find(params[:id])
@page.update_attributes(params[:page])
puts @page.errors.inspect
respond_with @page, :location => edit_page_url(@page._id)
# do |format|
# format.json do
# render :json => {
# :notice => t('flash.locomotive.pages.update.notice'),
# :editable_elements => @page.template_changed ?
# render_to_string(:partial => 'locomotive/pages/editable_elements.html.haml') : ''
# }
# end
# end
end
def destroy

View File

@ -4,7 +4,7 @@ module Locomotive
delegate :title, :slug, :fullpath, :raw_template, :published, :template_changed, :cache_strategy, :to => :source
def editable_elements
self.source.enabled_editable_elements
self.source.enabled_editable_elements.collect(&:as_json)
end
def included_methods

View File

@ -49,42 +49,4 @@
%p.inline-hints {{hint}}
{{/if}}
= hidden_field_tag 'page[editable_elements_attributes][{{index}}][id]', '{{id}}', :id => 'page_editable_elements_attributes_{{index}}_id'
/ - grouped_editable_elements = @page.editable_elements_grouped_by_blocks
/
/ = semantic_fields_for(@page) do |f|
/ - unless grouped_editable_elements.empty?
/ #editable-elements
/ .nav
/ - grouped_editable_elements.keys.each_with_index do |name, index|
/ = link_to (name.try(:humanize) || t('locomotive.pages.form.default_block')).gsub('\'', ''), "#block-#{index}", :id => "block-nav-#{index}", :class => "#{'on' if index == 0}"
/ .clear
/
/ .wrapper
/ %ul{ :id => "blocks" }
/ - grouped_editable_elements.keys.each_with_index do |name, index|
/ - elements = grouped_editable_elements[name]
/ %li{ :id => "block-#{index}", :class => 'block', :style => "display: #{index == 0 ? 'block' : 'none' }" }
/ %fieldset.inputs
/ %ol
/ - elements.each do |el|
/ = f.fields_for 'editable_elements', el, :child_index => el._index do |g|
/ - case el
/ - when ::Locomotive::EditableLongText
/ = g.input :content, :label => el.slug.humanize, :hint => el.hint, :as => :text, :input_html => { :class => 'html' }
/ - when ::Locomotive::EditableShortText
/ = g.input :content, :label => el.slug.humanize, :hint => el.hint
/ - when ::Locomotive::EditableFile
/ = g.input :source, :label => el.slug.humanize, :hint => el.hint, :css => 'file'
/ / do
/ / = g.file_field :source
/ / - if el.source?
/ / %p.remove
/ / %strong
/ / = link_to File.basename(el.source.url), el.source.url
/ / %span
/ / &nbsp;/&nbsp;
/ / != t('locomotive.pages.form.delete_file')
/ / = g.check_box :remove_source
= hidden_field_tag 'page[editable_elements_attributes][{{index}}][id]', '{{id}}', :id => 'page_editable_elements_attributes_{{index}}_id'

View File

@ -279,7 +279,6 @@ es:
site: Información del Sitio
content_types: Modelos
assets: Contenido multi-media
asset_collections: Colecciones
snippets: Fragmentos
pages: Páginas
messages:

View File

@ -262,7 +262,6 @@ nl:
site: Website informatie
content_types: aangepaste content typen
assets: Thema bestanden
asset_collections: Bronbestand verzamelingen
snippets: Fragmenten
pages: Pagina's'
messages:

View File

@ -71,9 +71,6 @@ es:
highlighted_field_name: Campo destacado
group_by_field_name: Champ para agrupar
api_enabled: API activada
asset_collection:
name: Nombre
slug: Enlace
asset:
name: Nombre
source: Fichero

View File

@ -73,16 +73,6 @@ es:
alert: "La afiliaciónMember no se pudo crear."
already_created: "La cuenta ya estaba añadida al sitio web actual."
asset_collections:
create:
notice: "Colección creada con éxito."
alert: "La colección no se pudo crear."
update:
notice: "Colección actualizada con éxito."
alert: "La colección no se pudo actualizar."
destroy:
notice: "Colección eliminada con éxito."
assets:
create:
notice: "Contenido multi-media creado con éxito."

View File

@ -73,16 +73,6 @@ nl:
alert: "Lidmaatschap is niet gemaakt."
already_created: "Lidmaatschap is al gemaakt voor de website."
asset_collection:
create:
notice: "Verzameling is gemaakt."
alert: "Verzameling is niet gemaakt."
update:
notice: "Verzameling is gewijzigd."
alert: "Verzameling is niet gewijzigd."
destroy:
notice: "Verzameling is verwijderd."
assets:
create:
notice: "Bronbestand is gemaakt."

View File

@ -11,14 +11,17 @@ x editable_short_text => tinymce
- editable_file =>
x backbone / handlebar
- new formtastic inputs
- menu / submenu in full css3 (no images)
x menu / submenu in full css3 (no images)
- create/edit page in ajax
x edit
- create
- fix other sections
- content types
- edit my account
- create a new site
- edit my site
- theme assets
- fix css in firefox
BACKLOG:

View File

@ -1,18 +0,0 @@
require 'locomotive/liquid/drops/contents'
module Locomotive
module Liquid
module Drops
class AssetCollections < Contents
def before_method(meth)
Rails.logger.warn "\n[WARNING] asset_collections is deprecated and will be removed in the next commits. Please, use contents.<slug> instead.\n\n"
super(meth)
end
end
end
end
end

View File

@ -61,7 +61,6 @@ module Locomotive
assigns = {
'site' => current_site,
'page' => @page,
'asset_collections' => Locomotive::Liquid::Drops::AssetCollections.new, # deprecated, will be removed shortly
'contents' => Locomotive::Liquid::Drops::Contents.new,
'current_page' => self.params[:page],
'params' => self.params,

View File

@ -12,7 +12,8 @@ module Locomotive
if get?
display resource
elsif has_errors?
with_flash_message(:error) do |message|
Rails.logger.debug "--> ERRORS #{resource.errors.inspect}" # FIXME: debug purpose
with_flash_message(:alert) do |message|
display resource.errors, :status => :unprocessable_entity
end
elsif post?
@ -38,7 +39,8 @@ module Locomotive
set_flash_message!
message = controller.flash[type]
controller.headers[:flash] = message
controller.headers['X-Message'] = message
controller.headers['X-Message-Type'] = type
yield(message) if block_given?

View File

@ -0,0 +1,529 @@
// Backbone.ModelBinding v0.4.1
//
// Copyright (C)2011 Derick Bailey, Muted Solutions, LLC
// Distributed Under MIT Liscene
//
// WARNING: Modified by Didier Lafforgue for LocomotiveCMS
//
// Documentation and Full Licence Availabe at:
// http://github.com/derickbailey/backbone.modelbinding
// ----------------------------
// Backbone.ModelBinding
// ----------------------------
Backbone.ModelBinding = (function(Backbone, _, $){
modelBinding = {
version: "0.4.1",
bind: function(view, options){
view.modelBinder = new ModelBinder(view, options);
view.modelBinder.bind();
},
unbind: function(view){
if (view.modelBinder){
view.modelBinder.unbind()
}
}
};
ModelBinder = function(view, options){
this.config = new modelBinding.Configuration(options);
this.modelBindings = [];
this.elementBindings = [];
this.bind = function(){
var conventions = modelBinding.Conventions;
for (var conventionName in conventions){
if (conventions.hasOwnProperty(conventionName)){
var conventionElement = conventions[conventionName];
var handler = conventionElement.handler;
var conventionSelector = conventionElement.selector;
handler.bind.call(this, conventionSelector, view, view.model, this.config);
}
}
}
this.unbind = function(){
// unbind the html element bindings
_.each(this.elementBindings, function(binding){
binding.element.unbind(binding.eventName, binding.callback);
});
// unbind the model bindings
_.each(this.modelBindings, function(binding){
binding.model.unbind(binding.eventName, binding.callback);
});
}
this.registerModelBinding = function(model, attribute_name, callback){
// bind the model changes to the form elements
var eventName = "change:" + attribute_name;
model.bind(eventName, callback);
this.modelBindings.push({model: model, eventName: eventName, callback: callback});
}
this.registerElementBinding = function(element, callback){
// bind the form changes to the model
element.bind("change", callback);
this.elementBindings.push({element: element, eventName: "change", callback: callback});
}
}
// ----------------------------
// Model Binding Configuration
// ----------------------------
modelBinding.Configuration = function(options){
this.bindingAttrConfig = {};
_.extend(this.bindingAttrConfig,
modelBinding.Configuration.bindindAttrConfig,
options
);
if (this.bindingAttrConfig.all){
var attr = this.bindingAttrConfig.all;
delete this.bindingAttrConfig.all;
for (var inputType in this.bindingAttrConfig){
if (this.bindingAttrConfig.hasOwnProperty(inputType)){
this.bindingAttrConfig[inputType] = attr;
}
}
}
this.getBindingAttr = function(type){
return this.bindingAttrConfig[type];
};
this.getBindingValue = function(element, type){
var bindingAttr = this.getBindingAttr(type);
return element.attr(bindingAttr);
};
};
modelBinding.Configuration.bindindAttrConfig = {
text: "id",
textarea: "id",
password: "id",
radio: "name",
checkbox: "id",
select: "id",
number: "id",
range: "id",
tel: "id",
search: "id",
url: "id",
email: "id"
};
modelBinding.Configuration.store = function(){
modelBinding.Configuration.originalConfig = _.clone(modelBinding.Configuration.bindindAttrConfig);
};
modelBinding.Configuration.restore = function(){
modelBinding.Configuration.bindindAttrConfig = modelBinding.Configuration.originalConfig;
};
modelBinding.Configuration.configureBindingAttributes = function(options){
if (options.all){
this.configureAllBindingAttributes(options.all);
delete options.all;
}
_.extend(modelBinding.Configuration.bindindAttrConfig, options);
};
modelBinding.Configuration.configureAllBindingAttributes = function(attribute){
var config = modelBinding.Configuration.bindindAttrConfig;
config.text = attribute;
config.textarea = attribute;
config.password = attribute;
config.radio = attribute;
config.checkbox = attribute;
config.select = attribute;
config.number = attribute;
config.range = attribute;
config.tel = attribute;
config.search = attribute;
config.url = attribute;
config.email = attribute;
};
// ----------------------------
// Text, Textarea, and Password Bi-Directional Binding Methods
// ----------------------------
StandardBinding = (function(Backbone){
var methods = {};
var _getElementType = function(element) {
var type = element[0].tagName.toLowerCase();
if (type == "input"){
type = element.attr("type");
if (type == undefined || type == ''){
type = 'text';
}
}
return type;
};
methods.bind = function(selector, view, model, config){
var modelBinder = this;
view.$(selector).each(function(index){
var element = view.$(this);
var elementType = _getElementType(element);
var attribute_name = config.getBindingValue(element, elementType);
// HACK (Did)
if (model.paramRoot) {
regexp = new RegExp('^' + model.paramRoot + '_')
attribute_name = attribute_name.replace(regexp, '')
}
// console.log(attribute_name);
var modelChange = function(changed_model, val){ element.val(val); };
var setModelValue = function(attr_name, value){
var data = {};
data[attr_name] = value;
model.set(data);
};
var elementChange = function(ev){
setModelValue(attribute_name, view.$(ev.target).val());
};
modelBinder.registerModelBinding(model, attribute_name, modelChange);
modelBinder.registerElementBinding(element, elementChange);
// set the default value on the form, from the model
var attr_value = model.get(attribute_name);
if (typeof attr_value !== "undefined" && attr_value !== null) {
element.val(attr_value);
} else {
var elVal = element.val();
if (elVal){
setModelValue(attribute_name, elVal);
}
}
});
};
return methods;
})(Backbone);
// ----------------------------
// Select Box Bi-Directional Binding Methods
// ----------------------------
SelectBoxBinding = (function(Backbone){
var methods = {};
methods.bind = function(selector, view, model, config){
var modelBinder = this;
view.$(selector).each(function(index){
var element = view.$(this);
var attribute_name = config.getBindingValue(element, 'select');
// HACK (Did)
if (model.paramRoot) {
regexp = new RegExp('^' + model.paramRoot + '_')
attribute_name = attribute_name.replace(regexp, '')
}
// console.log(attribute_name);
var modelChange = function(changed_model, val){ element.val(val); };
var setModelValue = function(attr, val, text){
var data = {};
data[attr] = val;
data[attr + "_text"] = text;
model.set(data);
};
var elementChange = function(ev){
var targetEl = view.$(ev.target);
var value = targetEl.val();
var text = targetEl.find(":selected").text();
setModelValue(attribute_name, value, text);
};
modelBinder.registerModelBinding(model, attribute_name, modelChange);
modelBinder.registerElementBinding(element, elementChange);
// set the default value on the form, from the model
var attr_value = model.get(attribute_name);
if (typeof attr_value !== "undefined" && attr_value !== null) {
element.val(attr_value);
}
// set the model to the form's value if there is no model value
if (element.val() != attr_value) {
var value = element.val();
var text = element.find(":selected").text();
setModelValue(attribute_name, value, text);
}
});
};
return methods;
})(Backbone);
// ----------------------------
// Radio Button Group Bi-Directional Binding Methods
// ----------------------------
RadioGroupBinding = (function(Backbone){
var methods = {};
methods.bind = function(selector, view, model, config){
var modelBinder = this;
var foundElements = [];
view.$(selector).each(function(index){
var element = view.$(this);
var group_name = config.getBindingValue(element, 'radio');
if (!foundElements[group_name]) {
foundElements[group_name] = true;
var bindingAttr = config.getBindingAttr('radio');
var modelChange = function(model, val){
var value_selector = "input[type=radio][" + bindingAttr + "=" + group_name + "][value=" + val + "]";
view.$(value_selector).attr("checked", "checked");
};
modelBinder.registerModelBinding(model, group_name, modelChange);
var setModelValue = function(attr, val){
var data = {};
data[attr] = val;
model.set(data);
};
// bind the form changes to the model
var elementChange = function(ev){
var element = view.$(ev.currentTarget);
if (element.is(":checked")){
setModelValue(group_name, element.val());
}
};
var group_selector = "input[type=radio][" + bindingAttr + "=" + group_name + "]";
view.$(group_selector).each(function(){
var groupEl = $(this);
modelBinder.registerElementBinding(groupEl, elementChange);
});
var attr_value = model.get(group_name);
if (typeof attr_value !== "undefined" && attr_value !== null) {
// set the default value on the form, from the model
var value_selector = "input[type=radio][" + bindingAttr + "=" + group_name + "][value=" + attr_value + "]";
view.$(value_selector).attr("checked", "checked");
} else {
// set the model to the currently selected radio button
var value_selector = "input[type=radio][" + bindingAttr + "=" + group_name + "]:checked";
var value = view.$(value_selector).val();
setModelValue(group_name, value);
}
}
});
};
return methods;
})(Backbone);
// ----------------------------
// Checkbox Bi-Directional Binding Methods
// ----------------------------
CheckboxBinding = (function(Backbone){
var methods = {};
methods.bind = function(selector, view, model, config){
var modelBinder = this;
view.$(selector).each(function(index){
var element = view.$(this);
var bindingAttr = config.getBindingAttr('checkbox');
var attribute_name = config.getBindingValue(element, 'checkbox');
// HACK (Did)
if (model.paramRoot) {
regexp = new RegExp('^' + model.paramRoot + '_')
attribute_name = attribute_name.replace(regexp, '')
}
// console.log(attribute_name);
var modelChange = function(model, val){
if (val){
element.attr("checked", "checked");
}
else{
element.removeAttr("checked");
}
};
var setModelValue = function(attr_name, value){
var data = {};
data[attr_name] = value;
model.set(data);
};
var elementChange = function(ev){
// console.log('[SELECT] elementChange');
var changedElement = view.$(ev.target);
var checked = changedElement.is(":checked")? true : false;
setModelValue(attribute_name, checked);
};
modelBinder.registerModelBinding(model, attribute_name, modelChange);
modelBinder.registerElementBinding(element, elementChange);
var attr_exists = model.attributes.hasOwnProperty(attribute_name);
if (attr_exists) {
// set the default value on the form, from the model
var attr_value = model.get(attribute_name);
if (typeof attr_value !== "undefined" && attr_value !== null && attr_value != false) {
element.attr("checked", "checked");
}
else{
element.removeAttr("checked");
}
} else {
// bind the form's value to the model
var checked = element.is(":checked")? true : false;
setModelValue(attribute_name, checked);
}
});
};
return methods;
})(Backbone);
// ----------------------------
// Data-Bind Binding Methods
// ----------------------------
DataBindBinding = (function(Backbone, _, $){
var dataBindSubstConfig = {
"default": ""
};
modelBinding.Configuration.dataBindSubst = function(config){
this.storeDataBindSubstConfig();
_.extend(dataBindSubstConfig, config);
};
modelBinding.Configuration.storeDataBindSubstConfig = function(){
modelBinding.Configuration._dataBindSubstConfig = _.clone(dataBindSubstConfig);
};
modelBinding.Configuration.restoreDataBindSubstConfig = function(){
if (modelBinding.Configuration._dataBindSubstConfig){
dataBindSubstConfig = modelBinding.Configuration._dataBindSubstConfig;
delete modelBinding.Configuration._dataBindSubstConfig;
}
};
modelBinding.Configuration.getDataBindSubst = function(elementType, value){
var returnValue = value;
if (value === undefined){
if (dataBindSubstConfig.hasOwnProperty(elementType)){
returnValue = dataBindSubstConfig[elementType];
} else {
returnValue = dataBindSubstConfig["default"];
}
}
return returnValue;
};
setOnElement = function(element, attr, val){
var valBefore = val;
val = modelBinding.Configuration.getDataBindSubst(attr, val);
switch(attr){
case "html":
element.html(val);
break;
case "text":
element.text(val);
break;
case "enabled":
element.attr("disabled", !val);
break;
case "displayed":
element[val? "show" : "hide"]();
break;
case "hidden":
element[val? "hide" : "show"]();
break;
default:
element.attr(attr, val);
}
};
splitBindingAttr = function(element)
{
var dataBindConfigList = [];
var databindList = element.attr("data-bind").split(";");
_.each(databindList, function(attrbind){
var databind = $.trim(attrbind).split(" ");
// make the default special case "text" if none specified
if( databind.length == 1 ) databind.unshift("text");
dataBindConfigList.push({
elementAttr: databind[0],
modelAttr: databind[1]
});
});
return dataBindConfigList;
};
var methods = {};
methods.bind = function(selector, view, model, config){
var modelBinder = this;
view.$(selector).each(function(index){
var element = view.$(this);
var databindList = splitBindingAttr(element);
_.each(databindList, function(databind){
var modelChange = function(model, val){
setOnElement(element, databind.elementAttr, val);
};
modelBinder.registerModelBinding(model, databind.modelAttr, modelChange);
// set default on data-bind element
setOnElement(element, databind.elementAttr, model.get(databind.modelAttr));
});
});
};
return methods;
})(Backbone, _, $);
// ----------------------------
// Binding Conventions
// ----------------------------
modelBinding.Conventions = {
text: {selector: "input:text", handler: StandardBinding},
textarea: {selector: "textarea", handler: StandardBinding},
password: {selector: "input:password", handler: StandardBinding},
radio: {selector: "input:radio", handler: RadioGroupBinding},
checkbox: {selector: "input:checkbox", handler: CheckboxBinding},
select: {selector: "select", handler: SelectBoxBinding},
databind: { selector: "*[data-bind]", handler: DataBindBinding},
// HTML5 input
number: {selector: "input[type=number]", handler: StandardBinding},
range: {selector: "input[type=range]", handler: StandardBinding},
tel: {selector: "input[type=tel]", handler: StandardBinding},
search: {selector: "input[type=search]", handler: StandardBinding},
url: {selector: "input[type=url]", handler: StandardBinding},
email: {selector: "input[type=email]", handler: StandardBinding}
};
return modelBinding;
})(Backbone, _, jQuery);

View File

@ -0,0 +1,93 @@
(function() {
var methodMap = {
'create': 'POST',
'update': 'PUT',
'delete': 'DELETE',
'read' : 'GET'
};
var getUrl = function(object) {
if (!(object && object.url)) return null;
return _.isFunction(object.url) ? object.url() : object.url;
};
var urlError = function() {
throw new Error("A 'url' property or function must be specified");
};
Backbone.sync = function(method, model, options) {
var type = methodMap[method];
// Default JSON-request options.
var params = _.extend({
type: type,
dataType: 'json',
beforeSend: function( xhr ) {
var token = $('meta[name="csrf-token"]').attr('content');
if (token) xhr.setRequestHeader('X-CSRF-Token', token);
}
}, options);
if (!params.url) {
params.url = getUrl(model) || urlError();
}
// Ensure that we have the appropriate request data.
if (!params.data && model && (method == 'create' || method == 'update')) {
params.contentType = 'application/json';
var data = {}
if(model.paramRoot) {
data[model.paramRoot] = model.toJSON();
} else {
data = model.toJSON();
}
if (typeof(FormData) != 'undefined') { // XHR2
var formData = new FormData();
var _buildParams = function(prefix, obj, fn) { // code grabbed from jquery
if (jQuery.isArray(obj)) {
jQuery.each(obj, function(i, v) {
if (/\[\]$/.test(prefix)) { // rbracket
fn(prefix, v);
} else {
_buildParams(prefix + "[" + ( typeof v === "object" || jQuery.isArray(v) ? i : "" ) + "]", v, fn);
}
});
} else if (obj != null && typeof obj === "object" && !(obj instanceof File)) {
for (var name in obj) {
_buildParams(prefix + "[" + name + "]", obj[name], fn);
}
} else {
fn(prefix, obj);
}
}
for (var prefix in data) {
_buildParams(prefix, data[prefix], function(key, value) {
// console.log('append ' + key + ', ' + value);
if (value != null)
formData.append(key, value);
});
}
params.data = formData;
params.processData = false;
params.contentType = false;
} else {
params.data = JSON.stringify(data);
}
}
// Don't process data on a non-GET request.
if (params.type !== 'GET') {
params.processData = false;
}
// Make the request.
return $.ajax(params);
}
}).call(this);

View File

@ -64,7 +64,7 @@
$(element).parent().prev().css("color","#cccccc");
$(element).parent().next().css("color","#333333");
$(element).parent().css("background-color", settings.off_bg_color).removeClass('on');
$(element).parent().parent().prev().removeAttr("checked");
$(element).parent().parent().prev().removeAttr("checked").trigger('change');
$(element).removeClass("left").addClass("right");
if (typeof $.fn.publish != 'undefined')
@ -82,7 +82,7 @@
$(element).parent().prev().css("color","#333333");
$(element).parent().next().css("color","#cccccc");
$(element).parent().css("background-color", settings.on_bg_color).addClass('on');
$(element).parent().parent().prev().attr("checked","checked");
$(element).parent().parent().prev().attr("checked", "checked").trigger('change');
$(element).removeClass("right").addClass("left");
if (typeof $.fn.publish != 'undefined')