Skip to content

Commit 3d587a9

Browse files
committed
Trigger change events in parent object when related collection changes
externally This makes sense because a collection changing externally is the equivalent of a parent set() operation for the collection's key. This enables scenarios like listening for changes on a parent object and persisting them without listening for changes to each of the parent object's relationed collections separately.
1 parent eaabb4d commit 3d587a9

File tree

2 files changed

+81
-6
lines changed

2 files changed

+81
-6
lines changed

backbone-relational.js

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -989,7 +989,8 @@
989989

990990
this.listenTo( collection, 'relational:add', this.handleAddition )
991991
.listenTo( collection, 'relational:remove', this.handleRemoval )
992-
.listenTo( collection, 'relational:reset', this.handleReset );
992+
.listenTo( collection, 'relational:reset', this.handleReset )
993+
.listenTo( collection, 'relational:update', this.handleUpdate );
993994

994995
return collection;
995996
},
@@ -1139,6 +1140,16 @@
11391140
});
11401141
},
11411142

1143+
handleUpdate: function ( coll, options ) {
1144+
// If this change is not inside a set (an onChanged handler), ensure that
1145+
// change events for the key and the object are queued.
1146+
var dit = this;
1147+
!options.silent && !dit.instance._relationalModelChangingViaSet && Backbone.Relational.eventQueue.add( function() {
1148+
dit.instance.trigger( 'change:' + dit.key, dit.instance, dit.related, options, true );
1149+
dit.instance.trigger( 'change', dit.instance, options, null, true );
1150+
});
1151+
},
1152+
11421153
tryAddRelated: function( model, coll, options ) {
11431154
var item = _.contains( this.keyIds, model.id );
11441155

@@ -1505,6 +1516,7 @@
15051516
},
15061517

15071518
set: function( key, value, options ) {
1519+
this._relationalModelChangingViaSet = true;
15081520
Backbone.Relational.eventQueue.block();
15091521

15101522
// Duplicate backbone's behavior to allow separate key/value parameters, instead of a single 'attributes' object
@@ -1554,6 +1566,7 @@
15541566
Backbone.Relational.eventQueue.unblock();
15551567
}
15561568

1569+
this._relationalModelChangingViaSet = false;
15571570
return result;
15581571
},
15591572

@@ -1973,6 +1986,8 @@
19731986
}
19741987
}
19751988

1989+
this.trigger('relational:update', this, options)
1990+
19761991
return result;
19771992
};
19781993

@@ -2000,6 +2015,8 @@
20002015
this.trigger( 'relational:remove', model, this, options );
20012016
}, this );
20022017

2018+
this.trigger('relational:update', this, options);
2019+
20032020
return result;
20042021
};
20052022

test/tests.js

Lines changed: 63 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1639,6 +1639,61 @@ $(document).ready(function() {
16391639
equal( changedAttrs.color, 'red', '... with correct properties in "changedAttributes"' );
16401640
});
16411641

1642+
test( 'collection updates should fire change events on parent objects', function() {
1643+
var scope = {};
1644+
Backbone.Relational.store.addModelScope( scope );
1645+
1646+
scope.PetAnimal = Backbone.RelationalModel.extend({
1647+
subModelTypes: {
1648+
'cat': 'Cat',
1649+
'dog': 'Dog'
1650+
}
1651+
});
1652+
scope.Dog = scope.PetAnimal.extend();
1653+
scope.Cat = scope.PetAnimal.extend();
1654+
1655+
scope.PetOwner = Backbone.RelationalModel.extend({
1656+
relations: [{
1657+
type: Backbone.HasMany,
1658+
key: 'pets',
1659+
relatedModel: scope.PetAnimal,
1660+
reverseRelation: {
1661+
key: 'owner'
1662+
}
1663+
}]
1664+
});
1665+
1666+
var owner = new scope.PetOwner( { id: 'owner-2354' } );
1667+
var animal = new scope.Dog( { type: 'dog', id: '238902', color: 'blue' } );
1668+
owner.set(owner.parse({
1669+
id: 'owner-2354',
1670+
pets: [ animal ]
1671+
}));
1672+
1673+
var pets = owner.get('pets');
1674+
equal( pets.size(), 1, 'pets starts with one animal' );
1675+
1676+
var changes = 0;
1677+
owner.on('change change:pets', function () {
1678+
changes++;
1679+
});
1680+
pets.add( { id: '456780', type: 'dog', color: 'yellow' } );
1681+
equal( pets.size(), 2, 'added one animal to pets' );
1682+
equal( changes, 2, 'change events get called on owner for pets collection change' );
1683+
1684+
pets.remove( '456780' );
1685+
equal( pets.size(), 1, 'removed one animal from pets' );
1686+
equal( changes, 4, 'change events get called on owner for pets collection change' );
1687+
1688+
var animal2 = new scope.Dog( { id: '34567', type: 'dog', color: 'black' } );
1689+
owner.set(owner.parse({
1690+
id: 'owner-2354',
1691+
pets: [ animal, animal2 ]
1692+
}));
1693+
equal( pets.size(), 2, 'new array of pets is of size 2' );
1694+
equal( changes, 6, 'change events get called on owner for pets collection set via owner.set' );
1695+
});
1696+
16421697
test( 'change events should not fire on new items in Collection#set', function() {
16431698
var modelChangeEvents = 0,
16441699
collectionChangeEvents = 0;
@@ -4657,8 +4712,8 @@ $(document).ready(function() {
46574712
change = changeAnimals = animalChange = 0;
46584713
animals.add( 'a2' );
46594714

4660-
ok( change === 0, 'change event not should fire' );
4661-
ok( changeAnimals === 0, 'no change:animals event should fire' );
4715+
ok( change === 1, 'change event should fire' );
4716+
ok( changeAnimals === 1, 'change:animals event should fire' );
46624717
ok( animalChange === 0, 'no animals:change event should fire' );
46634718

46644719
// Update an animal directly
@@ -4673,8 +4728,8 @@ $(document).ready(function() {
46734728
change = changeAnimals = animalChange = 0;
46744729
animals.remove( 'a2' );
46754730

4676-
ok( change === 0, 'no change event should fire' );
4677-
ok( changeAnimals === 0, 'no change:animals event should fire' );
4731+
ok( change === 1, 'change event should fire' );
4732+
ok( changeAnimals === 1, 'change:animals event should fire' );
46784733
ok( animalChange === 0, 'no animals:change event should fire' );
46794734
});
46804735

@@ -4772,16 +4827,19 @@ $(document).ready(function() {
47724827
person
47734828
.on('change:livesIn', function() {
47744829
//console.log( arguments );
4830+
4831+
// Will cause a second change notification in `house`
47754832
house.set({livesIn: house});
47764833
})
47774834
.on( 'change', function () {
47784835
//console.log( arguments );
47794836
changeEventsTriggered++;
47804837
});
47814838

4839+
// Will cause a change notification in `house`
47824840
person.set({livesIn: house});
47834841

4784-
ok( changeEventsTriggered === 2, 'one change each triggered for `house` and `person`' );
4842+
ok( changeEventsTriggered === 3, 'one change triggered for `person`, two for `house`' );
47854843
});
47864844

47874845
module( "Performance", { setup: reset } );

0 commit comments

Comments
 (0)