diff --git a/ajax/libs/backbone-associations/0.5.4/backbone-associations-min.js b/ajax/libs/backbone-associations/0.5.4/backbone-associations-min.js new file mode 100644 index 000000000..342b19153 --- /dev/null +++ b/ajax/libs/backbone-associations/0.5.4/backbone-associations-min.js @@ -0,0 +1,13 @@ +(function(){var z=this,f,e,w,p,u,v,C,s,D,E,F,q;"undefined"!==typeof exports?(f=require("underscore"),e=require("backbone"),"undefined"!==typeof module&&module.exports&&(module.exports=e),exports=e):(f=z._,e=z.Backbone);w=e.Model;p=e.Collection;u=w.prototype;v=p.prototype;C="change add remove reset sort destroy".split(" ");E=["reset","sort"];e.Associations={VERSION:"0.5.4"};var G=function(){return q},A=function(a){if(!f.isString(a)||1>f.size(a))a=".";q=a;D=RegExp("[\\"+q+"\\[\\]]+","g");F=RegExp("[^\\"+ +q+"\\[\\]]+","g")};try{Object.defineProperty(e.Associations,"SEPARATOR",{enumerable:!0,get:G,set:A})}catch(J){}e.Associations.Many=e.Many="Many";e.Associations.One=e.One="One";e.Associations.Self=e.Self="Self";e.Associations.SEPARATOR=".";e.Associations.getSeparator=G;e.Associations.setSeparator=A;A();s=e.AssociatedModel=e.Associations.AssociatedModel=w.extend({relations:void 0,_proxyCalls:void 0,get:function(a){var c=u.get.call(this,a);return c?c:this._getAttr.apply(this,arguments)},set:function(a, +c,d){var b;f.isObject(a)||null==a?(b=a,d=c):(b={},b[a]=c);a=this._set(b,d);this._processPendingEvents();return a},_set:function(a,c){var d,b,H,g,e=this;if(!a)return this;for(d in a)if(b||(b={}),d.match(D)){var n=B(d);g=f.initial(n);n=n[n.length-1];g=this.get(g);g instanceof s&&(g=b[g.cid]||(b[g.cid]={model:g,data:{}}),g.data[n]=a[d])}else g=b[this.cid]||(b[this.cid]={model:this,data:{}}),g.data[d]=a[d];if(b)for(H in b)g=b[H],this._setAttr.call(g.model,g.data,c)||(e=!1);else e=this._setAttr.call(this, +a,c);return e},_setAttr:function(a,c){var d;c||(c={});if(c.unset)for(d in a)a[d]=void 0;this.parents=this.parents||[];this.relations&&f.each(this.relations,function(b){var d=b.key,g=b.relatedModel,m=b.collectionType,n=b.map,k=this.attributes[d],l=k&&k.idAttribute,h,t,r,q=!1;!g||g.prototype instanceof w||(g=f.isFunction(g)?g.call(this,b,a):g);g&&f.isString(g)&&(g=g===e.Self?this.constructor:x(g));m&&f.isString(m)&&(m=x(m));n&&f.isString(n)&&(n=x(n));t=b.options?f.extend({},b.options,c):c;if(!g&&!m)throw Error("specify either a relatedModel or collectionType"); +if(a[d]){h=f.result(a,d);h=n?n.call(this,h,m?m:g):h;if(b.type===e.Many){if(m&&!m.prototype instanceof p)throw Error("collectionType must inherit from Backbone.Collection");k?(k._deferEvents=!0,k[t.reset?"reset":"set"](h instanceof p?h.models:h,t),b=k):(q=!0,h instanceof p?b=h:(b=m?new m:this._createCollection(g),b[t.reset?"reset":"set"](h,t)))}else if(b.type===e.One){if(!g)throw Error("specify a relatedModel for Backbone.One type");if(!(g.prototype instanceof e.AssociatedModel))throw Error("specify an AssociatedModel for Backbone.One type"); +b=h instanceof s?h:new g(h,t);k&&b.attributes[l]&&k.attributes[l]===b.attributes[l]?(k._deferEvents=!0,k._set(h instanceof s?h.attributes:h,t),b=k):q=!0}else throw Error("type attribute must be specified and have the values Backbone.One or Backbone.Many");r=a[d]=b;if(q||r&&!r._proxyCallback)r._proxyCallback=function(){return this._bubbleEvent.call(this,d,r,arguments)},r.on("all",r._proxyCallback,this)}a.hasOwnProperty(d)&&(k=a[d],l=this.attributes[d],k?(k.parents=k.parents||[],-1==f.indexOf(k.parents, +this)&&k.parents.push(this)):l&&0f.size(a))){for(b=0;b 0) { // New value is undefined + original.parents = _.difference(original.parents, [this]); + // Don't bubble to this parent anymore + original._proxyCallback && original.off("all", original._proxyCallback, this); + } + } + }, this); + } + // Return results for `BackboneModel.set`. + return ModelProto.set.call(this, attributes, options); + }, + // Bubble-up event to `parent` Model + _bubbleEvent:function (relationKey, relationValue, eventArguments) { + var args = eventArguments, + opt = args[0].split(":"), + eventType = opt[0], + catch_all = args[0] == "nested-change", + eventObject = args[1], + colObject = args[2], + indexEventObject = -1, + _proxyCalls = relationValue._proxyCalls, + cargs, + eventPath, + basecolEventPath, + isDefaultEvent = _.indexOf(defaultEvents, eventType) !== -1; + + //Short circuit the listen in to the nested-graph event + if (catch_all) return; + + // Change the event name to a fully qualified path. + _.size(opt) > 1 && (eventPath = opt[1]); + + if (_.indexOf(collectionEvents, eventType) !== -1) { + colObject = eventObject; + } + + // Find the specific object in the collection which has changed. + if (relationValue instanceof BackboneCollection && isDefaultEvent && eventObject) { + var pathTokens = getPathArray(eventPath), + initialTokens = _.initial(pathTokens), colModel; + + colModel = relationValue.find(function (model) { + if (eventObject === model) return true; + if (!model) return false; + var changedModel = model.get(initialTokens); + + if ((changedModel instanceof AssociatedModel || changedModel instanceof BackboneCollection) + && eventObject === changedModel) + return true; + + changedModel = model.get(pathTokens); + + if ((changedModel instanceof AssociatedModel || changedModel instanceof BackboneCollection) + && eventObject === changedModel) + return true; + + if (changedModel instanceof BackboneCollection && colObject + && colObject === changedModel) + return true; + }); + colModel && (indexEventObject = relationValue.indexOf(colModel)); + } + + // Manipulate `eventPath`. + eventPath = relationKey + ((indexEventObject !== -1 && (eventType === "change" || eventPath)) ? + "[" + indexEventObject + "]" : "") + (eventPath ? pathSeparator + eventPath : ""); + + // Short circuit collection * events + if (/\[\*\]/g.test(eventPath)) return this; + basecolEventPath = eventPath.replace(/\[\d+\]/g, '[*]'); + + cargs = []; + cargs.push.apply(cargs, args); + cargs[0] = eventType + ":" + eventPath; + + // If event has been already triggered as result of same source `eventPath`, + // no need to re-trigger event to prevent cycle. + _proxyCalls = relationValue._proxyCalls = (_proxyCalls || {}); + if (this._isEventAvailable.call(this, _proxyCalls, eventPath)) return this; + + // Add `eventPath` in `_proxyCalls` to keep track of already triggered `event`. + _proxyCalls[eventPath] = true; + + // Set up previous attributes correctly. + if ("change" === eventType) { + this._previousAttributes[relationKey] = relationValue._previousAttributes; + this.changed[relationKey] = relationValue; + } + + // Bubble up event to parent `model` with new changed arguments. + this.trigger.apply(this, cargs); + + //Only fire for change. Not change:attribute + if ("change" === eventType && this.get(eventPath) != args[2]) { + var ncargs = ["nested-change", eventPath, args[1]]; + args[2] && ncargs.push(args[2]); //args[2] will be options if present + this.trigger.apply(this, ncargs); + } + + // Remove `eventPath` from `_proxyCalls`, + // if `eventPath` and `_proxyCalls` are available, + // which allow event to be triggered on for next operation of `set`. + if (_proxyCalls && eventPath) delete _proxyCalls[eventPath]; + + // Create a collection modified event with wild-card + if (eventPath !== basecolEventPath) { + cargs[0] = eventType + ":" + basecolEventPath; + this.trigger.apply(this, cargs); + } + + return this; + }, + + // Has event been fired from this source. Used to prevent event recursion in cyclic graphs + _isEventAvailable:function (_proxyCalls, path) { + return _.find(_proxyCalls, function (value, eventKey) { + return path.indexOf(eventKey, path.length - eventKey.length) !== -1; + }); + }, + + // Returns New `collection` of type `relation.relatedModel`. + _createCollection:function (type) { + var collection, relatedModel = type; + _.isString(relatedModel) && (relatedModel = map2Scope(relatedModel)); + // Creates new `Backbone.Collection` and defines model class. + if (relatedModel && (relatedModel.prototype instanceof AssociatedModel) || _.isFunction(relatedModel)) { + collection = new BackboneCollection(); + collection.model = relatedModel; + } else { + throw new Error('type must inherit from Backbone.AssociatedModel'); + } + return collection; + }, + + // Process all pending events after the entire object graph has been updated + _processPendingEvents:function () { + if (!this._processedEvents) { + this._processedEvents = true; + + this._deferEvents = false; + + // Trigger all pending events + _.each(this._pendingEvents, function (e) { + e.c.trigger.apply(e.c, e.a); + }); + + this._pendingEvents = []; + + // Traverse down the object graph and call process pending events on sub-trees + _.each(this.relations, function (relation) { + var val = this.attributes[relation.key]; + val && val._processPendingEvents(); + }, this); + + delete this._processedEvents; + } + }, + + // Override trigger to defer events in the object graph. + trigger:function (name) { + // Defer event processing + if (this._deferEvents) { + this._pendingEvents = this._pendingEvents || []; + // Maintain a queue of pending events to trigger after the entire object graph is updated. + this._pendingEvents.push({c:this, a:arguments}); + } else { + ModelProto.trigger.apply(this, arguments); + } + }, + + // The JSON representation of the model. + toJSON:function (options) { + var json = {}, aJson; + json[this.idAttribute] = this.id; + if (!this.visited) { + this.visited = true; + // Get json representation from `BackboneModel.toJSON`. + json = ModelProto.toJSON.apply(this, arguments); + // If `this.relations` is defined, iterate through each `relation` + // and added it's json representation to parents' json representation. + if (this.relations) { + _.each(this.relations, function (relation) { + var attr = this.attributes[relation.key]; + if (attr) { + aJson = attr.toJSON ? attr.toJSON(options) : attr; + json[relation.key] = _.isArray(aJson) ? _.compact(aJson) : aJson; + } + }, this); + } + delete this.visited; + } + return json; + }, + + // Create a new model with identical attributes to this one. + clone:function () { + return new this.constructor(this.toJSON()); + }, + + // Call this if you want to set an `AssociatedModel` to a falsy value like undefined/null directly. + // Not calling this will leak memory and have wrong parents. + // See test case "parent relations" + cleanup:function () { + _.each(this.relations, function (relation) { + var val = this.attributes[relation.key]; + val && (val.parents = _.difference(val.parents, [this])); + }, this); + this.off(); + }, + + // Navigate the path to the leaf object in the path to query for the attribute value + _getAttr:function (path) { + + var result = this, + //Tokenize the path + attrs = getPathArray(path), + key, + i; + if (_.size(attrs) < 1) return; + for (i = 0; i < attrs.length; i++) { + key = attrs[i]; + if (!result) break; + //Navigate the path to get to the result + result = result instanceof BackboneCollection + ? (isNaN(key) ? undefined : result.at(key)) + : result.attributes[key]; + } + return result; + } + }); + + // Tokenize the fully qualified event path + var getPathArray = function (path) { + if (path === '') return ['']; + return _.isString(path) ? (path.match(delimiters)) : path || []; + }; + + var map2Scope = function (path) { + return _.reduce(path.split(pathSeparator), function (memo, elem) { + return memo[elem]; + }, root); + }; + + //Infer the relation from the collection's parents and find the appropriate map for the passed in `models` + var map2models = function (parents, target, models) { + var relation, surrogate; + //Iterate over collection's parents + _.find(parents, function (parent) { + //Iterate over relations + relation = _.find(parent.relations, function (rel) { + return parent.get(rel.key) === target; + }, this); + if (relation) { + surrogate = parent;//surrogate for transformation + return true;//break; + } + }, this); + + //If we found a relation and it has a mapping function + if (relation && relation.map) { + return relation.map.call(surrogate, models, target); + } + return models; + }; + + var proxies = {}; + // Proxy Backbone collection methods + _.each(['set', 'remove', 'reset'], function (method) { + proxies[method] = BackboneCollection.prototype[method]; + + CollectionProto[method] = function (models, options) { + //Short-circuit if this collection doesn't hold `AssociatedModels` + if (this.model.prototype instanceof AssociatedModel && this.parents) { + //Find a map function if available and perform a transformation + arguments[0] = map2models(this.parents, this, models); + } + return proxies[method].apply(this, arguments); + } + }); + + // Override trigger to defer events in the object graph. + proxies['trigger'] = CollectionProto['trigger']; + CollectionProto['trigger'] = function (name) { + if (this._deferEvents) { + this._pendingEvents = this._pendingEvents || []; + // Maintain a queue of pending events to trigger after the entire object graph is updated. + this._pendingEvents.push({c:this, a:arguments}); + } else { + proxies['trigger'].apply(this, arguments); + } + }; + + // Attach process pending event functionality on collections as well. Re-use from `AssociatedModel` + CollectionProto._processPendingEvents = AssociatedModel.prototype._processPendingEvents; + + +}).call(this);