|
165 | 165 |
|
166 | 166 | replace: function(model, other) { |
167 | 167 | var other = this.alter(model, other) |
168 | | - if( other ) this.associate(other, model) |
| 168 | + if( other ) { |
| 169 | + this.associate(other, model); |
| 170 | + |
| 171 | + // Triggers the association replace. |
| 172 | + model.trigger("replace:"+this.name, model, other); |
| 173 | + } |
169 | 174 | }, |
170 | 175 |
|
171 | 176 | // Replace the current association with `other`, taking care to remove the |
|
198 | 203 | // Tear down the current association. |
199 | 204 | if (!other) model.unset(this.id); |
200 | 205 | if (current) { |
| 206 | + current.off('all', null, this); |
201 | 207 | delete model[this.store]; |
202 | 208 | this.dissociate(current, model); |
203 | 209 | } |
|
206 | 212 | // Set up the new association. |
207 | 213 | model.set(this.id, other.id); |
208 | 214 | model[this.store] = other; |
| 215 | + |
| 216 | + // Listening association events. |
| 217 | + other |
| 218 | + .on('all', function(eventName, object) { |
| 219 | + var splittedEvent = eventName.split(":"); |
| 220 | + |
| 221 | + // Avoids a loop generated by two-way associations. |
| 222 | + var reservedNamespace = splittedEvent[1]; |
| 223 | + if(this.inverse == reservedNamespace) |
| 224 | + return; |
| 225 | + |
| 226 | + var headEvent = splittedEvent[0]; |
| 227 | + var tailEvents = _.rest(splittedEvent).join(":"); |
| 228 | + |
| 229 | + // Building the new event to trigger (namespace, association name). |
| 230 | + var newEvent = headEvent+":"+this.name; |
| 231 | + if(tailEvents) newEvent += ":"+tailEvents; |
| 232 | + |
| 233 | + // Trigger new event from the concrete object. |
| 234 | + arguments[0] = newEvent; |
| 235 | + model.trigger.apply(model, arguments); |
| 236 | + }, this); |
| 237 | + |
209 | 238 | return other; |
210 | 239 | } |
211 | 240 | } |
|
247 | 276 | // handle events that it triggers. |
248 | 277 | collection.owner = model; |
249 | 278 |
|
| 279 | + // Listening association events. |
| 280 | + collection |
| 281 | + .on('all', function(eventName, object) { |
| 282 | + var splittedEvents = eventName.split(":"); |
| 283 | + |
| 284 | + // Avoids a loop generated by two-way associations |
| 285 | + var reservedNamespace = splittedEvents[1]; |
| 286 | + if(this.inverse == reservedNamespace) |
| 287 | + return; |
| 288 | + |
| 289 | + var headEvent = splittedEvents[0]; |
| 290 | + var tailEvents = _.rest(splittedEvents).join(":"); |
| 291 | + |
| 292 | + // Building the new event to trigger (namespace, association name). |
| 293 | + var newEvent = headEvent+":"+this.name; |
| 294 | + if(tailEvents) newEvent += ":"+tailEvents; |
| 295 | + |
| 296 | + // Trigger new event from the concrete object |
| 297 | + arguments[0] = newEvent; |
| 298 | + model.trigger.apply(model, arguments); |
| 299 | + }, this); |
| 300 | + |
250 | 301 | return collection; |
251 | 302 | }, |
252 | 303 |
|
|
378 | 429 | .on('associate:' + this.source, this._associate) |
379 | 430 | .on('dissociate:' + this.source, this._dissociate)); |
380 | 431 |
|
| 432 | + // Listening association events. |
| 433 | + collection |
| 434 | + .on('all', function(eventName) { |
| 435 | + var splittedEvents = eventName.split(":"); |
| 436 | + |
| 437 | + // Avoids a loop generated by two-way associations |
| 438 | + var reservedNamespace = [splittedEvents[1], splittedEvents[2]]; |
| 439 | + if(this.through === reservedNamespace[0] || |
| 440 | + _.indexOf(reservedNamespace, this.name) > -1) |
| 441 | + return; |
| 442 | + |
| 443 | + var headEvent = splittedEvents[0]; |
| 444 | + var tailEvents = _.rest(splittedEvents).join(":"); |
| 445 | + |
| 446 | + // Building a new event to trigger (namespace, association name). |
| 447 | + var newEventName = headEvent+":"+this.name; |
| 448 | + if(tailEvents) newEventName += ":"+tailEvents; |
| 449 | + |
| 450 | + arguments[0] = newEventName; |
| 451 | + model.trigger.apply(model, arguments); |
| 452 | + |
| 453 | + // Building a new event to trigger (namespace, association through). |
| 454 | + var newEventThrough = headEvent+":"+this.through; |
| 455 | + if(tailEvents) newEventThrough += ":"+tailEvents; |
| 456 | + |
| 457 | + arguments[0] = newEventThrough; |
| 458 | + model.trigger.apply(model, arguments); |
| 459 | + }, this); |
| 460 | + |
381 | 461 | return collection; |
382 | 462 | }, |
383 | 463 |
|
|
539 | 619 | getId: function() { |
540 | 620 | if(this.id) return this.id; |
541 | 621 | else return this.cid; |
| 622 | + }, |
| 623 | + |
| 624 | + // Alters clone method to prepare a model copy ready to work as a Supermodel |
| 625 | + // but without associations |
| 626 | + clone: function() { |
| 627 | + var attrsCopy = this.cloneAttributes(); |
| 628 | + return new this.constructor(attrsCopy); |
| 629 | + }, |
| 630 | + |
| 631 | + // Returns attributes to be cloned |
| 632 | + cloneAttributes: function() { |
| 633 | + // Attributes to copy |
| 634 | + var attrsCopy = _.extend({}, this.attributes); |
| 635 | + |
| 636 | + var ctor = this.constructor; |
| 637 | + |
| 638 | + // Remove id attributes |
| 639 | + delete attrsCopy[ctor.prototype.cidAttribute]; |
| 640 | + delete attrsCopy[ctor.prototype.idAttribute]; |
| 641 | + |
| 642 | + // Remove associations |
| 643 | + var allAssociations = ctor.allAssociations(); |
| 644 | + for(var assoc in allAssociations) { |
| 645 | + delete attrsCopy[allAssociations[assoc].id]; |
| 646 | + } |
| 647 | + |
| 648 | + return attrsCopy; |
542 | 649 | } |
543 | 650 |
|
544 | 651 | }, { |
|
617 | 724 | return this._associations || (this._associations = {}); |
618 | 725 | }, |
619 | 726 |
|
| 727 | + // Return a hash of all associations for each constructor in its prototype chain. |
| 728 | + allAssociations: function() { |
| 729 | + var allAssociations = {}; |
| 730 | + |
| 731 | + var ctor = this; |
| 732 | + do { |
| 733 | + _.extend(allAssociations, ctor._associations); |
| 734 | + } while (ctor = ctor.parent); |
| 735 | + |
| 736 | + return allAssociations; |
| 737 | + }, |
| 738 | + |
620 | 739 | // Models and associations are tracked via `all` and `associations`, |
621 | 740 | // respectively. `reset` removes all model references to allow garbage |
622 | 741 | // collection. |
|
0 commit comments