backbone-associations-rails 0.4.2 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/Rakefile
CHANGED
@@ -44,11 +44,12 @@ namespace :backbone_associations do
|
|
44
44
|
exit
|
45
45
|
end
|
46
46
|
|
47
|
-
# Update
|
47
|
+
# Update backbone-associations
|
48
48
|
puts "Updating backbone-associations..."
|
49
|
-
|
49
|
+
base_url = "https://raw.github.com/dhruvaray/backbone-associations/#{sha}"
|
50
|
+
files = %w{backbone-associations.js backbone-associations-min.js}
|
50
51
|
Dir.chdir './vendor/assets/javascripts' do
|
51
|
-
`curl -O #{
|
52
|
+
files.each {|file| `curl -O #{base_url}/#{file}`}
|
52
53
|
end
|
53
54
|
|
54
55
|
# Update version file
|
@@ -0,0 +1,11 @@
|
|
1
|
+
(function(){var v=this,g,h,w,m,r,s,z,o,A,B;"undefined"===typeof window?(g=require("underscore"),h=require("backbone"),"undefined"!==typeof exports&&(exports=module.exports=h)):(g=v._,h=v.Backbone);w=h.Model;m=h.Collection;r=w.prototype;s=m.prototype;A=/[\.\[\]]+/g;z="change add remove reset sort destroy".split(" ");B=["reset","sort"];h.Associations={VERSION:"0.5.0"};h.Associations.Many=h.Many="Many";h.Associations.One=h.One="One";o=h.AssociatedModel=h.Associations.AssociatedModel=w.extend({relations:void 0,
|
2
|
+
_proxyCalls:void 0,get:function(a){var c=r.get.call(this,a);return c?c:this._getAttr.apply(this,arguments)},set:function(a,c,d){var b;if(g.isObject(a)||a==null){b=a;d=c}else{b={};b[a]=c}a=this._set(b,d);this._processPendingEvents();return a},_set:function(a,c){var d,b,n,f,j=this;if(!a)return this;for(d in a){b||(b={});if(d.match(A)){var k=x(d);f=g.initial(k);k=k[k.length-1];f=this.get(f);if(f instanceof o){f=b[f.cid]||(b[f.cid]={model:f,data:{}});f.data[k]=a[d]}}else{f=b[this.cid]||(b[this.cid]={model:this,
|
3
|
+
data:{}});f.data[d]=a[d]}}if(b)for(n in b){f=b[n];this._setAttr.call(f.model,f.data,c)||(j=false)}else j=this._setAttr.call(this,a,c);return j},_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&&g.each(this.relations,function(b){var d=b.key,f=b.relatedModel,j=b.collectionType,k=b.map,i=this.attributes[d],y=i&&i.idAttribute,e,q,l,p;f&&g.isString(f)&&(f=t(f));j&&g.isString(j)&&(j=t(j));k&&g.isString(k)&&(k=t(k));q=b.options?g.extend({},
|
4
|
+
b.options,c):c;if(a[d]){e=g.result(a,d);e=k?k(e):e;if(b.type===h.Many){if(j&&!j.prototype instanceof m)throw Error("collectionType must inherit from Backbone.Collection");if(e instanceof m)l=e;else if(i){i._deferEvents=true;i.set(e,c);l=i}else{l=j?new j:this._createCollection(f);l.add(e,q)}}else if(b.type===h.One&&f)if(e instanceof o)l=e;else if(i)if(i&&e[y]&&i.get(y)===e[y]){i._deferEvents=true;i._set(e,c);l=i}else l=new f(e,q);else l=new f(e,q);if((p=a[d]=l)&&!p._proxyCallback){p._proxyCallback=
|
5
|
+
function(){return this._bubbleEvent.call(this,d,p,arguments)};p.on("all",p._proxyCallback,this)}}if(a.hasOwnProperty(d)){b=a[d];f=this.attributes[d];if(b){b.parents=b.parents||[];g.indexOf(b.parents,this)==-1&&b.parents.push(this)}else if(f&&f.parents.length>0)f.parents=g.difference(f.parents,[this])}},this);return r.set.call(this,a,c)},_bubbleEvent:function(a,c,d){var b=d[0].split(":"),n=b[0],f=d[0]=="nested-change",j=d[1],k=d[2],i=-1,h=c._proxyCalls,e,q=g.indexOf(z,n)!==-1;if(!f){g.size(b)>1&&(e=
|
6
|
+
b[1]);g.indexOf(B,n)!==-1&&(k=j);if(c instanceof m&&q&&j){var l=x(e),p=g.initial(l);(b=c.find(function(a){if(j===a)return true;if(!a)return false;var b=a.get(p);if((b instanceof o||b instanceof m)&&j===b)return true;b=a.get(l);if((b instanceof o||b instanceof m)&&j===b||b instanceof m&&k&&k===b)return true}))&&(i=c.indexOf(b))}e=a+(i!==-1&&(n==="change"||e)?"["+i+"]":"")+(e?"."+e:"");if(/\[\*\]/g.test(e))return this;b=e.replace(/\[\d+\]/g,"[*]");i=[];i.push.apply(i,d);i[0]=n+":"+e;h=c._proxyCalls=
|
7
|
+
h||{};if(this._isEventAvailable.call(this,h,e))return this;h[e]=true;if("change"===n){this._previousAttributes[a]=c._previousAttributes;this.changed[a]=c}this.trigger.apply(this,i);"change"===n&&this.get(e)!=d[2]&&this.trigger.apply(this,["nested-change",e,d[1]]);h&&e&&delete h[e];if(e!==b){i[0]=n+":"+b;this.trigger.apply(this,i)}return this}},_isEventAvailable:function(a,c){return g.find(a,function(a,b){return c.indexOf(b,c.length-b.length)!==-1})},_createCollection:function(a){var c=a;g.isString(c)&&
|
8
|
+
(c=t(c));if(c&&c.prototype instanceof o){a=new m;a.model=c}else throw Error("type must inherit from Backbone.AssociatedModel");return a},_processPendingEvents:function(){if(!this.visited){this.visited=true;this._deferEvents=false;g.each(this._pendingEvents,function(a){a.c.trigger.apply(a.c,a.a)});this._pendingEvents=[];g.each(this.relations,function(a){(a=this.attributes[a.key])&&a._processPendingEvents()},this);delete this.visited}},trigger:function(a){if(this._deferEvents){this._pendingEvents=this._pendingEvents||
|
9
|
+
[];this._pendingEvents.push({c:this,a:arguments})}else r.trigger.apply(this,arguments)},toJSON:function(a){var c,d;if(!this.visited){this.visited=true;c=r.toJSON.apply(this,arguments);this.relations&&g.each(this.relations,function(b){var h=this.attributes[b.key];if(h){d=h.toJSON(a);c[b.key]=g.isArray(d)?g.compact(d):d}},this);delete this.visited}return c},clone:function(){return new this.constructor(this.toJSON())},_getAttr:function(a){var c=this,a=x(a),d,b;if(!(g.size(a)<1)){for(b=0;b<a.length;b++){d=
|
10
|
+
a[b];if(!c)break;c=c instanceof m?isNaN(d)?void 0:c.at(d):c.attributes[d]}return c}}});var C=/[^\.\[\]]+/g,x=function(a){return a===""?[""]:g.isString(a)?a.match(C):a||[]},t=function(a){return g.reduce(a.split("."),function(a,d){return a[d]},v)},D=function(a,c,d){var b;g.find(a,function(a){if(b=g.find(a.relations,function(b){return a.get(b.key)===c},this))return true},this);return b&&b.map?b.map(d):d},u={};g.each(["set","remove","reset"],function(a){u[a]=m.prototype[a];s[a]=function(c,d){this.model.prototype instanceof
|
11
|
+
o&&this.parents&&(arguments[0]=D(this.parents,this,c));return u[a].apply(this,arguments)}});u.trigger=s.trigger;s.trigger=function(a){if(this._deferEvents){this._pendingEvents=this._pendingEvents||[];this._pendingEvents.push({c:this,a:arguments})}else u.trigger.apply(this,arguments)};s._processPendingEvents=o.prototype._processPendingEvents}).call(this);
|
@@ -1,5 +1,5 @@
|
|
1
1
|
//
|
2
|
-
// Backbone-associations.js 0.
|
2
|
+
// Backbone-associations.js 0.5.0
|
3
3
|
//
|
4
4
|
// (c) 2013 Dhruva Ray, Jaynti Kanani, Persistent Systems Ltd.
|
5
5
|
// Backbone-associations may be freely distributed under the MIT license.
|
@@ -19,12 +19,15 @@
|
|
19
19
|
// The top-level namespace. All public Backbone classes and modules will be attached to this.
|
20
20
|
// Exported for the browser and CommonJS.
|
21
21
|
var _, Backbone, BackboneModel, BackboneCollection, ModelProto,
|
22
|
-
defaultEvents, AssociatedModel, pathChecker
|
22
|
+
CollectionProto, defaultEvents, AssociatedModel, pathChecker,
|
23
|
+
collectionEvents;
|
23
24
|
|
24
|
-
if (typeof
|
25
|
+
if (typeof window === 'undefined') {
|
25
26
|
_ = require('underscore');
|
26
27
|
Backbone = require('backbone');
|
27
|
-
|
28
|
+
if (typeof exports !== 'undefined') {
|
29
|
+
exports = module.exports = Backbone;
|
30
|
+
}
|
28
31
|
} else {
|
29
32
|
_ = root._;
|
30
33
|
Backbone = root.Backbone;
|
@@ -33,20 +36,25 @@
|
|
33
36
|
BackboneModel = Backbone.Model;
|
34
37
|
BackboneCollection = Backbone.Collection;
|
35
38
|
ModelProto = BackboneModel.prototype;
|
39
|
+
CollectionProto = BackboneCollection.prototype;
|
36
40
|
pathChecker = /[\.\[\]]+/g;
|
37
41
|
|
38
42
|
// Built-in Backbone `events`.
|
39
|
-
defaultEvents = ["change", "add", "remove", "reset", "destroy"
|
40
|
-
|
43
|
+
defaultEvents = ["change", "add", "remove", "reset", "sort", "destroy"];
|
44
|
+
collectionEvents = ["reset", "sort"];
|
45
|
+
|
46
|
+
Backbone.Associations = {
|
47
|
+
VERSION:"0.5.0"
|
48
|
+
};
|
41
49
|
|
42
50
|
// Backbone.AssociatedModel
|
43
51
|
// --------------
|
44
52
|
|
45
53
|
//Add `Many` and `One` relations to Backbone Object.
|
46
|
-
Backbone.Many = "Many";
|
47
|
-
Backbone.One = "One";
|
54
|
+
Backbone.Associations.Many = Backbone.Many = "Many";
|
55
|
+
Backbone.Associations.One = Backbone.One = "One";
|
48
56
|
// Define `AssociatedModel` (Extends Backbone.Model).
|
49
|
-
AssociatedModel = Backbone.AssociatedModel = BackboneModel.extend({
|
57
|
+
AssociatedModel = Backbone.AssociatedModel = Backbone.Associations.AssociatedModel = BackboneModel.extend({
|
50
58
|
// Define relations with Associated Model.
|
51
59
|
relations:undefined,
|
52
60
|
// Define `Model` property which can keep track of already fired `events`,
|
@@ -56,12 +64,12 @@
|
|
56
64
|
// Get the value of an attribute.
|
57
65
|
get:function (attr) {
|
58
66
|
var obj = ModelProto.get.call(this, attr);
|
59
|
-
return obj ? obj : this.
|
67
|
+
return obj ? obj : this._getAttr.apply(this, arguments);
|
60
68
|
},
|
61
69
|
|
62
70
|
// Set a hash of model attributes on the Backbone Model.
|
63
71
|
set:function (key, value, options) {
|
64
|
-
var attributes,
|
72
|
+
var attributes, result;
|
65
73
|
// Duplicate backbone's behavior to allow separate key/value parameters,
|
66
74
|
// instead of a single 'attributes' object.
|
67
75
|
if (_.isObject(key) || key == null) {
|
@@ -71,12 +79,23 @@
|
|
71
79
|
attributes = {};
|
72
80
|
attributes[key] = value;
|
73
81
|
}
|
82
|
+
result = this._set(attributes, options);
|
83
|
+
// Trigger events which have been blocked until the entire object graph is updated.
|
84
|
+
this._processPendingEvents();
|
85
|
+
return result;
|
86
|
+
|
87
|
+
},
|
88
|
+
|
89
|
+
// Works with an attribute hash and options + fully qualified paths
|
90
|
+
_set:function (attributes, options) {
|
91
|
+
var attr, modelMap, modelId, obj, result = this;
|
74
92
|
if (!attributes) return this;
|
75
93
|
for (attr in attributes) {
|
76
94
|
//Create a map for each unique object whose attributes we want to set
|
77
95
|
modelMap || (modelMap = {});
|
78
96
|
if (attr.match(pathChecker)) {
|
79
|
-
var pathTokens = getPathArray(attr), initials = _.initial(pathTokens),
|
97
|
+
var pathTokens = getPathArray(attr), initials = _.initial(pathTokens),
|
98
|
+
last = pathTokens[pathTokens.length - 1],
|
80
99
|
parentModel = this.get(initials);
|
81
100
|
if (parentModel instanceof AssociatedModel) {
|
82
101
|
obj = modelMap[parentModel.cid] || (modelMap[parentModel.cid] = {'model':parentModel, 'data':{}});
|
@@ -87,26 +106,30 @@
|
|
87
106
|
obj.data[attr] = attributes[attr];
|
88
107
|
}
|
89
108
|
}
|
109
|
+
|
90
110
|
if (modelMap) {
|
91
111
|
for (modelId in modelMap) {
|
92
112
|
obj = modelMap[modelId];
|
93
|
-
this.
|
113
|
+
this._setAttr.call(obj.model, obj.data, options) || (result = false);
|
114
|
+
|
94
115
|
}
|
95
116
|
} else {
|
96
|
-
|
117
|
+
result = this._setAttr.call(this, attributes, options);
|
97
118
|
}
|
98
119
|
return result;
|
120
|
+
|
99
121
|
},
|
100
122
|
|
101
123
|
// Set a hash of model attributes on the object,
|
102
124
|
// fire Backbone `event` with options.
|
103
125
|
// It maintains relations between models during the set operation.
|
104
126
|
// It also bubbles up child events to the parent.
|
105
|
-
|
127
|
+
_setAttr:function (attributes, options) {
|
106
128
|
var attr;
|
107
129
|
// Extract attributes and options.
|
108
130
|
options || (options = {});
|
109
131
|
if (options.unset) for (attr in attributes) attributes[attr] = void 0;
|
132
|
+
this.parents = this.parents || [];
|
110
133
|
|
111
134
|
if (this.relations) {
|
112
135
|
// Iterate over `this.relations` and `set` model and collection values
|
@@ -114,17 +137,26 @@
|
|
114
137
|
_.each(this.relations, function (relation) {
|
115
138
|
var relationKey = relation.key, relatedModel = relation.relatedModel,
|
116
139
|
collectionType = relation.collectionType,
|
140
|
+
map = relation.map,
|
141
|
+
currVal = this.attributes[relationKey],
|
142
|
+
idKey = currVal && currVal.idAttribute,
|
117
143
|
val, relationOptions, data, relationValue;
|
144
|
+
|
145
|
+
//Get class if relation and map is stored as a string.
|
146
|
+
relatedModel && _.isString(relatedModel) && (relatedModel = map2Scope(relatedModel));
|
147
|
+
collectionType && _.isString(collectionType) && (collectionType = map2Scope(collectionType));
|
148
|
+
map && _.isString(map) && (map = map2Scope(map));
|
149
|
+
// Merge in `options` specific to this relation.
|
150
|
+
relationOptions = relation.options ? _.extend({}, relation.options, options) : options;
|
151
|
+
|
118
152
|
if (attributes[relationKey]) {
|
119
|
-
//Get value of attribute with relation key in `val`.
|
153
|
+
// Get value of attribute with relation key in `val`.
|
120
154
|
val = _.result(attributes, relationKey);
|
121
|
-
//
|
122
|
-
|
123
|
-
|
124
|
-
// Merge in `options` specific to this relation.
|
125
|
-
relationOptions = relation.options ? _.extend({}, relation.options, options) : options;
|
155
|
+
// Map `val` if a transformation function is provided.
|
156
|
+
val = map ? map(val) : val;
|
157
|
+
|
126
158
|
// If `relation.type` is `Backbone.Many`,
|
127
|
-
//
|
159
|
+
// Create `Backbone.Collection` with passed data and perform Backbone `set`.
|
128
160
|
if (relation.type === Backbone.Many) {
|
129
161
|
// `collectionType` of defined `relation` should be instance of `Backbone.Collection`.
|
130
162
|
if (collectionType && !collectionType.prototype instanceof BackboneCollection) {
|
@@ -133,18 +165,46 @@
|
|
133
165
|
|
134
166
|
if (val instanceof BackboneCollection) {
|
135
167
|
data = val;
|
136
|
-
attributes[relationKey] = data;
|
137
168
|
} else {
|
138
|
-
|
139
|
-
|
140
|
-
|
169
|
+
// Create a new collection
|
170
|
+
if (!currVal) {
|
171
|
+
data = collectionType ? new collectionType() : this._createCollection(relatedModel);
|
172
|
+
data.add(val, relationOptions);
|
173
|
+
} else {
|
174
|
+
// Setting this flag will prevent events from firing immediately. That way clients
|
175
|
+
// will not get events until the entire object graph is updated.
|
176
|
+
currVal._deferEvents = true;
|
177
|
+
// Use Backbone.Collection's smart `set` method
|
178
|
+
currVal.set(val, options);
|
179
|
+
data = currVal;
|
180
|
+
}
|
141
181
|
}
|
142
182
|
|
143
183
|
} else if (relation.type === Backbone.One && relatedModel) {
|
144
|
-
|
145
|
-
|
184
|
+
if (val instanceof AssociatedModel) {
|
185
|
+
data = val;
|
186
|
+
} else {
|
187
|
+
//Create a new model
|
188
|
+
if (!currVal) {
|
189
|
+
data = new relatedModel(val, relationOptions);
|
190
|
+
} else {
|
191
|
+
//Is the passed in data for the same key?
|
192
|
+
if (currVal && val[idKey] && currVal.get(idKey) === val[idKey]) {
|
193
|
+
// Setting this flag will prevent events from firing immediately. That way clients
|
194
|
+
// will not get events until the entire object graph is updated.
|
195
|
+
currVal._deferEvents = true;
|
196
|
+
// Perform the traditional `set` operation
|
197
|
+
currVal._set(val, options);
|
198
|
+
data = currVal;
|
199
|
+
} else {
|
200
|
+
data = new relatedModel(val, relationOptions);
|
201
|
+
}
|
202
|
+
}
|
203
|
+
}
|
204
|
+
|
146
205
|
}
|
147
206
|
|
207
|
+
attributes[relationKey] = data;
|
148
208
|
relationValue = data;
|
149
209
|
|
150
210
|
// Add proxy events to respective parents.
|
@@ -157,80 +217,134 @@
|
|
157
217
|
}
|
158
218
|
|
159
219
|
}
|
220
|
+
//Distinguish between the value of undefined versus a set no-op
|
221
|
+
if (attributes.hasOwnProperty(relationKey)) {
|
222
|
+
//Maintain reverse pointers - a.k.a parents
|
223
|
+
var updated = attributes[relationKey];
|
224
|
+
var original = this.attributes[relationKey];
|
225
|
+
if (updated) {
|
226
|
+
updated.parents = updated.parents || [];
|
227
|
+
(_.indexOf(updated.parents, this) == -1) && updated.parents.push(this);
|
228
|
+
} else if (original && original.parents.length > 0) {
|
229
|
+
original.parents = _.difference(original.parents, [this]);
|
230
|
+
}
|
231
|
+
}
|
160
232
|
}, this);
|
161
233
|
}
|
162
234
|
// Return results for `BackboneModel.set`.
|
163
|
-
return
|
235
|
+
return ModelProto.set.call(this, attributes, options);
|
164
236
|
},
|
165
237
|
// Bubble-up event to `parent` Model
|
166
238
|
_bubbleEvent:function (relationKey, relationValue, eventArguments) {
|
167
239
|
var args = eventArguments,
|
168
240
|
opt = args[0].split(":"),
|
169
241
|
eventType = opt[0],
|
242
|
+
catch_all = args[0] == "nested-change",
|
170
243
|
eventObject = args[1],
|
244
|
+
colObject = args[2],
|
171
245
|
indexEventObject = -1,
|
172
246
|
_proxyCalls = relationValue._proxyCalls,
|
247
|
+
cargs,
|
173
248
|
eventPath,
|
174
|
-
|
249
|
+
basecolEventPath,
|
250
|
+
isDefaultEvent = _.indexOf(defaultEvents, eventType) !== -1;
|
251
|
+
|
252
|
+
//Short circuit the listen in to the nested-graph event
|
253
|
+
if (catch_all) return;
|
254
|
+
|
175
255
|
// Change the event name to a fully qualified path.
|
176
256
|
_.size(opt) > 1 && (eventPath = opt[1]);
|
257
|
+
|
258
|
+
if (_.indexOf(collectionEvents, eventType) !== -1) {
|
259
|
+
colObject = eventObject;
|
260
|
+
}
|
261
|
+
|
177
262
|
// Find the specific object in the collection which has changed.
|
178
|
-
if (relationValue instanceof BackboneCollection &&
|
263
|
+
if (relationValue instanceof BackboneCollection && isDefaultEvent && eventObject) {
|
179
264
|
var pathTokens = getPathArray(eventPath),
|
180
265
|
initialTokens = _.initial(pathTokens), colModel;
|
181
266
|
|
182
267
|
colModel = relationValue.find(function (model) {
|
183
268
|
if (eventObject === model) return true;
|
184
|
-
if (model)
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
269
|
+
if (!model) return false;
|
270
|
+
var changedModel = model.get(initialTokens);
|
271
|
+
|
272
|
+
if ((changedModel instanceof AssociatedModel || changedModel instanceof BackboneCollection)
|
273
|
+
&& eventObject === changedModel)
|
274
|
+
return true;
|
275
|
+
|
276
|
+
changedModel = model.get(pathTokens);
|
277
|
+
|
278
|
+
if ((changedModel instanceof AssociatedModel || changedModel instanceof BackboneCollection)
|
279
|
+
&& eventObject === changedModel)
|
280
|
+
return true;
|
281
|
+
|
282
|
+
if (changedModel instanceof BackboneCollection && colObject
|
283
|
+
&& colObject === changedModel)
|
284
|
+
return true;
|
191
285
|
});
|
192
286
|
colModel && (indexEventObject = relationValue.indexOf(colModel));
|
193
287
|
}
|
288
|
+
|
194
289
|
// Manipulate `eventPath`.
|
195
|
-
eventPath = relationKey + (indexEventObject !== -1 ?
|
290
|
+
eventPath = relationKey + ((indexEventObject !== -1 && (eventType === "change" || eventPath)) ?
|
196
291
|
"[" + indexEventObject + "]" : "") + (eventPath ? "." + eventPath : "");
|
197
|
-
|
292
|
+
|
293
|
+
// Short circuit collection * events
|
294
|
+
if (/\[\*\]/g.test(eventPath)) return this;
|
295
|
+
basecolEventPath = eventPath.replace(/\[\d+\]/g, '[*]');
|
296
|
+
|
297
|
+
cargs = [];
|
298
|
+
cargs.push.apply(cargs, args);
|
299
|
+
cargs[0] = eventType + ":" + eventPath;
|
198
300
|
|
199
301
|
// If event has been already triggered as result of same source `eventPath`,
|
200
302
|
// no need to re-trigger event to prevent cycle.
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
});
|
205
|
-
if (eventAvailable) return this;
|
206
|
-
} else {
|
207
|
-
_proxyCalls = relationValue._proxyCalls = {};
|
208
|
-
}
|
303
|
+
_proxyCalls = relationValue._proxyCalls = (_proxyCalls || {});
|
304
|
+
if (this._isEventAvailable.call(this, _proxyCalls, eventPath)) return this;
|
305
|
+
|
209
306
|
// Add `eventPath` in `_proxyCalls` to keep track of already triggered `event`.
|
210
307
|
_proxyCalls[eventPath] = true;
|
211
308
|
|
212
|
-
|
213
|
-
//Set up previous attributes correctly. Backbone v0.9.10 upwards...
|
309
|
+
// Set up previous attributes correctly.
|
214
310
|
if ("change" === eventType) {
|
215
311
|
this._previousAttributes[relationKey] = relationValue._previousAttributes;
|
216
312
|
this.changed[relationKey] = relationValue;
|
217
313
|
}
|
218
314
|
|
219
315
|
// Bubble up event to parent `model` with new changed arguments.
|
220
|
-
this.trigger.apply(this,
|
316
|
+
this.trigger.apply(this, cargs);
|
317
|
+
|
318
|
+
//Only fire for change. Not change:attribute
|
319
|
+
if ("change" === eventType && this.get(eventPath) != args[2]) {
|
320
|
+
this.trigger.apply(this, ["nested-change", eventPath, args[1]]);
|
321
|
+
}
|
221
322
|
|
222
323
|
// Remove `eventPath` from `_proxyCalls`,
|
223
|
-
// if `eventPath` and `
|
324
|
+
// if `eventPath` and `_proxyCalls` are available,
|
224
325
|
// which allow event to be triggered on for next operation of `set`.
|
225
|
-
if (
|
226
|
-
|
326
|
+
if (_proxyCalls && eventPath) delete _proxyCalls[eventPath];
|
327
|
+
|
328
|
+
// Create a collection modified event with wild-card
|
329
|
+
if (eventPath !== basecolEventPath) {
|
330
|
+
cargs[0] = eventType + ":" + basecolEventPath;
|
331
|
+
this.trigger.apply(this, cargs);
|
227
332
|
}
|
333
|
+
|
228
334
|
return this;
|
229
335
|
},
|
336
|
+
|
337
|
+
// Has event been fired from this source. Used to prevent event recursion in cyclic graphs
|
338
|
+
_isEventAvailable:function (_proxyCalls, path) {
|
339
|
+
return _.find(_proxyCalls, function (value, eventKey) {
|
340
|
+
return path.indexOf(eventKey, path.length - eventKey.length) !== -1;
|
341
|
+
});
|
342
|
+
},
|
343
|
+
|
230
344
|
// Returns New `collection` of type `relation.relatedModel`.
|
231
345
|
_createCollection:function (type) {
|
232
346
|
var collection, relatedModel = type;
|
233
|
-
_.isString(relatedModel) && (relatedModel =
|
347
|
+
_.isString(relatedModel) && (relatedModel = map2Scope(relatedModel));
|
234
348
|
// Creates new `Backbone.Collection` and defines model class.
|
235
349
|
if (relatedModel && relatedModel.prototype instanceof AssociatedModel) {
|
236
350
|
collection = new BackboneCollection();
|
@@ -240,6 +354,43 @@
|
|
240
354
|
}
|
241
355
|
return collection;
|
242
356
|
},
|
357
|
+
|
358
|
+
// Process all pending events after the entire object graph has been updated
|
359
|
+
_processPendingEvents:function () {
|
360
|
+
if (!this.visited) {
|
361
|
+
this.visited = true;
|
362
|
+
|
363
|
+
this._deferEvents = false;
|
364
|
+
|
365
|
+
// Trigger all pending events
|
366
|
+
_.each(this._pendingEvents, function (e) {
|
367
|
+
e.c.trigger.apply(e.c, e.a);
|
368
|
+
});
|
369
|
+
|
370
|
+
this._pendingEvents = [];
|
371
|
+
|
372
|
+
// Traverse down the object graph and call process pending events on sub-trees
|
373
|
+
_.each(this.relations, function (relation) {
|
374
|
+
var val = this.attributes[relation.key];
|
375
|
+
val && val._processPendingEvents();
|
376
|
+
}, this);
|
377
|
+
|
378
|
+
delete this.visited;
|
379
|
+
}
|
380
|
+
},
|
381
|
+
|
382
|
+
// Override trigger to defer events in the object graph.
|
383
|
+
trigger:function (name) {
|
384
|
+
// Defer event processing
|
385
|
+
if (this._deferEvents) {
|
386
|
+
this._pendingEvents = this._pendingEvents || [];
|
387
|
+
// Maintain a queue of pending events to trigger after the entire object graph is updated.
|
388
|
+
this._pendingEvents.push({c:this, a:arguments});
|
389
|
+
} else {
|
390
|
+
ModelProto.trigger.apply(this, arguments);
|
391
|
+
}
|
392
|
+
},
|
393
|
+
|
243
394
|
// The JSON representation of the model.
|
244
395
|
toJSON:function (options) {
|
245
396
|
var json, aJson;
|
@@ -268,8 +419,8 @@
|
|
268
419
|
return new this.constructor(this.toJSON());
|
269
420
|
},
|
270
421
|
|
271
|
-
//Navigate the path to the leaf object in the path to query for the attribute value
|
272
|
-
|
422
|
+
// Navigate the path to the leaf object in the path to query for the attribute value
|
423
|
+
_getAttr:function (path) {
|
273
424
|
|
274
425
|
var result = this,
|
275
426
|
//Tokenize the path
|
@@ -281,7 +432,9 @@
|
|
281
432
|
key = attrs[i];
|
282
433
|
if (!result) break;
|
283
434
|
//Navigate the path to get to the result
|
284
|
-
result = result instanceof BackboneCollection
|
435
|
+
result = result instanceof BackboneCollection
|
436
|
+
? (isNaN(key) ? undefined : result.at(key))
|
437
|
+
: result.attributes[key];
|
285
438
|
}
|
286
439
|
return result;
|
287
440
|
}
|
@@ -293,5 +446,62 @@
|
|
293
446
|
var getPathArray = function (path) {
|
294
447
|
if (path === '') return [''];
|
295
448
|
return _.isString(path) ? (path.match(delimiters)) : path || [];
|
296
|
-
}
|
297
|
-
|
449
|
+
};
|
450
|
+
|
451
|
+
var map2Scope = function (path) {
|
452
|
+
return _.reduce(path.split('.'), function (memo, elem) {
|
453
|
+
return memo[elem]
|
454
|
+
}, root);
|
455
|
+
};
|
456
|
+
|
457
|
+
//Infer the relation from the collection's parents and find the appropriate map for the passed in `models`
|
458
|
+
var map2models = function (parents, target, models) {
|
459
|
+
var relation;
|
460
|
+
//Iterate over collection's parents
|
461
|
+
_.find(parents, function (parent) {
|
462
|
+
//Iterate over relations
|
463
|
+
relation = _.find(parent.relations, function (rel) {
|
464
|
+
return parent.get(rel.key) === target;
|
465
|
+
}, this);
|
466
|
+
if (relation) return true;//break;
|
467
|
+
}, this);
|
468
|
+
|
469
|
+
//If we found a relation and it has a mapping function
|
470
|
+
if (relation && relation.map) {
|
471
|
+
return relation.map(models)
|
472
|
+
}
|
473
|
+
return models;
|
474
|
+
};
|
475
|
+
|
476
|
+
var proxies = {};
|
477
|
+
// Proxy Backbone collection methods
|
478
|
+
_.each(['set', 'remove', 'reset'], function (method) {
|
479
|
+
proxies[method] = BackboneCollection.prototype[method];
|
480
|
+
|
481
|
+
CollectionProto[method] = function (models, options) {
|
482
|
+
//Short-circuit if this collection doesn't hold `AssociatedModels`
|
483
|
+
if (this.model.prototype instanceof AssociatedModel && this.parents) {
|
484
|
+
//Find a map function if available and perform a transformation
|
485
|
+
arguments[0] = map2models(this.parents, this, models);
|
486
|
+
}
|
487
|
+
return proxies[method].apply(this, arguments);
|
488
|
+
}
|
489
|
+
});
|
490
|
+
|
491
|
+
// Override trigger to defer events in the object graph.
|
492
|
+
proxies['trigger'] = CollectionProto['trigger'];
|
493
|
+
CollectionProto['trigger'] = function (name) {
|
494
|
+
if (this._deferEvents) {
|
495
|
+
this._pendingEvents = this._pendingEvents || [];
|
496
|
+
// Maintain a queue of pending events to trigger after the entire object graph is updated.
|
497
|
+
this._pendingEvents.push({c:this, a:arguments});
|
498
|
+
} else {
|
499
|
+
proxies['trigger'].apply(this, arguments);
|
500
|
+
}
|
501
|
+
};
|
502
|
+
|
503
|
+
// Attach process pending event functionality on collections as well. Re-use from `AssociatedModel`
|
504
|
+
CollectionProto._processPendingEvents = AssociatedModel.prototype._processPendingEvents;
|
505
|
+
|
506
|
+
|
507
|
+
}).call(this);
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: backbone-associations-rails
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-
|
12
|
+
date: 2013-06-20 00:00:00.000000000 Z
|
13
13
|
dependencies: []
|
14
14
|
description: Backbone-associations provides a way of specifying 1:1 and 1:N relationships
|
15
15
|
between Backbone models. Additionally, parent model instances (and objects extended
|
@@ -29,6 +29,7 @@ files:
|
|
29
29
|
- backbone-associations-rails.gemspec
|
30
30
|
- lib/backbone-associations-rails.rb
|
31
31
|
- lib/backbone-associations-rails/version.rb
|
32
|
+
- vendor/assets/javascripts/backbone-associations-min.js
|
32
33
|
- vendor/assets/javascripts/backbone-associations.js
|
33
34
|
homepage: https://github.com/wingrunr21/backbone-associations-rails
|
34
35
|
licenses: []
|