Skip to content

Commit

Permalink
Merge remote-tracking branch 'slindberg/master'
Browse files Browse the repository at this point in the history
  - Adds `parent` option
  - Adds `recalculate` method
  - Removes AMD require on underscore lib

Conflicts:
	backbone.subset.js
  • Loading branch information
masylum committed May 13, 2012
2 parents ec1b3ef + 39619f0 commit b5b6100
Show file tree
Hide file tree
Showing 5 changed files with 87 additions and 31 deletions.
5 changes: 3 additions & 2 deletions Readme.md
Expand Up @@ -13,9 +13,10 @@ or changing a *Task* will update that subset collection according to a given `si

The API is almost the same as `Backbone.Collection`.

* You must implement a `parent` function that returns the collection the subset belongs.
* You must implement a `parent` function that returns the collection the subset belongs or pass a `parent` option.
* You must implement a `sieve` function that will be used to filter the parent collection.
* You can pass the option `{noproxy: true}` if you don't want the event to bubble from the parent to the subset.
* You can pass the option `{noproxy: true}` if you don't want the event to bubble from the parent to the subset
(not recommended since it can leave your collections in a weird state).

``` javascript
Models.Task = Backbone.Model.extend({
Expand Down
93 changes: 66 additions & 27 deletions backbone.subset.js
Expand Up @@ -5,13 +5,7 @@
*/
(function () {

var root = this
, Subset = {}
, _ = root._;

if (!_ && (typeof require !== 'undefined')) {
_ = require('underscore')._;
}
var Subset = {};

/**
* Returns the xor of two sets
Expand All @@ -32,27 +26,38 @@
* @return {Object}
*/
Backbone.Subset = function Subset(models, options) {
var parent;

options = options || {};

this.model = this.parent().model;
this.comparator = this.comparator || options.comparator || this.parent().comparator;
this.liveupdate_keys = this.liveupdate_keys || options.liveupdate_keys;
if (options.parent) {
this.parent = options.parent;
}

// A parent is required at this point
if (!(parent = _.result(this, 'parent'))) {
throw new Error("Can't create a subset without a parent collection");
}

this.model = parent.model;
this.comparator = this.comparator || options.comparator || parent.comparator;
this.liveupdate_keys = this.liveupdate_keys || options.liveupdate_keys || 'none';

_.bindAll(this, '_onModelEvent', '_unbindModelEvents', '_proxyAdd'
, '_proxyReset', '_proxyRemove', '_proxyChange');

this.parent().bind('add', this._proxyAdd);
this.parent().bind('remove', this._proxyRemove);
this.parent().bind('reset', this._proxyReset);
this.parent().bind('all', this._proxyChange);
parent.bind('add', this._proxyAdd);
parent.bind('remove', this._proxyRemove);
parent.bind('reset', this._proxyReset);
parent.bind('all', this._proxyChange);

if (this.beforeInitialize) {
this.beforeInitialize.apply(this, arguments);
}

if (!options.no_reset) {
this._reset();
this.reset(models || this.parent().models, {silent: true});
this.reset(models || parent.models, {silent: true});
} else {
this._resetSubset({silent: true});
}
Expand All @@ -68,7 +73,8 @@
* @return {Object} collection
*/
Subset.reset = function (models, options) {
var parent_models = _.clone(this.parent().models)
var parent = _.result(this, 'parent')
, parent_models = _.clone(parent.models)
, xored_ids
, ids = this.pluck('id');

Expand All @@ -89,8 +95,33 @@
// xored ids are the ones added/removed
xored_ids = xor(ids, _.pluck(models, 'id'));

this.parent().reset(parent_models, _.extend({silent: true}, options));
this.parent().trigger('reset', this, _.extend({model_ids: xored_ids}, options));
parent.reset(parent_models, _.extend({silent: true}, options));
parent.trigger('reset', this, _.extend({model_ids: xored_ids}, options));

return this;
};

/**
* Re-applies the sieve to the subset
*
* @param {Object} options
* @return {Object} collection
*/
Subset.recalculate = function (options) {
options = options || {};

var changed
, self = this;

// re-evaluate each model's eligibility
changed = _.result(this, 'parent').reduce(function (changed, model) {
return changed || self._updateModelMembership(model, {silent: true});
}, false);

// only trigger reset event if the subset actually changed
if (changed && !options.silent) {
this.trigger('reset', this, options);
}

return this;
};
Expand All @@ -107,7 +138,7 @@
this.each(this._unbindModelEvents);
this._reset();

this.parent().each(function (model) {
_.result(this, 'parent').each(function (model) {
this._addToSubset(model, {silent: true});
}, this);

Expand All @@ -126,7 +157,7 @@
* @return {Object} model
*/
Subset.add = function (model, options) {
return this.parent().add(model, options);
return _.result(this, 'parent').add(model, options);
};

/**
Expand All @@ -150,7 +181,7 @@
* @return {Object} model
*/
Subset.remove = function (model, options) {
return this.parent().remove(model, options);
return _.result(this, 'parent').remove(model, options);
};

/**
Expand All @@ -172,15 +203,17 @@
* @return {Object} model
*/
Subset._prepareModel = function (model, options) {
var parent = _.result(this, 'parent');

if (!(model instanceof Backbone.Model)) {
var attrs = model;
model = new this.model(attrs, {collection: this.parent()});
model = new this.model(attrs, {collection: parent});

if (model.validate && !model._performValidation(model.attributes, options)) {
model = false;
}
} else if (!model.collection) {
model.collection = this.parent();
model.collection = parent;
}
model = this.sieve(model) ? model : false;
return model;
Expand Down Expand Up @@ -257,23 +290,29 @@
};

/**
* Determines whether a model should be in the subset, and adds or removes it
* Determines whether a model should be in the subset, and adds or removes it.
* Returns a boolean indicating if the model's membership changed.
*
* @param {Object} model
* @return {Boolean} changed
*/
Subset._updateModelMembership = function (model) {
Subset._updateModelMembership = function (model, options) {
var hasId = !model.id
, alreadyInSubset = this._byCid[model.cid] || (hasId && this._byId[model.id]);

if (this.sieve(model)) {
if (!alreadyInSubset) {
this._addToSubset(model);
this._addToSubset(model, options);
return true;
}
} else {
if (alreadyInSubset) {
this._removeFromSubset(model);
this._removeFromSubset(model, options);
return true;
}
}

return false;
};

/**
Expand Down
6 changes: 4 additions & 2 deletions package.json
Expand Up @@ -3,8 +3,10 @@
"description": "Provides a collection-like constructor that allows you create a collection that is subset from a parent one",
"version": "0.0.4",
"author": "Pau ramon <masylum@gmail.com>",
"contributors": ["Saimon Moore <saimon@saimonmoore.net>", "Philip Roberts <phil@latentflip.com>"],
"contributors": ["Saimon Moore <saimon@saimonmoore.net>",
"Philip Roberts <phil@latentflip.com>",
"Steven Lindberg <stevenjohnlindberg@gmail.com>"],
"repository": {"type": "git", "url": "git://github.com/masylum/Backbone.Subset.git"},
"devDependencies": {"backbone": "0.5.3", "underscore": "*", "mocha": "*"},
"devDependencies": {"backbone": "0.9.1", "underscore": "1.3.3", "mocha": "*"},
"main": "./backbone.subset"
}
1 change: 1 addition & 0 deletions test/benchmarks.js
Expand Up @@ -6,6 +6,7 @@ var _ = require('underscore')
// instances
, tasks, archived_tasks, urgent_tasks, project, project_tasks;

GLOBAL._ = _
GLOBAL.Backbone = require('backbone');
require('../backbone.subset');

Expand Down
13 changes: 13 additions & 0 deletions test/specs.js
Expand Up @@ -24,6 +24,7 @@ _.uniqueId = function (prefix) {
return prefix ? prefix + id : id;
};

GLOBAL._ = _
GLOBAL.Backbone = require('backbone');
require('../backbone.subset');

Expand Down Expand Up @@ -115,6 +116,18 @@ describe('Subset', function () {
}
});

it('should throw an error if a parent collection is not specified', function() {
happened = false;

try {
new Backbone.Subset();
} catch (e) {
happened = true;
}

assert.equal(happened, true);
});

it('has a `add` function that behaves like the `Collection` one + bubbling', function () {
tasks.bind('add', inc('tasks'));
archived_tasks.bind('add', inc('archived_tasks'));
Expand Down

0 comments on commit b5b6100

Please sign in to comment.