From 414fafb1f4e5daf4d22bfaa1f4e50ee190cf24c4 Mon Sep 17 00:00:00 2001 From: Brad Dunbar Date: Sun, 11 Mar 2012 12:39:13 -0400 Subject: [PATCH 01/16] Allow natural multi-line code evaluation in templates. By escaping `\r`, `\n`, and `\t` earlier, we can unescape them along with backslashes and single quotes allowing for the inclusion of single line comments and code without a terminating semicolon. --- test/test.html | 2 ++ test/utility.js | 10 +++++++++- underscore.js | 28 ++++++++++++++++++---------- 3 files changed, 29 insertions(+), 11 deletions(-) diff --git a/test/test.html b/test/test.html index f8609aa26..86e7300a6 100644 --- a/test/test.html +++ b/test/test.html @@ -18,6 +18,7 @@

Underscore Test Suite

+

    @@ -34,6 +35,7 @@ diff --git a/test/utility.js b/test/utility.js index 6207d3be3..4a59faf1f 100644 --- a/test/utility.js +++ b/test/utility.js @@ -1,6 +1,14 @@ $(document).ready(function() { - module("Utility"); + var templateSettings = _.templateSettings; + + module("Utility", { + + teardown: function() { + _.templateSettings = templateSettings; + } + + }); test("utility: noConflict", function() { var underscore = _.noConflict(); diff --git a/underscore.js b/underscore.js index 88da6e1d8..c0b4a84e2 100644 --- a/underscore.js +++ b/underscore.js @@ -905,7 +905,15 @@ // Within an interpolation, evaluation, or escaping, remove HTML escaping // that had been previously added. var unescape = function(code) { - return code.replace(/\\\\/g, '\\').replace(/\\'/g, "'"); + return code.replace(/\\(\\|'|r|n|t)/g, function(match, char) { + switch (char) { + case '\\': return '\\'; + case "'": return "'"; + case 'r': return '\r'; + case 'n': return '\n'; + case 't': return '\t'; + } + }); }; // JavaScript micro-templating, similar to John Resig's implementation. @@ -917,18 +925,18 @@ 'with(obj||{}){__p.push(\'' + str.replace(/\\/g, '\\\\') .replace(/'/g, "\\'") - .replace(c.escape || noMatch, function(match, code) { - return "',_.escape(" + unescape(code) + "),'"; - }) - .replace(c.interpolate || noMatch, function(match, code) { - return "'," + unescape(code) + ",'"; - }) - .replace(c.evaluate || noMatch, function(match, code) { - return "');" + unescape(code).replace(/[\r\n\t]/g, ' ') + ";__p.push('"; - }) .replace(/\r/g, '\\r') .replace(/\n/g, '\\n') .replace(/\t/g, '\\t') + .replace(c.escape || noMatch, function(match, code) { + return "',_.escape(" + unescape(code) + "),\n'"; + }) + .replace(c.interpolate || noMatch, function(match, code) { + return "'," + unescape(code) + ",\n'"; + }) + .replace(c.evaluate || noMatch, function(match, code) { + return "');" + unescape(code) + ";\n__p.push('"; + }) + "');}return __p.join('');"; var func = new Function('obj', '_', tmpl); if (data) return func(data, _); From 826e743262792c8cca93c38d2c774a34c81c7cdd Mon Sep 17 00:00:00 2001 From: Brad Dunbar Date: Mon, 12 Mar 2012 19:15:18 -0400 Subject: [PATCH 02/16] Handle \u2028 & \u2029 in _.template. --- test/utility.js | 5 +++++ underscore.js | 6 +++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/test/utility.js b/test/utility.js index 4a59faf1f..c285751ea 100644 --- a/test/utility.js +++ b/test/utility.js @@ -160,4 +160,9 @@ $(document).ready(function() { equal(templateWithNull({planet : "world"}), "a null undefined world", "can handle missing escape and evaluate settings"); }); + test('_.template handles \\u2028 & \\u2029', function() { + var tmpl = _.template('

    \u2028<%= "\\u2028\\u2029" %>\u2029

    '); + strictEqual(tmpl(), '

    \u2028\u2028\u2029\u2029

    '); + }); + }); diff --git a/underscore.js b/underscore.js index c0b4a84e2..b956e21ce 100644 --- a/underscore.js +++ b/underscore.js @@ -905,13 +905,15 @@ // Within an interpolation, evaluation, or escaping, remove HTML escaping // that had been previously added. var unescape = function(code) { - return code.replace(/\\(\\|'|r|n|t)/g, function(match, char) { + return code.replace(/\\(\\|'|r|n|t|u2028|u2029)/g, function(match, char) { switch (char) { case '\\': return '\\'; case "'": return "'"; case 'r': return '\r'; case 'n': return '\n'; case 't': return '\t'; + case 'u2028': return '\u2028'; + case 'u2029': return '\u2029'; } }); }; @@ -928,6 +930,8 @@ .replace(/\r/g, '\\r') .replace(/\n/g, '\\n') .replace(/\t/g, '\\t') + .replace(/\u2028/g, '\\u2028') + .replace(/\u2029/g, '\\u2029') .replace(c.escape || noMatch, function(match, code) { return "',_.escape(" + unescape(code) + "),\n'"; }) From f5eb4b09157384c69f28e5cda38c620af8200c41 Mon Sep 17 00:00:00 2001 From: Brad Dunbar Date: Wed, 14 Mar 2012 08:25:01 -0400 Subject: [PATCH 03/16] Clean up _.template. * Cache regexes. * Use object properties for lookup instead of switch. --- underscore.js | 59 ++++++++++++++++++++++++++++----------------------- 1 file changed, 32 insertions(+), 27 deletions(-) diff --git a/underscore.js b/underscore.js index b956e21ce..4470eab44 100644 --- a/underscore.js +++ b/underscore.js @@ -902,19 +902,27 @@ // guaranteed not to match. var noMatch = /.^/; + // Certain characters need to be escaped so that they can be put into a + // string literal. + var escapes = { + '\\': '\\', + "'": "'", + 'r': '\r', + 'n': '\n', + 't': '\t', + 'u2028': '\u2028', + 'u2029': '\u2029' + }; + + for (var p in escapes) escapes[escapes[p]] = p; + var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g; + var unescaper = /\\(\\|'|r|n|t|u2028|u2029)/g; + // Within an interpolation, evaluation, or escaping, remove HTML escaping // that had been previously added. var unescape = function(code) { - return code.replace(/\\(\\|'|r|n|t|u2028|u2029)/g, function(match, char) { - switch (char) { - case '\\': return '\\'; - case "'": return "'"; - case 'r': return '\r'; - case 'n': return '\n'; - case 't': return '\t'; - case 'u2028': return '\u2028'; - case 'u2029': return '\u2029'; - } + return code.replace(unescaper, function(match, escape) { + return escapes[escape]; }); }; @@ -925,23 +933,20 @@ var c = _.templateSettings; var tmpl = 'var __p=[],print=function(){__p.push.apply(__p,arguments);};' + 'with(obj||{}){__p.push(\'' + - str.replace(/\\/g, '\\\\') - .replace(/'/g, "\\'") - .replace(/\r/g, '\\r') - .replace(/\n/g, '\\n') - .replace(/\t/g, '\\t') - .replace(/\u2028/g, '\\u2028') - .replace(/\u2029/g, '\\u2029') - .replace(c.escape || noMatch, function(match, code) { - return "',_.escape(" + unescape(code) + "),\n'"; - }) - .replace(c.interpolate || noMatch, function(match, code) { - return "'," + unescape(code) + ",\n'"; - }) - .replace(c.evaluate || noMatch, function(match, code) { - return "');" + unescape(code) + ";\n__p.push('"; - }) - + "');}return __p.join('');"; + str + .replace(escaper, function(match) { + return '\\' + escapes[match]; + }) + .replace(c.escape || noMatch, function(match, code) { + return "',_.escape(" + unescape(code) + "),\n'"; + }) + .replace(c.interpolate || noMatch, function(match, code) { + return "'," + unescape(code) + ",\n'"; + }) + .replace(c.evaluate || noMatch, function(match, code) { + return "');" + unescape(code) + ";\n__p.push('"; + }) + + "');}return __p.join('');"; var func = new Function('obj', '_', tmpl); if (data) return func(data, _); return function(data) { From 666049ac5db5634ba8694e64684b31ab2d1f1549 Mon Sep 17 00:00:00 2001 From: Brad Dunbar Date: Sat, 17 Mar 2012 13:02:17 -0400 Subject: [PATCH 04/16] Add utility function `getValue`. --- index.html | 195 ++++++++++++++++++++++++++---------------------- test/utility.js | 9 +++ underscore.js | 6 ++ 3 files changed, 119 insertions(+), 91 deletions(-) diff --git a/index.html b/index.html index 9165f1428..c6818b7a7 100644 --- a/index.html +++ b/index.html @@ -3,7 +3,7 @@ - + Underscore.js - +
    @@ -297,7 +298,7 @@ A complete Test & Benchmark Suite is included for your perusal.

    - +

    You may also read through the annotated source code.

    @@ -327,7 +328,7 @@ < 4kb, Minified and Gzipped - +
    Upgrade warning: version 1.3.0 removes AMD (RequireJS) support.
    @@ -525,7 +526,7 @@ _.min(numbers);

    sortBy_.sortBy(list, iterator, [context])
    - Returns a sorted copy of list, ranked in ascending order by the + Returns a sorted copy of list, ranked in ascending order by the results of running each value through iterator.

    @@ -565,7 +566,7 @@ _.sortedIndex([10, 20, 30, 40, 50], 35);
           

    shuffle_.shuffle(list)
    - Returns a shuffled copy of the list, using a version of the + Returns a shuffled copy of the list, using a version of the Fisher-Yates shuffle.

    @@ -751,7 +752,7 @@ _.zip(['moe', 'larry', 'curly'], [30, 40, 50], [true, false, false]);
             
    Returns the index at which value can be found in the array, or -1 if value is not present in the array. Uses the native - indexOf function unless it's missing. If you're working with a + indexOf function unless it's missing. If you're working with a large array, and you know that the array is already sorted, pass true for isSorted to use a faster binary search.

    @@ -836,7 +837,7 @@ jQuery('#underscore_button').bind('click', buttonView.onClick); memoize_.memoize(function, [hashFunction])
    Memoizes a given function by caching the computed result. Useful - for speeding up slow-running computations. If passed an optional + for speeding up slow-running computations. If passed an optional hashFunction, it will be used to compute the hash key for storing the result, based on the arguments to the original function. The default hashFunction just uses the first argument to the memoized function @@ -877,8 +878,8 @@ _.defer(function(){ alert('deferred'); });

    throttle_.throttle(function, wait)
    - Creates and returns a new, throttled version of the passed function, - that, when invoked repeatedly, will only actually call the original function + Creates and returns a new, throttled version of the passed function, + that, when invoked repeatedly, will only actually call the original function at most once per every wait milliseconds. Useful for rate-limiting events that occur faster than you can keep up with. @@ -892,11 +893,11 @@ $(window).scroll(throttled); debounce_.debounce(function, wait)
    Creates and returns a new debounced version of the passed function that - will postpone its execution until after - wait milliseconds have elapsed since the last time it - was invoked. Useful for implementing behavior that should only happen - after the input has stopped arriving. For example: rendering a - preview of a Markdown comment, recalculating a layout after the window + will postpone its execution until after + wait milliseconds have elapsed since the last time it + was invoked. Useful for implementing behavior that should only happen + after the input has stopped arriving. For example: rendering a + preview of a Markdown comment, recalculating a layout after the window has stopped being resized, and so on.

    @@ -907,7 +908,7 @@ $(window).resize(lazyLayout);
           

    once_.once(function)
    - Creates a version of the function that can only be called one time. + Creates a version of the function that can only be called one time. Repeated calls to the modified function will have no effect, returning the value from the original call. Useful for initialization functions, instead of having to set a boolean flag and then check it later. @@ -922,7 +923,7 @@ initialize();

    after_.after(count, function)
    - Creates a version of the function that will only be run after first + Creates a version of the function that will only be run after first being called count times. Useful for grouping asynchronous responses, where you want to be sure that all the async calls have finished, before proceeding. @@ -930,7 +931,7 @@ initialize();

     var renderNotes = _.after(notes.length, render);
     _.each(notes, function(note) {
    -  note.asyncSave({success: renderNotes}); 
    +  note.asyncSave({success: renderNotes});
     });
     // renderNotes is run once, after all notes have saved.
     
    @@ -1058,9 +1059,9 @@ _.chain([1,2,3,200])

    has_.has(object, key)
    - Does the object contain the given key? Identical to + Does the object contain the given key? Identical to object.hasOwnProperty(key), but uses a safe reference to the - hasOwnProperty function, in case it's been + hasOwnProperty function, in case it's been overridden accidentally.

    @@ -1238,7 +1239,7 @@ _.isNull(undefined);
     _.isUndefined(window.missingVariable);
     => true
     
    - +

    Utility Functions

    @@ -1275,7 +1276,7 @@ _(3).times(function(){ genie.grantWish(); });

    mixin_.mixin(object)
    Allows you to extend Underscore with your own utility functions. Pass - a hash of {name: function} definitions to have your functions + a hash of {name: function} definitions to have your functions added to the Underscore object, as well as the OOP wrapper.

    @@ -1302,13 +1303,25 @@ _.uniqueId('contact_');
           

    escape_.escape(string)
    - Escapes a string for insertion into HTML, replacing + Escapes a string for insertion into HTML, replacing &, <, >, ", ', and / characters.

     _.escape('Curly, Larry & Moe');
     => "Curly, Larry &amp; Moe"
    +

    + getValue_.getValue(object, property) +
    + Returns a value from an object as a property or as a function. +

    +
    +var object = {cheese: 'crumpets', stuff: function(){ return 'nonsense'; }};
    +_.getValue(object, 'cheese');
    +=> "crumpets"
    +_.getValue(object, 'stuff');
    +=> "nonsense"
    +

    template_.template(templateString, [context])
    @@ -1340,7 +1353,7 @@ template({value : '<script>'}); You can also use print from within JavaScript code. This is sometimes more convenient than using <%= ... %>.

    - +
     var compiled = _.template("<% print('Hello ' + epithet); %>");
     compiled({epithet: "stooge"});
    @@ -1349,8 +1362,8 @@ compiled({epithet: "stooge"});
           

    If ERB-style delimiters aren't your cup of tea, you can change Underscore's template settings to use different symbols to set off interpolated code. - Define an interpolate regex to match expressions that should be - interpolated verbatim, an escape regex to match expressions that should + Define an interpolate regex to match expressions that should be + interpolated verbatim, an escape regex to match expressions that should be inserted after being HTML escaped, and an evaluate regex to match expressions that should be evaluated without insertion into the resulting string. You may define or omit any combination of the three. @@ -1451,7 +1464,7 @@ _([1, 2, 3]).value(); The source is available on GitHub.

    - +

    Underscore.php, a PHP port of the functions that are applicable in both languages. @@ -1459,17 +1472,17 @@ _([1, 2, 3]).value(); The source is available on GitHub.

    - +

    Underscore-perl, - a Perl port of many of the Underscore.js functions, - aimed at on Perl hashes and arrays, also + a Perl port of many of the Underscore.js functions, + aimed at on Perl hashes and arrays, also available on GitHub.

    - +

    Underscore.string, - an Underscore extension that adds functions for string-manipulation: + an Underscore extension that adds functions for string-manipulation: trim, startsWith, contains, capitalize, reverse, sprintf, and more.

    @@ -1488,7 +1501,7 @@ _([1, 2, 3]).value(); Functional JavaScript, which includes comprehensive higher-order function support as well as string lambdas.

    - +

    Michael Aufreiter's Data.js, a data manipulation + persistence library for JavaScript. @@ -1499,7 +1512,7 @@ _([1, 2, 3]).value();

    Change Log

    - +

    1.3.1Jan. 23, 2012

      @@ -1510,7 +1523,7 @@ _([1, 2, 3]).value(); Added _.collect as an alias for _.map. Smalltalkers, rejoice.
    • - Reverted an old change so that _.extend will correctly copy + Reverted an old change so that _.extend will correctly copy over keys with undefined values again.
    • @@ -1518,7 +1531,7 @@ _([1, 2, 3]).value();

    - +

    1.3.0Jan. 11, 2012

      @@ -1529,12 +1542,12 @@ _([1, 2, 3]).value();

    - +

    1.2.4Jan. 4, 2012

    • - You now can (and probably should) write _.chain(list) + You now can (and probably should) write _.chain(list) instead of _(list).chain().
    • @@ -1551,7 +1564,7 @@ _([1, 2, 3]).value();

    - +

    1.2.3Dec. 7, 2011

      @@ -1564,12 +1577,12 @@ _([1, 2, 3]).value();
    • Both _.reduce and _.reduceRight can now be passed an - explicitly undefined value. (There's no reason why you'd + explicitly undefined value. (There's no reason why you'd want to do this.)

    - +

    1.2.2Nov. 14, 2011

      @@ -1588,22 +1601,22 @@ _([1, 2, 3]).value();
    • _.after(callback, 0) will now trigger the callback immediately, - making "after" easier to use with asynchronous APIs (#366). + making "after" easier to use with asynchronous APIs (#366).

    - +

    1.2.1Oct. 24, 2011

    • - Several important bug fixes for _.isEqual, which should now - do better on mutated Arrays, and on non-Array objects with + Several important bug fixes for _.isEqual, which should now + do better on mutated Arrays, and on non-Array objects with length properties. (#329)
    • jrburke contributed Underscore exporting for AMD module loaders, - and tonylukasavage for Appcelerator Titanium. + and tonylukasavage for Appcelerator Titanium. (#335, #338)
    • @@ -1611,7 +1624,7 @@ _([1, 2, 3]).value(); grouping values by a particular common property.
    • - _.throttle'd functions now fire immediately upon invocation, + _.throttle'd functions now fire immediately upon invocation, and are rate-limited thereafter (#170, #266).
    • @@ -1619,7 +1632,7 @@ _([1, 2, 3]).value();
    • The _.bind function now also works on constructors, a-la - ES5 ... but you would never want to use _.bind on a + ES5 ... but you would never want to use _.bind on a constructor function.
    • @@ -1631,25 +1644,25 @@ _([1, 2, 3]).value();

    - +

    1.2.0Oct. 5, 2011

    • - The _.isEqual function now + The _.isEqual function now supports true deep equality comparisons, with checks for cyclic structures, thanks to Kit Cambridge.
    • - Underscore templates now support HTML escaping interpolations, using - <%- ... %> syntax. + Underscore templates now support HTML escaping interpolations, using + <%- ... %> syntax.
    • - Ryan Tenney contributed _.shuffle, which uses a modified - Fisher-Yates to give you a shuffled copy of an array. + Ryan Tenney contributed _.shuffle, which uses a modified + Fisher-Yates to give you a shuffled copy of an array.
    • - _.uniq can now be passed an optional iterator, to determine by + _.uniq can now be passed an optional iterator, to determine by what criteria an object should be considered unique.
    • @@ -1658,22 +1671,22 @@ _([1, 2, 3]).value();
    • A new _.initial function was added, as a mirror of _.rest, - which returns all the initial values of a list (except the last N). + which returns all the initial values of a list (except the last N).

    - +

    1.1.7July 13, 2011
    Added _.groupBy, which aggregates a collection into groups of like items. - Added _.union and _.difference, to complement the + Added _.union and _.difference, to complement the (re-named) _.intersection. Various improvements for support of sparse arrays. _.toArray now returns a clone, if directly passed an array. _.functions now also returns the names of functions that are present in the prototype chain.

    - +

    1.1.6April 18, 2011
    Added _.after, which will return a function that only runs after @@ -1684,30 +1697,30 @@ _([1, 2, 3]).value(); _.extend no longer copies keys when the value is undefined. _.bind now errors when trying to bind an undefined value.

    - +

    1.1.5Mar 20, 2011
    Added an _.defaults function, for use merging together JS objects representing default options. Added an _.once function, for manufacturing functions that should only ever execute a single time. - _.bind now delegates to the native ECMAScript 5 version, + _.bind now delegates to the native ECMAScript 5 version, where available. _.keys now throws an error when used on non-Object values, as in ECMAScript 5. Fixed a bug with _.keys when used over sparse arrays.

    - +

    1.1.4Jan 9, 2011
    - Improved compliance with ES5's Array methods when passing null + Improved compliance with ES5's Array methods when passing null as a value. _.wrap now correctly sets this for the wrapped function. _.indexOf now takes an optional flag for finding the insertion index in an array that is guaranteed to already be sorted. Avoiding the use of .callee, to allow _.isArray to work properly in ES5's strict mode.

    - +

    1.1.3Dec 1, 2010
    In CommonJS, Underscore may now be required with just:
    @@ -1719,26 +1732,26 @@ _([1, 2, 3]).value(); Improved the isType family of functions for better interoperability with Internet Explorer host objects. _.template now correctly escapes backslashes in templates. - Improved _.reduce compatibility with the ECMA5 version: + Improved _.reduce compatibility with the ECMA5 version: if you don't pass an initial value, the first item in the collection is used. _.each no longer returns the iterated collection, for improved consistency with ES5's forEach.

    - +

    1.1.2
    - Fixed _.contains, which was mistakenly pointing at - _.intersect instead of _.include, like it should + Fixed _.contains, which was mistakenly pointing at + _.intersect instead of _.include, like it should have been. Added _.unique as an alias for _.uniq.

    - +

    1.1.1
    Improved the speed of _.template, and its handling of multiline - interpolations. Ryan Tenney contributed optimizations to many Underscore + interpolations. Ryan Tenney contributed optimizations to many Underscore functions. An annotated version of the source code is now available.

    - +

    1.1.0
    The method signature of _.reduce has been changed to match @@ -1747,33 +1760,33 @@ _([1, 2, 3]).value(); called with no arguments, and preserves whitespace. _.contains is a new alias for _.include.

    - +

    1.0.4
    - Andri Möll contributed the _.memoize - function, which can be used to speed up expensive repeated computations + Andri Möll contributed the _.memoize + function, which can be used to speed up expensive repeated computations by caching the results.

    - +

    1.0.3
    Patch that makes _.isEqual return false if any property of the compared object has a NaN value. Technically the correct thing to do, but of questionable semantics. Watch out for NaN comparisons.

    - +

    1.0.2
    Fixes _.isArguments in recent versions of Opera, which have arguments objects as real Arrays.

    - +

    1.0.1
    - Bugfix for _.isEqual, when comparing two objects with the same + Bugfix for _.isEqual, when comparing two objects with the same number of undefined keys, but with different names.

    - +

    1.0.0
    Things have been stable for many months now, so Underscore is now @@ -1781,15 +1794,15 @@ _([1, 2, 3]).value(); include _.isBoolean, and the ability to have _.extend take multiple source objects.

    - +

    0.6.0
    - Major release. Incorporates a number of + Major release. Incorporates a number of Mile Frawley's refactors for safer duck-typing on collection functions, and cleaner internals. A new _.mixin method that allows you to extend Underscore with utility - functions of your own. Added _.times, which works the same as in - Ruby or Prototype.js. Native support for ECMAScript 5's Array.isArray, + functions of your own. Added _.times, which works the same as in + Ruby or Prototype.js. Native support for ECMAScript 5's Array.isArray, and Object.keys.

    @@ -1847,7 +1860,7 @@ _([1, 2, 3]).value(); 0.5.1
    Added an _.isArguments function. Lots of little safety checks and optimizations contributed by - Noah Sloan and + Noah Sloan and Andri Möll.

    diff --git a/test/utility.js b/test/utility.js index c285751ea..5f2485478 100644 --- a/test/utility.js +++ b/test/utility.js @@ -165,4 +165,13 @@ $(document).ready(function() { strictEqual(tmpl(), '

    \u2028\u2028\u2029\u2029

    '); }); + test('getValue calls functions and returns primitives', function() { + var obj = {w: '', x: 'x', y: function(){ return 'y'; }}; + strictEqual(_.getValue(obj, 'w'), ''); + strictEqual(_.getValue(obj, 'x'), 'x'); + strictEqual(_.getValue(obj, 'y'), 'y'); + strictEqual(_.getValue(obj, 'z'), undefined); + strictEqual(_.getValue(null, 'x'), null); + }); + }); diff --git a/underscore.js b/underscore.js index b956e21ce..720801588 100644 --- a/underscore.js +++ b/underscore.js @@ -873,6 +873,12 @@ return (''+string).replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"').replace(/'/g, ''').replace(/\//g,'/'); }; + // Get a value from an object as a property or as a function. + _.getValue = function(object, prop) { + if (object == null) return null; + return _.isFunction(object[prop]) ? object[prop]() : object[prop]; + }; + // Add your own custom functions to the Underscore object, ensuring that // they're correctly added to the OOP wrapper as well. _.mixin = function(obj) { From d0e7b397c1427bf69f2c03d45c5ce31dc60f8612 Mon Sep 17 00:00:00 2001 From: Brad Dunbar Date: Mon, 19 Mar 2012 14:24:59 -0400 Subject: [PATCH 05/16] Cache property value. --- underscore.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/underscore.js b/underscore.js index 720801588..24dcc72be 100644 --- a/underscore.js +++ b/underscore.js @@ -876,7 +876,8 @@ // Get a value from an object as a property or as a function. _.getValue = function(object, prop) { if (object == null) return null; - return _.isFunction(object[prop]) ? object[prop]() : object[prop]; + var value = object[prop]; + return _.isFunction(value) ? value() : value; }; // Add your own custom functions to the Underscore object, ensuring that From 5c7ccb21ef5404dbbf4d925d03f35e49967e9a3a Mon Sep 17 00:00:00 2001 From: Brad Dunbar Date: Mon, 19 Mar 2012 14:33:38 -0400 Subject: [PATCH 06/16] Rename `getValue` to `result`. --- index.html | 10 +++++----- test/utility.js | 12 ++++++------ underscore.js | 2 +- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/index.html b/index.html index c6818b7a7..f2a00cae3 100644 --- a/index.html +++ b/index.html @@ -245,7 +245,7 @@
  1. - mixin
  2. - uniqueId
  3. - escape
  4. -
  5. - getValue
  6. +
  7. - result
  8. - template
  9. @@ -1310,16 +1310,16 @@ _.uniqueId('contact_'); _.escape('Curly, Larry & Moe'); => "Curly, Larry &amp; Moe"
    -

    - getValue_.getValue(object, property) +

    + result_.result(object, property)
    Returns a value from an object as a property or as a function.

     var object = {cheese: 'crumpets', stuff: function(){ return 'nonsense'; }};
    -_.getValue(object, 'cheese');
    +_.result(object, 'cheese');
     => "crumpets"
    -_.getValue(object, 'stuff');
    +_.result(object, 'stuff');
     => "nonsense"

    diff --git a/test/utility.js b/test/utility.js index 5f2485478..ec9960eae 100644 --- a/test/utility.js +++ b/test/utility.js @@ -165,13 +165,13 @@ $(document).ready(function() { strictEqual(tmpl(), '

    \u2028\u2028\u2029\u2029

    '); }); - test('getValue calls functions and returns primitives', function() { + test('result calls functions and returns primitives', function() { var obj = {w: '', x: 'x', y: function(){ return 'y'; }}; - strictEqual(_.getValue(obj, 'w'), ''); - strictEqual(_.getValue(obj, 'x'), 'x'); - strictEqual(_.getValue(obj, 'y'), 'y'); - strictEqual(_.getValue(obj, 'z'), undefined); - strictEqual(_.getValue(null, 'x'), null); + strictEqual(_.result(obj, 'w'), ''); + strictEqual(_.result(obj, 'x'), 'x'); + strictEqual(_.result(obj, 'y'), 'y'); + strictEqual(_.result(obj, 'z'), undefined); + strictEqual(_.result(null, 'x'), null); }); }); diff --git a/underscore.js b/underscore.js index 24dcc72be..c00438cc5 100644 --- a/underscore.js +++ b/underscore.js @@ -874,7 +874,7 @@ }; // Get a value from an object as a property or as a function. - _.getValue = function(object, prop) { + _.result = function(object, prop) { if (object == null) return null; var value = object[prop]; return _.isFunction(value) ? value() : value; From 5545a3b68da905b7cc956c1070f45cf9127ec045 Mon Sep 17 00:00:00 2001 From: Brad Dunbar Date: Mon, 19 Mar 2012 14:43:20 -0400 Subject: [PATCH 07/16] Dispense with abbreviation. --- underscore.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/underscore.js b/underscore.js index c00438cc5..eff693d3a 100644 --- a/underscore.js +++ b/underscore.js @@ -874,9 +874,9 @@ }; // Get a value from an object as a property or as a function. - _.result = function(object, prop) { + _.result = function(object, property) { if (object == null) return null; - var value = object[prop]; + var value = object[property]; return _.isFunction(value) ? value() : value; }; From 9a27b1b083f8651e95f3622bfe5a338f36f7511a Mon Sep 17 00:00:00 2001 From: Brad Dunbar Date: Mon, 19 Mar 2012 15:07:33 -0400 Subject: [PATCH 08/16] Clarify documentation for _.result. --- index.html | 3 ++- underscore.js | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/index.html b/index.html index f2a00cae3..7ebdc3f6e 100644 --- a/index.html +++ b/index.html @@ -1313,7 +1313,8 @@ _.escape('Curly, Larry & Moe');

    result_.result(object, property)
    - Returns a value from an object as a property or as a function. + If the value of the named property is a function then invoke it. + Otherwise, return its value.

     var object = {cheese: 'crumpets', stuff: function(){ return 'nonsense'; }};
    diff --git a/underscore.js b/underscore.js
    index 6c9512594..429a9b142 100644
    --- a/underscore.js
    +++ b/underscore.js
    @@ -873,7 +873,8 @@
         return (''+string).replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"').replace(/'/g, ''').replace(/\//g,'/');
       };
     
    -  // Get a value from an object as a property or as a function.
    +  // If the value of the named property is a function then invoke it.
    +  // Otherwise, return its value.
       _.result = function(object, property) {
         if (object == null) return null;
         var value = object[property];
    
    From 33be5c62b8fe39b185024ea7d4e8b0e196f29d6b Mon Sep 17 00:00:00 2001
    From: Brad Dunbar 
    Date: Mon, 19 Mar 2012 15:45:44 -0400
    Subject: [PATCH 09/16] Make a small documentation change.
    
    ---
     index.html    | 3 +--
     underscore.js | 4 ++--
     2 files changed, 3 insertions(+), 4 deletions(-)
    
    diff --git a/index.html b/index.html
    index 7ebdc3f6e..9b163c16b 100644
    --- a/index.html
    +++ b/index.html
    @@ -1313,8 +1313,7 @@ _.escape('Curly, Larry & Moe');
           

    result_.result(object, property)
    - If the value of the named property is a function then invoke it. - Otherwise, return its value. + If the value of the named property is a function then invoke it; otherwise, return it.

     var object = {cheese: 'crumpets', stuff: function(){ return 'nonsense'; }};
    diff --git a/underscore.js b/underscore.js
    index 429a9b142..21a45c0d9 100644
    --- a/underscore.js
    +++ b/underscore.js
    @@ -873,8 +873,8 @@
         return (''+string).replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"').replace(/'/g, ''').replace(/\//g,'/');
       };
     
    -  // If the value of the named property is a function then invoke it.
    -  // Otherwise, return its value.
    +  // If the value of the named property is a function then invoke it;
    +  // otherwise, return it.
       _.result = function(object, property) {
         if (object == null) return null;
         var value = object[property];
    
    From 2d3edb88f0bfda175082a2f55cf3cda980e5e489 Mon Sep 17 00:00:00 2001
    From: Raymond May Jr 
    Date: Mon, 19 Mar 2012 19:08:36 -0500
    Subject: [PATCH 10/16] _.size ludicrous speed improvement
    
    ---
     underscore.js | 6 +++++-
     1 file changed, 5 insertions(+), 1 deletion(-)
    
    diff --git a/underscore.js b/underscore.js
    index 21a45c0d9..ba2ee7600 100644
    --- a/underscore.js
    +++ b/underscore.js
    @@ -309,7 +309,11 @@
     
       // Return the number of elements in an object.
       _.size = function(obj) {
    -    return _.toArray(obj).length;
    +      if (_.isArray(obj)) {
    +	  return obj.length;
    +      } else {
    +	  return _.keys(obj).length;
    +      }
       };
     
       // Array Functions
    
    From 5827e4a40a5a30f7a00b2bd1ab01111510e8a2d4 Mon Sep 17 00:00:00 2001
    From: Raymond May Jr 
    Date: Mon, 19 Mar 2012 19:10:59 -0500
    Subject: [PATCH 11/16] _.size ludicrous speed improvement - formatting
    
    ---
     underscore.js | 10 +++++-----
     1 file changed, 5 insertions(+), 5 deletions(-)
    
    diff --git a/underscore.js b/underscore.js
    index ba2ee7600..6f3ac8f47 100644
    --- a/underscore.js
    +++ b/underscore.js
    @@ -309,11 +309,11 @@
     
       // Return the number of elements in an object.
       _.size = function(obj) {
    -      if (_.isArray(obj)) {
    -	  return obj.length;
    -      } else {
    -	  return _.keys(obj).length;
    -      }
    +    if (_.isArray(obj)) {
    +      return obj.length;
    +    }else{
    +      return _.keys(obj).length;
    +    }
       };
     
       // Array Functions
    
    From 4c2a85f9c5f38ce466e9b827c2f7e3024196b579 Mon Sep 17 00:00:00 2001
    From: Raymond May Jr 
    Date: Tue, 20 Mar 2012 09:31:50 -0500
    Subject: [PATCH 12/16] size enhancement as ternary and _.size(arr) test case
    
    ---
     test/collections.js | 1 +
     underscore.js       | 6 +-----
     2 files changed, 2 insertions(+), 5 deletions(-)
    
    diff --git a/test/collections.js b/test/collections.js
    index 71ef5a71c..18b242769 100644
    --- a/test/collections.js
    +++ b/test/collections.js
    @@ -275,6 +275,7 @@ $(document).ready(function() {
     
       test('collections: size', function() {
         equal(_.size({one : 1, two : 2, three : 3}), 3, 'can compute the size of an object');
    +    equal(_.size([1, 2, 3]), 3, 'can compute the size of an array');
       });
     
     });
    diff --git a/underscore.js b/underscore.js
    index 6f3ac8f47..c699c991b 100644
    --- a/underscore.js
    +++ b/underscore.js
    @@ -309,11 +309,7 @@
     
       // Return the number of elements in an object.
       _.size = function(obj) {
    -    if (_.isArray(obj)) {
    -      return obj.length;
    -    }else{
    -      return _.keys(obj).length;
    -    }
    +    return _.isArray(obj) ? obj.length : _.keys(obj).length;
       };
     
       // Array Functions
    
    From ebb9db4e8e62a8760d8d24cbda6b7c66dc211d0e Mon Sep 17 00:00:00 2001
    From: Brad Dunbar 
    Date: Wed, 21 Mar 2012 06:33:37 -0400
    Subject: [PATCH 13/16] _.result calls `property` with the correct context.
    
    ---
     test/utility.js | 4 ++--
     underscore.js   | 2 +-
     2 files changed, 3 insertions(+), 3 deletions(-)
    
    diff --git a/test/utility.js b/test/utility.js
    index ec9960eae..da374b59d 100644
    --- a/test/utility.js
    +++ b/test/utility.js
    @@ -166,10 +166,10 @@ $(document).ready(function() {
       });
     
       test('result calls functions and returns primitives', function() {
    -    var obj = {w: '', x: 'x', y: function(){ return 'y'; }};
    +    var obj = {w: '', x: 'x', y: function(){ return this.x; }};
         strictEqual(_.result(obj, 'w'), '');
         strictEqual(_.result(obj, 'x'), 'x');
    -    strictEqual(_.result(obj, 'y'), 'y');
    +    strictEqual(_.result(obj, 'y'), 'x');
         strictEqual(_.result(obj, 'z'), undefined);
         strictEqual(_.result(null, 'x'), null);
       });
    diff --git a/underscore.js b/underscore.js
    index c699c991b..aeaa8b1b0 100644
    --- a/underscore.js
    +++ b/underscore.js
    @@ -878,7 +878,7 @@
       _.result = function(object, property) {
         if (object == null) return null;
         var value = object[property];
    -    return _.isFunction(value) ? value() : value;
    +    return _.isFunction(value) ? value.call(object) : value;
       };
     
       // Add your own custom functions to the Underscore object, ensuring that
    
    From 85672298d9f90f7aea038aa3d439227412e3ac53 Mon Sep 17 00:00:00 2001
    From: Mark Rushakoff 
    Date: Thu, 22 Mar 2012 19:21:23 -0700
    Subject: [PATCH 14/16] Prefer equal(x, y) over ok(x == y)
    
    ---
     test/functions.js | 10 +++++-----
     1 file changed, 5 insertions(+), 5 deletions(-)
    
    diff --git a/test/functions.js b/test/functions.js
    index ff95f4368..086233960 100644
    --- a/test/functions.js
    +++ b/test/functions.js
    @@ -101,8 +101,8 @@ $(document).ready(function() {
         setTimeout(throttledIncr, 190);
         setTimeout(throttledIncr, 220);
         setTimeout(throttledIncr, 240);
    -    _.delay(function(){ ok(counter == 1, "incr was called immediately"); }, 30);
    -    _.delay(function(){ ok(counter == 4, "incr was throttled"); start(); }, 400);
    +    _.delay(function(){ equal(counter, 1, "incr was called immediately"); }, 30);
    +    _.delay(function(){ equal(counter, 4, "incr was throttled"); start(); }, 400);
       });
     
       asyncTest("functions: throttle arguments", 2, function() {
    @@ -122,7 +122,7 @@ $(document).ready(function() {
         var incr = function(){ counter++; };
         var throttledIncr = _.throttle(incr, 100);
         throttledIncr();
    -    _.delay(function(){ ok(counter == 1, "incr was called once"); start(); }, 220);
    +    _.delay(function(){ equal(counter, 1, "incr was called once"); start(); }, 220);
       });
     
       asyncTest("functions: throttle twice", 1, function() {
    @@ -130,7 +130,7 @@ $(document).ready(function() {
         var incr = function(){ counter++; };
         var throttledIncr = _.throttle(incr, 100);
         throttledIncr(); throttledIncr();
    -    _.delay(function(){ ok(counter == 2, "incr was called twice"); start(); }, 220);
    +    _.delay(function(){ equal(counter, 2, "incr was called twice"); start(); }, 220);
       });
     
       asyncTest("functions: debounce", 1, function() {
    @@ -143,7 +143,7 @@ $(document).ready(function() {
         setTimeout(debouncedIncr, 90);
         setTimeout(debouncedIncr, 120);
         setTimeout(debouncedIncr, 150);
    -    _.delay(function(){ ok(counter == 1, "incr was debounced"); start(); }, 220);
    +    _.delay(function(){ equal(counter, 1, "incr was debounced"); start(); }, 220);
       });
     
       asyncTest("functions: debounce asap", 2, function() {
    
    From 2055d745db5ee91e99476e837754d9bc052c3ab7 Mon Sep 17 00:00:00 2001
    From: Brad Dunbar 
    Date: Fri, 23 Mar 2012 12:52:50 -0400
    Subject: [PATCH 15/16] Attach template source to returned function.
    
    ---
     underscore.js | 28 +++++++++++++++-------------
     1 file changed, 15 insertions(+), 13 deletions(-)
    
    diff --git a/underscore.js b/underscore.js
    index aeaa8b1b0..1649df208 100644
    --- a/underscore.js
    +++ b/underscore.js
    @@ -938,28 +938,30 @@
       // Underscore templating handles arbitrary delimiters, preserves whitespace,
       // and correctly escapes quotes within interpolated code.
       _.template = function(str, data) {
    -    var c  = _.templateSettings;
    -    var tmpl = 'var __p=[],print=function(){__p.push.apply(__p,arguments);};' +
    +    var settings  = _.templateSettings;
    +    var source = 'var __p=[],print=function(){__p.push.apply(__p,arguments);};' +
           'with(obj||{}){__p.push(\'' +
           str
             .replace(escaper, function(match) {
               return '\\' + escapes[match];
             })
    -        .replace(c.escape || noMatch, function(match, code) {
    -          return "',_.escape(" + unescape(code) + "),\n'";
    +        .replace(settings.escape || noMatch, function(match, code) {
    +          return "',\n_.escape(" + unescape(code) + "),\n'";
             })
    -        .replace(c.interpolate || noMatch, function(match, code) {
    -          return "'," + unescape(code) + ",\n'";
    +        .replace(settings.interpolate || noMatch, function(match, code) {
    +          return "',\n" + unescape(code) + ",\n'";
             })
    -        .replace(c.evaluate || noMatch, function(match, code) {
    -          return "');" + unescape(code) + ";\n__p.push('";
    +        .replace(settings.evaluate || noMatch, function(match, code) {
    +          return "');\n" + unescape(code) + "\n;__p.push('";
             })
    -        + "');}return __p.join('');";
    -    var func = new Function('obj', '_', tmpl);
    -    if (data) return func(data, _);
    -    return function(data) {
    -      return func.call(this, data, _);
    +        + "');\n}\nreturn __p.join('');";
    +    var render = new Function('obj', '_', source);
    +    if (data) return render(data, _);
    +    var template = function(data) {
    +      return render.call(this, data, _);
         };
    +    template.source = 'function(obj, _){\n' + source + '\n}';
    +    return template;
       };
     
       // Add a "chain" function, which will delegate to the wrapper.
    
    From 2c0ccf03ef1b5d0d54b267341f57fd7f8161b75c Mon Sep 17 00:00:00 2001
    From: Brad Dunbar 
    Date: Mon, 26 Mar 2012 13:37:06 -0400
    Subject: [PATCH 16/16] Documentation for `_.template(...).source`.
    
    ---
     index.html    | 14 +++++++++++++-
     underscore.js |  2 +-
     2 files changed, 14 insertions(+), 2 deletions(-)
    
    diff --git a/index.html b/index.html
    index 9b163c16b..99453e2ce 100644
    --- a/index.html
    +++ b/index.html
    @@ -1327,7 +1327,7 @@ _.result(object, 'stuff');
             
    Compiles JavaScript templates into functions that can be evaluated for rendering. Useful for rendering complicated bits of HTML from JSON - data sources. Template functions can both interpolate variables, using
    + data sources. Template functions can both interpolate variables, using <%= … %>, as well as execute arbitrary JavaScript code, with <% … %>. If you wish to interpolate a value, and have it be HTML-escaped, use <%- … %> When you evaluate a template function, pass in a @@ -1381,6 +1381,18 @@ var template = _.template("Hello {{ name }}!"); template({name : "Mustache"}); => "Hello Mustache!"
    +

    + Precompiling your templates can be a big help when debugging errors you can't + reproduce. This is because precompiled templates can provide line numbers and + a stack trace, something that is not possible when compiling templates on the client. + template provides the source property on the compiled template + function for easy precompilation. +

    + +
    <script>
    +  JST.project = <%= _.template(jstText).source %>;
    +</script>
    +

    Chaining

    diff --git a/underscore.js b/underscore.js index 1649df208..44226a07c 100644 --- a/underscore.js +++ b/underscore.js @@ -960,7 +960,7 @@ var template = function(data) { return render.call(this, data, _); }; - template.source = 'function(obj, _){\n' + source + '\n}'; + template.source = 'function(obj){\n' + source + '\n}'; return template; };