Permalink
Browse files

Add a liveupdate_keys option to keep the subset updated, when model a…

…ttributes change that would make them now fail/pass the sieve.

By default subset's will be liveupdated (liveupdate_keys = 'all').

To limit the model keys on which the subset will be liveupdated, set liveupdate_keys to an array of model attributes. liveupdate_keys = ['archived', 'dated_on'].
  • Loading branch information...
1 parent d858edd commit 5ec537bbae1090fc713264ec9715c4e89416d0e5 @latentflip latentflip committed Jan 25, 2012
Showing with 110 additions and 6 deletions.
  1. +30 −6 backbone.subset.js
  2. +80 −0 test/test.js
View
@@ -25,6 +25,7 @@
this.model = this.parent().model;
this.comparator = this.comparator || options.comparator || this.parent().comparator;
+ this.liveupdate_keys = options.liveupdate_keys || 'all';
_.bindAll(this, '_onModelEvent', '_unbindModelEvents', '_proxyEvents');
@@ -161,13 +162,21 @@
* @return {Object} model
*/
Subset._proxyEvents = function (ev, model, collection, options) {
- if (ev === 'add' && collection !== this && this.sieve(model) && !options.noproxy) {
- this._addToSubset(model, options);
- }
+ if ( collection !== this) {
+ if (ev === 'change' && this.liveupdate_keys === 'all') {
+ this._updateModelMembership(model);
+ } else if (ev.slice(0,7) == 'change:' && _.isArray(this.liveupdate_keys) && _.include(this.liveupdate_keys, ev.slice(7))) {
+ this._updateModelMembership(model);
+ }
- if (ev === 'remove' && collection !== this && this.sieve(model) && !options.noproxy) {
- this._removeFromSubset(model, options);
- }
+ if (ev === 'add' && this.sieve(model) && !options.noproxy) {
+ this._addToSubset(model, options);
+ }
+
+ if (ev === 'remove' && this.sieve(model) && !options.noproxy) {
+ this._removeFromSubset(model, options);
+ }
+ }
// model == collection
if (ev === 'reset' && model !== this && model.any(this.sieve)) {
@@ -176,6 +185,21 @@
};
/**
+ * Determines whether a model should be in the subset, and adds or removes it
+ * @param {Object} model
+ */
+ Subset._updateModelMembership = function(model) {
+ var hasId = model.id != null;
+ var alreadyInSubset = this._byCid[model.cid] || (hasId && this._byId[model.id]);
+
+ if (this.sieve(model)) {
+ !alreadyInSubset && this._addToSubset(model);
+ } else {
+ alreadyInSubset && this._removeFromSubset(model);
+ }
+ };
+
+ /**
* Unbinds the _onModelEvent listener
*
* @param {Object} model
View
@@ -202,3 +202,83 @@ describe('Aggregated collections', function () {
assert.deepEqual(_.pluck(archived_tasks.models, 'cid'), ['c11', 'c10']);
});
});
+
+describe('Live updating subset membership', function() {
+ it('removes a model from the subset if the model\'s attributes change such that it\'s no longer part of the set', function() {
+ tasks = new Collections.Tasks();
+ archived_tasks = new Collections.ArchivedTasks();
+
+ tasks.reset([{id: 0, archived: 0}, {id: 1, archived: 1}, {id: 2, archived: 1}]);
+ assert.equal(tasks.length, 3);
+ assert.equal(archived_tasks.length, 2);
+
+ //Update 1 so that it's not in the archived set
+ tasks.get(1).set({archived: 0});
+ assert.equal(tasks.length, 3);
+ assert.equal(archived_tasks.length, 1);
+ });
+
+ it('adds a model to the set if the model\'s attributes change such that it should now be sieved into the set', function() {
+ tasks = new Collections.Tasks();
+ archived_tasks = new Collections.ArchivedTasks();
+
+ tasks.reset([{id: 0, archived: 0}, {id: 1, archived: 1}, {id: 2, archived: 1}]);
+ assert.equal(tasks.length, 3);
+ assert.equal(archived_tasks.length, 2);
+
+ //Update 0 so that it should be in the archived set
+ tasks.get(0).set({archived: 1});
+ assert.equal(tasks.length, 3);
+ assert.equal(archived_tasks.length, 3);
+ });
+
+ it('adds a model to the set if the model\'s attributes change such that it should now be sieved into the set', function() {
+ tasks = new Collections.Tasks();
+ archived_tasks = new Collections.ArchivedTasks();
+
+ tasks.reset([{id: 0, archived: 0}, {id: 1, archived: 1}, {id: 2, archived: 1}]);
+ assert.equal(tasks.length, 3);
+ assert.equal(archived_tasks.length, 2);
+
+ //Update 0 so that it should be in the archived set
+ tasks.get(0).set({archived: 1});
+ assert.equal(tasks.length, 3);
+ assert.equal(archived_tasks.length, 3);
+ });
+
+ it('won\'t update a subset\'s members if a key changes that is not listed in liveupdate_keys', function() {
+ tasks = new Collections.Tasks();
+ archived_tasks = new Collections.ArchivedTasks(null, {liveupdate_keys: ['order']});
+
+ tasks.reset([{id: 0, archived: 0, order: 0}, {id: 1, archived: 1, order: 1}, {id: 2, archived: 1, order: 2}]);
+ assert.equal(tasks.length, 3);
+ assert.equal(archived_tasks.length, 2);
+
+ //Update 0 so that it should be in the archived set, but our set won't update because it updates on 'change:order'
+ tasks.get(0).set({archived: 1});
+ assert.equal(tasks.length, 3);
+ assert.equal(archived_tasks.length, 2);
+
+ tasks.get(1).set({archived: 1});
+ assert.equal(tasks.length, 3);
+ assert.equal(archived_tasks.length, 2);
+ });
+
+ it('will update a subset\'s members if a key changes that is listed in liveupdate_keys', function() {
+ tasks = new Collections.Tasks();
+ archived_tasks = new Collections.ArchivedTasks(null, {liveupdate_keys: ['archived']});
+
+ tasks.reset([{id: 0, archived: 0, order: 0}, {id: 1, archived: 1, order: 1}, {id: 2, archived: 1, order: 2}]);
+ assert.equal(tasks.length, 3);
+ assert.equal(archived_tasks.length, 2);
+
+ //Update 0 so that it should be in the archived set, but our set won't update because it updates on 'change:order'
+ tasks.get(0).set({archived: 1});
+ assert.equal(tasks.length, 3);
+ assert.equal(archived_tasks.length, 3);
+
+ tasks.get(1).set({archived: 0});
+ assert.equal(tasks.length, 3);
+ assert.equal(archived_tasks.length, 2);
+ });
+});

0 comments on commit 5ec537b

Please sign in to comment.