Skip to content
Browse files

Merge pull request #1 from latentflip/liveupdating-subsets

Liveupdating subsets
  • Loading branch information...
2 parents d858edd + 53a2324 commit b0c29c4c069882dd9cb554180c18e08ea4eb1377 @saimonmoore saimonmoore committed Jan 26, 2012
Showing with 143 additions and 7 deletions.
  1. +33 −1 Readme.md
  2. +30 −6 backbone.subset.js
  3. +80 −0 test/test.js
View
34 Readme.md
@@ -42,10 +42,42 @@ archivedTasks = new Collections.ArchivedTasks();
tasks.reset([{archived: true}, {archived: false}]);
-assert.equal(task.length, 2);
+assert.equal(tasks.length, 2);
assert.equal(archivedTasks.length, 1);
```
+## Live Updating of subsets
+
+Subsets can be optionally updated live. That is, if a models attributes
+change such that affect it's membership of a subset, that update will
+happen automatically.
+
+By default live updating is disabled. Continuing the above example:
+
+```
+tasks.reset([{archived: true}, {archived: false}]);
+archivedTasks.liveupdate_keys = 'all'
+
+assert.equal(tasks.length, 2);
+assert.equal(archivedTasks.length, 1);
+
+tasks.first().set({archived: false});
+assert.equal(tasks.length, 2);
+assert.equal(archivedTasks.length, 0);
+```
+
+### Controlling live updating
+
+Live updating is controlled by a subset's `liveupdate\_keys` attribute.
+
+* To prevent live updating of a subset, set it's `liveupdate\_keys` attribute
+to be 'none' (this is the default).
+* To enable live updating when any model key changes, set
+ `liveupdate\_keys = 'all'`.
+* To limit which model keys trigger a live update, set `liveupdate\_keys`
+to be an array of attributes: `liveupdate\_keys = ['archived']`.
+
+
## Tests
You must have node installed in order to run the tests.
View
36 backbone.subset.js
@@ -25,6 +25,7 @@
this.model = this.parent().model;
this.comparator = this.comparator || options.comparator || this.parent().comparator;
+ this.liveupdate_keys = this.liveupdate_keys || options.liveupdate_keys || 'none';
_.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
80 test/test.js
@@ -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(null, {liveupdate_keys: 'all'});
+
+ 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(null, {liveupdate_keys: 'all'});
+
+ 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(null, {liveupdate_keys: 'all'});
+
+ 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 b0c29c4

Please sign in to comment.
Something went wrong with that request. Please try again.