diff --git a/backbone.js b/backbone.js index a0fb826ac..41ee2aa1b 100644 --- a/backbone.js +++ b/backbone.js @@ -68,54 +68,6 @@ // form param named `model`. Backbone.emulateJSON = false; - // Proxy Backbone class methods to Underscore functions, wrapping the model's - // `attributes` object or collection's `models` array behind the scenes. - // - // collection.filter(function(model) { return model.get('age') > 10 }); - // collection.each(this.addView); - // - // `Function#apply` can be slow so we use the method's arg count, if we know it. - var addMethod = function(length, method, attribute) { - switch (length) { - case 1: return function() { - return _[method](this[attribute]); - }; - case 2: return function(value) { - return _[method](this[attribute], value); - }; - case 3: return function(iteratee, context) { - return _[method](this[attribute], cb(iteratee, this), context); - }; - case 4: return function(iteratee, defaultVal, context) { - return _[method](this[attribute], cb(iteratee, this), defaultVal, context); - }; - default: return function() { - var args = slice.call(arguments); - args.unshift(this[attribute]); - return _[method].apply(_, args); - }; - } - }; - var addUnderscoreMethods = function(Class, methods, attribute) { - _.each(methods, function(length, method) { - if (_[method]) Class.prototype[method] = addMethod(length, method, attribute); - }); - }; - - // Support `collection.sortBy('attr')` and `collection.findWhere({id: 1})`. - var cb = function(iteratee, instance) { - if (_.isFunction(iteratee)) return iteratee; - if (_.isObject(iteratee) && !instance._isModel(iteratee)) return modelMatcher(iteratee); - if (_.isString(iteratee)) return function(model) { return model.get(iteratee); }; - return iteratee; - }; - var modelMatcher = function(attrs) { - var matcher = _.matches(attrs); - return function(model) { - return matcher(model.attributes); - }; - }; - // Backbone.Events // --------------- @@ -790,14 +742,6 @@ }); - // Underscore methods that we want to implement on the Model, mapped to the - // number of arguments they take. - var modelMethods = {keys: 1, values: 1, pairs: 1, invert: 1, pick: 0, - omit: 0, chain: 1, isEmpty: 1}; - - // Mix in each Underscore method as a proxy to `Model#attributes`. - addUnderscoreMethods(Model, modelMethods, 'attributes'); - // Backbone.Collection // ------------------- @@ -1335,21 +1279,6 @@ 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: - var collectionMethods = {forEach: 3, each: 3, map: 3, collect: 3, reduce: 0, - foldl: 0, inject: 0, reduceRight: 0, foldr: 0, find: 3, detect: 3, filter: 3, - select: 3, reject: 3, every: 3, all: 3, some: 3, any: 3, include: 3, includes: 3, - contains: 3, invoke: 0, max: 3, min: 3, toArray: 1, size: 1, first: 3, - head: 3, take: 3, initial: 3, rest: 3, tail: 3, drop: 3, last: 3, - without: 0, difference: 0, indexOf: 3, shuffle: 1, lastIndexOf: 3, - isEmpty: 1, chain: 1, sample: 3, partition: 3, groupBy: 3, countBy: 3, - sortBy: 3, indexBy: 3, findIndex: 3, findLastIndex: 3}; - - // Mix in each Underscore method as a proxy to `Collection#models`. - addUnderscoreMethods(Collection, collectionMethods, 'models'); - // Backbone.View // ------------- @@ -1518,6 +1447,94 @@ }); + // Proxy Backbone class methods to Underscore functions, wrapping the model's + // `attributes` object or collection's `models` array behind the scenes. + // + // collection.filter(function(model) { return model.get('age') > 10 }); + // collection.each(this.addView); + // + // `Function#apply` can be slow so we use the method's arg count, if we know it. + var addMethod = function(base, length, method, attribute) { + switch (length) { + case 1: return function() { + return base[method](this[attribute]); + }; + case 2: return function(value) { + return base[method](this[attribute], value); + }; + case 3: return function(iteratee, context) { + return base[method](this[attribute], cb(iteratee, this), context); + }; + case 4: return function(iteratee, defaultVal, context) { + return base[method](this[attribute], cb(iteratee, this), defaultVal, context); + }; + default: return function() { + var args = slice.call(arguments); + args.unshift(this[attribute]); + return base[method].apply(base, args); + }; + } + }; + + var addUnderscoreMethods = function(Class, base, methods, attribute) { + _.each(methods, function(length, method) { + if (base[method]) Class.prototype[method] = addMethod(base, length, method, attribute); + }); + }; + + // Support `collection.sortBy('attr')` and `collection.findWhere({id: 1})`. + var cb = function(iteratee, instance) { + if (_.isFunction(iteratee)) return iteratee; + if (_.isObject(iteratee) && !instance._isModel(iteratee)) return modelMatcher(iteratee); + if (_.isString(iteratee)) return function(model) { return model.get(iteratee); }; + return iteratee; + }; + var modelMatcher = function(attrs) { + var matcher = _.matches(attrs); + return function(model) { + return matcher(model.attributes); + }; + }; + + // Underscore methods that we want to implement on the Collection. + // 90% of the core usefulness of Backbone Collections is actually implemented + // right here: + var collectionMethods = {forEach: 3, each: 3, map: 3, collect: 3, reduce: 0, + foldl: 0, inject: 0, reduceRight: 0, foldr: 0, find: 3, detect: 3, filter: 3, + select: 3, reject: 3, every: 3, all: 3, some: 3, any: 3, include: 3, includes: 3, + contains: 3, invoke: 0, max: 3, min: 3, toArray: 1, size: 1, first: 3, + head: 3, take: 3, initial: 3, rest: 3, tail: 3, drop: 3, last: 3, + without: 0, difference: 0, indexOf: 3, shuffle: 1, lastIndexOf: 3, + isEmpty: 1, chain: 1, sample: 3, partition: 3, groupBy: 3, countBy: 3, + sortBy: 3, indexBy: 3, findIndex: 3, findLastIndex: 3}; + + + // Underscore methods that we want to implement on the Model, mapped to the + // number of arguments they take. + var modelMethods = {keys: 1, values: 1, pairs: 1, invert: 1, pick: 0, + omit: 0, chain: 1, isEmpty: 1}; + + // Mix in each Underscore method as a proxy to `Collection#models`. + + _.each([ + [Collection, collectionMethods, 'models'], + [Model, modelMethods, 'attributes'] + ], function(config) { + var Base = config[0], + methods = config[1], + attribute = config[2]; + + Base.mixin = function(obj) { + var mappings = _.reduce(_.functions(obj), function(memo, name) { + memo[name] = 0; + return memo; + }, {}); + addUnderscoreMethods(Base, obj, mappings, attribute); + }; + + addUnderscoreMethods(Base, _, methods, attribute); + }); + // Backbone.sync // ------------- diff --git a/test/collection.js b/test/collection.js index 467b4581c..139eb9961 100644 --- a/test/collection.js +++ b/test/collection.js @@ -711,6 +711,27 @@ assert.equal(coll.findWhere({a: 4}), void 0); }); + QUnit.test('mixin', function(assert) { + Backbone.Collection.mixin({ + sum: function(models, iteratee) { + return _.reduce(models, function(s, m) { + return s + iteratee(m); + }, 0); + } + }); + + var coll = new Backbone.Collection([ + {a: 1}, + {a: 1, b: 2}, + {a: 2, b: 2}, + {a: 3} + ]); + + assert.equal(coll.sum(function(m) { + return m.get('a'); + }), 7); + }); + QUnit.test('Underscore methods', function(assert) { assert.expect(21); assert.equal(col.map(function(model){ return model.get('label'); }).join(' '), 'a b c d'); diff --git a/test/model.js b/test/model.js index 440047fef..b3092df7f 100644 --- a/test/model.js +++ b/test/model.js @@ -1395,6 +1395,28 @@ assert.ok(!model.set('valid', false, {validate: true})); }); + QUnit.test('mixin', function(assert) { + Backbone.Model.mixin({ + isEqual: function(model1, model2) { + return _.isEqual(model1, model2.attributes); + } + }); + + var model1 = new Backbone.Model({ + a: {b: 2}, c: 3 + }); + var model2 = new Backbone.Model({ + a: {b: 2}, c: 3 + }); + var model3 = new Backbone.Model({ + a: {b: 4}, c: 3 + }); + + assert.equal(model1.isEqual(model2), true); + assert.equal(model1.isEqual(model3), false); + }); + + QUnit.test('#1179 - isValid returns true in the absence of validate.', function(assert) { assert.expect(1); var model = new Backbone.Model();