backbone-rel
Version:
Relationships between Backbone models in the flavor of MongoDB's document references and embeddings
2 lines • 16.7 kB
JavaScript
(function(e,t){if(typeof define==="function"&&define.amd){define(["underscore","backbone","exports"],function(i,s,r){e.Backbone=t(e,r,i,s)})}else if(typeof exports!=="undefined"){var i=require("underscore"),s=require("backbone");t(e,exports,i,s)}else{e.Backbone=t(e,{},e._,e.Backbone)}})(this,function(e,t,i,s){var r=i.extend({},s);var n=["url","urlRoot","collection"];r.Model=s.Model.extend({references:{},embeddings:{},inlineJSON:[],autoFetchRelated:true,constructor:function(e,t){var s=e||{};this.cid=i.uniqueId("c");t||(t={});this.attributes={};i.extend(this,i.pick(t,n));this.relatedObjects={};this._relatedObjectsToFetch=[];this._updateIdRefFor={};var r,h=this.references,a=this.referenceAttributeName.bind(this);if(t.parse)s=this.parse(s,t)||{};if(r=i.result(this,"defaults")){r=i.extend({},r);i.each(i.keys(h),function(e){if(a(e)in s&&e in r){delete r[e]}});s=i.defaults({},s,r)}this.set(s,t);this.changed={};if(!this.isNew()){this._autoFetchEmbeddings(true)}this.initialize.apply(this,arguments)},url:function(){var e=i.result(this,"urlRoot")||i.result(this.collection,"url");var t=i.result(this,"urlSuffix");if(e){if(this.isNew())return e;return e.replace(/([^\/])$/,"$1/")+encodeURIComponent(this.id)}else if(this.parent){if(this.parent.isNew()&&!this.parent.parent){throw new Error("Could not get the parent model's URL as it has not been saved yet.")}e=i.result(this.parent,"url");if(e&&t){return e.replace(/([^\/])$/,"$1/")+t.replace(/(\/?)(.*)/,"$2")}}throw new Error('Could not build url for the model with ID "'+this.id+'" (URL suffix: "'+t+'")')},urlSuffix:function(){var e=this,t=this.parent;return t&&i.find(i.keys(t.embeddings),function(i){return t.get(i)===e})},referenceAttributeName:function(e){var t=h(this.references[e]);return o(t,e)},get:function(e){if(this.embeddings[e]||this.references[e]){return this.relatedObjects[e]}else{return s.Model.prototype.get.apply(this,arguments)}},set:function(e,t,s){var r,n,a,o,c,d,u,l,f;if(e===null)return this;if(typeof e==="object"){n=e;s=t}else{(n={})[e]=t}s||(s={});var p=i.extend({setOriginId:i.uniqueId()},s,{clear:undefined},{nestedSetOptions:undefined},s.nestedSetOptions);if(p.collection){delete p.collection}this._deepChangePropagatedFor=[];if(!this._validate(n,s))return false;a=s.unset;c=s.silent;o=[];d=this._changing;this._changing=true;if(!d){this._previousAttributes=i.clone(this.attributes);this._previousRelatedObjects=i.clone(this.relatedObjects);this.changed={}}l=this.attributes,u=this._previousAttributes;if(this.idAttribute in n)this.id=n[this.idAttribute];var g=i.keys(this.references);var b={};var y;for(y=0;y<g.length;y++){b[g[y]]=g[y];b[this.referenceAttributeName(g[y])]=g[y]}var O=function(e){return b[e]};var _=[];if(s.clear){var v=i.result(this,"defaults")||{};_=i.difference(i.union(i.keys(this.attributes),i.keys(this.relatedObjects)),i.keys(n),i.map(i.keys(n),O));var j=i.clone(_);for(y=0,I=j.length;y<I;++y){var F=j[y];if(v.hasOwnProperty(F)){var m=v[F];if(m&&!m._representsToMany&&!m._representsToOne){var T=this.embeddings[F]||this.references[F];var S=T&&h(T);if(S)m=new S(m,p)}n[F]=m;_=i.without(_,F)}else{n[F]=void 0}}}for(r in n){t=n[r];if(this.embeddings[r]){var R=i.extend({},p,{clear:s.clear},{unset:a||i.includes(_,r)});this._setEmbedding(r,t,R,o)}else if(f=O(r)){if(r!==f&&n[f]){continue}var R=i.extend({},p,{unset:a||i.includes(_,f)});this._setReference(f,t,R,o)}else{if(!i.isEqual(l[r],t))o.push(r);if(!i.isEqual(u[r],t)){this.changed[r]=t}else{delete this.changed[r]}a||i.includes(_,r)?delete l[r]:l[r]=t}}var w=i.extend({},l,this.relatedObjects);if(!c){if(o.length)this._pending=true;var I;for(y=0,I=o.length;y<I;y++){this.trigger("change:"+o[y],this,w[o[y]],s)}}if(d)return this;if(!c){while(this._pending){this._pending=false;this.trigger("change",this,s)}}this._pending=false;this._changing=false;if(!c&&o.length&&!i.includes(this._deepChangePropagatedFor,p.setOriginId)){this._deepChangePropagatedFor.push(p.setOriginId);this.trigger("deepchange",this,i.extend({setOriginId:p.setOriginId},s));this.trigger("deepchange_propagated",this,i.extend({setOriginId:p.setOriginId},s))}this._fetchRelatedObjects();return this},fetchRelated:function(e){if(!e){var t=i.filter(i.keys(this.embeddings),function(e){return!this.get(e)||!this.get(e).isSyncing&&!this.get(e).isSynced}.bind(this));var s=i.filter(i.keys(this.references),function(e){return this.get(e)&&(!this.get(e).isSyncing&&!this.get(e).isSynced)}.bind(this));e=i.union(t,s)}if(i.isString(e)){e=[e]}for(var r=0;r<e.length;r++){var n=e[r];if(!this.embeddings[n]&&!this.references[n]){throw new Error("Invalid relationship key '"+n+"'")}if(!this.get(n)&&this.embeddings[n]){var a=h(this.embeddings[n]);this.set(n,new a)}var o=this.get(n);if(o&&!o.isSyncing&&!i.includes(this._relatedObjectsToFetch,o)){this._relatedObjectsToFetch.push(o)}}this._fetchRelatedObjects()},setParent:function(e,t){var s=this;this.keyInParent=t||i.find(i.keys(e.embeddings),function(t){return e.get(t)===s});if(!this.keyInParent){throw new Error("A key for the embedding in the parent must be specified as it could not be detected automatically.")}this.parent=e;if(this.parent.get(this.keyInParent)!==this){this.parent.set(this.keyInParent,this)}this.trigger("embedded",this,e,t)},previous:function(e){var t=s.Model.prototype.previous.apply(this,arguments);if(t)return t;if(e===null||!this._previousRelatedObjects)return null;return this._previousRelatedObjects[e]},toJSON:function(e){e=e||{};var t=this;var r=s.Model.prototype.toJSON.apply(this,arguments);var n=i.uniq(i.compact(i.flatten(i.union([e.inlineJSON],[i.result(this,"inlineJSON")]))));i.each(n,function(e){var s=t;var n=e.split("."),h=r;while(s&&n.length>0&&i.isFunction(s.toJSON)){e=n.shift();s=s.get(e);if(s&&i.isFunction(s.toJSON)){h[e]=s.toJSON();h=h[e]}else if(s===null){h[e]=null}}});return r},fetch:function(){var e=s.Model.prototype.fetch.apply(this,arguments);this._autoFetchEmbeddings();return e},sync:function(e,t,i){this._beforeSync();i=c(this._afterSyncBeforeSet.bind(this),i);if(this.parent&&e==="create")e="update";if(i.forceMethod){e=i.forceMethod}return s.Model.prototype.sync.apply(this,arguments)},_setEmbedding:function(e,t,i,s){var r=h(this.embeddings[e]);var n=this.relatedObjects[e];if(i.unset||i.clear){delete this.relatedObjects[e]}if(!i.unset&&t&&t!==n){if(t._representsToMany||t._representsToOne){this.relatedObjects[e]=t;this.relatedObjects[e].setParent(this,e)}else if(!this.relatedObjects[e]){this.relatedObjects[e]=new r(t,i);this.relatedObjects[e].setParent(this,e)}else{if(this.relatedObjects[e]._representsToMany){this.relatedObjects[e][i.reset?"reset":"set"](t,i)}else{if(i.parse){t=this.relatedObjects[e].parse(t,i)}this.relatedObjects[e].set(t,i)}}}else if(!i.unset){this.relatedObjects[e]=t}if(n!==this.relatedObjects[e]){s.push(e);this._listenToRelatedObject(e,n);if(n){n.parent=null}}if(this._previousRelatedObjects[e]!==this.relatedObjects[e]){this.changed[e]=this.relatedObjects[e]}else{delete this.changed[e]}},_setReference:function(e,t,s,r){var n=h(this.references[e]),a=this.referenceAttributeName(e);var o=this.relatedObjects[e],c=this.attributes[a];if(s.unset||s.clear){delete this.relatedObjects[e];delete this.attributes[a]}if(!s.unset&&t!==undefined&&t!==null){if(n.prototype._representsToOne){this._setToOneReference(e,n,t,s)}else if(n.prototype._representsToMany){this._setToManyReference(e,n,t,s)}this._ensureIdReference(a,e)}else if(!s.unset){this.relatedObjects[e]=t;this.attributes[a]=t}if(!i.isEqual(c,this.attributes[a])){r.push(a)}if(o!==this.relatedObjects[e]){r.push(e);this._listenToRelatedObject(e,o)}if(this._previousRelatedObjects[e]!==this.relatedObjects[e]){this.changed[e]=this.relatedObjects[e]}else{delete this.changed[e]}if(!i.isEqual(this._previousAttributes[a],this.attributes[a])){this.changed[a]=this.attributes[a]}else{delete this.changed[a]}},_ensureIdReference:function(e,t){var s=this.relatedObjects[t];if(s._representsToOne){if(s.isNew()){if(this.attributes[e]){delete this.attributes[e]}s.once("change:"+(s.idAttribute||"id"),function(){this.set(e,s.id)},this)}else{this.attributes[e]=s.id}}else{var r=false,n;this.attributes[e]=i.compact(s.map(function(e){if(e.isNew()){r=true;n=e.idAttribute||"id";return undefined}else{return e.id}}));if(r){s.once("change:"+n,this._ensureIdReference.bind(this,e,t))}}},_setToOneReference:function(e,t,s,r){var n=this.relatedObjects[e];var h=s[t.prototype.idAttribute||"id"]||s;if(n&&n.id!==h&&(n[n.idAttribute||"id"]||!(s instanceof Object))){n=undefined}if(s._representsToOne){if(s===n)return;n=s;this.relatedObjects[e]=n;return}if(s instanceof Object){if(n){if(r.parse){s=n.parse(s,r)}n.set(s,r)}else{n=new t(s,r)}n.isSynced=true;if(n!==this)this._relatedObjectsToFetch=i.without(this._relatedObjectsToFetch,n)}else{if(!n){var a={};a[t.prototype.idAttribute||"id"]=h;n=new t(a,r);var o=this.autoFetchRelated===true||i.isArray(this.autoFetchRelated)&&i.includes(this.autoFetchRelated,e);var c;try{c=i.result(n,"url")}catch(d){if(o&&console&&i.isFunction(console.warn)){console.warn("Could not build url to auto-fetch referenced model for key '"+e+"'",d.stack)}}if(o&&c&&!n.isSynced&&!n.isSyncing&&!i.includes(this._relatedObjectsToFetch,n)){this._relatedObjectsToFetch.push(n)}}}this.relatedObjects[e]=n},_setToManyReference:function(e,t,s,n){var h=t.prototype.model;var a=this.relatedObjects[e];if(s._representsToMany){if(s===a)return;if(a){a.parent=undefined}a=s;a.parent=this;this.relatedObjects[e]=a;return}if(!i.isArray(s)){throw new Error("Got an unexpected value to set reference '"+e+"'")}if(!a){a=new t([],{parent:this})}var o=i.map(s,function(t){var s=t.id||t;var o=a.get(s);if(t instanceof r.Model){return t}if(t instanceof Object){if(o){if(n.parse){t=o.parse(t,n)}o.set(t,n)}else{o=new h(t,n)}o.isSynced=true;if(o!==this){this._relatedObjectsToFetch=i.without(this._relatedObjectsToFetch,o)}}else{if(!o){var c={};c[h.prototype.idAttribute||"id"]=s;o=new h(c,i.extend({},n,{parse:undefined}));var d=this.autoFetchRelated===true||i.isArray(this.autoFetchRelated)&&i.includes(this.autoFetchRelated,e);var u;try{u=i.result(o,"url")}catch(l){if(d&&console&&i.isFunction(console.warn)){console.warn("Could not build url to auto-fetch referenced model for key '"+e+"'",l)}}if(d&&u&&!o.isSynced&&!o.isSyncing&&!i.includes(this._relatedObjectsToFetch,o)){this._relatedObjectsToFetch.push(o)}}}return o}.bind(this));a.set(o,{merge:false});this.relatedObjects[e]=a},_listenToRelatedObject:function(e,t){if(t){this.stopListening(t,"deepchange",this._propagateDeepChange);if(t._representsToOne){this.stopListening(t,"destroy",this._relatedObjectDestroyHandler);this.stopListening(t,"change:"+(t.idAttribute||"id"),this._updateIdRefFor[e])}else{this.stopListening(t,"add remove reset change:"+(t.idAttribute||"id"),this._updateIdRefFor[e])}}if(this.relatedObjects[e]){this.listenTo(this.relatedObjects[e],"deepchange",this._propagateDeepChange);if(this.relatedObjects[e]._representsToOne){this.listenTo(this.relatedObjects[e],"destroy",this._relatedObjectDestroyHandler);this._updateIdRefFor[e]=this._updateIdRefFor[e]||this._updateIdRef.bind(this,e);this.listenTo(this.relatedObjects[e],"change:"+(this.relatedObjects[e].idAttribute||"id"),this._updateIdRefFor[e])}else{this._updateIdRefFor[e]=this._updateIdRefFor[e]||this._updateIdRef.bind(this,e);this.listenTo(this.relatedObjects[e],"add remove reset change:"+(this.relatedObjects[e].idAttribute||"id"),this._updateIdRefFor[e])}}},_updateIdRef:function(e){if(this.references[e]){var t=this.referenceAttributeName(e);this._ensureIdReference(t,e);this.trigger("change:"+t,this,this.get(t),{});this.trigger("change",this,{})}},_autoFetchEmbeddings:function(e){var t=i.keys(this.embeddings);for(var s=0;s<t.length;s++){var r=t[s];var n=this.autoFetchRelated===true||i.isArray(this.autoFetchRelated)&&i.includes(this.autoFetchRelated,r);if(n){if(!this.get(r)){var a=h(this.embeddings[r]);this.set(r,new a)}else if(e){continue}var o=this.get(r);if(!o.isSyncing&&!i.includes(this._relatedObjectsToFetch,o)){this._relatedObjectsToFetch.push(o)}}}this._fetchRelatedObjects()},_beforeSync:function(){this.isSyncing=true;this._relatedObjectsToFetch.push(this);var e=this;var t=function(){e._relatedObjectFetchSuccessHandler(e);e.off("error",i)};var i=function(){e._relatedObjectsToFetch.splice(e._relatedObjectsToFetch.indexOf(e),1);e.off("sync",t)};this.once("sync",t);this.once("error",i)},_afterSyncBeforeSet:function(){this.isSynced=true;delete this.isSyncing},_propagateDeepChange:function(e,t){if(i.includes(this._deepChangePropagatedFor,t.setOriginId)){return}this._deepChangePropagatedFor.push(t.setOriginId);this.trigger("deepchange",e,t);e.once("deepchange_propagated",function(){this.trigger("deepchange_propagated",e,t)},this)},_fetchRelatedObjects:function(){for(var e=0;e<this._relatedObjectsToFetch.length;e++){var t=this._relatedObjectsToFetch[e];if(t===this)continue;if(t.isSyncing){t.once("sync",this._relatedObjectFetchSuccessHandler.bind(this,t));continue}else if(t.isSynced){this._relatedObjectFetchSuccessHandler(t);continue}t.fetch({success:this._relatedObjectFetchSuccessHandler.bind(this),error:this._relatedObjectFetchErrorHandler.bind(this),isAutoFetch:true})}},_relatedObjectFetchSuccessHandler:function(e){this._relatedObjectsToFetch.splice(this._relatedObjectsToFetch.indexOf(e),1);if(this._relatedObjectsToFetch.length===0){this.trigger("deepsync",this)}},_relatedObjectFetchErrorHandler:function(e,t,i){this._relatedObjectsToFetch.splice(this._relatedObjectsToFetch.indexOf(e),1);this.trigger("error",e,t,i)},_relatedObjectDestroyHandler:function(e){i.each(this.relatedObjects,function(t,i){if(t===e){this.unset(i)}}.bind(this))},_representsToOne:true});r.Collection=s.Collection.extend({constructor:function(){var e=function(e){e=e||{};var t=e.setOriginId||i.uniqueId();this._deepChangePropagatedFor.push(t);this.trigger("deepchange",this,i.extend({setOriginId:t},e));this.trigger("deepchange_propagated",this,i.extend({setOriginId:t},e))}.bind(this);this.on("add remove",function(t,i,s){e(s)});this.on("reset",function(t,i){e(i)});this.on("sort",function(t,i){e(i)});return s.Collection.prototype.constructor.apply(this,arguments)},url:function(){var e=i.result(this,"urlRoot");if(e){return e}else if(this.parent){if(this.parent.isNew()&&!this.parent.parent){throw new Error("Could not get the parent model's URL as it has not been saved yet.")}e=i.result(this.parent,"url");var t=i.result(this,"urlSuffix");if(e&&t){return e.replace(/([^\/])$/,"$1/")+t.replace(/(\/?)(.*)/,"$2")}}throw new Error("Could not build url for the collection")},urlSuffix:function(){var e=this,t=this.parent;return t&&i.find(i.keys(t.embeddings),function(i){return t.get(i)===e})},set:function(){this._deepChangePropagatedFor=[];return s.Collection.prototype.set.apply(this,arguments)},setParent:function(e,t){var s=this;this.keyInParent=t||i.find(i.keys(e.embeddings),function(t){return e.get(t)===s});if(!this.keyInParent){throw new Error("A key for the embedding in the parent must be specified as it could not be detected automatically.")}this.parent=e;if(this.parent.get(this.keyInParent)!==this){this.parent.set(this.keyInParent,this)}this.trigger("embedded",this,e,t)},sync:function(){this._beforeSync();return s.Collection.prototype.sync.apply(this,arguments)},fetch:function(e){e=c(this._afterSetBeforeTrigger.bind(this),e);return s.Collection.prototype.fetch.apply(this,[e])},_beforeSync:function(){this.isSyncing=true},_prepareModel:function(){var e=s.Collection.prototype._prepareModel.apply(this,arguments);if(e&&this.isSyncing){e.isSynced=true}return e},_afterSetBeforeTrigger:function(){this.isSynced=true;delete this.isSyncing;var e=function(){this.trigger("deepsync",this)}.bind(this);var t,s=0,r=function(){t()};this.each(function(e){if(e.isSyncing){s++;e.once("deepsync",r)}});if(s>0){t=i.after(s,e)}else{e()}},_representsToMany:true});function h(e){if(i.isFunction(e)){if(e.prototype._representsToOne||e.prototype._representsToMany){return e}else{var t=e();if(!t.prototype._representsToOne&&!t.prototype._representsToMany){throw new Error("The model class for the relation could not be resolved. "+"It must extend either Backbone.Model or Backbone.Collection and the "+"backbone-rel extension must be loaded")}return t}}else{throw new Error("Cannot resolve relation class from "+e)}}function a(e){return e.charAt(0).toUpperCase()+e.substring(1)}function o(e,t){var i=e.prototype.idAttribute||"id";if(e.prototype._representsToMany){return t.replace(/s{0,1}$/,a(i)+"s")}else{return t+a(i)}}function c(e,t){t=t||{};var i=t.success;var s=t.error;t.success=function(s){e(t);if(i)i(s)};t.error=function(i){e(t);if(s)s(i)};return t}i.extend(t,r);return r});
//# sourceMappingURL=backbone-rel-min.js.map