backbone-relational-rails 0.7.1 → 0.8.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/README.md +1 -1
- data/lib/backbone-relational-rails/version.rb +1 -1
- data/vendor/assets/javascripts/backbone-relational.js +727 -631
- metadata +4 -4
data/README.md
CHANGED
@@ -18,7 +18,7 @@ Add the following directive to your Javascript manifest file (application.js):
|
|
18
18
|
|
19
19
|
## Versioning
|
20
20
|
|
21
|
-
backbone-relational-rails 0.
|
21
|
+
backbone-relational-rails 0.8.0 == Backbone-relational 0.8.0
|
22
22
|
|
23
23
|
Every attempt is made to mirror the currently shipping Backbone-relational version number wherever possible.
|
24
24
|
The major, minor, and patch version numbers will always represent the Backbone-relational version. Should a gem
|
@@ -1,6 +1,6 @@
|
|
1
1
|
/* vim: set tabstop=4 softtabstop=4 shiftwidth=4 noexpandtab: */
|
2
2
|
/**
|
3
|
-
* Backbone-relational.js 0.
|
3
|
+
* Backbone-relational.js 0.8.0
|
4
4
|
* (c) 2011-2013 Paul Uithol and contributors (https://github.com/PaulUithol/Backbone-relational/graphs/contributors)
|
5
5
|
*
|
6
6
|
* Backbone-relational may be freely distributed under the MIT license; see the accompanying LICENSE.txt.
|
@@ -9,7 +9,7 @@
|
|
9
9
|
*/
|
10
10
|
( function( undefined ) {
|
11
11
|
"use strict";
|
12
|
-
|
12
|
+
|
13
13
|
/**
|
14
14
|
* CommonJS shim
|
15
15
|
**/
|
@@ -35,7 +35,7 @@
|
|
35
35
|
Backbone.Semaphore = {
|
36
36
|
_permitsAvailable: null,
|
37
37
|
_permitsUsed: 0,
|
38
|
-
|
38
|
+
|
39
39
|
acquire: function() {
|
40
40
|
if ( this._permitsAvailable && this._permitsUsed >= this._permitsAvailable ) {
|
41
41
|
throw new Error( 'Max permits acquired' );
|
@@ -44,7 +44,7 @@
|
|
44
44
|
this._permitsUsed++;
|
45
45
|
}
|
46
46
|
},
|
47
|
-
|
47
|
+
|
48
48
|
release: function() {
|
49
49
|
if ( this._permitsUsed === 0 ) {
|
50
50
|
throw new Error( 'All permits released' );
|
@@ -53,11 +53,11 @@
|
|
53
53
|
this._permitsUsed--;
|
54
54
|
}
|
55
55
|
},
|
56
|
-
|
56
|
+
|
57
57
|
isLocked: function() {
|
58
58
|
return this._permitsUsed > 0;
|
59
59
|
},
|
60
|
-
|
60
|
+
|
61
61
|
setAvailablePermits: function( amount ) {
|
62
62
|
if ( this._permitsUsed > amount ) {
|
63
63
|
throw new Error( 'Available permits cannot be less than used permits' );
|
@@ -65,7 +65,7 @@
|
|
65
65
|
this._permitsAvailable = amount;
|
66
66
|
}
|
67
67
|
};
|
68
|
-
|
68
|
+
|
69
69
|
/**
|
70
70
|
* A BlockingQueue that accumulates items while blocked (via 'block'),
|
71
71
|
* and processes them when unblocked (via 'unblock').
|
@@ -76,7 +76,7 @@
|
|
76
76
|
};
|
77
77
|
_.extend( Backbone.BlockingQueue.prototype, Backbone.Semaphore, {
|
78
78
|
_queue: null,
|
79
|
-
|
79
|
+
|
80
80
|
add: function( func ) {
|
81
81
|
if ( this.isBlocked() ) {
|
82
82
|
this._queue.push( func );
|
@@ -85,34 +85,34 @@
|
|
85
85
|
func();
|
86
86
|
}
|
87
87
|
},
|
88
|
-
|
88
|
+
|
89
89
|
process: function() {
|
90
90
|
while ( this._queue && this._queue.length ) {
|
91
91
|
this._queue.shift()();
|
92
92
|
}
|
93
93
|
},
|
94
|
-
|
94
|
+
|
95
95
|
block: function() {
|
96
96
|
this.acquire();
|
97
97
|
},
|
98
|
-
|
98
|
+
|
99
99
|
unblock: function() {
|
100
100
|
this.release();
|
101
101
|
if ( !this.isBlocked() ) {
|
102
102
|
this.process();
|
103
103
|
}
|
104
104
|
},
|
105
|
-
|
105
|
+
|
106
106
|
isBlocked: function() {
|
107
107
|
return this.isLocked();
|
108
108
|
}
|
109
109
|
});
|
110
110
|
/**
|
111
|
-
* Global event queue. Accumulates external events ('add:<key>', 'remove:<key>' and '
|
111
|
+
* Global event queue. Accumulates external events ('add:<key>', 'remove:<key>' and 'change:<key>')
|
112
112
|
* until the top-level object is fully initialized (see 'Backbone.RelationalModel').
|
113
113
|
*/
|
114
114
|
Backbone.Relational.eventQueue = new Backbone.BlockingQueue();
|
115
|
-
|
115
|
+
|
116
116
|
/**
|
117
117
|
* Backbone.Store keeps track of all created (and destruction of) Backbone.RelationalModel.
|
118
118
|
* Handles lookup for relations.
|
@@ -120,10 +120,31 @@
|
|
120
120
|
Backbone.Store = function() {
|
121
121
|
this._collections = [];
|
122
122
|
this._reverseRelations = [];
|
123
|
+
this._orphanRelations = [];
|
123
124
|
this._subModels = [];
|
124
125
|
this._modelScopes = [ exports ];
|
125
126
|
};
|
126
127
|
_.extend( Backbone.Store.prototype, Backbone.Events, {
|
128
|
+
/**
|
129
|
+
* Create a new `Relation`.
|
130
|
+
* @param {Backbone.RelationalModel} [model]
|
131
|
+
* @param {Object} relation
|
132
|
+
* @param {Object} [options]
|
133
|
+
*/
|
134
|
+
initializeRelation: function( model, relation, options ) {
|
135
|
+
var type = !_.isString( relation.type ) ? relation.type : Backbone[ relation.type ] || this.getObjectByName( relation.type );
|
136
|
+
if ( type && type.prototype instanceof Backbone.Relation ) {
|
137
|
+
new type( model, relation, options ); // Also pushes the new Relation into `model._relations`
|
138
|
+
}
|
139
|
+
else {
|
140
|
+
Backbone.Relational.showWarnings && typeof console !== 'undefined' && console.warn( 'Relation=%o; missing or invalid relation type!', relation );
|
141
|
+
}
|
142
|
+
},
|
143
|
+
|
144
|
+
/**
|
145
|
+
* Add a scope for `getObjectByName` to look for model types by name.
|
146
|
+
* @param {Object} scope
|
147
|
+
*/
|
127
148
|
addModelScope: function( scope ) {
|
128
149
|
this._modelScopes.push( scope );
|
129
150
|
},
|
@@ -149,7 +170,7 @@
|
|
149
170
|
* @param {Backbone.RelationalModel} modelType
|
150
171
|
*/
|
151
172
|
setupSuperModel: function( modelType ) {
|
152
|
-
_.find( this._subModels
|
173
|
+
_.find( this._subModels, function( subModelDef ) {
|
153
174
|
return _.find( subModelDef.subModels || [], function( subModelTypeName, typeValue ) {
|
154
175
|
var subModelType = this.getObjectByName( subModelTypeName );
|
155
176
|
|
@@ -166,7 +187,7 @@
|
|
166
187
|
}, this );
|
167
188
|
}, this );
|
168
189
|
},
|
169
|
-
|
190
|
+
|
170
191
|
/**
|
171
192
|
* Add a reverse relation. Is added to the 'relations' property on model's prototype, and to
|
172
193
|
* existing instances of 'model' in the store as well.
|
@@ -177,75 +198,109 @@
|
|
177
198
|
* @param {String|Object} relation.relatedModel
|
178
199
|
*/
|
179
200
|
addReverseRelation: function( relation ) {
|
180
|
-
var exists = _.any( this._reverseRelations
|
181
|
-
|
182
|
-
|
183
|
-
});
|
201
|
+
var exists = _.any( this._reverseRelations, function( rel ) {
|
202
|
+
return _.all( relation || [], function( val, key ) {
|
203
|
+
return val === rel[ key ];
|
184
204
|
});
|
205
|
+
});
|
185
206
|
|
186
207
|
if ( !exists && relation.model && relation.type ) {
|
187
208
|
this._reverseRelations.push( relation );
|
188
|
-
|
189
|
-
var addRelation = function( model, relation ) {
|
190
|
-
if ( !model.prototype.relations ) {
|
191
|
-
model.prototype.relations = [];
|
192
|
-
}
|
193
|
-
model.prototype.relations.push( relation );
|
194
|
-
|
195
|
-
_.each( model._subModels || [], function( subModel ) {
|
196
|
-
addRelation( subModel, relation );
|
197
|
-
}, this );
|
198
|
-
};
|
199
|
-
|
200
|
-
addRelation( relation.model, relation );
|
201
|
-
|
209
|
+
this._addRelation( relation.model, relation );
|
202
210
|
this.retroFitRelation( relation );
|
203
211
|
}
|
204
212
|
},
|
205
|
-
|
213
|
+
|
214
|
+
/**
|
215
|
+
* Deposit a `relation` for which the `relatedModel` can't be resolved at the moment.
|
216
|
+
*
|
217
|
+
* @param {Object} relation
|
218
|
+
*/
|
219
|
+
addOrphanRelation: function( relation ) {
|
220
|
+
var exists = _.any( this._orphanRelations, function( rel ) {
|
221
|
+
return _.all( relation || [], function( val, key ) {
|
222
|
+
return val === rel[ key ];
|
223
|
+
});
|
224
|
+
});
|
225
|
+
|
226
|
+
if ( !exists && relation.model && relation.type ) {
|
227
|
+
this._orphanRelations.push( relation );
|
228
|
+
}
|
229
|
+
},
|
230
|
+
|
231
|
+
/**
|
232
|
+
* Try to initialize any `_orphanRelation`s
|
233
|
+
*/
|
234
|
+
processOrphanRelations: function() {
|
235
|
+
// Make sure to operate on a copy since we're removing while iterating
|
236
|
+
_.each( this._orphanRelations.slice( 0 ), function( rel ) {
|
237
|
+
var relatedModel = Backbone.Relational.store.getObjectByName( rel.relatedModel );
|
238
|
+
if ( relatedModel ) {
|
239
|
+
this.initializeRelation( null, rel );
|
240
|
+
this._orphanRelations = _.without( this._orphanRelations, rel );
|
241
|
+
}
|
242
|
+
}, this );
|
243
|
+
},
|
244
|
+
|
245
|
+
/**
|
246
|
+
*
|
247
|
+
* @param {Backbone.RelationalModel.constructor} type
|
248
|
+
* @param {Object} relation
|
249
|
+
* @private
|
250
|
+
*/
|
251
|
+
_addRelation: function( type, relation ) {
|
252
|
+
if ( !type.prototype.relations ) {
|
253
|
+
type.prototype.relations = [];
|
254
|
+
}
|
255
|
+
type.prototype.relations.push( relation );
|
256
|
+
|
257
|
+
_.each( type._subModels || [], function( subModel ) {
|
258
|
+
this._addRelation( subModel, relation );
|
259
|
+
}, this );
|
260
|
+
},
|
261
|
+
|
206
262
|
/**
|
207
263
|
* Add a 'relation' to all existing instances of 'relation.model' in the store
|
208
264
|
* @param {Object} relation
|
209
265
|
*/
|
210
266
|
retroFitRelation: function( relation ) {
|
211
|
-
var coll = this.getCollection( relation.model );
|
212
|
-
coll.each( function( model ) {
|
267
|
+
var coll = this.getCollection( relation.model, false );
|
268
|
+
coll && coll.each( function( model ) {
|
213
269
|
if ( !( model instanceof relation.model ) ) {
|
214
270
|
return;
|
215
271
|
}
|
216
272
|
|
217
273
|
new relation.type( model, relation );
|
218
|
-
}, this);
|
274
|
+
}, this );
|
219
275
|
},
|
220
|
-
|
276
|
+
|
221
277
|
/**
|
222
278
|
* Find the Store's collection for a certain type of model.
|
223
|
-
* @param {Backbone.RelationalModel}
|
279
|
+
* @param {Backbone.RelationalModel} type
|
280
|
+
* @param {Boolean} [create=true] Should a collection be created if none is found?
|
224
281
|
* @return {Backbone.Collection} A collection if found (or applicable for 'model'), or null
|
225
282
|
*/
|
226
|
-
getCollection: function(
|
227
|
-
if (
|
228
|
-
|
283
|
+
getCollection: function( type, create ) {
|
284
|
+
if ( type instanceof Backbone.RelationalModel ) {
|
285
|
+
type = type.constructor;
|
229
286
|
}
|
230
287
|
|
231
|
-
var rootModel =
|
288
|
+
var rootModel = type;
|
232
289
|
while ( rootModel._superModel ) {
|
233
290
|
rootModel = rootModel._superModel;
|
234
291
|
}
|
235
292
|
|
236
|
-
var coll = _.
|
237
|
-
return c.model === rootModel;
|
238
|
-
});
|
293
|
+
var coll = _.findWhere( this._collections, { model: rootModel } );
|
239
294
|
|
240
|
-
if ( !coll ) {
|
295
|
+
if ( !coll && create !== false ) {
|
241
296
|
coll = this._createCollection( rootModel );
|
242
297
|
}
|
243
298
|
|
244
299
|
return coll;
|
245
300
|
},
|
246
|
-
|
301
|
+
|
247
302
|
/**
|
248
|
-
* Find a type on the
|
303
|
+
* Find a model type on one of the modelScopes by name. Names are split on dots.
|
249
304
|
* @param {String} name
|
250
305
|
* @return {Object}
|
251
306
|
*/
|
@@ -253,7 +308,7 @@
|
|
253
308
|
var parts = name.split( '.' ),
|
254
309
|
type = null;
|
255
310
|
|
256
|
-
_.find( this._modelScopes
|
311
|
+
_.find( this._modelScopes, function( scope ) {
|
257
312
|
type = _.reduce( parts || [], function( memo, val ) {
|
258
313
|
return memo ? memo[ val ] : undefined;
|
259
314
|
}, scope );
|
@@ -265,7 +320,7 @@
|
|
265
320
|
|
266
321
|
return type;
|
267
322
|
},
|
268
|
-
|
323
|
+
|
269
324
|
_createCollection: function( type ) {
|
270
325
|
var coll;
|
271
326
|
|
@@ -332,9 +387,9 @@
|
|
332
387
|
|
333
388
|
return null;
|
334
389
|
},
|
335
|
-
|
390
|
+
|
336
391
|
/**
|
337
|
-
* Add a 'model' to
|
392
|
+
* Add a 'model' to its appropriate collection. Retain the original contents of 'model.collection'.
|
338
393
|
* @param {Backbone.RelationalModel} model
|
339
394
|
*/
|
340
395
|
register: function( model ) {
|
@@ -342,42 +397,57 @@
|
|
342
397
|
|
343
398
|
if ( coll ) {
|
344
399
|
if ( coll.get( model ) ) {
|
400
|
+
if ( Backbone.Relational.showWarnings && typeof console !== 'undefined' ) {
|
401
|
+
console.warn( 'Duplicate id! Old RelationalModel=%o, new RelationalModel=%o', coll.get( model ), model );
|
402
|
+
}
|
345
403
|
throw new Error( "Cannot instantiate more than one Backbone.RelationalModel with the same id per type!" );
|
346
404
|
}
|
347
405
|
|
348
406
|
var modelColl = model.collection;
|
349
407
|
coll.add( model );
|
350
|
-
|
408
|
+
this.listenTo( model, 'destroy', this.unregister, this );
|
351
409
|
model.collection = modelColl;
|
352
410
|
}
|
353
411
|
},
|
354
|
-
|
412
|
+
|
355
413
|
/**
|
356
|
-
* Explicitly update a model's id in
|
414
|
+
* Explicitly update a model's id in its store collection
|
357
415
|
* @param {Backbone.RelationalModel} model
|
358
|
-
|
416
|
+
*/
|
359
417
|
update: function( model ) {
|
360
418
|
var coll = this.getCollection( model );
|
361
419
|
coll._onModelEvent( 'change:' + model.idAttribute, model, coll );
|
362
420
|
},
|
363
|
-
|
421
|
+
|
364
422
|
/**
|
365
423
|
* Remove a 'model' from the store.
|
366
424
|
* @param {Backbone.RelationalModel} model
|
367
425
|
*/
|
368
426
|
unregister: function( model ) {
|
369
|
-
|
427
|
+
this.stopListening( model, 'destroy', this.unregister );
|
370
428
|
var coll = this.getCollection( model );
|
371
429
|
coll && coll.remove( model );
|
430
|
+
},
|
431
|
+
|
432
|
+
/**
|
433
|
+
* Reset the `store` to it's original state. The `reverseRelations` are kept though, since attempting to
|
434
|
+
* re-initialize these on models would lead to a large amount of warnings.
|
435
|
+
*/
|
436
|
+
reset: function() {
|
437
|
+
this.stopListening();
|
438
|
+
this._collections = [];
|
439
|
+
this._subModels = [];
|
440
|
+
this._modelScopes = [ exports ];
|
372
441
|
}
|
373
442
|
});
|
374
443
|
Backbone.Relational.store = new Backbone.Store();
|
375
|
-
|
444
|
+
|
376
445
|
/**
|
377
446
|
* The main Relation class, from which 'HasOne' and 'HasMany' inherit. Internally, 'relational:<key>' events
|
378
447
|
* are used to regulate addition and removal of models from relations.
|
379
448
|
*
|
380
|
-
* @param {Backbone.RelationalModel} instance
|
449
|
+
* @param {Backbone.RelationalModel} [instance] Model that this relation is created for. If no model is supplied,
|
450
|
+
* Relation just tries to instantiate it's `reverseRelation` if specified, and bails out after that.
|
381
451
|
* @param {Object} options
|
382
452
|
* @param {string} options.key
|
383
453
|
* @param {Backbone.RelationalModel.constructor} options.relatedModel
|
@@ -386,22 +456,23 @@
|
|
386
456
|
* @param {Object} [options.reverseRelation] Specify a bi-directional relation. If provided, Relation will reciprocate
|
387
457
|
* the relation to the 'relatedModel'. Required and optional properties match 'options', except that it also needs
|
388
458
|
* {Backbone.Relation|String} type ('HasOne' or 'HasMany').
|
459
|
+
* @param {Object} opts
|
389
460
|
*/
|
390
|
-
Backbone.Relation = function( instance, options ) {
|
461
|
+
Backbone.Relation = function( instance, options, opts ) {
|
391
462
|
this.instance = instance;
|
392
463
|
// Make sure 'options' is sane, and fill with defaults from subclasses and this object's prototype
|
393
464
|
options = _.isObject( options ) ? options : {};
|
394
465
|
this.reverseRelation = _.defaults( options.reverseRelation || {}, this.options.reverseRelation );
|
466
|
+
this.options = _.defaults( options, this.options, Backbone.Relation.prototype.options );
|
467
|
+
|
395
468
|
this.reverseRelation.type = !_.isString( this.reverseRelation.type ) ? this.reverseRelation.type :
|
396
469
|
Backbone[ this.reverseRelation.type ] || Backbone.Relational.store.getObjectByName( this.reverseRelation.type );
|
397
|
-
|
398
|
-
this.options = _.defaults( options, this.options, Backbone.Relation.prototype.options );
|
399
|
-
|
470
|
+
|
400
471
|
this.key = this.options.key;
|
401
472
|
this.keySource = this.options.keySource || this.key;
|
402
473
|
this.keyDestination = this.options.keyDestination || this.keySource || this.key;
|
403
474
|
|
404
|
-
|
475
|
+
this.model = this.options.model || this.instance.constructor;
|
405
476
|
this.relatedModel = this.options.relatedModel;
|
406
477
|
if ( _.isString( this.relatedModel ) ) {
|
407
478
|
this.relatedModel = Backbone.Relational.store.getObjectByName( this.relatedModel );
|
@@ -411,13 +482,26 @@
|
|
411
482
|
return;
|
412
483
|
}
|
413
484
|
|
485
|
+
// Add the reverse relation on 'relatedModel' to the store's reverseRelations
|
486
|
+
if ( !this.options.isAutoRelation && this.reverseRelation.type && this.reverseRelation.key ) {
|
487
|
+
Backbone.Relational.store.addReverseRelation( _.defaults( {
|
488
|
+
isAutoRelation: true,
|
489
|
+
model: this.relatedModel,
|
490
|
+
relatedModel: this.model,
|
491
|
+
reverseRelation: this.options // current relation is the 'reverseRelation' for its own reverseRelation
|
492
|
+
},
|
493
|
+
this.reverseRelation // Take further properties from this.reverseRelation (type, key, etc.)
|
494
|
+
) );
|
495
|
+
}
|
496
|
+
|
414
497
|
if ( instance ) {
|
415
498
|
var contentKey = this.keySource;
|
416
499
|
if ( contentKey !== this.key && typeof this.instance.get( this.key ) === 'object' ) {
|
417
500
|
contentKey = this.key;
|
418
501
|
}
|
419
502
|
|
420
|
-
this.
|
503
|
+
this.setKeyContents( this.instance.get( contentKey ) );
|
504
|
+
this.relatedCollection = Backbone.Relational.store.getCollection( this.relatedModel );
|
421
505
|
|
422
506
|
// Explicitly clear 'keySource', to prevent a leaky abstraction if 'keySource' differs from 'key'.
|
423
507
|
if ( this.keySource !== this.key ) {
|
@@ -425,38 +509,18 @@
|
|
425
509
|
}
|
426
510
|
|
427
511
|
// Add this Relation to instance._relations
|
428
|
-
this.instance._relations.
|
429
|
-
}
|
512
|
+
this.instance._relations[ this.key ] = this;
|
430
513
|
|
431
|
-
|
432
|
-
if ( !this.options.isAutoRelation && this.reverseRelation.type && this.reverseRelation.key ) {
|
433
|
-
Backbone.Relational.store.addReverseRelation( _.defaults( {
|
434
|
-
isAutoRelation: true,
|
435
|
-
model: this.relatedModel,
|
436
|
-
relatedModel: this.model,
|
437
|
-
reverseRelation: this.options // current relation is the 'reverseRelation' for it's own reverseRelation
|
438
|
-
},
|
439
|
-
this.reverseRelation // Take further properties from this.reverseRelation (type, key, etc.)
|
440
|
-
) );
|
441
|
-
}
|
442
|
-
|
443
|
-
_.bindAll( this, '_modelRemovedFromCollection', '_relatedModelAdded', '_relatedModelRemoved' );
|
444
|
-
|
445
|
-
if ( instance ) {
|
446
|
-
this.initialize();
|
514
|
+
this.initialize( opts );
|
447
515
|
|
448
|
-
if ( options.autoFetch ) {
|
449
|
-
this.instance.fetchRelated(
|
516
|
+
if ( this.options.autoFetch ) {
|
517
|
+
this.instance.fetchRelated( this.key, _.isObject( this.options.autoFetch ) ? this.options.autoFetch : {} );
|
450
518
|
}
|
451
519
|
|
452
|
-
// When a model in the store is destroyed, check if it is 'this.instance'.
|
453
|
-
Backbone.Relational.store.getCollection( this.instance )
|
454
|
-
.bind( 'relational:remove', this._modelRemovedFromCollection );
|
455
|
-
|
456
520
|
// When 'relatedModel' are created or destroyed, check if it affects this relation.
|
457
|
-
|
458
|
-
.
|
459
|
-
.
|
521
|
+
this.listenTo( this.instance, 'destroy', this.destroy )
|
522
|
+
.listenTo( this.relatedCollection, 'relational:add', this.tryAddRelated )
|
523
|
+
.listenTo( this.relatedCollection, 'relational:remove', this.removeRelated )
|
460
524
|
}
|
461
525
|
};
|
462
526
|
// Fix inheritance :\
|
@@ -467,35 +531,18 @@
|
|
467
531
|
createModels: true,
|
468
532
|
includeInJSON: true,
|
469
533
|
isAutoRelation: false,
|
470
|
-
autoFetch: false
|
534
|
+
autoFetch: false,
|
535
|
+
parse: false
|
471
536
|
},
|
472
|
-
|
537
|
+
|
473
538
|
instance: null,
|
474
539
|
key: null,
|
475
540
|
keyContents: null,
|
476
541
|
relatedModel: null,
|
542
|
+
relatedCollection: null,
|
477
543
|
reverseRelation: null,
|
478
544
|
related: null,
|
479
|
-
|
480
|
-
_relatedModelAdded: function( model, coll, options ) {
|
481
|
-
// Allow 'model' to set up it's relations, before calling 'tryAddRelated'
|
482
|
-
// (which can result in a call to 'addRelated' on a relation of 'model')
|
483
|
-
var dit = this;
|
484
|
-
model.queue( function() {
|
485
|
-
dit.tryAddRelated( model, options );
|
486
|
-
});
|
487
|
-
},
|
488
|
-
|
489
|
-
_relatedModelRemoved: function( model, coll, options ) {
|
490
|
-
this.removeRelated( model, options );
|
491
|
-
},
|
492
|
-
|
493
|
-
_modelRemovedFromCollection: function( model ) {
|
494
|
-
if ( model === this.instance ) {
|
495
|
-
this.destroy();
|
496
|
-
}
|
497
|
-
},
|
498
|
-
|
545
|
+
|
499
546
|
/**
|
500
547
|
* Check several pre-conditions.
|
501
548
|
* @return {Boolean} True if pre-conditions are satisfied, false if they're not.
|
@@ -508,36 +555,33 @@
|
|
508
555
|
warn = Backbone.Relational.showWarnings && typeof console !== 'undefined';
|
509
556
|
|
510
557
|
if ( !m || !k || !rm ) {
|
511
|
-
warn && console.warn( 'Relation=%o
|
558
|
+
warn && console.warn( 'Relation=%o: missing model, key or relatedModel (%o, %o, %o).', this, m, k, rm );
|
512
559
|
return false;
|
513
560
|
}
|
514
561
|
// Check if the type in 'model' inherits from Backbone.RelationalModel
|
515
562
|
if ( !( m.prototype instanceof Backbone.RelationalModel ) ) {
|
516
|
-
warn && console.warn( 'Relation=%o
|
563
|
+
warn && console.warn( 'Relation=%o: model does not inherit from Backbone.RelationalModel (%o).', this, i );
|
517
564
|
return false;
|
518
565
|
}
|
519
566
|
// Check if the type in 'relatedModel' inherits from Backbone.RelationalModel
|
520
567
|
if ( !( rm.prototype instanceof Backbone.RelationalModel ) ) {
|
521
|
-
warn && console.warn( 'Relation=%o
|
568
|
+
warn && console.warn( 'Relation=%o: relatedModel does not inherit from Backbone.RelationalModel (%o).', this, rm );
|
522
569
|
return false;
|
523
570
|
}
|
524
571
|
// Check if this is not a HasMany, and the reverse relation is HasMany as well
|
525
572
|
if ( this instanceof Backbone.HasMany && this.reverseRelation.type === Backbone.HasMany ) {
|
526
|
-
warn && console.warn( 'Relation=%o
|
573
|
+
warn && console.warn( 'Relation=%o: relation is a HasMany, and the reverseRelation is HasMany as well.', this );
|
527
574
|
return false;
|
528
575
|
}
|
576
|
+
// Check if we're not attempting to create a relationship on a `key` that's already used.
|
577
|
+
if ( i && _.keys( i._relations ).length ) {
|
578
|
+
var existing = _.find( i._relations, function( rel ) {
|
579
|
+
return rel.key === k;
|
580
|
+
}, this );
|
529
581
|
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
var hasReverseRelation = this.reverseRelation.key && rel.reverseRelation.key;
|
534
|
-
return rel.relatedModel === rm && rel.key === k &&
|
535
|
-
( !hasReverseRelation || this.reverseRelation.key === rel.reverseRelation.key );
|
536
|
-
}, this );
|
537
|
-
|
538
|
-
if ( exists ) {
|
539
|
-
warn && console.warn( 'Relation=%o between instance=%o.%s and relatedModel=%o.%s already exists',
|
540
|
-
this, i, k, rm, this.reverseRelation.key );
|
582
|
+
if ( existing ) {
|
583
|
+
warn && console.warn( 'Cannot create relation=%o on %o for model=%o: already taken by relation=%o.',
|
584
|
+
this, k, i, existing );
|
541
585
|
return false;
|
542
586
|
}
|
543
587
|
}
|
@@ -548,16 +592,15 @@
|
|
548
592
|
/**
|
549
593
|
* Set the related model(s) for this relation
|
550
594
|
* @param {Backbone.Model|Backbone.Collection} related
|
551
|
-
* @param {Object} [options]
|
552
595
|
*/
|
553
|
-
setRelated: function( related
|
596
|
+
setRelated: function( related ) {
|
554
597
|
this.related = related;
|
555
598
|
|
556
599
|
this.instance.acquire();
|
557
600
|
this.instance.attributes[ this.key ] = related;
|
558
601
|
this.instance.release();
|
559
602
|
},
|
560
|
-
|
603
|
+
|
561
604
|
/**
|
562
605
|
* Determine if a relation (on a different RelationalModel) is the reverse
|
563
606
|
* relation of the current one.
|
@@ -565,13 +608,10 @@
|
|
565
608
|
* @return {Boolean}
|
566
609
|
*/
|
567
610
|
_isReverseRelation: function( relation ) {
|
568
|
-
|
569
|
-
|
570
|
-
return true;
|
571
|
-
}
|
572
|
-
return false;
|
611
|
+
return relation.instance instanceof this.relatedModel && this.reverseRelation.key === relation.key &&
|
612
|
+
this.key === relation.reverseRelation.key;
|
573
613
|
},
|
574
|
-
|
614
|
+
|
575
615
|
/**
|
576
616
|
* Get the reverse relations (pointing back to 'this.key' on 'this.instance') for the currently related model(s).
|
577
617
|
* @param {Backbone.RelationalModel} [model] Get the reverse relations for a specific model.
|
@@ -583,96 +623,86 @@
|
|
583
623
|
// Iterate over 'model', 'this.related.models' (if this.related is a Backbone.Collection), or wrap 'this.related' in an array.
|
584
624
|
var models = !_.isUndefined( model ) ? [ model ] : this.related && ( this.related.models || [ this.related ] );
|
585
625
|
_.each( models || [], function( related ) {
|
586
|
-
|
587
|
-
|
588
|
-
|
589
|
-
|
590
|
-
|
591
|
-
|
626
|
+
_.each( related.getRelations() || [], function( relation ) {
|
627
|
+
if ( this._isReverseRelation( relation ) ) {
|
628
|
+
reverseRelations.push( relation );
|
629
|
+
}
|
630
|
+
}, this );
|
631
|
+
}, this );
|
592
632
|
|
593
633
|
return reverseRelations;
|
594
634
|
},
|
595
|
-
|
596
|
-
/**
|
597
|
-
* Rename options.silent to options.silentChange, so events propagate properly.
|
598
|
-
* (for example in HasMany, from 'addRelated'->'handleAddition')
|
599
|
-
* @param {Object} [options]
|
600
|
-
* @return {Object}
|
601
|
-
*/
|
602
|
-
sanitizeOptions: function( options ) {
|
603
|
-
options = options ? _.clone( options ) : {};
|
604
|
-
if ( options.silent ) {
|
605
|
-
options.silentChange = true;
|
606
|
-
delete options.silent;
|
607
|
-
}
|
608
|
-
return options;
|
609
|
-
},
|
610
635
|
|
611
636
|
/**
|
612
|
-
*
|
613
|
-
*
|
614
|
-
* @param {Object} [options]
|
615
|
-
* @return {Object}
|
637
|
+
* When `this.instance` is destroyed, cleanup our relations.
|
638
|
+
* Get reverse relation, call removeRelated on each.
|
616
639
|
*/
|
617
|
-
unsanitizeOptions: function( options ) {
|
618
|
-
options = options ? _.clone( options ) : {};
|
619
|
-
if ( options.silentChange ) {
|
620
|
-
options.silent = true;
|
621
|
-
delete options.silentChange;
|
622
|
-
}
|
623
|
-
return options;
|
624
|
-
},
|
625
|
-
|
626
|
-
// Cleanup. Get reverse relation, call removeRelated on each.
|
627
640
|
destroy: function() {
|
628
|
-
|
629
|
-
|
630
|
-
|
631
|
-
|
632
|
-
|
633
|
-
|
641
|
+
this.stopListening();
|
642
|
+
|
643
|
+
if ( this instanceof Backbone.HasOne ) {
|
644
|
+
this.setRelated( null );
|
645
|
+
}
|
646
|
+
else if ( this instanceof Backbone.HasMany ) {
|
647
|
+
this.setRelated( this._prepareCollection() );
|
648
|
+
}
|
634
649
|
|
635
|
-
_.each( this.getReverseRelations()
|
636
|
-
|
637
|
-
|
650
|
+
_.each( this.getReverseRelations(), function( relation ) {
|
651
|
+
relation.removeRelated( this.instance );
|
652
|
+
}, this );
|
638
653
|
}
|
639
654
|
});
|
640
|
-
|
655
|
+
|
641
656
|
Backbone.HasOne = Backbone.Relation.extend({
|
642
657
|
options: {
|
643
658
|
reverseRelation: { type: 'HasMany' }
|
644
659
|
},
|
645
|
-
|
646
|
-
initialize: function() {
|
647
|
-
_.bindAll( this, 'onChange' );
|
648
660
|
|
649
|
-
|
661
|
+
initialize: function( opts ) {
|
662
|
+
this.listenTo( this.instance, 'relational:change:' + this.key, this.onChange );
|
650
663
|
|
651
|
-
var
|
652
|
-
this.setRelated(
|
664
|
+
var related = this.findRelated( opts );
|
665
|
+
this.setRelated( related );
|
653
666
|
|
654
667
|
// Notify new 'related' object of the new relation.
|
655
|
-
_.each( this.getReverseRelations()
|
656
|
-
|
657
|
-
|
668
|
+
_.each( this.getReverseRelations(), function( relation ) {
|
669
|
+
relation.addRelated( this.instance, opts );
|
670
|
+
}, this );
|
658
671
|
},
|
659
|
-
|
672
|
+
|
673
|
+
/**
|
674
|
+
* Find related Models.
|
675
|
+
* @param {Object} [options]
|
676
|
+
* @return {Backbone.Model}
|
677
|
+
*/
|
660
678
|
findRelated: function( options ) {
|
661
|
-
var
|
662
|
-
|
663
|
-
|
664
|
-
|
665
|
-
|
679
|
+
var related = null;
|
680
|
+
|
681
|
+
options = _.defaults( { parse: this.options.parse }, options );
|
682
|
+
|
683
|
+
if ( this.keyContents instanceof this.relatedModel ) {
|
684
|
+
related = this.keyContents;
|
666
685
|
}
|
667
|
-
else if (
|
668
|
-
|
686
|
+
else if ( this.keyContents || this.keyContents === 0 ) { // since 0 can be a valid `id` as well
|
687
|
+
var opts = _.defaults( { create: this.options.createModels }, options );
|
688
|
+
related = this.relatedModel.findOrCreate( this.keyContents, opts );
|
669
689
|
}
|
670
|
-
|
671
|
-
return
|
690
|
+
|
691
|
+
return related;
|
672
692
|
},
|
673
|
-
|
693
|
+
|
694
|
+
/**
|
695
|
+
* Normalize and reduce `keyContents` to an `id`, for easier comparison
|
696
|
+
* @param {String|Number|Backbone.Model} keyContents
|
697
|
+
*/
|
698
|
+
setKeyContents: function( keyContents ) {
|
699
|
+
this.keyContents = keyContents;
|
700
|
+
this.keyId = Backbone.Relational.store.resolveIdForItem( this.relatedModel, this.keyContents );
|
701
|
+
},
|
702
|
+
|
674
703
|
/**
|
675
|
-
*
|
704
|
+
* Event handler for `change:<key>`.
|
705
|
+
* If the key is changed, notify old & new reverse relations and initialize the new relation.
|
676
706
|
*/
|
677
707
|
onChange: function( model, attr, options ) {
|
678
708
|
// Don't accept recursive calls to onChange (like onChange->findRelated->findOrCreate->initializeRelations->addRelated->onChange)
|
@@ -680,81 +710,70 @@
|
|
680
710
|
return;
|
681
711
|
}
|
682
712
|
this.acquire();
|
683
|
-
options =
|
713
|
+
options = options ? _.clone( options ) : {};
|
684
714
|
|
685
|
-
// 'options.
|
715
|
+
// 'options.__related' is set by 'addRelated'/'removeRelated'. If it is set, the change
|
686
716
|
// is the result of a call from a relation. If it's not, the change is the result of
|
687
717
|
// a 'set' call on this.instance.
|
688
|
-
var changed = _.isUndefined( options.
|
689
|
-
|
718
|
+
var changed = _.isUndefined( options.__related ),
|
719
|
+
oldRelated = changed ? this.related : options.__related;
|
690
720
|
|
691
|
-
if ( changed ) {
|
692
|
-
this.
|
693
|
-
|
694
|
-
|
695
|
-
if ( attr instanceof this.relatedModel ) {
|
696
|
-
this.related = attr;
|
697
|
-
}
|
698
|
-
else if ( attr ) {
|
699
|
-
var related = this.findRelated( options );
|
700
|
-
this.setRelated( related );
|
701
|
-
}
|
702
|
-
else {
|
703
|
-
this.setRelated( null );
|
704
|
-
}
|
721
|
+
if ( changed ) {
|
722
|
+
this.setKeyContents( attr );
|
723
|
+
var related = this.findRelated( options );
|
724
|
+
this.setRelated( related );
|
705
725
|
}
|
706
726
|
|
707
727
|
// Notify old 'related' object of the terminated relation
|
708
728
|
if ( oldRelated && this.related !== oldRelated ) {
|
709
|
-
_.each( this.getReverseRelations( oldRelated )
|
710
|
-
|
711
|
-
|
729
|
+
_.each( this.getReverseRelations( oldRelated ), function( relation ) {
|
730
|
+
relation.removeRelated( this.instance, null, options );
|
731
|
+
}, this );
|
712
732
|
}
|
713
|
-
|
733
|
+
|
714
734
|
// Notify new 'related' object of the new relation. Note we do re-apply even if this.related is oldRelated;
|
715
735
|
// that can be necessary for bi-directional relations if 'this.instance' was created after 'this.related'.
|
716
736
|
// In that case, 'this.instance' will already know 'this.related', but the reverse might not exist yet.
|
717
|
-
_.each( this.getReverseRelations()
|
718
|
-
|
719
|
-
|
737
|
+
_.each( this.getReverseRelations(), function( relation ) {
|
738
|
+
relation.addRelated( this.instance, options );
|
739
|
+
}, this );
|
720
740
|
|
721
|
-
// Fire the '
|
722
|
-
if ( !options.
|
741
|
+
// Fire the 'change:<key>' event if 'related' was updated
|
742
|
+
if ( !options.silent && this.related !== oldRelated ) {
|
723
743
|
var dit = this;
|
744
|
+
this.changed = true;
|
724
745
|
Backbone.Relational.eventQueue.add( function() {
|
725
|
-
dit.instance.trigger( '
|
746
|
+
dit.instance.trigger( 'change:' + dit.key, dit.instance, dit.related, options, true );
|
747
|
+
dit.changed = false;
|
726
748
|
});
|
727
749
|
}
|
728
750
|
this.release();
|
729
751
|
},
|
730
|
-
|
752
|
+
|
731
753
|
/**
|
732
754
|
* If a new 'this.relatedModel' appears in the 'store', try to match it to the last set 'keyContents'
|
733
755
|
*/
|
734
|
-
tryAddRelated: function( model, options ) {
|
735
|
-
if ( this.
|
736
|
-
|
737
|
-
|
738
|
-
options = this.sanitizeOptions( options );
|
739
|
-
|
740
|
-
var item = this.keyContents;
|
741
|
-
if ( item || item === 0 ) { // since 0 can be a valid `id` as well
|
742
|
-
var id = Backbone.Relational.store.resolveIdForItem( this.relatedModel, item );
|
743
|
-
if ( !_.isNull( id ) && model.id === id ) {
|
744
|
-
this.addRelated( model, options );
|
745
|
-
}
|
756
|
+
tryAddRelated: function( model, coll, options ) {
|
757
|
+
if ( ( this.keyId || this.keyId === 0 ) && model.id === this.keyId ) { // since 0 can be a valid `id` as well
|
758
|
+
this.addRelated( model, options );
|
759
|
+
this.keyId = null;
|
746
760
|
}
|
747
761
|
},
|
748
|
-
|
762
|
+
|
749
763
|
addRelated: function( model, options ) {
|
750
|
-
|
751
|
-
|
752
|
-
|
753
|
-
|
754
|
-
|
764
|
+
// Allow 'model' to set up its relations before proceeding.
|
765
|
+
// (which can result in a call to 'addRelated' from a relation of 'model')
|
766
|
+
var dit = this;
|
767
|
+
model.queue( function() {
|
768
|
+
if ( model !== dit.related ) {
|
769
|
+
var oldRelated = dit.related || null;
|
770
|
+
dit.setRelated( model );
|
771
|
+
dit.onChange( dit.instance, model, _.defaults( { __related: oldRelated }, options ) );
|
772
|
+
}
|
773
|
+
});
|
755
774
|
},
|
756
|
-
|
757
|
-
removeRelated: function( model, options ) {
|
775
|
+
|
776
|
+
removeRelated: function( model, coll, options ) {
|
758
777
|
if ( !this.related ) {
|
759
778
|
return;
|
760
779
|
}
|
@@ -762,24 +781,23 @@
|
|
762
781
|
if ( model === this.related ) {
|
763
782
|
var oldRelated = this.related || null;
|
764
783
|
this.setRelated( null );
|
765
|
-
this.onChange( this.instance, model, {
|
784
|
+
this.onChange( this.instance, model, _.defaults( { __related: oldRelated }, options ) );
|
766
785
|
}
|
767
786
|
}
|
768
787
|
});
|
769
|
-
|
788
|
+
|
770
789
|
Backbone.HasMany = Backbone.Relation.extend({
|
771
790
|
collectionType: null,
|
772
|
-
|
791
|
+
|
773
792
|
options: {
|
774
793
|
reverseRelation: { type: 'HasOne' },
|
775
794
|
collectionType: Backbone.Collection,
|
776
795
|
collectionKey: true,
|
777
796
|
collectionOptions: {}
|
778
797
|
},
|
779
|
-
|
780
|
-
initialize: function() {
|
781
|
-
|
782
|
-
this.instance.bind( 'relational:change:' + this.key, this.onChange );
|
798
|
+
|
799
|
+
initialize: function( opts ) {
|
800
|
+
this.listenTo( this.instance, 'relational:change:' + this.key, this.onChange );
|
783
801
|
|
784
802
|
// Handle a custom 'collectionType'
|
785
803
|
this.collectionType = this.options.collectionType;
|
@@ -787,41 +805,29 @@
|
|
787
805
|
this.collectionType = Backbone.Relational.store.getObjectByName( this.collectionType );
|
788
806
|
}
|
789
807
|
if ( !this.collectionType.prototype instanceof Backbone.Collection ){
|
790
|
-
throw new Error( 'collectionType must inherit from Backbone.Collection' );
|
808
|
+
throw new Error( '`collectionType` must inherit from Backbone.Collection' );
|
791
809
|
}
|
792
810
|
|
793
|
-
|
794
|
-
|
795
|
-
this.setRelated( this._prepareCollection( this.keyContents ) );
|
796
|
-
}
|
797
|
-
else {
|
798
|
-
this.setRelated( this._prepareCollection() );
|
799
|
-
}
|
800
|
-
|
801
|
-
this.findRelated( { silent: true } );
|
802
|
-
},
|
803
|
-
|
804
|
-
_getCollectionOptions: function() {
|
805
|
-
return _.isFunction( this.options.collectionOptions ) ?
|
806
|
-
this.options.collectionOptions( this.instance ) :
|
807
|
-
this.options.collectionOptions;
|
811
|
+
var related = this.findRelated( opts );
|
812
|
+
this.setRelated( related );
|
808
813
|
},
|
809
814
|
|
810
815
|
/**
|
811
816
|
* Bind events and setup collectionKeys for a collection that is to be used as the backing store for a HasMany.
|
812
817
|
* If no 'collection' is supplied, a new collection will be created of the specified 'collectionType' option.
|
813
818
|
* @param {Backbone.Collection} [collection]
|
819
|
+
* @return {Backbone.Collection}
|
814
820
|
*/
|
815
821
|
_prepareCollection: function( collection ) {
|
816
822
|
if ( this.related ) {
|
817
|
-
this.related
|
818
|
-
.unbind( 'relational:add', this.handleAddition )
|
819
|
-
.unbind( 'relational:remove', this.handleRemoval )
|
820
|
-
.unbind( 'relational:reset', this.handleReset )
|
823
|
+
this.stopListening( this.related );
|
821
824
|
}
|
822
825
|
|
823
826
|
if ( !collection || !( collection instanceof Backbone.Collection ) ) {
|
824
|
-
|
827
|
+
var options = _.isFunction( this.options.collectionOptions ) ?
|
828
|
+
this.options.collectionOptions( this.instance ) : this.options.collectionOptions;
|
829
|
+
|
830
|
+
collection = new this.collectionType( null, options );
|
825
831
|
}
|
826
832
|
|
827
833
|
collection.model = this.relatedModel;
|
@@ -838,203 +844,184 @@
|
|
838
844
|
collection[ key ] = this.instance;
|
839
845
|
}
|
840
846
|
}
|
841
|
-
|
842
|
-
collection
|
843
|
-
.
|
844
|
-
.
|
845
|
-
.bind( 'relational:reset', this.handleReset );
|
847
|
+
|
848
|
+
this.listenTo( collection, 'relational:add', this.handleAddition )
|
849
|
+
.listenTo( collection, 'relational:remove', this.handleRemoval )
|
850
|
+
.listenTo( collection, 'relational:reset', this.handleReset );
|
846
851
|
|
847
852
|
return collection;
|
848
853
|
},
|
849
|
-
|
850
|
-
findRelated: function( options ) {
|
851
|
-
if ( this.keyContents ) {
|
852
|
-
var models = [];
|
853
854
|
|
854
|
-
|
855
|
-
|
856
|
-
|
857
|
-
|
858
|
-
|
859
|
-
|
860
|
-
|
861
|
-
// Try to find instances of the appropriate 'relatedModel' in the store
|
862
|
-
_.each( this.keyContents || [], function( item ) {
|
863
|
-
var model = null;
|
864
|
-
if ( item instanceof this.relatedModel ) {
|
865
|
-
model = item;
|
866
|
-
}
|
867
|
-
else if ( item || item === 0 ) { // since 0 can be a valid `id` as well
|
868
|
-
model = this.relatedModel.findOrCreate( item, { create: this.options.createModels } );
|
869
|
-
}
|
855
|
+
/**
|
856
|
+
* Find related Models.
|
857
|
+
* @param {Object} [options]
|
858
|
+
* @return {Backbone.Collection}
|
859
|
+
*/
|
860
|
+
findRelated: function( options ) {
|
861
|
+
var related = null;
|
870
862
|
|
871
|
-
|
872
|
-
models.push( model );
|
873
|
-
}
|
874
|
-
}, this );
|
875
|
-
}
|
863
|
+
options = _.defaults( { parse: this.options.parse }, options );
|
876
864
|
|
877
|
-
|
878
|
-
|
879
|
-
|
880
|
-
|
881
|
-
}
|
865
|
+
// Replace 'this.related' by 'this.keyContents' if it is a Backbone.Collection
|
866
|
+
if ( this.keyContents instanceof Backbone.Collection ) {
|
867
|
+
this._prepareCollection( this.keyContents );
|
868
|
+
related = this.keyContents;
|
882
869
|
}
|
883
|
-
|
884
|
-
|
885
|
-
/**
|
886
|
-
* If the key is changed, notify old & new reverse relations and initialize the new relation
|
887
|
-
*/
|
888
|
-
onChange: function( model, attr, options ) {
|
889
|
-
options = this.sanitizeOptions( options );
|
890
|
-
this.keyContents = attr;
|
891
|
-
|
892
|
-
// Replace 'this.related' by 'attr' if it is a Backbone.Collection
|
893
|
-
if ( attr instanceof Backbone.Collection ) {
|
894
|
-
this._prepareCollection( attr );
|
895
|
-
this.related = attr;
|
896
|
-
}
|
897
|
-
// Otherwise, 'attr' should be an array of related object ids.
|
898
|
-
// Re-use the current 'this.related' if it is a Backbone.Collection, and remove any current entries.
|
899
|
-
// Otherwise, create a new collection.
|
870
|
+
// Otherwise, 'this.keyContents' should be an array of related object ids.
|
871
|
+
// Re-use the current 'this.related' if it is a Backbone.Collection; otherwise, create a new collection.
|
900
872
|
else {
|
901
|
-
var
|
873
|
+
var toAdd = [];
|
902
874
|
|
903
|
-
|
904
|
-
|
905
|
-
|
875
|
+
_.each( this.keyContents, function( attributes ) {
|
876
|
+
if ( attributes instanceof this.relatedModel ) {
|
877
|
+
var model = attributes;
|
878
|
+
}
|
879
|
+
else {
|
880
|
+
// If `merge` is true, update models here, instead of during update.
|
881
|
+
model = this.relatedModel.findOrCreate( attributes, _.extend( { merge: true }, options, { create: this.options.createModels } ) );
|
882
|
+
}
|
906
883
|
|
907
|
-
|
908
|
-
|
909
|
-
});
|
884
|
+
model && toAdd.push( model );
|
885
|
+
}, this );
|
910
886
|
|
911
|
-
|
912
|
-
|
913
|
-
|
914
|
-
|
915
|
-
|
916
|
-
// client-created new models when the fetch is completed.
|
917
|
-
if ( !options.keepNewModels || !model.isNew() ) {
|
918
|
-
oldIds[ model.id ] = true;
|
919
|
-
coll.remove( model, { silent: (model.id in newIds) } );
|
920
|
-
}
|
921
|
-
});
|
922
|
-
} else {
|
923
|
-
coll = this._prepareCollection();
|
887
|
+
if ( this.related instanceof Backbone.Collection ) {
|
888
|
+
related = this.related;
|
889
|
+
}
|
890
|
+
else {
|
891
|
+
related = this._prepareCollection();
|
924
892
|
}
|
925
893
|
|
926
|
-
|
927
|
-
|
928
|
-
if (model) {
|
929
|
-
coll.add( model, { silent: (attributes.id in oldIds)} );
|
930
|
-
}
|
931
|
-
}, this);
|
894
|
+
related.update( toAdd, _.defaults( { merge: false, parse: false }, options ) );
|
895
|
+
}
|
932
896
|
|
933
|
-
|
897
|
+
return related;
|
898
|
+
},
|
934
899
|
|
900
|
+
/**
|
901
|
+
* Normalize and reduce `keyContents` to a list of `ids`, for easier comparison
|
902
|
+
* @param {String|Number|String[]|Number[]|Backbone.Collection} keyContents
|
903
|
+
*/
|
904
|
+
setKeyContents: function( keyContents ) {
|
905
|
+
this.keyContents = keyContents instanceof Backbone.Collection ? keyContents : null;
|
906
|
+
this.keyIds = [];
|
907
|
+
|
908
|
+
if ( !this.keyContents && ( keyContents || keyContents === 0 ) ) { // since 0 can be a valid `id` as well
|
909
|
+
// Handle cases the an API/user supplies just an Object/id instead of an Array
|
910
|
+
this.keyContents = _.isArray( keyContents ) ? keyContents : [ keyContents ];
|
911
|
+
|
912
|
+
_.each( this.keyContents, function( item ) {
|
913
|
+
var itemId = Backbone.Relational.store.resolveIdForItem( this.relatedModel, item );
|
914
|
+
if ( itemId || itemId === 0 ) {
|
915
|
+
this.keyIds.push( itemId );
|
916
|
+
}
|
917
|
+
}, this );
|
935
918
|
}
|
936
|
-
|
937
|
-
var dit = this;
|
938
|
-
Backbone.Relational.eventQueue.add( function() {
|
939
|
-
!options.silentChange && dit.instance.trigger( 'update:' + dit.key, dit.instance, dit.related, options );
|
940
|
-
});
|
941
919
|
},
|
942
|
-
|
943
|
-
|
944
|
-
|
945
|
-
|
946
|
-
|
947
|
-
|
948
|
-
|
949
|
-
|
950
|
-
|
951
|
-
|
952
|
-
|
953
|
-
|
954
|
-
|
920
|
+
|
921
|
+
/**
|
922
|
+
* Event handler for `change:<key>`.
|
923
|
+
* If the contents of the key are changed, notify old & new reverse relations and initialize the new relation.
|
924
|
+
*/
|
925
|
+
onChange: function( model, attr, options ) {
|
926
|
+
options = options ? _.clone( options ) : {};
|
927
|
+
this.setKeyContents( attr );
|
928
|
+
this.changed = false;
|
929
|
+
|
930
|
+
var related = this.findRelated( options );
|
931
|
+
this.setRelated( related );
|
932
|
+
|
933
|
+
if ( !options.silent ) {
|
934
|
+
var dit = this;
|
935
|
+
Backbone.Relational.eventQueue.add( function() {
|
936
|
+
// The `changed` flag can be set in `handleAddition` or `handleRemoval`
|
937
|
+
if ( dit.changed ) {
|
938
|
+
dit.instance.trigger( 'change:' + dit.key, dit.instance, dit.related, options, true );
|
939
|
+
dit.changed = false;
|
940
|
+
}
|
941
|
+
});
|
955
942
|
}
|
956
943
|
},
|
957
|
-
|
944
|
+
|
958
945
|
/**
|
959
946
|
* When a model is added to a 'HasMany', trigger 'add' on 'this.instance' and notify reverse relations.
|
960
947
|
* (should be 'HasOne', must set 'this.instance' as their related).
|
961
|
-
|
948
|
+
*/
|
962
949
|
handleAddition: function( model, coll, options ) {
|
963
950
|
//console.debug('handleAddition called; args=%o', arguments);
|
964
|
-
|
965
|
-
|
966
|
-
if ( !( model instanceof Backbone.Model ) ) {
|
967
|
-
return;
|
968
|
-
}
|
969
|
-
|
970
|
-
options = this.sanitizeOptions( options );
|
951
|
+
options = options ? _.clone( options ) : {};
|
952
|
+
this.changed = true;
|
971
953
|
|
972
|
-
_.each( this.getReverseRelations( model )
|
973
|
-
|
974
|
-
|
954
|
+
_.each( this.getReverseRelations( model ), function( relation ) {
|
955
|
+
relation.addRelated( this.instance, options );
|
956
|
+
}, this );
|
975
957
|
|
976
|
-
// Only trigger 'add' once the newly added model is initialized (so, has
|
958
|
+
// Only trigger 'add' once the newly added model is initialized (so, has its relations set up)
|
977
959
|
var dit = this;
|
978
|
-
Backbone.Relational.eventQueue.add( function() {
|
979
|
-
|
960
|
+
!options.silent && Backbone.Relational.eventQueue.add( function() {
|
961
|
+
dit.instance.trigger( 'add:' + dit.key, model, dit.related, options );
|
980
962
|
});
|
981
963
|
},
|
982
|
-
|
964
|
+
|
983
965
|
/**
|
984
966
|
* When a model is removed from a 'HasMany', trigger 'remove' on 'this.instance' and notify reverse relations.
|
985
967
|
* (should be 'HasOne', which should be nullified)
|
986
968
|
*/
|
987
969
|
handleRemoval: function( model, coll, options ) {
|
988
970
|
//console.debug('handleRemoval called; args=%o', arguments);
|
989
|
-
|
990
|
-
|
991
|
-
}
|
992
|
-
|
993
|
-
options = this.sanitizeOptions( options );
|
971
|
+
options = options ? _.clone( options ) : {};
|
972
|
+
this.changed = true;
|
994
973
|
|
995
|
-
_.each( this.getReverseRelations( model )
|
996
|
-
|
997
|
-
|
974
|
+
_.each( this.getReverseRelations( model ), function( relation ) {
|
975
|
+
relation.removeRelated( this.instance, null, options );
|
976
|
+
}, this );
|
998
977
|
|
999
978
|
var dit = this;
|
1000
|
-
Backbone.Relational.eventQueue.add( function() {
|
1001
|
-
|
979
|
+
!options.silent && Backbone.Relational.eventQueue.add( function() {
|
980
|
+
dit.instance.trigger( 'remove:' + dit.key, model, dit.related, options );
|
1002
981
|
});
|
1003
982
|
},
|
1004
983
|
|
1005
984
|
handleReset: function( coll, options ) {
|
1006
|
-
options = this.sanitizeOptions( options );
|
1007
|
-
|
1008
985
|
var dit = this;
|
1009
|
-
|
1010
|
-
|
986
|
+
options = options ? _.clone( options ) : {};
|
987
|
+
!options.silent && Backbone.Relational.eventQueue.add( function() {
|
988
|
+
dit.instance.trigger( 'reset:' + dit.key, dit.related, options );
|
1011
989
|
});
|
1012
990
|
},
|
1013
|
-
|
991
|
+
|
992
|
+
tryAddRelated: function( model, coll, options ) {
|
993
|
+
var item = _.contains( this.keyIds, model.id );
|
994
|
+
|
995
|
+
if ( item ) {
|
996
|
+
this.addRelated( model, options );
|
997
|
+
this.keyIds = _.without( this.keyIds, model.id );
|
998
|
+
}
|
999
|
+
},
|
1000
|
+
|
1014
1001
|
addRelated: function( model, options ) {
|
1002
|
+
// Allow 'model' to set up its relations before proceeding.
|
1003
|
+
// (which can result in a call to 'addRelated' from a relation of 'model')
|
1015
1004
|
var dit = this;
|
1016
|
-
|
1017
|
-
model.queue( function() { // Queued to avoid errors for adding 'model' to the 'this.related' set twice
|
1005
|
+
model.queue( function() {
|
1018
1006
|
if ( dit.related && !dit.related.get( model ) ) {
|
1019
1007
|
dit.related.add( model, options );
|
1020
1008
|
}
|
1021
1009
|
});
|
1022
1010
|
},
|
1023
|
-
|
1024
|
-
removeRelated: function( model, options ) {
|
1025
|
-
options = this.unsanitizeOptions( options );
|
1011
|
+
|
1012
|
+
removeRelated: function( model, coll, options ) {
|
1026
1013
|
if ( this.related.get( model ) ) {
|
1027
1014
|
this.related.remove( model, options );
|
1028
1015
|
}
|
1029
1016
|
}
|
1030
1017
|
});
|
1031
|
-
|
1018
|
+
|
1032
1019
|
/**
|
1033
1020
|
* A type of Backbone.Model that also maintains relations to other models and collections.
|
1034
1021
|
* New events when compared to the original:
|
1035
1022
|
* - 'add:<key>' (model, related collection, options)
|
1036
1023
|
* - 'remove:<key>' (model, related collection, options)
|
1037
|
-
* - '
|
1024
|
+
* - 'change:<key>' (model, related model or collection, options)
|
1038
1025
|
*/
|
1039
1026
|
Backbone.RelationalModel = Backbone.Model.extend({
|
1040
1027
|
relations: null, // Relation descriptions on the prototype
|
@@ -1042,56 +1029,98 @@
|
|
1042
1029
|
_isInitialized: false,
|
1043
1030
|
_deferProcessing: false,
|
1044
1031
|
_queue: null,
|
1045
|
-
|
1032
|
+
|
1046
1033
|
subModelTypeAttribute: 'type',
|
1047
1034
|
subModelTypes: null,
|
1048
|
-
|
1035
|
+
|
1049
1036
|
constructor: function( attributes, options ) {
|
1050
1037
|
// Nasty hack, for cases like 'model.get( <HasMany key> ).add( item )'.
|
1051
|
-
// Defer 'processQueue', so that when 'Relation.createModels' is used we
|
1052
|
-
//
|
1053
|
-
//
|
1054
|
-
// it's relations, then trying to add it to the collection).
|
1055
|
-
// b) Trigger 'HasMany' collection events only after the model is really fully set up.
|
1056
|
-
// Example that triggers both a and b: "p.get('jobs').add( { company: c, person: p } )".
|
1057
|
-
var dit = this;
|
1038
|
+
// Defer 'processQueue', so that when 'Relation.createModels' is used we trigger 'HasMany'
|
1039
|
+
// collection events only after the model is really fully set up.
|
1040
|
+
// Example: "p.get('jobs').add( { company: c, person: p } )".
|
1058
1041
|
if ( options && options.collection ) {
|
1042
|
+
var dit = this,
|
1043
|
+
collection = this.collection = options.collection;
|
1044
|
+
|
1045
|
+
// Prevent this option from cascading down to related models; they shouldn't go into this `if` clause.
|
1046
|
+
delete options.collection;
|
1047
|
+
|
1059
1048
|
this._deferProcessing = true;
|
1060
|
-
|
1049
|
+
|
1061
1050
|
var processQueue = function( model ) {
|
1062
1051
|
if ( model === dit ) {
|
1063
1052
|
dit._deferProcessing = false;
|
1064
1053
|
dit.processQueue();
|
1065
|
-
|
1054
|
+
collection.off( 'relational:add', processQueue );
|
1066
1055
|
}
|
1067
1056
|
};
|
1068
|
-
|
1069
|
-
|
1070
|
-
// So we do process the queue eventually, regardless of whether this model
|
1057
|
+
collection.on( 'relational:add', processQueue );
|
1058
|
+
|
1059
|
+
// So we do process the queue eventually, regardless of whether this model actually gets added to 'options.collection'.
|
1071
1060
|
_.defer( function() {
|
1072
1061
|
processQueue( dit );
|
1073
1062
|
});
|
1074
1063
|
}
|
1064
|
+
|
1065
|
+
Backbone.Relational.store.processOrphanRelations();
|
1075
1066
|
|
1076
1067
|
this._queue = new Backbone.BlockingQueue();
|
1077
1068
|
this._queue.block();
|
1078
1069
|
Backbone.Relational.eventQueue.block();
|
1079
|
-
|
1080
|
-
|
1081
|
-
|
1082
|
-
|
1083
|
-
|
1070
|
+
|
1071
|
+
try {
|
1072
|
+
Backbone.Model.apply( this, arguments );
|
1073
|
+
}
|
1074
|
+
finally {
|
1075
|
+
// Try to run the global queue holding external events
|
1076
|
+
Backbone.Relational.eventQueue.unblock();
|
1077
|
+
}
|
1084
1078
|
},
|
1085
|
-
|
1079
|
+
|
1086
1080
|
/**
|
1087
1081
|
* Override 'trigger' to queue 'change' and 'change:*' events
|
1088
1082
|
*/
|
1089
1083
|
trigger: function( eventName ) {
|
1090
|
-
if ( eventName.length > 5 && 'change' ===
|
1091
|
-
var dit = this,
|
1084
|
+
if ( eventName.length > 5 && eventName.indexOf( 'change' ) === 0 ) {
|
1085
|
+
var dit = this,
|
1086
|
+
args = arguments;
|
1087
|
+
|
1092
1088
|
Backbone.Relational.eventQueue.add( function() {
|
1093
|
-
|
1094
|
-
|
1089
|
+
if ( !dit._isInitialized ) {
|
1090
|
+
return;
|
1091
|
+
}
|
1092
|
+
|
1093
|
+
// Determine if the `change` event is still valid, now that all relations are populated
|
1094
|
+
var changed = true;
|
1095
|
+
if ( eventName === 'change' ) {
|
1096
|
+
changed = dit.hasChanged();
|
1097
|
+
}
|
1098
|
+
else {
|
1099
|
+
var attr = eventName.slice( 7 ),
|
1100
|
+
rel = dit.getRelation( attr );
|
1101
|
+
|
1102
|
+
if ( rel ) {
|
1103
|
+
// If `attr` is a relation, `change:attr` get triggered from `Relation.onChange`.
|
1104
|
+
// These take precedence over `change:attr` events triggered by `Model.set`.
|
1105
|
+
// The relation set a fourth attribute to `true`. If this attribute is present,
|
1106
|
+
// continue triggering this event; otherwise, it's from `Model.set` and should be stopped.
|
1107
|
+
changed = ( args[ 4 ] === true );
|
1108
|
+
|
1109
|
+
// If this event was triggered by a relation, set the right value in `this.changed`
|
1110
|
+
// (a Collection or Model instead of raw data).
|
1111
|
+
if ( changed ) {
|
1112
|
+
dit.changed[ attr ] = args[ 2 ];
|
1113
|
+
}
|
1114
|
+
// Otherwise, this event is from `Model.set`. If the relation doesn't report a change,
|
1115
|
+
// remove attr from `dit.changed` so `hasChanged` doesn't take it into account.
|
1116
|
+
else if ( !rel.changed ) {
|
1117
|
+
delete dit.changed[ attr ];
|
1118
|
+
}
|
1119
|
+
}
|
1120
|
+
}
|
1121
|
+
|
1122
|
+
changed && Backbone.Model.prototype.trigger.apply( dit, args );
|
1123
|
+
});
|
1095
1124
|
}
|
1096
1125
|
else {
|
1097
1126
|
Backbone.Model.prototype.trigger.apply( this, arguments );
|
@@ -1099,24 +1128,18 @@
|
|
1099
1128
|
|
1100
1129
|
return this;
|
1101
1130
|
},
|
1102
|
-
|
1131
|
+
|
1103
1132
|
/**
|
1104
1133
|
* Initialize Relations present in this.relations; determine the type (HasOne/HasMany), then creates a new instance.
|
1105
1134
|
* Invoked in the first call so 'set' (which is made from the Backbone.Model constructor).
|
1106
1135
|
*/
|
1107
|
-
initializeRelations: function() {
|
1136
|
+
initializeRelations: function( options ) {
|
1108
1137
|
this.acquire(); // Setting up relations often also involve calls to 'set', and we only want to enter this function once
|
1109
|
-
this._relations =
|
1138
|
+
this._relations = {};
|
1110
1139
|
|
1111
1140
|
_.each( this.relations || [], function( rel ) {
|
1112
|
-
|
1113
|
-
|
1114
|
-
new type( this, rel ); // Also pushes the new Relation into _relations
|
1115
|
-
}
|
1116
|
-
else {
|
1117
|
-
Backbone.Relational.showWarnings && typeof console !== 'undefined' && console.warn( 'Relation=%o; missing or invalid type!', rel );
|
1118
|
-
}
|
1119
|
-
}, this );
|
1141
|
+
Backbone.Relational.store.initializeRelation( this, rel, options );
|
1142
|
+
}, this );
|
1120
1143
|
|
1121
1144
|
this._isInitialized = true;
|
1122
1145
|
this.release();
|
@@ -1129,7 +1152,7 @@
|
|
1129
1152
|
*/
|
1130
1153
|
updateRelations: function( options ) {
|
1131
1154
|
if ( this._isInitialized && !this.isLocked() ) {
|
1132
|
-
_.each( this._relations
|
1155
|
+
_.each( this._relations, function( rel ) {
|
1133
1156
|
// Update from data in `rel.keySource` if set, or `rel.key` otherwise
|
1134
1157
|
var val = this.attributes[ rel.keySource ] || this.attributes[ rel.key ];
|
1135
1158
|
if ( rel.related !== val ) {
|
@@ -1138,14 +1161,14 @@
|
|
1138
1161
|
}, this );
|
1139
1162
|
}
|
1140
1163
|
},
|
1141
|
-
|
1164
|
+
|
1142
1165
|
/**
|
1143
1166
|
* Either add to the queue (if we're not initialized yet), or execute right away.
|
1144
1167
|
*/
|
1145
1168
|
queue: function( func ) {
|
1146
1169
|
this._queue.add( func );
|
1147
1170
|
},
|
1148
|
-
|
1171
|
+
|
1149
1172
|
/**
|
1150
1173
|
* Process _queue
|
1151
1174
|
*/
|
@@ -1154,62 +1177,58 @@
|
|
1154
1177
|
this._queue.unblock();
|
1155
1178
|
}
|
1156
1179
|
},
|
1157
|
-
|
1180
|
+
|
1158
1181
|
/**
|
1159
1182
|
* Get a specific relation.
|
1160
1183
|
* @param key {string} The relation key to look for.
|
1161
1184
|
* @return {Backbone.Relation} An instance of 'Backbone.Relation', if a relation was found for 'key', or null.
|
1162
1185
|
*/
|
1163
1186
|
getRelation: function( key ) {
|
1164
|
-
return
|
1165
|
-
if ( rel.key === key ) {
|
1166
|
-
return true;
|
1167
|
-
}
|
1168
|
-
}, this );
|
1187
|
+
return this._relations[ key ];
|
1169
1188
|
},
|
1170
|
-
|
1189
|
+
|
1171
1190
|
/**
|
1172
1191
|
* Get all of the created relations.
|
1173
1192
|
* @return {Backbone.Relation[]}
|
1174
1193
|
*/
|
1175
1194
|
getRelations: function() {
|
1176
|
-
return this._relations;
|
1195
|
+
return _.values( this._relations );
|
1177
1196
|
},
|
1178
|
-
|
1197
|
+
|
1179
1198
|
/**
|
1180
1199
|
* Retrieve related objects.
|
1181
1200
|
* @param key {string} The relation key to fetch models for.
|
1182
1201
|
* @param [options] {Object} Options for 'Backbone.Model.fetch' and 'Backbone.sync'.
|
1183
|
-
* @param [
|
1202
|
+
* @param [refresh=false] {boolean} Fetch existing models from the server as well (in order to update them).
|
1184
1203
|
* @return {jQuery.when[]} An array of request objects
|
1185
1204
|
*/
|
1186
|
-
fetchRelated: function( key, options,
|
1187
|
-
|
1205
|
+
fetchRelated: function( key, options, refresh ) {
|
1206
|
+
// Set default `options` for fetch
|
1207
|
+
options = _.extend( { update: true, remove: false }, options );
|
1208
|
+
|
1188
1209
|
var setUrl,
|
1189
1210
|
requests = [],
|
1190
1211
|
rel = this.getRelation( key ),
|
1191
|
-
|
1192
|
-
toFetch =
|
1193
|
-
|
1194
|
-
return !_.isNull( id ) && ( update || !Backbone.Relational.store.find( rel.relatedModel, id ) );
|
1212
|
+
keys = rel && ( rel.keyIds || [ rel.keyId ] ),
|
1213
|
+
toFetch = keys && _.select( keys || [], function( id ) {
|
1214
|
+
return ( id || id === 0 ) && ( refresh || !Backbone.Relational.store.find( rel.relatedModel, id ) );
|
1195
1215
|
}, this );
|
1196
1216
|
|
1197
1217
|
if ( toFetch && toFetch.length ) {
|
1198
|
-
//
|
1199
|
-
var
|
1200
|
-
|
1201
|
-
|
1202
|
-
|
1203
|
-
|
1204
|
-
|
1205
|
-
|
1206
|
-
|
1207
|
-
|
1208
|
-
|
1209
|
-
}
|
1218
|
+
// Find (or create) a model for each one that is to be fetched
|
1219
|
+
var created = [],
|
1220
|
+
models = _.map( toFetch, function( id ) {
|
1221
|
+
var model = Backbone.Relational.store.find( rel.relatedModel, id );
|
1222
|
+
|
1223
|
+
if ( !model ) {
|
1224
|
+
var attrs = {};
|
1225
|
+
attrs[ rel.relatedModel.prototype.idAttribute ] = id;
|
1226
|
+
model = rel.relatedModel.findOrCreate( attrs, options );
|
1227
|
+
created.push( model );
|
1228
|
+
}
|
1210
1229
|
|
1211
|
-
|
1212
|
-
|
1230
|
+
return model;
|
1231
|
+
}, this );
|
1213
1232
|
|
1214
1233
|
// Try if the 'collection' can provide a url to fetch a set of models in one request.
|
1215
1234
|
if ( rel.related instanceof Backbone.Collection && _.isFunction( rel.related.url ) ) {
|
@@ -1224,26 +1243,27 @@
|
|
1224
1243
|
{
|
1225
1244
|
error: function() {
|
1226
1245
|
var args = arguments;
|
1227
|
-
_.each(
|
1228
|
-
|
1229
|
-
|
1230
|
-
|
1246
|
+
_.each( created, function( model ) {
|
1247
|
+
model.trigger( 'destroy', model, model.collection, options );
|
1248
|
+
options.error && options.error.apply( model, args );
|
1249
|
+
});
|
1231
1250
|
},
|
1232
1251
|
url: setUrl
|
1233
1252
|
},
|
1234
|
-
options
|
1235
|
-
{ add: true }
|
1253
|
+
options
|
1236
1254
|
);
|
1237
1255
|
|
1238
1256
|
requests = [ rel.related.fetch( opts ) ];
|
1239
1257
|
}
|
1240
1258
|
else {
|
1241
|
-
requests = _.map( models
|
1259
|
+
requests = _.map( models, function( model ) {
|
1242
1260
|
var opts = _.defaults(
|
1243
1261
|
{
|
1244
1262
|
error: function() {
|
1245
|
-
|
1246
|
-
|
1263
|
+
if ( _.contains( created, model ) ) {
|
1264
|
+
model.trigger( 'destroy', model, model.collection, options );
|
1265
|
+
options.error && options.error.apply( model, arguments );
|
1266
|
+
}
|
1247
1267
|
}
|
1248
1268
|
},
|
1249
1269
|
options
|
@@ -1255,7 +1275,32 @@
|
|
1255
1275
|
|
1256
1276
|
return requests;
|
1257
1277
|
},
|
1258
|
-
|
1278
|
+
|
1279
|
+
get: function( attr ) {
|
1280
|
+
var originalResult = Backbone.Model.prototype.get.call( this, attr );
|
1281
|
+
|
1282
|
+
// Use `originalResult` get if dotNotation not enabled or not required because no dot is in `attr`
|
1283
|
+
if ( !this.dotNotation || attr.indexOf( '.' ) === -1 ) {
|
1284
|
+
return originalResult;
|
1285
|
+
}
|
1286
|
+
|
1287
|
+
// Go through all splits and return the final result
|
1288
|
+
var splits = attr.split( '.' );
|
1289
|
+
var result = _.reduce(splits, function( model, split ) {
|
1290
|
+
if ( !( model instanceof Backbone.Model ) ) {
|
1291
|
+
throw new Error( 'Attribute must be an instanceof Backbone.Model. Is: ' + model + ', currentSplit: ' + split );
|
1292
|
+
}
|
1293
|
+
|
1294
|
+
return Backbone.Model.prototype.get.call( model, split );
|
1295
|
+
}, this );
|
1296
|
+
|
1297
|
+
if ( originalResult !== undefined && result !== undefined ) {
|
1298
|
+
throw new Error( "Ambiguous result for '" + attr + "'. direct result: " + originalResult + ", dotNotation: " + result );
|
1299
|
+
}
|
1300
|
+
|
1301
|
+
return originalResult || result;
|
1302
|
+
},
|
1303
|
+
|
1259
1304
|
set: function( key, value, options ) {
|
1260
1305
|
Backbone.Relational.eventQueue.block();
|
1261
1306
|
|
@@ -1273,28 +1318,31 @@
|
|
1273
1318
|
var result = Backbone.Model.prototype.set.apply( this, arguments );
|
1274
1319
|
|
1275
1320
|
// Ideal place to set up relations :)
|
1276
|
-
|
1277
|
-
this.
|
1321
|
+
try {
|
1322
|
+
if ( !this._isInitialized && !this.isLocked() ) {
|
1323
|
+
this.constructor.initializeModelHierarchy();
|
1278
1324
|
|
1279
|
-
|
1325
|
+
Backbone.Relational.store.register( this );
|
1280
1326
|
|
1281
|
-
|
1282
|
-
|
1283
|
-
|
1284
|
-
|
1285
|
-
|
1327
|
+
this.initializeRelations( options );
|
1328
|
+
}
|
1329
|
+
// Update the 'idAttribute' in Backbone.store if; we don't want it to miss an 'id' update due to {silent:true}
|
1330
|
+
else if ( attributes && this.idAttribute in attributes ) {
|
1331
|
+
Backbone.Relational.store.update( this );
|
1332
|
+
}
|
1333
|
+
|
1334
|
+
if ( attributes ) {
|
1335
|
+
this.updateRelations( options );
|
1336
|
+
}
|
1286
1337
|
}
|
1287
|
-
|
1288
|
-
|
1289
|
-
|
1338
|
+
finally {
|
1339
|
+
// Try to run the global queue holding external events
|
1340
|
+
Backbone.Relational.eventQueue.unblock();
|
1290
1341
|
}
|
1291
1342
|
|
1292
|
-
// Try to run the global queue holding external events
|
1293
|
-
Backbone.Relational.eventQueue.unblock();
|
1294
|
-
|
1295
1343
|
return result;
|
1296
1344
|
},
|
1297
|
-
|
1345
|
+
|
1298
1346
|
unset: function( attribute, options ) {
|
1299
1347
|
Backbone.Relational.eventQueue.block();
|
1300
1348
|
|
@@ -1306,7 +1354,7 @@
|
|
1306
1354
|
|
1307
1355
|
return result;
|
1308
1356
|
},
|
1309
|
-
|
1357
|
+
|
1310
1358
|
clear: function( options ) {
|
1311
1359
|
Backbone.Relational.eventQueue.block();
|
1312
1360
|
|
@@ -1325,17 +1373,17 @@
|
|
1325
1373
|
attributes[ this.idAttribute ] = null;
|
1326
1374
|
}
|
1327
1375
|
|
1328
|
-
_.each( this.getRelations()
|
1329
|
-
|
1330
|
-
|
1376
|
+
_.each( this.getRelations(), function( rel ) {
|
1377
|
+
delete attributes[ rel.key ];
|
1378
|
+
});
|
1331
1379
|
|
1332
1380
|
return new this.constructor( attributes );
|
1333
1381
|
},
|
1334
|
-
|
1382
|
+
|
1335
1383
|
/**
|
1336
1384
|
* Convert relations to JSON, omits them when required
|
1337
1385
|
*/
|
1338
|
-
toJSON: function(options) {
|
1386
|
+
toJSON: function( options ) {
|
1339
1387
|
// If this Model has already been fully serialized in this branch once, return to avoid loops
|
1340
1388
|
if ( this.isLocked() ) {
|
1341
1389
|
return this.id;
|
@@ -1348,65 +1396,70 @@
|
|
1348
1396
|
json[ this.constructor._subModelTypeAttribute ] = this.constructor._subModelTypeValue;
|
1349
1397
|
}
|
1350
1398
|
|
1351
|
-
_.each( this._relations
|
1352
|
-
|
1399
|
+
_.each( this._relations, function( rel ) {
|
1400
|
+
var value = json[ rel.key ];
|
1353
1401
|
|
1354
|
-
|
1355
|
-
|
1356
|
-
|
1357
|
-
}
|
1358
|
-
else {
|
1359
|
-
json[ rel.keyDestination ] = null;
|
1360
|
-
}
|
1402
|
+
if ( rel.options.includeInJSON === true) {
|
1403
|
+
if ( value && _.isFunction( value.toJSON ) ) {
|
1404
|
+
json[ rel.keyDestination ] = value.toJSON( options );
|
1361
1405
|
}
|
1362
|
-
else
|
1363
|
-
|
1364
|
-
json[ rel.keyDestination ] = value.pluck( rel.options.includeInJSON );
|
1365
|
-
}
|
1366
|
-
else if ( value instanceof Backbone.Model ) {
|
1367
|
-
json[ rel.keyDestination ] = value.get( rel.options.includeInJSON );
|
1368
|
-
}
|
1369
|
-
else {
|
1370
|
-
json[ rel.keyDestination ] = null;
|
1371
|
-
}
|
1406
|
+
else {
|
1407
|
+
json[ rel.keyDestination ] = null;
|
1372
1408
|
}
|
1373
|
-
|
1374
|
-
|
1375
|
-
|
1376
|
-
|
1377
|
-
|
1378
|
-
|
1379
|
-
|
1380
|
-
|
1381
|
-
|
1382
|
-
|
1383
|
-
|
1384
|
-
|
1385
|
-
|
1386
|
-
|
1409
|
+
}
|
1410
|
+
else if ( _.isString( rel.options.includeInJSON ) ) {
|
1411
|
+
if ( value instanceof Backbone.Collection ) {
|
1412
|
+
json[ rel.keyDestination ] = value.pluck( rel.options.includeInJSON );
|
1413
|
+
}
|
1414
|
+
else if ( value instanceof Backbone.Model ) {
|
1415
|
+
json[ rel.keyDestination ] = value.get( rel.options.includeInJSON );
|
1416
|
+
}
|
1417
|
+
else {
|
1418
|
+
json[ rel.keyDestination ] = null;
|
1419
|
+
}
|
1420
|
+
}
|
1421
|
+
else if ( _.isArray( rel.options.includeInJSON ) ) {
|
1422
|
+
if ( value instanceof Backbone.Collection ) {
|
1423
|
+
var valueSub = [];
|
1424
|
+
value.each( function( model ) {
|
1425
|
+
var curJson = {};
|
1387
1426
|
_.each( rel.options.includeInJSON, function( key ) {
|
1388
|
-
|
1427
|
+
curJson[ key ] = model.get( key );
|
1389
1428
|
});
|
1390
|
-
|
1391
|
-
}
|
1392
|
-
|
1393
|
-
|
1394
|
-
|
1429
|
+
valueSub.push( curJson );
|
1430
|
+
});
|
1431
|
+
json[ rel.keyDestination ] = valueSub;
|
1432
|
+
}
|
1433
|
+
else if ( value instanceof Backbone.Model ) {
|
1434
|
+
var valueSub = {};
|
1435
|
+
_.each( rel.options.includeInJSON, function( key ) {
|
1436
|
+
valueSub[ key ] = value.get( key );
|
1437
|
+
});
|
1438
|
+
json[ rel.keyDestination ] = valueSub;
|
1395
1439
|
}
|
1396
1440
|
else {
|
1397
|
-
|
1441
|
+
json[ rel.keyDestination ] = null;
|
1398
1442
|
}
|
1443
|
+
}
|
1444
|
+
else {
|
1445
|
+
delete json[ rel.key ];
|
1446
|
+
}
|
1399
1447
|
|
1400
|
-
|
1401
|
-
|
1402
|
-
|
1403
|
-
|
1448
|
+
if ( rel.keyDestination !== rel.key ) {
|
1449
|
+
delete json[ rel.key ];
|
1450
|
+
}
|
1451
|
+
});
|
1404
1452
|
|
1405
1453
|
this.release();
|
1406
1454
|
return json;
|
1407
1455
|
}
|
1408
1456
|
},
|
1409
1457
|
{
|
1458
|
+
/**
|
1459
|
+
*
|
1460
|
+
* @param superModel
|
1461
|
+
* @returns {Backbone.RelationalModel.constructor}
|
1462
|
+
*/
|
1410
1463
|
setup: function( superModel ) {
|
1411
1464
|
// We don't want to share a relations array with a parent, as this will cause problems with
|
1412
1465
|
// reverse relations.
|
@@ -1426,29 +1479,34 @@
|
|
1426
1479
|
|
1427
1480
|
// Initialize all reverseRelations that belong to this new model.
|
1428
1481
|
_.each( this.prototype.relations || [], function( rel ) {
|
1429
|
-
|
1430
|
-
|
1482
|
+
if ( !rel.model ) {
|
1483
|
+
rel.model = this;
|
1484
|
+
}
|
1485
|
+
|
1486
|
+
if ( rel.reverseRelation && rel.model === this ) {
|
1487
|
+
var preInitialize = true;
|
1488
|
+
if ( _.isString( rel.relatedModel ) ) {
|
1489
|
+
/**
|
1490
|
+
* The related model might not be defined for two reasons
|
1491
|
+
* 1. it is related to itself
|
1492
|
+
* 2. it never gets defined, e.g. a typo
|
1493
|
+
* 3. the model hasn't been defined yet, but will be later
|
1494
|
+
* In neither of these cases do we need to pre-initialize reverse relations.
|
1495
|
+
* However, for 3. (which is, to us, indistinguishable from 2.), we do need to attempt
|
1496
|
+
* setting up this relation again later, in case the related model is defined later.
|
1497
|
+
*/
|
1498
|
+
var relatedModel = Backbone.Relational.store.getObjectByName( rel.relatedModel );
|
1499
|
+
preInitialize = relatedModel && ( relatedModel.prototype instanceof Backbone.RelationalModel );
|
1431
1500
|
}
|
1432
1501
|
|
1433
|
-
if (
|
1434
|
-
|
1435
|
-
if ( _.isString( rel.relatedModel ) ) {
|
1436
|
-
/**
|
1437
|
-
* The related model might not be defined for two reasons
|
1438
|
-
* 1. it never gets defined, e.g. a typo
|
1439
|
-
* 2. it is related to itself
|
1440
|
-
* In neither of these cases do we need to pre-initialize reverse relations.
|
1441
|
-
*/
|
1442
|
-
var relatedModel = Backbone.Relational.store.getObjectByName( rel.relatedModel );
|
1443
|
-
preInitialize = relatedModel && ( relatedModel.prototype instanceof Backbone.RelationalModel );
|
1444
|
-
}
|
1445
|
-
|
1446
|
-
var type = !_.isString( rel.type ) ? rel.type : Backbone[ rel.type ] || Backbone.Relational.store.getObjectByName( rel.type );
|
1447
|
-
if ( preInitialize && type && type.prototype instanceof Backbone.Relation ) {
|
1448
|
-
new type( null, rel );
|
1449
|
-
}
|
1502
|
+
if ( preInitialize ) {
|
1503
|
+
Backbone.Relational.store.initializeRelation( null, rel );
|
1450
1504
|
}
|
1451
|
-
|
1505
|
+
else if ( _.isString( rel.relatedModel ) ) {
|
1506
|
+
Backbone.Relational.store.addOrphanRelation( rel );
|
1507
|
+
}
|
1508
|
+
}
|
1509
|
+
}, this );
|
1452
1510
|
|
1453
1511
|
return this;
|
1454
1512
|
},
|
@@ -1478,6 +1536,9 @@
|
|
1478
1536
|
return new model( attributes, options );
|
1479
1537
|
},
|
1480
1538
|
|
1539
|
+
/**
|
1540
|
+
*
|
1541
|
+
*/
|
1481
1542
|
initializeModelHierarchy: function() {
|
1482
1543
|
// If we're here for the first time, try to determine if this modelType has a 'superModel'.
|
1483
1544
|
if ( _.isUndefined( this._superModel ) || _.isNull( this._superModel ) ) {
|
@@ -1516,24 +1577,27 @@
|
|
1516
1577
|
/**
|
1517
1578
|
* Find an instance of `this` type in 'Backbone.Relational.store'.
|
1518
1579
|
* - If `attributes` is a string or a number, `findOrCreate` will just query the `store` and return a model if found.
|
1519
|
-
* - If `attributes` is an object and is found in the store, the model will be updated with `attributes` unless `options.update` is `false`.
|
1580
|
+
* - If `attributes` is an object and is found in the store, the model will be updated with `attributes` unless `options.update` is `false`.
|
1520
1581
|
* Otherwise, a new model is created with `attributes` (unless `options.create` is explicitly set to `false`).
|
1521
1582
|
* @param {Object|String|Number} attributes Either a model's id, or the attributes used to create or update a model.
|
1522
1583
|
* @param {Object} [options]
|
1523
1584
|
* @param {Boolean} [options.create=true]
|
1524
|
-
* @param {Boolean} [options.
|
1585
|
+
* @param {Boolean} [options.merge=true]
|
1586
|
+
* @param {Boolean} [options.parse=false]
|
1525
1587
|
* @return {Backbone.RelationalModel}
|
1526
1588
|
*/
|
1527
1589
|
findOrCreate: function( attributes, options ) {
|
1528
1590
|
options || ( options = {} );
|
1529
|
-
var parsedAttributes = (_.isObject( attributes ) &&
|
1591
|
+
var parsedAttributes = ( _.isObject( attributes ) && options.parse && this.prototype.parse ) ?
|
1592
|
+
this.prototype.parse( attributes ) : attributes;
|
1593
|
+
|
1530
1594
|
// Try to find an instance of 'this' model type in the store
|
1531
1595
|
var model = Backbone.Relational.store.find( this, parsedAttributes );
|
1532
1596
|
|
1533
|
-
// If we found an instance, update it with the data in 'item' (unless 'options.
|
1597
|
+
// If we found an instance, update it with the data in 'item' (unless 'options.merge' is false).
|
1534
1598
|
// If not, create an instance (unless 'options.create' is false).
|
1535
1599
|
if ( _.isObject( attributes ) ) {
|
1536
|
-
if ( model && options.
|
1600
|
+
if ( model && options.merge !== false ) {
|
1537
1601
|
model.set( parsedAttributes, options );
|
1538
1602
|
}
|
1539
1603
|
else if ( !model && options.create !== false ) {
|
@@ -1545,10 +1609,12 @@
|
|
1545
1609
|
}
|
1546
1610
|
});
|
1547
1611
|
_.extend( Backbone.RelationalModel.prototype, Backbone.Semaphore );
|
1548
|
-
|
1612
|
+
|
1549
1613
|
/**
|
1550
1614
|
* Override Backbone.Collection._prepareModel, so objects will be built using the correct type
|
1551
1615
|
* if the collection.model has subModels.
|
1616
|
+
* Attempts to find a model for `attrs` in Backbone.store through `findOrCreate`
|
1617
|
+
* (which sets the new properties on it if found), or instantiates a new model.
|
1552
1618
|
*/
|
1553
1619
|
Backbone.Collection.prototype.__prepareModel = Backbone.Collection.prototype._prepareModel;
|
1554
1620
|
Backbone.Collection.prototype._prepareModel = function ( attrs, options ) {
|
@@ -1571,7 +1637,8 @@
|
|
1571
1637
|
model = new this.model( attrs, options );
|
1572
1638
|
}
|
1573
1639
|
|
1574
|
-
if ( !model._validate( attrs, options ) ) {
|
1640
|
+
if ( model && model.isNew() && !model._validate( attrs, options ) ) {
|
1641
|
+
this.trigger( 'invalid', this, attrs, options );
|
1575
1642
|
model = false;
|
1576
1643
|
}
|
1577
1644
|
}
|
@@ -1579,67 +1646,86 @@
|
|
1579
1646
|
return model;
|
1580
1647
|
};
|
1581
1648
|
|
1582
|
-
|
1649
|
+
|
1583
1650
|
/**
|
1584
|
-
* Override Backbone.Collection.add, so objects
|
1585
|
-
* update the existing
|
1651
|
+
* Override Backbone.Collection.add, so we'll create objects from attributes where required,
|
1652
|
+
* and update the existing models. Also, trigger 'relational:add'.
|
1586
1653
|
*/
|
1587
1654
|
var add = Backbone.Collection.prototype.__add = Backbone.Collection.prototype.add;
|
1588
1655
|
Backbone.Collection.prototype.add = function( models, options ) {
|
1589
|
-
|
1590
|
-
if ( !
|
1591
|
-
|
1656
|
+
// Short-circuit if this Collection doesn't hold RelationalModels
|
1657
|
+
if ( !( this.model.prototype instanceof Backbone.RelationalModel ) ) {
|
1658
|
+
return add.apply( this, arguments );
|
1592
1659
|
}
|
1593
1660
|
|
1594
|
-
|
1661
|
+
models = _.isArray( models ) ? models.slice() : [ models ];
|
1662
|
+
// Set default options to the same values as `add` uses, so `findOrCreate` will also respect those.
|
1663
|
+
options = _.extend( { merge: false }, options );
|
1664
|
+
|
1665
|
+
var newModels = [],
|
1666
|
+
toAdd = [];
|
1595
1667
|
|
1596
1668
|
//console.debug( 'calling add on coll=%o; model=%o, options=%o', this, models, options );
|
1597
|
-
_.each( models
|
1669
|
+
_.each( models, function( model ) {
|
1598
1670
|
if ( !( model instanceof Backbone.Model ) ) {
|
1599
|
-
// `_prepareModel` attempts to find `model` in Backbone.store through `findOrCreate`,
|
1600
|
-
// and sets the new properties on it if is found. Otherwise, a new model is instantiated.
|
1601
1671
|
model = Backbone.Collection.prototype._prepareModel.call( this, model, options );
|
1602
1672
|
}
|
1603
1673
|
|
1604
|
-
|
1605
|
-
|
1674
|
+
if ( model ) {
|
1675
|
+
toAdd.push( model );
|
1676
|
+
|
1677
|
+
if ( !( this.get( model ) || this.get( model.cid ) ) ) {
|
1678
|
+
newModels.push( model );
|
1606
1679
|
}
|
1607
|
-
|
1680
|
+
// If we arrive in `add` while performing a `set` (after a create, so the model gains an `id`),
|
1681
|
+
// we may get here before `_onModelEvent` has had the chance to update `_byId`.
|
1682
|
+
else if ( model.id != null ) {
|
1683
|
+
this._byId[ model.id ] = model;
|
1684
|
+
}
|
1685
|
+
}
|
1686
|
+
}, this );
|
1608
1687
|
|
1609
1688
|
// Add 'models' in a single batch, so the original add will only be called once (and thus 'sort', etc).
|
1610
|
-
|
1611
|
-
add.call( this, modelsToAdd, options );
|
1689
|
+
add.call( this, toAdd, options );
|
1612
1690
|
|
1613
|
-
|
1691
|
+
_.each( newModels, function( model ) {
|
1692
|
+
// Fire a `relational:add` event for any model in `newModels` that has actually been added to the collection.
|
1693
|
+
if ( this.get( model ) || this.get( model.cid ) ) {
|
1614
1694
|
this.trigger( 'relational:add', model, this, options );
|
1615
|
-
}
|
1616
|
-
}
|
1695
|
+
}
|
1696
|
+
}, this );
|
1617
1697
|
|
1618
1698
|
return this;
|
1619
1699
|
};
|
1620
|
-
|
1700
|
+
|
1621
1701
|
/**
|
1622
1702
|
* Override 'Backbone.Collection.remove' to trigger 'relational:remove'.
|
1623
1703
|
*/
|
1624
1704
|
var remove = Backbone.Collection.prototype.__remove = Backbone.Collection.prototype.remove;
|
1625
1705
|
Backbone.Collection.prototype.remove = function( models, options ) {
|
1626
|
-
|
1627
|
-
if ( !
|
1628
|
-
|
1629
|
-
}
|
1630
|
-
else {
|
1631
|
-
models = models.slice( 0 );
|
1706
|
+
// Short-circuit if this Collection doesn't hold RelationalModels
|
1707
|
+
if ( !( this.model.prototype instanceof Backbone.RelationalModel ) ) {
|
1708
|
+
return remove.apply( this, arguments );
|
1632
1709
|
}
|
1633
1710
|
|
1711
|
+
models = _.isArray( models ) ? models.slice() : [ models ];
|
1712
|
+
options || ( options = {} );
|
1713
|
+
|
1714
|
+
var toRemove = [];
|
1715
|
+
|
1634
1716
|
//console.debug('calling remove on coll=%o; models=%o, options=%o', this, models, options );
|
1635
|
-
_.each( models
|
1636
|
-
|
1717
|
+
_.each( models, function( model ) {
|
1718
|
+
model = this.get( model ) || this.get( model.cid );
|
1719
|
+
model && toRemove.push( model );
|
1720
|
+
}, this );
|
1637
1721
|
|
1638
|
-
|
1639
|
-
|
1640
|
-
|
1641
|
-
|
1722
|
+
if ( toRemove.length ) {
|
1723
|
+
remove.call( this, toRemove, options );
|
1724
|
+
|
1725
|
+
_.each( toRemove, function( model ) {
|
1726
|
+
this.trigger('relational:remove', model, this, options);
|
1642
1727
|
}, this );
|
1728
|
+
}
|
1643
1729
|
|
1644
1730
|
return this;
|
1645
1731
|
};
|
@@ -1650,7 +1736,10 @@
|
|
1650
1736
|
var reset = Backbone.Collection.prototype.__reset = Backbone.Collection.prototype.reset;
|
1651
1737
|
Backbone.Collection.prototype.reset = function( models, options ) {
|
1652
1738
|
reset.call( this, models, options );
|
1653
|
-
|
1739
|
+
|
1740
|
+
if ( this.model.prototype instanceof Backbone.RelationalModel ) {
|
1741
|
+
this.trigger( 'relational:reset', this, options );
|
1742
|
+
}
|
1654
1743
|
|
1655
1744
|
return this;
|
1656
1745
|
};
|
@@ -1661,32 +1750,39 @@
|
|
1661
1750
|
var sort = Backbone.Collection.prototype.__sort = Backbone.Collection.prototype.sort;
|
1662
1751
|
Backbone.Collection.prototype.sort = function( options ) {
|
1663
1752
|
sort.call( this, options );
|
1664
|
-
|
1753
|
+
|
1754
|
+
if ( this.model.prototype instanceof Backbone.RelationalModel ) {
|
1755
|
+
this.trigger( 'relational:reset', this, options );
|
1756
|
+
}
|
1665
1757
|
|
1666
1758
|
return this;
|
1667
1759
|
};
|
1668
|
-
|
1760
|
+
|
1669
1761
|
/**
|
1670
1762
|
* Override 'Backbone.Collection.trigger' so 'add', 'remove' and 'reset' events are queued until relations
|
1671
1763
|
* are ready.
|
1672
1764
|
*/
|
1673
1765
|
var trigger = Backbone.Collection.prototype.__trigger = Backbone.Collection.prototype.trigger;
|
1674
1766
|
Backbone.Collection.prototype.trigger = function( eventName ) {
|
1767
|
+
// Short-circuit if this Collection doesn't hold RelationalModels
|
1768
|
+
if ( !( this.model.prototype instanceof Backbone.RelationalModel ) ) {
|
1769
|
+
return trigger.apply( this, arguments );
|
1770
|
+
}
|
1771
|
+
|
1675
1772
|
if ( eventName === 'add' || eventName === 'remove' || eventName === 'reset' ) {
|
1676
|
-
var dit = this,
|
1773
|
+
var dit = this,
|
1774
|
+
args = arguments;
|
1677
1775
|
|
1678
|
-
if (
|
1776
|
+
if ( _.isObject( args[ 3 ] ) ) {
|
1679
1777
|
args = _.toArray( args );
|
1680
|
-
// the fourth argument
|
1778
|
+
// the fourth argument is the option object.
|
1681
1779
|
// we need to clone it, as it could be modified while we wait on the eventQueue to be unblocked
|
1682
|
-
|
1683
|
-
args[3] = _.clone( args[3] );
|
1684
|
-
}
|
1780
|
+
args[ 3 ] = _.clone( args[ 3 ] );
|
1685
1781
|
}
|
1686
1782
|
|
1687
1783
|
Backbone.Relational.eventQueue.add( function() {
|
1688
|
-
|
1689
|
-
|
1784
|
+
trigger.apply( dit, args );
|
1785
|
+
});
|
1690
1786
|
}
|
1691
1787
|
else {
|
1692
1788
|
trigger.apply( this, arguments );
|