--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/backbone.shard.js Wed Dec 05 00:36:27 2012 +0900
+ * (c) 2012 Anton Shestakov.
+ * This extension to Backbone may be freely distributed
+ * under the MIT license:
+ * http://opensource.org/licenses/mit-license.php
+Backbone.Shard = function(options) {
+ this._collection = options.collection;
+ this._filter = options.filter;
+ this.models = this._collection.filter(this._filter);
+ .on('all', function(event, model) {
+ if (/^change/.test(event) && this._filter(model) && this.models.indexOf(model) != -1) {
+ this.trigger.apply(this, arguments);
+ .on('add', function(model) {
+ if (this._filter(model) && this.models.indexOf(model) == -1) {
+ this.models.push(model);
+ this.trigger('add', model);
+ .on('remove', function(model) {
+ if (this._filter(model) && this.models.indexOf(model) != -1) {
+ this.models = _.without(this.models, model);
+ this.trigger('remove', model);
+ .on('reset', function() {
+ this.models = this._collection.filter(this._filter);
+ this.initialize.apply(this, arguments);
+_.extend(Backbone.Shard.prototype, Backbone.Events, {
+ initialize: function() {}
+// Underscore methods that we want to implement on the Shard.
+var methods = ['forEach', 'each', 'map', 'reduce', 'reduceRight', 'find',
+ 'detect', 'filter', 'select', 'reject', 'every', 'all', 'some', 'any',
+ 'include', 'contains', 'invoke', 'max', 'min', 'sortBy', 'sortedIndex',
+ 'toArray', 'size', 'first', 'initial', 'rest', 'last', 'without', 'indexOf',
+ 'shuffle', 'lastIndexOf', 'isEmpty', 'groupBy'];
+// Mix in each Underscore method as a proxy to `Shard#models`.
+_.each(methods, function(method) {
+ Backbone.Shard.prototype[method] = function() {
+ return _[method].apply(_, [this.models].concat(_.toArray(arguments)));
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/basic-filters.js Wed Dec 05 00:36:27 2012 +0900
+var a, b, c, col, otherCol;
+module('Backbone.Shard', {
+ a = new Backbone.Model({data: 'a'});
+ b = new Backbone.Model({data: 'b'});
+ c = new Backbone.Model({data: 'c'});
+ col = new Backbone.Collection([a, b, c]);
+ otherCol = new Backbone.Collection();
+test('All-exclusive filter function, collection is prepopulated', function() {
+ var shard = new Backbone.Shard({
+ filter: function() { return false; }
+ equal(shard.size(), 0, 'shard is empty');
+ equal(shard.first(), undefined, 'nothing is the first element');
+ equal(shard.last(), undefined, 'nothing is the last element');
+test('All-exclusive filter function, collection populated dynamically', function() {
+ var shard = new Backbone.Shard({
+ filter: function() { return false; }
+ shard.on('add', function() {
+ }).on('remove', function() {
+ }).on('reset', function() {
+ otherCol.add([a, b, c]);
+ equal(additions, 0, 'adding models not matching shard filter to collection does not trigger add events');
+ equal(shard.size(), 0, '... and the shard is empty');
+ otherCol.remove([a, b, c]);
+ equal(removals, 0, 'removing models not matching shard filter from collection does not trigger remove events');
+ equal(shard.size(), 0, '... and the shard is empty');
+ otherCol.reset([a, b, c]);
+ equal(resets, 1, 'resettings collection triggers reset events');
+ equal(shard.size(), 0, '... and the shard is empty');
+test('All-inclusive filter function, collection is prepopulated', function() {
+ var shard = new Backbone.Shard({
+ filter: function() { return true; }
+ equal(shard.size(), 3, 'shard has 3 items');
+ equal(shard.first(), a, 'a is the first element');
+ equal(shard.indexOf(b), 1, 'b is the second element');
+ equal(shard.last(), c, 'c is the last element');
+test('All-inclusive filter function, collection populated dynamically', function() {
+ var shard = new Backbone.Shard({
+ filter: function() { return true; }
+ shard.on('add', function() {
+ }).on('remove', function() {
+ }).on('reset', function() {
+ otherCol.add([a, b, c]);
+ equal(additions, 3, 'adding models matching shard filter to collection triggers add events');
+ equal(shard.size(), 3, '... and the shard has 3 items');
+ otherCol.remove([a, b, c]);
+ equal(removals, 3, 'removing models matching shard filter from collection triggers remove events');
+ equal(shard.size(), 0, '... and the shard is empty');
+ otherCol.reset([a, b, c]);
+ equal(resets, 1, 'resetting collection triggers reset events');
+ equal(shard.size(), 3, '... and the shard has 3 items');
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/index.html Wed Dec 05 00:36:27 2012 +0900
+ <title>Shard Test Suite</title>
+ <link rel="stylesheet" href="http://code.jquery.com/qunit/qunit-1.10.0.css" type="text/css" media="screen">
+ <script src="http://code.jquery.com/qunit/qunit-1.10.0.js"></script>
+ <script src="http://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.4.2/underscore-min.js"></script>
+ <script src="http://cdnjs.cloudflare.com/ajax/libs/backbone.js/0.9.2/backbone-min.js"></script>
+ <script src="../backbone.shard.js"></script>
+ <script src="basic-filters.js"></script>
+ <script src="sensible-filters.js"></script>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/sensible-filters.js Wed Dec 05 00:36:27 2012 +0900
+var a, b, c, col, otherCol;
+module('Backbone.Shard', {
+ a = new Backbone.Model({data: 'a'});
+ b = new Backbone.Model({data: 'b'});
+ c = new Backbone.Model({data: 'c'});
+ d = new Backbone.Model({data: 'd'});
+ col = new Backbone.Collection([a, b, c, d]);
+ otherCol = new Backbone.Collection();
+test('Sensible filter function, collection is prepopulated', function() {
+ var shard = new Backbone.Shard({
+ filter: function(model) { return _(['a', 'c']).contains(model.get('data')); }
+ equal(shard.size(), 2, 'shard has 2 items');
+ equal(shard.first(), a, 'a is the first element');
+ equal(shard.last(), c, 'c is the last element');
+test('Sensible filter function, collection populated dynamically', function() {
+ var shard = new Backbone.Shard({
+ filter: function(model) { return _(['a', 'c']).contains(model.get('data')); }
+ shard.on('add', function() {
+ }).on('remove', function() {
+ }).on('reset', function() {
+ otherCol.add([a, b, c, d]);
+ equal(additions, 2, 'adding models matching shard filter to collection triggers add events');
+ equal(shard.size(), 2, '... and the shard has 2 items');
+ otherCol.remove([b, d]);
+ equal(removals, 0, 'removing models not matching shard filter from collection does not trigger remove events');
+ equal(shard.size(), 2, '... and the shard has 2 items');
+ otherCol.remove([a, c]);
+ equal(removals, 2, 'removing models matching shard filter from collection triggers remove events');
+ equal(shard.size(), 0, '... and the shard is empty');
+ otherCol.reset([a, b, c, d]);
+ equal(resets, 1, 'resettings collection triggers reset events');
+ equal(shard.size(), 2, '... and the shard has 2 items');