backbone-associations-rails 0.4.2 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
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: []
|