engine/vendor/assets/javascripts/locomotive/backbone.modelbinding.js

566 lines
18 KiB
JavaScript
Raw Normal View History

// 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});
}
2012-05-24 13:46:48 +00:00
this.registerElementBinding = function(element, callback, callbackSilently){
// bind the form changes to the model
element.bind("change", callback);
this.elementBindings.push({element: element, eventName: "change", callback: callback});
2012-05-24 13:46:48 +00:00
if (callbackSilently)
element.bind("changeSilently", callbackSilently)
}
}
// ----------------------------
// 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);
2012-02-02 15:53:26 +00:00
var modelChange = function(changed_model, val){
val = val == null ? '' : val;
element.val(val);
};
2012-05-24 13:46:48 +00:00
var setModelValue = function(attr_name, value, silent){
if (typeof(silent) === 'undefined' || silent == null) silent = false
var data = {};
data[attr_name] = value;
2012-05-24 13:46:48 +00:00
model.set(data, { 'silent': silent });
};
var elementChange = function(ev){
setModelValue(attribute_name, view.$(ev.target).val());
};
2012-05-24 13:46:48 +00:00
// FIXME (Did): simple solution to update the model without triggering related callbacks (solving a bug with tinymce)
var elementChangeSilently = function(ev) {
setModelValue(attribute_name, view.$(ev.target).val(), true);
}
modelBinder.registerModelBinding(model, attribute_name, modelChange);
2012-05-24 13:46:48 +00:00
modelBinder.registerElementBinding(element, elementChange, elementChangeSilently);
// 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');
// HACK (Did)
var attribute_name = group_name;
if (model.paramRoot) {
regexp = new RegExp('^' + model.paramRoot + "\\[");
attribute_name = attribute_name.replace(regexp, '').replace(/\]$/, '');
}
// console.log(attribute_name);
// console.log(group_name);
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(attribute_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(attribute_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(attribute_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 isArray = _.isArray(model.get(attribute_name))
var modelChange = function(model, val){
var checked = val;
if (isArray)
checked = _.include(val, element.val());
if (checked)
element.attr("checked", "checked");
else
element.removeAttr("checked");
};
var setModelValue = function(attr_name, checked, value){
var data = {};
if (isArray) {
var array = model.get(attr_name);
if (array == null) array = [];
if (checked)
array.push(value);
else
array = _.without(array, value);
data[attr_name] = array;
} else
data[attr_name] = checked;
model.set(data);
};
var elementChange = function(ev){
// console.log('[CHECKBOX] elementChange');
var changedElement = view.$(ev.target);
var checked = changedElement.is(":checked")? true : false;
setModelValue(attribute_name, checked, changedElement.val());
};
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 (isArray && !_.isArray(attr_value)) attr_value = [];
if (typeof attr_value !== "undefined" && attr_value !== null && (attr_value == true || _.include(attr_value, element.val()))) {
element.attr("checked", "checked");
}else{
element.removeAttr("checked");
}
} else {
// console.log('// bind the form\'s value to the model');
// bind the form's value to the model
var checked = element.is(":checked")? true : false;
setModelValue(attribute_name, checked, element.val());
}
});
};
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);