mirror of
https://github.com/Readarr/Readarr.git
synced 2026-04-21 22:04:31 -04:00
Moved source code under src folder - massive change
This commit is contained in:
@@ -0,0 +1,576 @@
|
||||
// Backbone.ModelBinder v1.0.2
|
||||
// (c) 2013 Bart Wood
|
||||
// Distributed Under MIT License
|
||||
|
||||
(function (factory) {
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
// AMD. Register as an anonymous module.
|
||||
define(['underscore', 'jquery', 'backbone'], factory);
|
||||
} else {
|
||||
// Browser globals
|
||||
factory(_, $, Backbone);
|
||||
}
|
||||
}(function(_, $, Backbone){
|
||||
|
||||
if(!Backbone){
|
||||
throw 'Please include Backbone.js before Backbone.ModelBinder.js';
|
||||
}
|
||||
|
||||
Backbone.ModelBinder = function(){
|
||||
_.bindAll.apply(_, [this].concat(_.functions(this)));
|
||||
};
|
||||
|
||||
// Static setter for class level options
|
||||
Backbone.ModelBinder.SetOptions = function(options){
|
||||
Backbone.ModelBinder.options = options;
|
||||
};
|
||||
|
||||
// Current version of the library.
|
||||
Backbone.ModelBinder.VERSION = '1.0.2';
|
||||
Backbone.ModelBinder.Constants = {};
|
||||
Backbone.ModelBinder.Constants.ModelToView = 'ModelToView';
|
||||
Backbone.ModelBinder.Constants.ViewToModel = 'ViewToModel';
|
||||
|
||||
_.extend(Backbone.ModelBinder.prototype, {
|
||||
|
||||
bind:function (model, rootEl, attributeBindings, options) {
|
||||
this.unbind();
|
||||
|
||||
this._model = model;
|
||||
this._rootEl = rootEl;
|
||||
this._setOptions(options);
|
||||
|
||||
if (!this._model) this._throwException('model must be specified');
|
||||
if (!this._rootEl) this._throwException('rootEl must be specified');
|
||||
|
||||
if(attributeBindings){
|
||||
// Create a deep clone of the attribute bindings
|
||||
this._attributeBindings = $.extend(true, {}, attributeBindings);
|
||||
|
||||
this._initializeAttributeBindings();
|
||||
this._initializeElBindings();
|
||||
}
|
||||
else {
|
||||
this._initializeDefaultBindings();
|
||||
}
|
||||
|
||||
this._bindModelToView();
|
||||
this._bindViewToModel();
|
||||
},
|
||||
|
||||
bindCustomTriggers: function (model, rootEl, triggers, attributeBindings, modelSetOptions) {
|
||||
this._triggers = triggers;
|
||||
this.bind(model, rootEl, attributeBindings, modelSetOptions)
|
||||
},
|
||||
|
||||
unbind:function () {
|
||||
this._unbindModelToView();
|
||||
this._unbindViewToModel();
|
||||
|
||||
if(this._attributeBindings){
|
||||
delete this._attributeBindings;
|
||||
this._attributeBindings = undefined;
|
||||
}
|
||||
},
|
||||
|
||||
_setOptions: function(options){
|
||||
this._options = _.extend({
|
||||
boundAttribute: 'name'
|
||||
}, Backbone.ModelBinder.options, options);
|
||||
|
||||
// initialize default options
|
||||
if(!this._options['modelSetOptions']){
|
||||
this._options['modelSetOptions'] = {};
|
||||
}
|
||||
this._options['modelSetOptions'].changeSource = 'ModelBinder';
|
||||
|
||||
if(!this._options['changeTriggers']){
|
||||
this._options['changeTriggers'] = {'': 'change', '[contenteditable]': 'blur'};
|
||||
}
|
||||
|
||||
if(!this._options['initialCopyDirection']){
|
||||
this._options['initialCopyDirection'] = Backbone.ModelBinder.Constants.ModelToView;
|
||||
}
|
||||
},
|
||||
|
||||
// Converts the input bindings, which might just be empty or strings, to binding objects
|
||||
_initializeAttributeBindings:function () {
|
||||
var attributeBindingKey, inputBinding, attributeBinding, elementBindingCount, elementBinding;
|
||||
|
||||
for (attributeBindingKey in this._attributeBindings) {
|
||||
inputBinding = this._attributeBindings[attributeBindingKey];
|
||||
|
||||
if (_.isString(inputBinding)) {
|
||||
attributeBinding = {elementBindings: [{selector: inputBinding}]};
|
||||
}
|
||||
else if (_.isArray(inputBinding)) {
|
||||
attributeBinding = {elementBindings: inputBinding};
|
||||
}
|
||||
else if(_.isObject(inputBinding)){
|
||||
attributeBinding = {elementBindings: [inputBinding]};
|
||||
}
|
||||
else {
|
||||
this._throwException('Unsupported type passed to Model Binder ' + attributeBinding);
|
||||
}
|
||||
|
||||
// Add a linkage from the element binding back to the attribute binding
|
||||
for(elementBindingCount = 0; elementBindingCount < attributeBinding.elementBindings.length; elementBindingCount++){
|
||||
elementBinding = attributeBinding.elementBindings[elementBindingCount];
|
||||
elementBinding.attributeBinding = attributeBinding;
|
||||
}
|
||||
|
||||
attributeBinding.attributeName = attributeBindingKey;
|
||||
this._attributeBindings[attributeBindingKey] = attributeBinding;
|
||||
}
|
||||
},
|
||||
|
||||
// If the bindings are not specified, the default binding is performed on the specified attribute, name by default
|
||||
_initializeDefaultBindings: function(){
|
||||
var elCount, elsWithAttribute, matchedEl, name, attributeBinding;
|
||||
|
||||
this._attributeBindings = {};
|
||||
elsWithAttribute = $('[' + this._options['boundAttribute'] + ']', this._rootEl);
|
||||
|
||||
for(elCount = 0; elCount < elsWithAttribute.length; elCount++){
|
||||
matchedEl = elsWithAttribute[elCount];
|
||||
name = $(matchedEl).attr(this._options['boundAttribute']);
|
||||
|
||||
// For elements like radio buttons we only want a single attribute binding with possibly multiple element bindings
|
||||
if(!this._attributeBindings[name]){
|
||||
attributeBinding = {attributeName: name};
|
||||
attributeBinding.elementBindings = [{attributeBinding: attributeBinding, boundEls: [matchedEl]}];
|
||||
this._attributeBindings[name] = attributeBinding;
|
||||
}
|
||||
else{
|
||||
this._attributeBindings[name].elementBindings.push({attributeBinding: this._attributeBindings[name], boundEls: [matchedEl]});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_initializeElBindings:function () {
|
||||
var bindingKey, attributeBinding, bindingCount, elementBinding, foundEls, elCount, el;
|
||||
for (bindingKey in this._attributeBindings) {
|
||||
attributeBinding = this._attributeBindings[bindingKey];
|
||||
|
||||
for (bindingCount = 0; bindingCount < attributeBinding.elementBindings.length; bindingCount++) {
|
||||
elementBinding = attributeBinding.elementBindings[bindingCount];
|
||||
if (elementBinding.selector === '') {
|
||||
foundEls = $(this._rootEl);
|
||||
}
|
||||
else {
|
||||
foundEls = $(elementBinding.selector, this._rootEl);
|
||||
}
|
||||
|
||||
if (foundEls.length === 0) {
|
||||
this._throwException('Bad binding found. No elements returned for binding selector ' + elementBinding.selector);
|
||||
}
|
||||
else {
|
||||
elementBinding.boundEls = [];
|
||||
for (elCount = 0; elCount < foundEls.length; elCount++) {
|
||||
el = foundEls[elCount];
|
||||
elementBinding.boundEls.push(el);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_bindModelToView: function () {
|
||||
this._model.on('change', this._onModelChange, this);
|
||||
|
||||
if(this._options['initialCopyDirection'] === Backbone.ModelBinder.Constants.ModelToView){
|
||||
this.copyModelAttributesToView();
|
||||
}
|
||||
},
|
||||
|
||||
// attributesToCopy is an optional parameter - if empty, all attributes
|
||||
// that are bound will be copied. Otherwise, only attributeBindings specified
|
||||
// in the attributesToCopy are copied.
|
||||
copyModelAttributesToView: function(attributesToCopy){
|
||||
var attributeName, attributeBinding;
|
||||
|
||||
for (attributeName in this._attributeBindings) {
|
||||
if(attributesToCopy === undefined || _.indexOf(attributesToCopy, attributeName) !== -1){
|
||||
attributeBinding = this._attributeBindings[attributeName];
|
||||
this._copyModelToView(attributeBinding);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
copyViewValuesToModel: function(){
|
||||
var bindingKey, attributeBinding, bindingCount, elementBinding, elCount, el;
|
||||
for (bindingKey in this._attributeBindings) {
|
||||
attributeBinding = this._attributeBindings[bindingKey];
|
||||
|
||||
for (bindingCount = 0; bindingCount < attributeBinding.elementBindings.length; bindingCount++) {
|
||||
elementBinding = attributeBinding.elementBindings[bindingCount];
|
||||
|
||||
if(this._isBindingUserEditable(elementBinding)){
|
||||
if(this._isBindingRadioGroup(elementBinding)){
|
||||
el = this._getRadioButtonGroupCheckedEl(elementBinding);
|
||||
if(el){
|
||||
this._copyViewToModel(elementBinding, el);
|
||||
}
|
||||
}
|
||||
else {
|
||||
for(elCount = 0; elCount < elementBinding.boundEls.length; elCount++){
|
||||
el = $(elementBinding.boundEls[elCount]);
|
||||
if(this._isElUserEditable(el)){
|
||||
this._copyViewToModel(elementBinding, el);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_unbindModelToView: function(){
|
||||
if(this._model){
|
||||
this._model.off('change', this._onModelChange);
|
||||
this._model = undefined;
|
||||
}
|
||||
},
|
||||
|
||||
_bindViewToModel: function () {
|
||||
_.each(this._options['changeTriggers'], function (event, selector) {
|
||||
$(this._rootEl).delegate(selector, event, this._onElChanged);
|
||||
}, this);
|
||||
|
||||
if(this._options['initialCopyDirection'] === Backbone.ModelBinder.Constants.ViewToModel){
|
||||
this.copyViewValuesToModel();
|
||||
}
|
||||
},
|
||||
|
||||
_unbindViewToModel: function () {
|
||||
if(this._options && this._options['changeTriggers']){
|
||||
_.each(this._options['changeTriggers'], function (event, selector) {
|
||||
$(this._rootEl).undelegate(selector, event, this._onElChanged);
|
||||
}, this);
|
||||
}
|
||||
},
|
||||
|
||||
_onElChanged:function (event) {
|
||||
var el, elBindings, elBindingCount, elBinding;
|
||||
|
||||
el = $(event.target)[0];
|
||||
elBindings = this._getElBindings(el);
|
||||
|
||||
for(elBindingCount = 0; elBindingCount < elBindings.length; elBindingCount++){
|
||||
elBinding = elBindings[elBindingCount];
|
||||
if (this._isBindingUserEditable(elBinding)) {
|
||||
this._copyViewToModel(elBinding, el);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_isBindingUserEditable: function(elBinding){
|
||||
return elBinding.elAttribute === undefined ||
|
||||
elBinding.elAttribute === 'text' ||
|
||||
elBinding.elAttribute === 'html';
|
||||
},
|
||||
|
||||
_isElUserEditable: function(el){
|
||||
var isContentEditable = el.attr('contenteditable');
|
||||
return isContentEditable || el.is('input') || el.is('select') || el.is('textarea');
|
||||
},
|
||||
|
||||
_isBindingRadioGroup: function(elBinding){
|
||||
var elCount, el;
|
||||
var isAllRadioButtons = elBinding.boundEls.length > 0;
|
||||
for(elCount = 0; elCount < elBinding.boundEls.length; elCount++){
|
||||
el = $(elBinding.boundEls[elCount]);
|
||||
if(el.attr('type') !== 'radio'){
|
||||
isAllRadioButtons = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return isAllRadioButtons;
|
||||
},
|
||||
|
||||
_getRadioButtonGroupCheckedEl: function(elBinding){
|
||||
var elCount, el;
|
||||
for(elCount = 0; elCount < elBinding.boundEls.length; elCount++){
|
||||
el = $(elBinding.boundEls[elCount]);
|
||||
if(el.attr('type') === 'radio' && el.attr('checked')){
|
||||
return el;
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
},
|
||||
|
||||
_getElBindings:function (findEl) {
|
||||
var attributeName, attributeBinding, elementBindingCount, elementBinding, boundElCount, boundEl;
|
||||
var elBindings = [];
|
||||
|
||||
for (attributeName in this._attributeBindings) {
|
||||
attributeBinding = this._attributeBindings[attributeName];
|
||||
|
||||
for (elementBindingCount = 0; elementBindingCount < attributeBinding.elementBindings.length; elementBindingCount++) {
|
||||
elementBinding = attributeBinding.elementBindings[elementBindingCount];
|
||||
|
||||
for (boundElCount = 0; boundElCount < elementBinding.boundEls.length; boundElCount++) {
|
||||
boundEl = elementBinding.boundEls[boundElCount];
|
||||
|
||||
if (boundEl === findEl) {
|
||||
elBindings.push(elementBinding);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return elBindings;
|
||||
},
|
||||
|
||||
_onModelChange:function () {
|
||||
var changedAttribute, attributeBinding;
|
||||
|
||||
for (changedAttribute in this._model.changedAttributes()) {
|
||||
attributeBinding = this._attributeBindings[changedAttribute];
|
||||
|
||||
if (attributeBinding) {
|
||||
this._copyModelToView(attributeBinding);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_copyModelToView:function (attributeBinding) {
|
||||
var elementBindingCount, elementBinding, boundElCount, boundEl, value, convertedValue;
|
||||
|
||||
value = this._model.get(attributeBinding.attributeName);
|
||||
|
||||
for (elementBindingCount = 0; elementBindingCount < attributeBinding.elementBindings.length; elementBindingCount++) {
|
||||
elementBinding = attributeBinding.elementBindings[elementBindingCount];
|
||||
|
||||
for (boundElCount = 0; boundElCount < elementBinding.boundEls.length; boundElCount++) {
|
||||
boundEl = elementBinding.boundEls[boundElCount];
|
||||
|
||||
if(!boundEl._isSetting){
|
||||
convertedValue = this._getConvertedValue(Backbone.ModelBinder.Constants.ModelToView, elementBinding, value);
|
||||
this._setEl($(boundEl), elementBinding, convertedValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_setEl: function (el, elementBinding, convertedValue) {
|
||||
if (elementBinding.elAttribute) {
|
||||
this._setElAttribute(el, elementBinding, convertedValue);
|
||||
}
|
||||
else {
|
||||
this._setElValue(el, convertedValue);
|
||||
}
|
||||
},
|
||||
|
||||
_setElAttribute:function (el, elementBinding, convertedValue) {
|
||||
switch (elementBinding.elAttribute) {
|
||||
case 'html':
|
||||
el.html(convertedValue);
|
||||
break;
|
||||
case 'text':
|
||||
el.text(convertedValue);
|
||||
break;
|
||||
case 'enabled':
|
||||
el.prop('disabled', !convertedValue);
|
||||
break;
|
||||
case 'displayed':
|
||||
el[convertedValue ? 'show' : 'hide']();
|
||||
break;
|
||||
case 'hidden':
|
||||
el[convertedValue ? 'hide' : 'show']();
|
||||
break;
|
||||
case 'css':
|
||||
el.css(elementBinding.cssAttribute, convertedValue);
|
||||
break;
|
||||
case 'class':
|
||||
var previousValue = this._model.previous(elementBinding.attributeBinding.attributeName);
|
||||
var currentValue = this._model.get(elementBinding.attributeBinding.attributeName);
|
||||
// is current value is now defined then remove the class the may have been set for the undefined value
|
||||
if(!_.isUndefined(previousValue) || !_.isUndefined(currentValue)){
|
||||
previousValue = this._getConvertedValue(Backbone.ModelBinder.Constants.ModelToView, elementBinding, previousValue);
|
||||
el.removeClass(previousValue);
|
||||
}
|
||||
|
||||
if(convertedValue){
|
||||
el.addClass(convertedValue);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
el.attr(elementBinding.elAttribute, convertedValue);
|
||||
}
|
||||
},
|
||||
|
||||
_setElValue:function (el, convertedValue) {
|
||||
if(el.attr('type')){
|
||||
switch (el.attr('type')) {
|
||||
case 'radio':
|
||||
if (el.val() === convertedValue) {
|
||||
// must defer the change trigger or the change will actually fire with the old value
|
||||
el.prop('checked') || _.defer(function() { el.trigger('change'); });
|
||||
el.prop('checked', true);
|
||||
}
|
||||
else {
|
||||
// must defer the change trigger or the change will actually fire with the old value
|
||||
el.prop('checked', false);
|
||||
}
|
||||
break;
|
||||
case 'checkbox':
|
||||
// must defer the change trigger or the change will actually fire with the old value
|
||||
el.prop('checked') === !!convertedValue || _.defer(function() { el.trigger('change') });
|
||||
el.prop('checked', !!convertedValue);
|
||||
break;
|
||||
case 'file':
|
||||
break;
|
||||
default:
|
||||
el.val(convertedValue);
|
||||
}
|
||||
}
|
||||
else if(el.is('input') || el.is('select') || el.is('textarea')){
|
||||
el.val(convertedValue || (convertedValue === 0 ? '0' : ''));
|
||||
}
|
||||
else {
|
||||
el.text(convertedValue || (convertedValue === 0 ? '0' : ''));
|
||||
}
|
||||
},
|
||||
|
||||
_copyViewToModel: function (elementBinding, el) {
|
||||
var result, value, convertedValue;
|
||||
|
||||
if (!el._isSetting) {
|
||||
|
||||
el._isSetting = true;
|
||||
result = this._setModel(elementBinding, $(el));
|
||||
el._isSetting = false;
|
||||
|
||||
if(result && elementBinding.converter){
|
||||
value = this._model.get(elementBinding.attributeBinding.attributeName);
|
||||
convertedValue = this._getConvertedValue(Backbone.ModelBinder.Constants.ModelToView, elementBinding, value);
|
||||
this._setEl($(el), elementBinding, convertedValue);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_getElValue: function(elementBinding, el){
|
||||
switch (el.attr('type')) {
|
||||
case 'checkbox':
|
||||
return el.prop('checked') ? true : false;
|
||||
default:
|
||||
if(el.attr('contenteditable') !== undefined){
|
||||
return el.html();
|
||||
}
|
||||
else {
|
||||
return el.val();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_setModel: function (elementBinding, el) {
|
||||
var data = {};
|
||||
var elVal = this._getElValue(elementBinding, el);
|
||||
elVal = this._getConvertedValue(Backbone.ModelBinder.Constants.ViewToModel, elementBinding, elVal);
|
||||
data[elementBinding.attributeBinding.attributeName] = elVal;
|
||||
return this._model.set(data, this._options['modelSetOptions']);
|
||||
},
|
||||
|
||||
_getConvertedValue: function (direction, elementBinding, value) {
|
||||
if (elementBinding.converter) {
|
||||
value = elementBinding.converter(direction, value, elementBinding.attributeBinding.attributeName, this._model, elementBinding.boundEls);
|
||||
}
|
||||
|
||||
return value;
|
||||
},
|
||||
|
||||
_throwException: function(message){
|
||||
if(this._options.suppressThrows){
|
||||
if(console && console.error){
|
||||
console.error(message);
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw message;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Backbone.ModelBinder.CollectionConverter = function(collection){
|
||||
this._collection = collection;
|
||||
|
||||
if(!this._collection){
|
||||
throw 'Collection must be defined';
|
||||
}
|
||||
_.bindAll(this, 'convert');
|
||||
};
|
||||
|
||||
_.extend(Backbone.ModelBinder.CollectionConverter.prototype, {
|
||||
convert: function(direction, value){
|
||||
if (direction === Backbone.ModelBinder.Constants.ModelToView) {
|
||||
return value ? value.id : undefined;
|
||||
}
|
||||
else {
|
||||
return this._collection.get(value);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// A static helper function to create a default set of bindings that you can customize before calling the bind() function
|
||||
// rootEl - where to find all of the bound elements
|
||||
// attributeType - probably 'name' or 'id' in most cases
|
||||
// converter(optional) - the default converter you want applied to all your bindings
|
||||
// elAttribute(optional) - the default elAttribute you want applied to all your bindings
|
||||
Backbone.ModelBinder.createDefaultBindings = function(rootEl, attributeType, converter, elAttribute){
|
||||
var foundEls, elCount, foundEl, attributeName;
|
||||
var bindings = {};
|
||||
|
||||
foundEls = $('[' + attributeType + ']', rootEl);
|
||||
|
||||
for(elCount = 0; elCount < foundEls.length; elCount++){
|
||||
foundEl = foundEls[elCount];
|
||||
attributeName = $(foundEl).attr(attributeType);
|
||||
|
||||
if(!bindings[attributeName]){
|
||||
var attributeBinding = {selector: '[' + attributeType + '="' + attributeName + '"]'};
|
||||
bindings[attributeName] = attributeBinding;
|
||||
|
||||
if(converter){
|
||||
bindings[attributeName].converter = converter;
|
||||
}
|
||||
|
||||
if(elAttribute){
|
||||
bindings[attributeName].elAttribute = elAttribute;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return bindings;
|
||||
};
|
||||
|
||||
// Helps you to combine 2 sets of bindings
|
||||
Backbone.ModelBinder.combineBindings = function(destination, source){
|
||||
_.each(source, function(value, key){
|
||||
var elementBinding = {selector: value.selector};
|
||||
|
||||
if(value.converter){
|
||||
elementBinding.converter = value.converter;
|
||||
}
|
||||
|
||||
if(value.elAttribute){
|
||||
elementBinding.elAttribute = value.elAttribute;
|
||||
}
|
||||
|
||||
if(!destination[key]){
|
||||
destination[key] = elementBinding;
|
||||
}
|
||||
else {
|
||||
destination[key] = [destination[key], elementBinding];
|
||||
}
|
||||
});
|
||||
|
||||
return destination;
|
||||
};
|
||||
|
||||
|
||||
return Backbone.ModelBinder;
|
||||
|
||||
}));
|
||||
Reference in New Issue
Block a user