diff --git a/build.js b/build.js index 1699b68a8..7aa408d44 100644 --- a/build.js +++ b/build.js @@ -653,72 +653,6 @@ * @returns {String} Returns the modified source. */ function addUnderscoreChaining(source) { - // add `_.chain` - source = source.replace(matchFunction(source, 'tap', true), function(match) { - var indent = getIndent(match); - return match && (indent + [ - '', - '/**', - ' * Creates a `lodash` object that wraps the given `value`.', - ' *', - ' * @static', - ' * @memberOf _', - ' * @category Chaining', - ' * @param {Mixed} value The value to wrap.', - ' * @returns {Object} Returns the wrapper object.', - ' * @example', - ' *', - ' * var stooges = [', - " * { 'name': 'moe', 'age': 40 },", - " * { 'name': 'larry', 'age': 50 },", - " * { 'name': 'curly', 'age': 60 }", - ' * ];', - ' *', - ' * var youngest = _.chain(stooges)', - ' * .sortBy(function(stooge) { return stooge.age; })', - " * .map(function(stooge) { return stooge.name + ' is ' + stooge.age; })", - ' * .first();', - " * // => 'moe is 40'", - ' */', - 'function chain(value) {', - ' value = new lodashWrapper(value);', - ' value.__chain__ = true;', - ' return value;', - '}', - '', - match - ].join('\n' + indent)); - }); - - // add `wrapperChain` - source = source.replace(matchFunction(source, 'wrapperToString', true), function(match) { - var indent = getIndent(match); - return match && (indent + [ - '', - '/**', - ' * Enables method chaining on the wrapper object.', - ' *', - ' * @name chain', - ' * @memberOf _', - ' * @category Chaining', - ' * @returns {Mixed} Returns the wrapper object.', - ' * @example', - ' *', - ' * var sum = _([1, 2, 3])', - ' * .chain()', - ' * .reduce(function(sum, num) { return sum + num; })', - ' * .value()', - ' * // => 6`', - ' */', - 'function wrapperChain() {', - ' this.__chain__ = true;', - ' return this;', - '}', - '', - match - ].join('\n' + indent)); - }); - // remove `lodash.prototype.toString` and `lodash.prototype.valueOf` assignments source = source.replace(/^ *lodash\.prototype\.(?:toString|valueOf) *=.+\n/gm, ''); @@ -783,11 +717,6 @@ ].join('\n' + indent); }); - // replace `_.chain` assignment - source = source.replace(getMethodAssignments(source), function(match) { - return match.replace(/^( *lodash\.chain *= *)[\s\S]+?(?=;\n)/m, '$1chain') - }); - // move `mixin(lodash)` to after the method assignments source = source.replace(/(?:\s*\/\/.*)*\n( *)mixin\(lodash\).+/, ''); source = source.replace(getMethodAssignments(source), function(match) { @@ -800,11 +729,6 @@ ].join('\n' + indent); }); - // move the `lodash.prototype.chain` assignment to after `mixin(lodash)` - source = source - .replace(/^ *lodash\.prototype\.chain *=[\s\S]+?;\n/m, '') - .replace(/^( *)lodash\.prototype\.value *=/m, '$1lodash.prototype.chain = wrapperChain;\n$&'); - return source; } diff --git a/lodash.js b/lodash.js index de6e2ff3d..f098205b9 100644 --- a/lodash.js +++ b/lodash.js @@ -585,7 +585,6 @@ * * @name _ * @constructor - * @alias chain * @category Chaining * @param {Mixed} value The value to wrap in a `lodash` instance. * @returns {Object} Returns a `lodash` instance. @@ -622,9 +621,11 @@ * * @private * @param {Mixed} value The value to wrap in a `lodash` instance. + * @param {Boolean} chainAll A flag to enable chaining for all methods * @returns {Object} Returns a `lodash` instance. */ - function lodashWrapper(value) { + function lodashWrapper(value, chainAll) { + this.__chain__ = !!chainAll; this.__wrapped__ = value; } // ensure `new lodashWrapper` is an instance of `lodash` @@ -5956,6 +5957,34 @@ /*--------------------------------------------------------------------------*/ + /** + * Creates a `lodash` object that wraps the given `value`. + * + * @static + * @memberOf _ + * @category Chaining + * @param {Mixed} value The value to wrap. + * @returns {Object} Returns the wrapper object. + * @example + * + * var stooges = [ + * { 'name': 'moe', 'age': 40 }, + * { 'name': 'larry', 'age': 50 }, + * { 'name': 'curly', 'age': 60 } + * ]; + * + * var youngest = _.chain(stooges) + * .sortBy(function(stooge) { return stooge.age; }) + * .map(function(stooge) { return stooge.name + ' is ' + stooge.age; }) + * .first(); + * // => 'moe is 40' + */ + function chain(value) { + value = new lodashWrapper(value); + value.__chain__ = true; + return value; + } + /** * Invokes `interceptor` with the `value` as the first argument, and then * returns `value`. The purpose of this method is to "tap into" a method chain, @@ -5982,6 +6011,26 @@ return value; } + /** + * Enables method chaining on the wrapper object. + * + * @name chain + * @memberOf _ + * @category Chaining + * @returns {Mixed} Returns the wrapper object. + * @example + * + * var sum = _([1, 2, 3]) + * .chain() + * .reduce(function(sum, num) { return sum + num; }) + * .value() + * // => 6` + */ + function wrapperChain() { + this.__chain__ = true; + return this; + } + /** * Produces the `toString` result of the wrapped value. * @@ -6024,6 +6073,7 @@ lodash.bind = bind; lodash.bindAll = bindAll; lodash.bindKey = bindKey; + lodash.chain = chain; lodash.compact = compact; lodash.compose = compose; lodash.countBy = countBy; @@ -6095,10 +6145,6 @@ // add functions to `lodash.prototype` mixin(lodash); - // add Underscore compat - lodash.chain = lodash; - lodash.prototype.chain = function() { return this; }; - /*--------------------------------------------------------------------------*/ // add functions that return unwrapped values when chaining @@ -6162,9 +6208,14 @@ forOwn(lodash, function(func, methodName) { if (!lodash.prototype[methodName]) { lodash.prototype[methodName] = function() { - var args = [this.__wrapped__]; + var args = [this.__wrapped__], + chainAll = this.__chain__; + push.apply(args, arguments); - return func.apply(lodash, args); + var result = func.apply(lodash, args); + return chainAll + ? new lodashWrapper(result, chainAll) + : result; }; } }); @@ -6182,10 +6233,12 @@ forOwn(lodash, function(func, methodName) { if (!lodash.prototype[methodName]) { lodash.prototype[methodName]= function(callback, thisArg) { - var result = func(this.__wrapped__, callback, thisArg); - return callback == null || (thisArg && typeof callback != 'function') + var chainAll = this.__chain__, + result = func(this.__wrapped__, callback, thisArg); + + return !chainAll && (callback == null || (thisArg && typeof callback != 'function')) ? result - : new lodashWrapper(result); + : new lodashWrapper(result, chainAll); }; } }); @@ -6202,6 +6255,7 @@ lodash.VERSION = '1.3.1'; // add "Chaining" functions to the wrapper + lodash.prototype.chain = wrapperChain; lodash.prototype.toString = wrapperToString; lodash.prototype.value = wrapperValueOf; lodash.prototype.valueOf = wrapperValueOf; @@ -6210,7 +6264,12 @@ baseEach(['join', 'pop', 'shift'], function(methodName) { var func = arrayRef[methodName]; lodash.prototype[methodName] = function() { - return func.apply(this.__wrapped__, arguments); + var chainAll = this.__chain__, + result = func.apply(this.__wrapped__, arguments); + + return chainAll + ? new lodashWrapper(result, chainAll) + : result; }; }); @@ -6227,7 +6286,7 @@ baseEach(['concat', 'slice', 'splice'], function(methodName) { var func = arrayRef[methodName]; lodash.prototype[methodName] = function() { - return new lodashWrapper(func.apply(this.__wrapped__, arguments)); + return new lodashWrapper(func.apply(this.__wrapped__, arguments), this.__chain__); }; }); @@ -6239,13 +6298,16 @@ isSplice = methodName == 'splice'; lodash.prototype[methodName] = function() { - var value = this.__wrapped__, + var chainAll = this.__chain__, + value = this.__wrapped__, result = func.apply(value, arguments); if (value.length === 0) { delete value[0]; } - return isSplice ? new lodashWrapper(result) : result; + return (chainAll || isSplice) + ? new lodashWrapper(result, chainAll) + : result; }; }); } diff --git a/test/test-build.js b/test/test-build.js index 2448768c8..892c56806 100644 --- a/test/test-build.js +++ b/test/test-build.js @@ -1214,8 +1214,9 @@ vm.runInContext(data.source, context); var lodash = context._; - ok(lodash.chain(1) instanceof lodash, '_.chain: ' + basename); - ok(lodash(1).chain() instanceof lodash, '_#chain: ' + basename); + var array = ['abc']; + ok(lodash.chain(array).first().first() instanceof lodash, '_.chain: ' + basename); + ok(lodash(array).chain().first().first() instanceof lodash, '_#chain: ' + basename); var wrapped = lodash(1); strictEqual(wrapped.identity(), 1, '_(...) wrapped values are not chainable by default: ' + basename); diff --git a/test/test.js b/test/test.js index 745014248..f450a189c 100644 --- a/test/test.js +++ b/test/test.js @@ -408,6 +408,19 @@ var wrapper = _({ 'a': 0 }); equal(wrapper.chain(), wrapper); }); + + test('should enable chaining of methods that return unwrapped values by default', function() { + var array = ['abc']; + + ok(_.chain(array).first() instanceof _); + ok(_(array).chain().first() instanceof _); + + ok(_.chain(array).isArray() instanceof _); + ok(_(array).chain().isArray() instanceof _); + + ok(_.chain(array).first().first() instanceof _); + ok(_(array).chain().first().first() instanceof _); + }); }()); /*--------------------------------------------------------------------------*/