Skip to content

Commit

Permalink
Merge pull request #4061 from leebyron/iterable
Browse files Browse the repository at this point in the history
Implements the JavaScript Iterable protocol.
  • Loading branch information
jridgewell authored Jul 21, 2016
2 parents 760ad49 + a2fc65b commit 96e94b3
Show file tree
Hide file tree
Showing 2 changed files with 135 additions and 0 deletions.
81 changes: 81 additions & 0 deletions backbone.js
Original file line number Diff line number Diff line change
Expand Up @@ -1106,6 +1106,21 @@
return attrs[this.model.prototype.idAttribute || 'id'];
},

// Get an iterator of all models in this collection.
values: function() {
return new CollectionIterator(this, ITERATOR_VALUES);
},

// Get an iterator of all model IDs in this collection.
keys: function() {
return new CollectionIterator(this, ITERATOR_KEYS);
},

// Get an iterator of all [ID, model] tuples in this collection.
entries: function() {
return new CollectionIterator(this, ITERATOR_KEYSVALUES);
},

// Private method to reset all internal state. Called when the collection
// is first initialized or reset.
_reset: function() {
Expand Down Expand Up @@ -1202,6 +1217,72 @@

});

// Defining an @@iterator method implements JavaScript's Iterable protocol.
// In modern ES2015 browsers, this value is found at Symbol.iterator.
/* global Symbol */
var $$iterator = typeof Symbol === 'function' && Symbol.iterator;
if ($$iterator) {
Collection.prototype[$$iterator] = Collection.prototype.values;
}

// CollectionIterator
// ------------------

// A CollectionIterator implements JavaScript's Iterator protocol, allowing the
// use of `for of` loops in modern browsers and interoperation between
// Backbone.Collection and other JavaScript functions and third-party libraries
// which can operate on Iterables.
var CollectionIterator = function(collection, kind) {
this._collection = collection;
this._kind = kind;
this._index = 0;
};

// This "enum" defines the three possible kinds of values which can be emitted
// by a CollectionIterator that correspond to the values(), keys() and entries()
// methods on Collection, respectively.
var ITERATOR_VALUES = 1;
var ITERATOR_KEYS = 2;
var ITERATOR_KEYSVALUES = 3;

// All Iterators should themselves be Iterable.
if ($$iterator) {
CollectionIterator.prototype[$$iterator] = function() {
return this;
};
}

CollectionIterator.prototype.next = function() {
if (this._collection) {

// Only continue iterating if the iterated collection is long enough.
if (this._index < this._collection.length) {
var model = this._collection.at(this._index);
this._index++;

// Construct a value depending on what kind of values should be iterated.
var value;
if (this._kind === ITERATOR_VALUES) {
value = model;
} else {
var id = this._collection.modelId(model.attributes);
if (this._kind === ITERATOR_KEYS) {
value = id;
} else { // ITERATOR_KEYSVALUES
value = [id, model];
}
}
return {value: value, done: false};
}

// Once exhausted, remove the reference to the collection so future
// calls to the next method always return done.
this._collection = void 0;
}

return {value: void 0, done: true};
};

// Underscore methods that we want to implement on the Collection.
// 90% of the core usefulness of Backbone Collections is actually implemented
// right here:
Expand Down
54 changes: 54 additions & 0 deletions test/collection.js
Original file line number Diff line number Diff line change
Expand Up @@ -1758,6 +1758,60 @@
assert.equal(c2.modelId(m.attributes), void 0);
});

QUnit.test('Collection implements Iterable, values is default iterator function', function(assert) {
/* global Symbol */
var $$iterator = typeof Symbol === 'function' && Symbol.iterator;
// This test only applies to environments which define Symbol.iterator.
if (!$$iterator) {
assert.expect(0);
return;
}
assert.expect(2);
var collection = new Backbone.Collection([]);
assert.strictEqual(collection[$$iterator], collection.values);
var iterator = collection[$$iterator]();
assert.deepEqual(iterator.next(), {value: void 0, done: true});
});

QUnit.test('Collection.values iterates models in sorted order', function(assert) {
assert.expect(4);
var one = new Backbone.Model({id: 1});
var two = new Backbone.Model({id: 2});
var three = new Backbone.Model({id: 3});
var collection = new Backbone.Collection([one, two, three]);
var iterator = collection.values();
assert.strictEqual(iterator.next().value, one);
assert.strictEqual(iterator.next().value, two);
assert.strictEqual(iterator.next().value, three);
assert.strictEqual(iterator.next().value, void 0);
});

QUnit.test('Collection.keys iterates ids in sorted order', function(assert) {
assert.expect(4);
var one = new Backbone.Model({id: 1});
var two = new Backbone.Model({id: 2});
var three = new Backbone.Model({id: 3});
var collection = new Backbone.Collection([one, two, three]);
var iterator = collection.keys();
assert.strictEqual(iterator.next().value, 1);
assert.strictEqual(iterator.next().value, 2);
assert.strictEqual(iterator.next().value, 3);
assert.strictEqual(iterator.next().value, void 0);
});

QUnit.test('Collection.entries iterates ids and models in sorted order', function(assert) {
assert.expect(4);
var one = new Backbone.Model({id: 1});
var two = new Backbone.Model({id: 2});
var three = new Backbone.Model({id: 3});
var collection = new Backbone.Collection([one, two, three]);
var iterator = collection.entries();
assert.deepEqual(iterator.next().value, [1, one]);
assert.deepEqual(iterator.next().value, [2, two]);
assert.deepEqual(iterator.next().value, [3, three]);
assert.strictEqual(iterator.next().value, void 0);
});

QUnit.test('#3039 #3951: adding at index fires with correct at', function(assert) {
assert.expect(4);
var collection = new Backbone.Collection([{val: 0}, {val: 4}]);
Expand Down

0 comments on commit 96e94b3

Please sign in to comment.