UNPKG

backbone-rel

Version:

Relationships between Backbone models in the flavor of MongoDB's document references and embeddings

1,002 lines (690 loc) 119 kB
<!DOCTYPE html> <html> <head> <title>backbone-rel.js</title> <meta http-equiv="content-type" content="text/html; charset=UTF-8"> <meta name="viewport" content="width=device-width, target-densitydpi=160dpi, initial-scale=1.0; maximum-scale=1.0; user-scalable=0;"> <link rel="stylesheet" media="all" href="docco.css" /> </head> <body> <div id="container"> <div id="background"></div> <ul class="sections"> <li id="title"> <div class="annotation"> <h1>backbone-rel.js</h1> </div> </li> <li id="section-1"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-1">&#182;</a> </div> </div> <div class="content"><div class='highlight'><pre>(<span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">root, factory</span>) </span>{ <span class="hljs-keyword">if</span>(<span class="hljs-keyword">typeof</span> define === <span class="hljs-string">'function'</span> &amp;&amp; define.amd) {</pre></div></div> </li> <li id="section-2"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-2">&#182;</a> </div> <p>AMD</p> </div> <div class="content"><div class='highlight'><pre> define([<span class="hljs-string">'underscore'</span>, <span class="hljs-string">'backbone'</span>, <span class="hljs-string">'exports'</span>], <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">_, Backbone, exports</span>) </span>{</pre></div></div> </li> <li id="section-3"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-3">&#182;</a> </div> <p>Export global even in AMD case in case this script is loaded with others that may still expect a global Backbone.</p> </div> <div class="content"><div class='highlight'><pre> root.Backbone = factory(root, exports, _, Backbone); }); } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span>(<span class="hljs-keyword">typeof</span> exports !== <span class="hljs-string">'undefined'</span>) {</pre></div></div> </li> <li id="section-4"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-4">&#182;</a> </div> <p>for Node.js or CommonJS</p> </div> <div class="content"><div class='highlight'><pre> <span class="hljs-keyword">var</span> _ = <span class="hljs-built_in">require</span>(<span class="hljs-string">'underscore'</span>), Backbone = <span class="hljs-built_in">require</span>(<span class="hljs-string">'backbone'</span>); factory(root, exports, _, Backbone); } <span class="hljs-keyword">else</span> {</pre></div></div> </li> <li id="section-5"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-5">&#182;</a> </div> <p>as a browser global</p> </div> <div class="content"><div class='highlight'><pre> root.Backbone = factory(root, {}, root._, root.Backbone); } }(<span class="hljs-keyword">this</span>, <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">root, exports, _, BackboneBase</span>) </span>{ <span class="hljs-keyword">var</span> Backbone = _.extend({}, BackboneBase); <span class="hljs-keyword">var</span> modelOptions = [<span class="hljs-string">'url'</span>, <span class="hljs-string">'urlRoot'</span>, <span class="hljs-string">'collection'</span>]; Backbone.Model = BackboneBase.Model.extend({ references: {}, embeddings: {},</pre></div></div> </li> <li id="section-6"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-6">&#182;</a> </div> <p>Property to control whether a related object shall be inlined in this model’s JSON representation. Useful when the related object shall be saved to the server together with its parent/referencing object. If a relationship key is added as a string to this array, the result of #toJSON() will have a property of that key, under which the related object’s JSON representation is nested.</p> </div> <div class="content"><div class='highlight'><pre> inlineJSON: [],</pre></div></div> </li> <li id="section-7"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-7">&#182;</a> </div> <p>Property to control whether referenced objects shall be fetched automcatically when set.</p> <ul> <li><code>true</code> (default) will cause all referenced objects to be fetched automatically</li> <li><code>false</code> will cause that referenced objects are never fetched automatically</li> <li>Setting an array of reference key strings, allows to explicitly specify which references shall be auto-fetched.</li> </ul> </div> <div class="content"><div class='highlight'><pre> autoFetchRelated: <span class="hljs-literal">true</span>, constructor: <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">attributes, options</span>) </span>{ <span class="hljs-keyword">var</span> attrs = attributes || {}; <span class="hljs-keyword">this</span>.cid = _.uniqueId(<span class="hljs-string">'c'</span>); options || (options = {}); <span class="hljs-keyword">this</span>.attributes = {}; _.extend(<span class="hljs-keyword">this</span>, _.pick(options, modelOptions)); <span class="hljs-keyword">this</span>.relatedObjects = {}; <span class="hljs-keyword">this</span>._relatedObjectsToFetch = []; <span class="hljs-keyword">this</span>._updateIdRefFor = {};</pre></div></div> </li> <li id="section-8"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-8">&#182;</a> </div> <p>handle default values for relations</p> </div> <div class="content"><div class='highlight'><pre> <span class="hljs-keyword">var</span> defaults, references = <span class="hljs-keyword">this</span>.references, referenceAttributeName = <span class="hljs-keyword">this</span>.referenceAttributeName.bind(<span class="hljs-keyword">this</span>); <span class="hljs-keyword">if</span>(options.parse) attrs = <span class="hljs-keyword">this</span>.parse(attrs, options) || {}; <span class="hljs-keyword">if</span>(defaults = _.result(<span class="hljs-keyword">this</span>, <span class="hljs-string">'defaults'</span>)) { defaults = _.extend({}, defaults); <span class="hljs-comment">// clone</span> _.each(_.keys(references), <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">refKey</span>) </span>{</pre></div></div> </li> <li id="section-9"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-9">&#182;</a> </div> <p>do not set default value for referenced object attribute if attrs contain a corresponding ID reference</p> </div> <div class="content"><div class='highlight'><pre> <span class="hljs-keyword">if</span>(referenceAttributeName(refKey) <span class="hljs-keyword">in</span> attrs &amp;&amp; refKey <span class="hljs-keyword">in</span> defaults) { <span class="hljs-keyword">delete</span> defaults[refKey]; } }); attrs = _.defaults({}, attrs, defaults); } <span class="hljs-keyword">this</span>.set(attrs, options); <span class="hljs-keyword">this</span>.changed = {}; <span class="hljs-keyword">if</span>(!<span class="hljs-keyword">this</span>.isNew()) { <span class="hljs-keyword">this</span>._autoFetchEmbeddings(<span class="hljs-literal">true</span>); } <span class="hljs-keyword">this</span>.initialize.apply(<span class="hljs-keyword">this</span>, <span class="hljs-built_in">arguments</span>); },</pre></div></div> </li> <li id="section-10"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-10">&#182;</a> </div> <p>Returns the URL for this model</p> </div> <div class="content"><div class='highlight'><pre> url: <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) </span>{ <span class="hljs-keyword">var</span> base = _.result(<span class="hljs-keyword">this</span>, <span class="hljs-string">'urlRoot'</span>) || _.result(<span class="hljs-keyword">this</span>.collection, <span class="hljs-string">'url'</span>); <span class="hljs-keyword">var</span> suffix = _.result(<span class="hljs-keyword">this</span>, <span class="hljs-string">'urlSuffix'</span>); <span class="hljs-keyword">if</span>(base) { <span class="hljs-keyword">if</span>(<span class="hljs-keyword">this</span>.isNew()) <span class="hljs-keyword">return</span> base; <span class="hljs-keyword">return</span> base.replace(<span class="hljs-regexp">/([^\/])$/</span>, <span class="hljs-string">'$1/'</span>) + <span class="hljs-built_in">encodeURIComponent</span>(<span class="hljs-keyword">this</span>.id); } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span>(<span class="hljs-keyword">this</span>.parent) { <span class="hljs-keyword">if</span>(<span class="hljs-keyword">this</span>.parent.isNew() &amp;&amp; !<span class="hljs-keyword">this</span>.parent.parent) { <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">"Could not get the parent model's URL as it has not been saved yet."</span>); } base = _.result(<span class="hljs-keyword">this</span>.parent, <span class="hljs-string">'url'</span>); <span class="hljs-keyword">if</span>(base &amp;&amp; suffix) { <span class="hljs-keyword">return</span> base.replace(<span class="hljs-regexp">/([^\/])$/</span>, <span class="hljs-string">'$1/'</span>) + suffix.replace(<span class="hljs-regexp">/(\/?)(.*)/</span>, <span class="hljs-string">'$2'</span>); } } <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">'Could not build url for the model with ID "'</span> + <span class="hljs-keyword">this</span>.id + <span class="hljs-string">'" (URL suffix: "'</span> + suffix + <span class="hljs-string">'")'</span>); },</pre></div></div> </li> <li id="section-11"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-11">&#182;</a> </div> <p>For embedded models, returns the suffix to append to the parent model’s URL for building the URL for the embedded model instance.</p> </div> <div class="content"><div class='highlight'><pre> urlSuffix: <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) </span>{ <span class="hljs-keyword">var</span> self = <span class="hljs-keyword">this</span>, parent = <span class="hljs-keyword">this</span>.parent; <span class="hljs-keyword">return</span> parent &amp;&amp; _.find(_.keys(parent.embeddings), <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">key</span>) </span>{ <span class="hljs-keyword">return</span> parent.get(key) === self; }); },</pre></div></div> </li> <li id="section-12"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-12">&#182;</a> </div> <p>For models with references, returns the attribute name under which the IDs of referenced objects for the given reference key are stored. Per default, the attribute name is built by using the reference key + the ID attribute of the referenced model. E.g: “userId” for reference key “user” to a model with ID attribute “id”, “userIds” for a to-many reference. Override this method to customize the reference attribute naming pattern.</p> </div> <div class="content"><div class='highlight'><pre> referenceAttributeName: <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">referenceKey</span>) </span>{ <span class="hljs-keyword">var</span> referencedModel = resolveRelClass(<span class="hljs-keyword">this</span>.references[referenceKey]); <span class="hljs-keyword">return</span> refKeyToIdRefKey(referencedModel, referenceKey); }, get: <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">attr</span>) </span>{ <span class="hljs-keyword">if</span>(<span class="hljs-keyword">this</span>.embeddings[attr] || <span class="hljs-keyword">this</span>.references[attr]) {</pre></div></div> </li> <li id="section-13"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-13">&#182;</a> </div> <p>return related object if the key corresponds to a reference or embedding</p> </div> <div class="content"><div class='highlight'><pre> <span class="hljs-keyword">return</span> <span class="hljs-keyword">this</span>.relatedObjects[attr]; } <span class="hljs-keyword">else</span> {</pre></div></div> </li> <li id="section-14"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-14">&#182;</a> </div> <p>otherwise return the regular attribute</p> </div> <div class="content"><div class='highlight'><pre> <span class="hljs-keyword">return</span> BackboneBase.Model.prototype.get.apply(<span class="hljs-keyword">this</span>, <span class="hljs-built_in">arguments</span>); } },</pre></div></div> </li> <li id="section-15"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-15">&#182;</a> </div> <p>Set a hash of model attributes and relations on the object, firing <code>&quot;change&quot;</code>. This is the core primitive operation of a model, updating the data and notifying anyone who needs to know about the change in state. The heart of the beast. ATTENTION: This is a full override of Backbone’s default implementation meaning that it will not call the base class method. If you are using third Backbone extensions that override #set, make sure that these extend backbone-rel’ Model class.</p> </div> <div class="content"><div class='highlight'><pre> set: <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">key, val, options</span>) </span>{ <span class="hljs-keyword">var</span> attr, attrs, unset, changes, silent, changing, prev, current, referenceKey; <span class="hljs-keyword">if</span>(key === <span class="hljs-literal">null</span>) <span class="hljs-keyword">return</span> <span class="hljs-keyword">this</span>;</pre></div></div> </li> <li id="section-16"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-16">&#182;</a> </div> <p>Handle both <code>&quot;key&quot;, value</code> and <code>{key: value}</code> -style arguments.</p> </div> <div class="content"><div class='highlight'><pre> <span class="hljs-keyword">if</span>(<span class="hljs-keyword">typeof</span> key === <span class="hljs-string">'object'</span>) { attrs = key; options = val; } <span class="hljs-keyword">else</span> { (attrs = {})[key] = val; } options || (options = {});</pre></div></div> </li> <li id="section-17"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-17">&#182;</a> </div> <p>Pass the setOriginId down to the nested set calls via options. Also support <code>nestedSetOptions</code> to define options that shall only be applied to nested</p> <h1 id="set-calls-for-related-object-">set calls for related object.</h1> </div> <div class="content"><div class='highlight'><pre> <span class="hljs-keyword">var</span> nestedOptions = _.extend( { setOriginId: _.uniqueId() }, options, { clear: <span class="hljs-literal">undefined</span> }, <span class="hljs-comment">// `clear` option should not propagate to nested set calls</span> { nestedSetOptions: <span class="hljs-literal">undefined</span> }, options.nestedSetOptions ); <span class="hljs-keyword">if</span>(nestedOptions.collection) {</pre></div></div> </li> <li id="section-18"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-18">&#182;</a> </div> <p><code>collection</code> option should not propagate to nested set calls</p> </div> <div class="content"><div class='highlight'><pre> <span class="hljs-keyword">delete</span> nestedOptions.collection; } <span class="hljs-keyword">this</span>._deepChangePropagatedFor = [];</pre></div></div> </li> <li id="section-19"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-19">&#182;</a> </div> <p>Run validation.</p> </div> <div class="content"><div class='highlight'><pre> <span class="hljs-keyword">if</span>(!<span class="hljs-keyword">this</span>._validate(attrs, options)) <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;</pre></div></div> </li> <li id="section-20"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-20">&#182;</a> </div> <p>Extract attributes and options.</p> </div> <div class="content"><div class='highlight'><pre> unset = options.unset; silent = options.silent; changes = []; changing = <span class="hljs-keyword">this</span>._changing; <span class="hljs-keyword">this</span>._changing = <span class="hljs-literal">true</span>; <span class="hljs-keyword">if</span>(!changing) { <span class="hljs-keyword">this</span>._previousAttributes = _.clone(<span class="hljs-keyword">this</span>.attributes); <span class="hljs-keyword">this</span>._previousRelatedObjects = _.clone(<span class="hljs-keyword">this</span>.relatedObjects); <span class="hljs-keyword">this</span>.changed = {}; } current = <span class="hljs-keyword">this</span>.attributes, prev = <span class="hljs-keyword">this</span>._previousAttributes;</pre></div></div> </li> <li id="section-21"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-21">&#182;</a> </div> <p>Check for changes of <code>id</code>.</p> </div> <div class="content"><div class='highlight'><pre> <span class="hljs-keyword">if</span>(<span class="hljs-keyword">this</span>.idAttribute <span class="hljs-keyword">in</span> attrs) <span class="hljs-keyword">this</span>.id = attrs[<span class="hljs-keyword">this</span>.idAttribute];</pre></div></div> </li> <li id="section-22"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-22">&#182;</a> </div> <p>Precalculate the idRefKeys for all references to improve performance of the lookups</p> </div> <div class="content"><div class='highlight'><pre> <span class="hljs-keyword">var</span> refKeys = _.keys(<span class="hljs-keyword">this</span>.references); <span class="hljs-keyword">var</span> refAndIdRefKeys = {}; <span class="hljs-keyword">var</span> i; <span class="hljs-keyword">for</span>(i=<span class="hljs-number">0</span>; i&lt;refKeys.length; i++) { refAndIdRefKeys[refKeys[i]] = refKeys[i]; refAndIdRefKeys[<span class="hljs-keyword">this</span>.referenceAttributeName(refKeys[i])] = refKeys[i]; } <span class="hljs-keyword">var</span> findReferenceKey = <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">key</span>) </span>{ <span class="hljs-keyword">return</span> refAndIdRefKeys[key]; };</pre></div></div> </li> <li id="section-23"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-23">&#182;</a> </div> <p>If <code>clear</code> is set, calculate the keys to be unset and add those keys to attrs</p> </div> <div class="content"><div class='highlight'><pre> <span class="hljs-keyword">var</span> keysToUnset = []; <span class="hljs-keyword">if</span>(options.clear) { <span class="hljs-keyword">var</span> defaults = _.result(<span class="hljs-keyword">this</span>, <span class="hljs-string">'defaults'</span>) || {}; keysToUnset = _.difference( _.union(_.keys(<span class="hljs-keyword">this</span>.attributes), _.keys(<span class="hljs-keyword">this</span>.relatedObjects)), _.keys(attrs), _.map(_.keys(attrs), findReferenceKey) );</pre></div></div> </li> <li id="section-24"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-24">&#182;</a> </div> <p>clone because the keysToUnset array is modified from within the loop</p> </div> <div class="content"><div class='highlight'><pre> <span class="hljs-keyword">var</span> keysToUnsetCopy = _.clone(keysToUnset); <span class="hljs-keyword">for</span>(i=<span class="hljs-number">0</span>, l=keysToUnsetCopy.length; i&lt;l; ++i) { <span class="hljs-keyword">var</span> keyToUnset = keysToUnsetCopy[i]; <span class="hljs-keyword">if</span>(defaults.hasOwnProperty(keyToUnset)) {</pre></div></div> </li> <li id="section-25"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-25">&#182;</a> </div> <p>reset to default value instead of deleting</p> </div> <div class="content"><div class='highlight'><pre> <span class="hljs-keyword">var</span> defVal = defaults[keyToUnset];</pre></div></div> </li> <li id="section-26"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-26">&#182;</a> </div> <p>ensure that the default for a reference/embedding is an actual Backbone model/collection and not just a plain JSON hash or array (which would be applied to the current referenced objects instead of replacing them)</p> </div> <div class="content"><div class='highlight'><pre> <span class="hljs-keyword">if</span>(defVal &amp;&amp; !defVal._representsToMany &amp;&amp; !defVal._representsToOne) { <span class="hljs-keyword">var</span> relationship = <span class="hljs-keyword">this</span>.embeddings[keyToUnset] || <span class="hljs-keyword">this</span>.references[keyToUnset]; <span class="hljs-keyword">var</span> RelClass = relationship &amp;&amp; resolveRelClass(relationship); <span class="hljs-keyword">if</span>(RelClass) defVal = <span class="hljs-keyword">new</span> RelClass(defVal, nestedOptions); } attrs[keyToUnset] = defVal; keysToUnset = _.without(keysToUnset, keyToUnset); } <span class="hljs-keyword">else</span> {</pre></div></div> </li> <li id="section-27"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-27">&#182;</a> </div> <p>add to attrs just to make sure the key will be traversed in the for loop</p> </div> <div class="content"><div class='highlight'><pre> attrs[keyToUnset] = <span class="hljs-keyword">void</span> <span class="hljs-number">0</span>; } } }</pre></div></div> </li> <li id="section-28"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-28">&#182;</a> </div> <p>For each <code>set</code> attribute, update or delete the current value.</p> </div> <div class="content"><div class='highlight'><pre> <span class="hljs-keyword">for</span> (attr <span class="hljs-keyword">in</span> attrs) { val = attrs[attr]; <span class="hljs-keyword">if</span>(<span class="hljs-keyword">this</span>.embeddings[attr]) { <span class="hljs-keyword">var</span> opts = _.extend({}, nestedOptions, { clear: options.clear }, { unset: unset || _.contains(keysToUnset, attr) }); <span class="hljs-keyword">this</span>._setEmbedding(attr, val, opts, changes); } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span>(referenceKey = findReferenceKey(attr)) {</pre></div></div> </li> <li id="section-29"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-29">&#182;</a> </div> <p>side-loaded JSON structures take precedence over ID references</p> </div> <div class="content"><div class='highlight'><pre> <span class="hljs-keyword">if</span>(attr !== referenceKey &amp;&amp; attrs[referenceKey]) {</pre></div></div> </li> <li id="section-30"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-30">&#182;</a> </div> <p>is ID ref, but also side-loaded data is present in attrs</p> </div> <div class="content"><div class='highlight'><pre> <span class="hljs-keyword">continue</span>; <span class="hljs-comment">// ignore attr</span> } <span class="hljs-keyword">var</span> opts = _.extend({}, nestedOptions, { unset: unset || _.contains(keysToUnset, referenceKey) }); <span class="hljs-keyword">this</span>._setReference(referenceKey, val, opts, changes); } <span class="hljs-keyword">else</span> {</pre></div></div> </li> <li id="section-31"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-31">&#182;</a> </div> <p>default Backbone behavior for plain attribute set</p> </div> <div class="content"><div class='highlight'><pre> <span class="hljs-keyword">if</span>(!_.isEqual(current[attr], val)) changes.push(attr); <span class="hljs-keyword">if</span>(!_.isEqual(prev[attr], val)) { <span class="hljs-keyword">this</span>.changed[attr] = val; } <span class="hljs-keyword">else</span> { <span class="hljs-keyword">delete</span> <span class="hljs-keyword">this</span>.changed[attr]; } unset || _.contains(keysToUnset, attr) ? <span class="hljs-keyword">delete</span> current[attr] : current[attr] = val; } } <span class="hljs-keyword">var</span> currentAll = _.extend({}, current, <span class="hljs-keyword">this</span>.relatedObjects);</pre></div></div> </li> <li id="section-32"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-32">&#182;</a> </div> <p>Trigger all relevant attribute changes.</p> </div> <div class="content"><div class='highlight'><pre> <span class="hljs-keyword">if</span>(!silent) { <span class="hljs-keyword">if</span>(changes.length) <span class="hljs-keyword">this</span>._pending = <span class="hljs-literal">true</span>; <span class="hljs-keyword">var</span> l; <span class="hljs-keyword">for</span> (i = <span class="hljs-number">0</span>, l = changes.length; i &lt; l; i++) { <span class="hljs-keyword">this</span>.trigger(<span class="hljs-string">'change:'</span> + changes[i], <span class="hljs-keyword">this</span>, currentAll[changes[i]], options); } }</pre></div></div> </li> <li id="section-33"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-33">&#182;</a> </div> <p>You might be wondering why there’s a <code>while</code> loop here. Changes can be recursively nested within <code>&quot;change&quot;</code> events.</p> </div> <div class="content"><div class='highlight'><pre> <span class="hljs-keyword">if</span>(changing) <span class="hljs-keyword">return</span> <span class="hljs-keyword">this</span>; <span class="hljs-keyword">if</span>(!silent) { <span class="hljs-keyword">while</span> (<span class="hljs-keyword">this</span>._pending) { <span class="hljs-keyword">this</span>._pending = <span class="hljs-literal">false</span>; <span class="hljs-keyword">this</span>.trigger(<span class="hljs-string">'change'</span>, <span class="hljs-keyword">this</span>, options); } } <span class="hljs-keyword">this</span>._pending = <span class="hljs-literal">false</span>; <span class="hljs-keyword">this</span>._changing = <span class="hljs-literal">false</span>;</pre></div></div> </li> <li id="section-34"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-34">&#182;</a> </div> <p>Trigger original ‘deepchange’ event, which will be propagated through the related object graph</p> </div> <div class="content"><div class='highlight'><pre> <span class="hljs-keyword">if</span>(!silent &amp;&amp; changes.length &amp;&amp; !_.contains(<span class="hljs-keyword">this</span>._deepChangePropagatedFor, nestedOptions.setOriginId)) { <span class="hljs-keyword">this</span>._deepChangePropagatedFor.push(nestedOptions.setOriginId); <span class="hljs-keyword">this</span>.trigger(<span class="hljs-string">'deepchange'</span>, <span class="hljs-keyword">this</span>, _.extend({ setOriginId: nestedOptions.setOriginId }, options)); <span class="hljs-keyword">this</span>.trigger(<span class="hljs-string">'deepchange_propagated'</span>, <span class="hljs-keyword">this</span>, _.extend({ setOriginId: nestedOptions.setOriginId }, options)); }</pre></div></div> </li> <li id="section-35"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-35">&#182;</a> </div> <p>finally, fetch all related objects that need a fetch</p> </div> <div class="content"><div class='highlight'><pre> <span class="hljs-keyword">this</span>._fetchRelatedObjects(); <span class="hljs-keyword">return</span> <span class="hljs-keyword">this</span>; },</pre></div></div> </li> <li id="section-36"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-36">&#182;</a> </div> <p>Fetches the related object for each key in the provided keys array If no keys array is provided, it fetches the related objects for all relations that have not been synced before</p> </div> <div class="content"><div class='highlight'><pre> fetchRelated: <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">keys</span>) </span>{ <span class="hljs-keyword">if</span>(!keys) { <span class="hljs-keyword">var</span> embeddingKeys = _.filter(_.keys(<span class="hljs-keyword">this</span>.embeddings), <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">key</span>) </span>{ <span class="hljs-keyword">return</span> !<span class="hljs-keyword">this</span>.get(key) || (!<span class="hljs-keyword">this</span>.get(key).isSyncing &amp;&amp; !<span class="hljs-keyword">this</span>.get(key).isSynced); }, <span class="hljs-keyword">this</span>); <span class="hljs-keyword">var</span> referencesKeys = _.filter(_.keys(<span class="hljs-keyword">this</span>.references), <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">key</span>) </span>{ <span class="hljs-keyword">return</span> <span class="hljs-keyword">this</span>.get(key) &amp;&amp; (!<span class="hljs-keyword">this</span>.get(key).isSyncing &amp;&amp; !<span class="hljs-keyword">this</span>.get(key).isSynced); }, <span class="hljs-keyword">this</span>); keys = _.union(embeddingKeys, referencesKeys); } <span class="hljs-keyword">if</span>(_.isString(keys)) { keys = [keys]; } <span class="hljs-keyword">for</span>(<span class="hljs-keyword">var</span> i=<span class="hljs-number">0</span>; i&lt;keys.length; i++) { <span class="hljs-keyword">var</span> key = keys[i]; <span class="hljs-keyword">if</span>(!<span class="hljs-keyword">this</span>.embeddings[key] &amp;&amp; !<span class="hljs-keyword">this</span>.references[key]) { <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">"Invalid relationship key '"</span> + key + <span class="hljs-string">"'"</span>); }</pre></div></div> </li> <li id="section-37"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-37">&#182;</a> </div> <p>init embeddings</p> </div> <div class="content"><div class='highlight'><pre> <span class="hljs-keyword">if</span>(!<span class="hljs-keyword">this</span>.get(key) &amp;&amp; <span class="hljs-keyword">this</span>.embeddings[key]) { <span class="hljs-keyword">var</span> RelClass = resolveRelClass(<span class="hljs-keyword">this</span>.embeddings[key]); <span class="hljs-keyword">this</span>.set(key, <span class="hljs-keyword">new</span> RelClass()); } <span class="hljs-keyword">var</span> relatedObject = <span class="hljs-keyword">this</span>.get(key); <span class="hljs-keyword">if</span>(relatedObject &amp;&amp; !relatedObject.isSyncing &amp;&amp; !_.contains(<span class="hljs-keyword">this</span>._relatedObjectsToFetch, relatedObject)) { <span class="hljs-keyword">this</span>._relatedObjectsToFetch.push(relatedObject); } } <span class="hljs-keyword">this</span>._fetchRelatedObjects(); },</pre></div></div> </li> <li id="section-38"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-38">&#182;</a> </div> <p>Sets the parent of an embedded object If the optional keyInParent parameter is omitted, is is automatically detected</p> </div> <div class="content"><div class='highlight'><pre> setParent: <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">parent, keyInParent</span>) </span>{ <span class="hljs-keyword">var</span> self = <span class="hljs-keyword">this</span>; <span class="hljs-keyword">this</span>.keyInParent = keyInParent || _.find(_.keys(parent.embeddings), <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">key</span>) </span>{ <span class="hljs-keyword">return</span> parent.get(key) === self; }); <span class="hljs-keyword">if</span>(!<span class="hljs-keyword">this</span>.keyInParent) { <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">"A key for the embedding in the parent must be specified as it could not be detected automatically."</span>); } <span class="hljs-keyword">this</span>.parent = parent; <span class="hljs-keyword">if</span>(<span class="hljs-keyword">this</span>.parent.get(<span class="hljs-keyword">this</span>.keyInParent) !== <span class="hljs-keyword">this</span>) { <span class="hljs-keyword">this</span>.parent.set(<span class="hljs-keyword">this</span>.keyInParent, <span class="hljs-keyword">this</span>); } <span class="hljs-keyword">this</span>.trigger(<span class="hljs-string">"embedded"</span>, <span class="hljs-keyword">this</span>, parent, keyInParent); },</pre></div></div> </li> <li id="section-39"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-39">&#182;</a> </div> <p>Override #previous to add support for getting previous values of references and embeddings in “change” events</p> </div> <div class="content"><div class='highlight'><pre> previous: <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">attr</span>) </span>{ <span class="hljs-keyword">var</span> result = BackboneBase.Model.prototype.previous.apply(<span class="hljs-keyword">this</span>, <span class="hljs-built_in">arguments</span>); <span class="hljs-keyword">if</span>(result) <span class="hljs-keyword">return</span> result; <span class="hljs-keyword">if</span> (attr === <span class="hljs-literal">null</span> || !<span class="hljs-keyword">this</span>._previousRelatedObjects) <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>; <span class="hljs-keyword">return</span> <span class="hljs-keyword">this</span>._previousRelatedObjects[attr]; },</pre></div></div> </li> <li id="section-40"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-40">&#182;</a> </div> <p>Override #toJSON to add support for inlining JSON representations of related objects in the JSON of this model. The related objects to be inlined can be specified via the <code>inlineJSON</code> property or option.</p> </div> <div class="content"><div class='highlight'><pre> toJSON: <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">options</span>) </span>{ options = options || {}; <span class="hljs-keyword">var</span> self = <span class="hljs-keyword">this</span>; <span class="hljs-keyword">var</span> json = BackboneBase.Model.prototype.toJSON.apply(<span class="hljs-keyword">this</span>, <span class="hljs-built_in">arguments</span>); <span class="hljs-keyword">var</span> inlineJSON = _.uniq(_.compact(_.flatten( _.union([options.inlineJSON], [_.result(<span class="hljs-keyword">this</span>, <span class="hljs-string">"inlineJSON"</span>)]) ))); _.each(inlineJSON, <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">key</span>) </span>{ <span class="hljs-keyword">var</span> obj = self; <span class="hljs-keyword">var</span> path = key.split(<span class="hljs-string">"."</span>), nestedJson = json; <span class="hljs-keyword">while</span>(obj &amp;&amp; path.length &gt; <span class="hljs-number">0</span> &amp;&amp; _.isFunction(obj.toJSON)) { key = path.shift(); obj = obj.get(key); <span class="hljs-keyword">if</span>(obj &amp;&amp; _.isFunction(obj.toJSON)) {</pre></div></div> </li> <li id="section-41"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-41">&#182;</a> </div> <p>nest JSON represention ob embedded object into the hierarchy</p> </div> <div class="content"><div class='highlight'><pre> nestedJson[key] = obj.toJSON(); nestedJson = nestedJson[key]; } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span>(obj===<span class="hljs-literal">null</span>) {</pre></div></div> </li> <li id="section-42"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-42">&#182;</a> </div> <p>if an embedded object was unset, i.e., set to null, we have to notify the server by nesting a null value into the JSON hierarchy</p> </div> <div class="content"><div class='highlight'><pre> nestedJson[key] = <span class="hljs-literal">null</span>; } } }); <span class="hljs-keyword">return</span> json; },</pre></div></div> </li> <li id="section-43"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-43">&#182;</a> </div> <p>Override #fetch to add support for auto-fetching of embedded objects</p> </div> <div class="content"><div class='highlight'><pre> fetch: <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) </span>{ <span class="hljs-keyword">var</span> result = BackboneBase.Model.prototype.fetch.apply(<span class="hljs-keyword">this</span>, <span class="hljs-built_in">arguments</span>); <span class="hljs-keyword">this</span>._autoFetchEmbeddings(); <span class="hljs-keyword">return</span> result; },</pre></div></div> </li> <li id="section-44"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-44">&#182;</a> </div> <p>Override #sync to force PUT method when creating embedded models</p> </div> <div class="content"><div class='highlight'><pre> sync: <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">method, obj, options</span>) </span>{ <span class="hljs-keyword">this</span>._beforeSync(); options = wrapOptionsCallbacks(<span class="hljs-keyword">this</span>._afterSyncBeforeSet.bind(<span class="hljs-keyword">this</span>), options); <span class="hljs-keyword">if</span>(<span class="hljs-keyword">this</span>.parent &amp;&amp; method === <span class="hljs-string">"create"</span>) method = <span class="hljs-string">"update"</span>; <span class="hljs-comment">// always PUT embedded models</span> <span class="hljs-keyword">if</span>(options.forceMethod) { method = options.forceMethod; } <span class="hljs-keyword">return</span> BackboneBase.Model.prototype.sync.apply(<span class="hljs-keyword">this</span>, <span class="hljs-b