resolving merge

This commit is contained in:
Jeremy Ashkenas
2012-04-02 13:06:44 -04:00
6 changed files with 197 additions and 120 deletions

View File

@@ -245,6 +245,7 @@
<li>- <a href="#mixin">mixin</a></li> <li>- <a href="#mixin">mixin</a></li>
<li>- <a href="#uniqueId">uniqueId</a></li> <li>- <a href="#uniqueId">uniqueId</a></li>
<li>- <a href="#escape">escape</a></li> <li>- <a href="#escape">escape</a></li>
<li>- <a href="#result">result</a></li>
<li>- <a href="#template">template</a></li> <li>- <a href="#template">template</a></li>
</ul> </ul>
@@ -1309,12 +1310,24 @@ _.uniqueId('contact_');
_.escape('Curly, Larry &amp; Moe'); _.escape('Curly, Larry &amp; Moe');
=&gt; "Curly, Larry &amp;amp; Moe"</pre> =&gt; "Curly, Larry &amp;amp; Moe"</pre>
<p id="result">
<b class="header">result</b><code>_.result(object, property)</code>
<br />
If the value of the named property is a function then invoke it; otherwise, return it.
</p>
<pre>
var object = {cheese: 'crumpets', stuff: function(){ return 'nonsense'; }};
_.result(object, 'cheese');
=&gt; "crumpets"
_.result(object, 'stuff');
=&gt; "nonsense"</pre>
<p id="template"> <p id="template">
<b class="header">template</b><code>_.template(templateString, [context])</code> <b class="header">template</b><code>_.template(templateString, [context])</code>
<br /> <br />
Compiles JavaScript templates into functions that can be evaluated Compiles JavaScript templates into functions that can be evaluated
for rendering. Useful for rendering complicated bits of HTML from JSON for rendering. Useful for rendering complicated bits of HTML from JSON
data sources. Template functions can both interpolate variables, using<br /> data sources. Template functions can both interpolate variables, using
<tt>&lt;%= &hellip; %&gt;</tt>, as well as execute arbitrary JavaScript code, with <tt>&lt;%= &hellip; %&gt;</tt>, as well as execute arbitrary JavaScript code, with
<tt>&lt;% &hellip; %&gt;</tt>. If you wish to interpolate a value, and have <tt>&lt;% &hellip; %&gt;</tt>. If you wish to interpolate a value, and have
it be HTML-escaped, use <tt>&lt;%- &hellip; %&gt;</tt> When you evaluate a template function, pass in a it be HTML-escaped, use <tt>&lt;%- &hellip; %&gt;</tt> When you evaluate a template function, pass in a
@@ -1368,6 +1381,18 @@ var template = _.template("Hello {{ name }}!");
template({name : "Mustache"}); template({name : "Mustache"});
=&gt; "Hello Mustache!"</pre> =&gt; "Hello Mustache!"</pre>
<p>
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.
<b>template</b> provides the <b>source</b> property on the compiled template
function for easy precompilation.
</p>
<pre>&lt;script&gt;
JST.project = <%= _.template(jstText).source %>;
&lt;/script&gt;</pre>
<h2 id="chaining">Chaining</h2> <h2 id="chaining">Chaining</h2>

View File

@@ -275,6 +275,7 @@ $(document).ready(function() {
test('collections: size', function() { test('collections: size', function() {
equal(_.size({one : 1, two : 2, three : 3}), 3, 'can compute the size of an object'); 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');
}); });
}); });

View File

@@ -101,8 +101,8 @@ $(document).ready(function() {
setTimeout(throttledIncr, 190); setTimeout(throttledIncr, 190);
setTimeout(throttledIncr, 220); setTimeout(throttledIncr, 220);
setTimeout(throttledIncr, 240); setTimeout(throttledIncr, 240);
_.delay(function(){ ok(counter == 1, "incr was called immediately"); }, 30); _.delay(function(){ equal(counter, 1, "incr was called immediately"); }, 30);
_.delay(function(){ ok(counter == 4, "incr was throttled"); start(); }, 400); _.delay(function(){ equal(counter, 4, "incr was throttled"); start(); }, 400);
}); });
asyncTest("functions: throttle arguments", 2, function() { asyncTest("functions: throttle arguments", 2, function() {
@@ -122,7 +122,7 @@ $(document).ready(function() {
var incr = function(){ counter++; }; var incr = function(){ counter++; };
var throttledIncr = _.throttle(incr, 100); var throttledIncr = _.throttle(incr, 100);
throttledIncr(); 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() { asyncTest("functions: throttle twice", 1, function() {
@@ -130,7 +130,7 @@ $(document).ready(function() {
var incr = function(){ counter++; }; var incr = function(){ counter++; };
var throttledIncr = _.throttle(incr, 100); var throttledIncr = _.throttle(incr, 100);
throttledIncr(); throttledIncr(); 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() { asyncTest("functions: debounce", 1, function() {
@@ -143,7 +143,7 @@ $(document).ready(function() {
setTimeout(debouncedIncr, 90); setTimeout(debouncedIncr, 90);
setTimeout(debouncedIncr, 120); setTimeout(debouncedIncr, 120);
setTimeout(debouncedIncr, 150); 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() { asyncTest("functions: debounce asap", 2, function() {

View File

@@ -18,6 +18,7 @@
<body> <body>
<div class="underscore-test"> <div class="underscore-test">
<h1 id="qunit-header">Underscore Test Suite</h1> <h1 id="qunit-header">Underscore Test Suite</h1>
<div id="qunit-testrunner-toolbar"></div>
<h2 id="qunit-banner"></h2> <h2 id="qunit-banner"></h2>
<h2 id="qunit-userAgent"></h2> <h2 id="qunit-userAgent"></h2>
<ol id="qunit-tests"></ol> <ol id="qunit-tests"></ol>
@@ -34,6 +35,7 @@
<script type="text/html" id="template"> <script type="text/html" id="template">
<% <%
// a comment
if (data) { data += 12345; }; %> if (data) { data += 12345; }; %>
<li><%= data %></li> <li><%= data %></li>
</script> </script>

View File

@@ -1,6 +1,14 @@
$(document).ready(function() { $(document).ready(function() {
module("Utility"); var templateSettings = _.templateSettings;
module("Utility", {
teardown: function() {
_.templateSettings = templateSettings;
}
});
test("utility: noConflict", function() { test("utility: noConflict", function() {
var underscore = _.noConflict(); var underscore = _.noConflict();
@@ -152,4 +160,18 @@ $(document).ready(function() {
equal(templateWithNull({planet : "world"}), "a null undefined world", "can handle missing escape and evaluate settings"); equal(templateWithNull({planet : "world"}), "a null undefined world", "can handle missing escape and evaluate settings");
}); });
test('_.template handles \\u2028 & \\u2029', function() {
var tmpl = _.template('<p>\u2028<%= "\\u2028\\u2029" %>\u2029</p>');
strictEqual(tmpl(), '<p>\u2028\u2028\u2029\u2029</p>');
});
test('result calls functions and returns primitives', function() {
var obj = {w: '', x: 'x', y: function(){ return this.x; }};
strictEqual(_.result(obj, 'w'), '');
strictEqual(_.result(obj, 'x'), 'x');
strictEqual(_.result(obj, 'y'), 'x');
strictEqual(_.result(obj, 'z'), undefined);
strictEqual(_.result(null, 'x'), null);
});
}); });

View File

@@ -309,7 +309,7 @@
// Return the number of elements in an object. // Return the number of elements in an object.
_.size = function(obj) { _.size = function(obj) {
return _.toArray(obj).length; return _.isArray(obj) ? obj.length : _.keys(obj).length;
}; };
// Array Functions // Array Functions
@@ -873,6 +873,14 @@
return (''+string).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#x27;').replace(/\//g,'&#x2F;'); return (''+string).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#x27;').replace(/\//g,'&#x2F;');
}; };
// 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];
return _.isFunction(value) ? value.call(object) : value;
};
// Add your own custom functions to the Underscore object, ensuring that // Add your own custom functions to the Underscore object, ensuring that
// they're correctly added to the OOP wrapper as well. // they're correctly added to the OOP wrapper as well.
_.mixin = function(obj) { _.mixin = function(obj) {
@@ -902,39 +910,58 @@
// guaranteed not to match. // guaranteed not to match.
var noMatch = /.^/; 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 // Within an interpolation, evaluation, or escaping, remove HTML escaping
// that had been previously added. // that had been previously added.
var unescape = function(code) { var unescape = function(code) {
return code.replace(/\\\\/g, '\\').replace(/\\'/g, "'"); return code.replace(unescaper, function(match, escape) {
return escapes[escape];
});
}; };
// JavaScript micro-templating, similar to John Resig's implementation. // JavaScript micro-templating, similar to John Resig's implementation.
// Underscore templating handles arbitrary delimiters, preserves whitespace, // Underscore templating handles arbitrary delimiters, preserves whitespace,
// and correctly escapes quotes within interpolated code. // and correctly escapes quotes within interpolated code.
_.template = function(str, data) { _.template = function(str, data) {
var c = _.templateSettings; var settings = _.templateSettings;
var tmpl = 'var __p=[],print=function(){__p.push.apply(__p,arguments);};' + var source = 'var __p=[],print=function(){__p.push.apply(__p,arguments);};' +
'with(obj||{}){__p.push(\'' + 'with(obj||{}){__p.push(\'' +
str.replace(/\\/g, '\\\\') str
.replace(/'/g, "\\'") .replace(escaper, function(match) {
.replace(c.escape || noMatch, function(match, code) { return '\\' + escapes[match];
return "',_.escape(" + unescape(code) + "),'"; })
}) .replace(settings.escape || noMatch, function(match, code) {
.replace(c.interpolate || noMatch, function(match, code) { return "',\n_.escape(" + unescape(code) + "),\n'";
return "'," + unescape(code) + ",'"; })
}) .replace(settings.interpolate || noMatch, function(match, code) {
.replace(c.evaluate || noMatch, function(match, code) { return "',\n" + unescape(code) + ",\n'";
return "');" + unescape(code).replace(/[\r\n\t]/g, ' ') + ";__p.push('"; })
}) .replace(settings.evaluate || noMatch, function(match, code) {
.replace(/\r/g, '\\r') return "');\n" + unescape(code) + "\n;__p.push('";
.replace(/\n/g, '\\n') })
.replace(/\t/g, '\\t') + "');\n}\nreturn __p.join('');";
+ "');}return __p.join('');"; var render = new Function('obj', '_', source);
var func = new Function('obj', '_', tmpl); if (data) return render(data, _);
if (data) return func(data, _); var template = function(data) {
return function(data) { return render.call(this, data, _);
return func.call(this, data, _);
}; };
template.source = 'function(obj){\n' + source + '\n}';
return template;
}; };
// Add a "chain" function, which will delegate to the wrapper. // Add a "chain" function, which will delegate to the wrapper.