summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Gemfile2
-rw-r--r--Gemfile.lock24
-rw-r--r--README.markdown190
-rw-r--r--Rakefile18
-rwxr-xr-xbin/handlebars2
-rw-r--r--lib/handlebars/base.js6
-rw-r--r--lib/handlebars/compiler/compiler.js35
-rw-r--r--package.json6
-rw-r--r--spec/acceptance_spec.rb3
-rw-r--r--spec/qunit_spec.js19
-rw-r--r--src/handlebars.l2
11 files changed, 198 insertions, 109 deletions
diff --git a/Gemfile b/Gemfile
index 4a3dcc6..b508d20 100644
--- a/Gemfile
+++ b/Gemfile
@@ -1,5 +1,5 @@
source "http://rubygems.org"
gem "rake"
-gem "therubyracer", ">= 0.8.0"
+gem "therubyracer", ">= 0.9.8"
gem "rspec"
diff --git a/Gemfile.lock b/Gemfile.lock
index edf394d..3021b60 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -1,17 +1,19 @@
GEM
remote: http://rubygems.org/
specs:
- diff-lcs (1.1.2)
- rake (0.9.2)
- rspec (2.1.0)
- rspec-core (~> 2.1.0)
- rspec-expectations (~> 2.1.0)
- rspec-mocks (~> 2.1.0)
- rspec-core (2.1.0)
- rspec-expectations (2.1.0)
+ diff-lcs (1.1.3)
+ libv8 (3.3.10.2)
+ rake (0.9.2.2)
+ rspec (2.7.0)
+ rspec-core (~> 2.7.0)
+ rspec-expectations (~> 2.7.0)
+ rspec-mocks (~> 2.7.0)
+ rspec-core (2.7.1)
+ rspec-expectations (2.7.0)
diff-lcs (~> 1.1.2)
- rspec-mocks (2.1.0)
- therubyracer (0.8.0)
+ rspec-mocks (2.7.0)
+ therubyracer (0.9.8)
+ libv8 (~> 3.3.10)
PLATFORMS
ruby
@@ -19,4 +21,4 @@ PLATFORMS
DEPENDENCIES
rake
rspec
- therubyracer (>= 0.8.0)
+ therubyracer (>= 0.9.8)
diff --git a/README.markdown b/README.markdown
index 8d54d79..97eb97d 100644
--- a/README.markdown
+++ b/README.markdown
@@ -1,3 +1,5 @@
+[![Build Status](https://secure.travis-ci.org/wycats/handlebars.js.png)](http://travis-ci.org/wycats/handlebars.js)
+
Handlebars.js
=============
@@ -16,21 +18,23 @@ In general, the syntax of Handlebars.js templates is a superset of Mustache temp
Once you have a template, use the Handlebars.compile method to compile the template into a function. The generated function takes a context argument, which will be used to render the template.
- var source = "<p>Hello, my name is {{name}}. I am from {{hometown}}. I have " +
- "{{kids.length}} kids:</p>" +
- "<ul>{{#kids}}<li>{{name}} is {{age}}</li>{{/kids}}</ul>";
- var template = Handlebars.compile(source);
+```js
+var source = "<p>Hello, my name is {{name}}. I am from {{hometown}}. I have " +
+ "{{kids.length}} kids:</p>" +
+ "<ul>{{#kids}}<li>{{name}} is {{age}}</li>{{/kids}}</ul>";
+var template = Handlebars.compile(source);
- var data = { "name": "Alan", "hometown": "Somewhere, TX",
- "kids": [{"name": "Jimmy", "age": "12"}, {"name": "Sally", "age": "4"}]};
- var result = template(data);
+var data = { "name": "Alan", "hometown": "Somewhere, TX",
+ "kids": [{"name": "Jimmy", "age": "12"}, {"name": "Sally", "age": "4"}]};
+var result = template(data);
- // Would render:
- // <p>Hello, my name is Alan. I am from Somewhere, TX. I have 2 kids:</p>
- // <ul>
- // <li>Jimmy is 12</li>
- // <li>Sally is 4</li>
- // </ul>
+// Would render:
+// <p>Hello, my name is Alan. I am from Somewhere, TX. I have 2 kids:</p>
+// <ul>
+// <li>Jimmy is 12</li>
+// <li>Sally is 4</li>
+// </ul>
+```
Registering Helpers
@@ -40,22 +44,23 @@ You can register helpers that Handlebars will use when evaluating your
template. Here's an example, which assumes that your objects have a URL
embedded in them, as well as the text for a link:
- Handlebars.registerHelper('link_to', function(context) {
- return "<a href='" + context.url + "'>" + context.body + "</a>";
- });
-
- var context = { posts: [{url: "/hello-world", body: "Hello World!"}] };
- var source = "<ul>{{#posts}}<li>{{{link_to this}}}</li>{{/posts}}</ul>"
+```js
+Handlebars.registerHelper('link_to', function(context) {
+ return "<a href='" + context.url + "'>" + context.body + "</a>";
+});
- var template = Handlebars.compile(source);
- template(context);
+var context = { posts: [{url: "/hello-world", body: "Hello World!"}] };
+var source = "<ul>{{#posts}}<li>{{{link_to this}}}</li>{{/posts}}</ul>"
- // Would render:
- //
- // <ul>
- // <li><a href='/hello-world'>Hello World!</a></li>
- // </ul>
+var template = Handlebars.compile(source);
+template(context);
+// Would render:
+//
+// <ul>
+// <li><a href='/hello-world'>Hello World!</a></li>
+// </ul>
+```
Escaping
--------
@@ -78,40 +83,50 @@ Handlebars.js supports an extended expression syntax that we call paths. Paths a
To display data from descendent contexts, use the `.` character. So, for example, if your data were structured like:
- var data = {"person": { "name": "Alan" }, company: {"name": "Rad, Inc." } };
+```js
+var data = {"person": { "name": "Alan" }, company: {"name": "Rad, Inc." } };
+```
you could display the person's name from the top-level context with the following expression:
- {{person.name}}
+```
+{{person.name}}
+```
You can backtrack using `../`. For example, if you've already traversed into the person object you could still display the company's name with an expression like `{{../company.name}}`, so:
- {{#person}}{{name}} - {{../company.name}}{{/person}}
+```
+{{#person}}{{name}} - {{../company.name}}{{/person}}
+```
would render:
- Alan - Rad, Inc.
+```
+Alan - Rad, Inc.
+```
### Strings
When calling a helper, you can pass paths or Strings as parameters. For
instance:
- Handlebars.registerHelper('link_to', function(title, context) {
- return "<a href='/posts" + context.id + "'>" + title + "</a>"
- });
+```js
+Handlebars.registerHelper('link_to', function(title, context) {
+ return "<a href='/posts" + context.id + "'>" + title + "</a>"
+});
- var context = { posts: [{url: "/hello-world", body: "Hello World!"}] };
- var source = '<ul>{{#posts}}<li>{{{link_to "Post" this}}}</li>{{/posts}}</ul>'
+var context = { posts: [{url: "/hello-world", body: "Hello World!"}] };
+var source = '<ul>{{#posts}}<li>{{{link_to "Post" this}}}</li>{{/posts}}</ul>'
- var template = Handlebars.compile(source);
- template(context);
+var template = Handlebars.compile(source);
+template(context);
- // Would render:
- //
- // <ul>
- // <li><a href='/hello-world'>Post!</a></li>
- // </ul>
+// Would render:
+//
+// <ul>
+// <li><a href='/hello-world'>Post!</a></li>
+// </ul>
+```
When you pass a String as a parameter to a helper, the literal String
gets passed to the helper function.
@@ -121,23 +136,25 @@ gets passed to the helper function.
Handlebars.js also adds the ability to define block helpers. Block helpers are functions that can be called from anywhere in the template. Here's an example:
- var source = "<ul>{{#people}}<li>{{{#link}}}{{name}}{{/link}}</li>{{/people}}</ul>";
- Handlebars.registerHelper('link', function(context, fn) {
- return '<a href="/people/' + this.__get__("id") + '">' + fn(this) + '</a>';
- });
- var template = Handlebars.compile(source);
-
- var data = { "people": [
- { "name": "Alan", "id": 1 },
- { "name": "Yehuda", "id": 2 }
- ]};
- template(data);
-
- // Should render:
- // <ul>
- // <li><a href="/people/1">Alan</a></li>
- // <li><a href="/people/2">Yehuda</a></li>
- // </ul>
+```js
+var source = "<ul>{{#people}}<li>{{{#link}}}{{name}}{{/link}}</li>{{/people}}</ul>";
+Handlebars.registerHelper('link', function(context, fn) {
+ return '<a href="/people/' + this.__get__("id") + '">' + fn(this) + '</a>';
+});
+var template = Handlebars.compile(source);
+
+var data = { "people": [
+ { "name": "Alan", "id": 1 },
+ { "name": "Yehuda", "id": 2 }
+ ]};
+template(data);
+
+// Should render:
+// <ul>
+// <li><a href="/people/1">Alan</a></li>
+// <li><a href="/people/2">Yehuda</a></li>
+// </ul>
+```
Whenever the block helper is called it is given two parameters, the argument that is passed to the helper, or the current context if no argument is passed and the compiled contents of the block. Inside of the block helper the value of `this` is the current context, wrapped to include a method named `__get__` that helps translate paths into values within the helpers.
@@ -148,23 +165,43 @@ Handlebars when it encounters a partial (`{{> partialName}}`). Partials
can either be String templates or compiled template functions. Here's an
example:
- var source = "<ul>{{#people}}<li>{{> link}}</li>{{/people}}</ul>";
+```js
+var source = "<ul>{{#people}}<li>{{> link}}</li>{{/people}}</ul>";
+
+Handlebars.registerPartial('link', '<a href="/people/{{id}}">{{name}}</a>')
+var template = Handlebars.compile(source);
+
+var data = { "people": [
+ { "name": "Alan", "id": 1 },
+ { "name": "Yehuda", "id": 2 }
+ ]};
+
+template(data);
+
+// Should render:
+// <ul>
+// <li><a href="/people/1">Alan</a></li>
+// <li><a href="/people/2">Yehuda</a></li>
+// </ul>
+```
+
+### Comments
+
+You can add comments to your templates with the following syntax:
- Handlebars.registerPartial('link', '<a href="/people/{{id}}">{{name}}</a>')
- var template = Handlebars.compile(source);
+```js
+{{! This is a comment }}
+```
- var data = { "people": [
- { "name": "Alan", "id": 1 },
- { "name": "Yehuda", "id": 2 }
- ]};
+You can also use real html comments if you want them to end up in the output.
- template(data);
+```html
+<div>
+ {{! This comment will not end up in the output }}
+ <!-- This comment will show up in the output -->
+</div>
+```
- // Should render:
- // <ul>
- // <li><a href="/people/1">Alan</a></li>
- // <li><a href="/people/2">Yehuda</a></li>
- // </ul>
Precompiling Templates
----------------------
@@ -233,11 +270,15 @@ changed.
Instead of:
- template(context, helpers, partials, [data])
+```js
+template(context, helpers, partials, [data])
+```
Use:
- template(context, {helpers: helpers, partials: partials, data: data})
+```js
+template(context, {helpers: helpers, partials: partials, data: data})
+```
Known Issues
------------
@@ -250,6 +291,7 @@ like to try out Handlebars.js in their browser.
* Don Park wrote an Express.js view engine adapter for Handlebars.js called [hbs](http://github.com/donpark/hbs).
* [sammy.js](http://github.com/quirkey/sammy) by Aaron Quint, a.k.a. quirkey, supports Handlebars.js as one of its template plugins.
* [SproutCore](http://www.sproutcore.com) uses Handlebars.js as its main templating engine, extending it with automatic data binding support.
+* Les Hill (@leshill) wrote a Rails Asset Pipeline gem named [handlebars_assets](http://github.com/leshill/handlebars_assets).
Helping Out
-----------
diff --git a/Rakefile b/Rakefile
index e00e0a8..c276485 100644
--- a/Rakefile
+++ b/Rakefile
@@ -1,16 +1,26 @@
require "rubygems"
require "bundler/setup"
-file "lib/handlebars/compiler/parser.js" => ["src/handlebars.yy","src/handlebars.l"] do
- if ENV['PATH'].split(':').any? {|folder| File.exists?(folder+'/jison')}
- system "jison src/handlebars.yy src/handlebars.l"
+def compile_parser
+ system "jison src/handlebars.yy src/handlebars.l"
+ if $?.success?
File.open("lib/handlebars/compiler/parser.js", "w") do |file|
file.puts File.read("handlebars.js") + ";"
end
sh "rm handlebars.js"
else
- puts "Jison is not installed. Try running `npm install jison`."
+ puts "Failed to run Jison."
+ end
+end
+
+file "lib/handlebars/compiler/parser.js" => ["src/handlebars.yy","src/handlebars.l"] do
+ if ENV['PATH'].split(':').any? {|folder| File.exists?(folder+'/jison')}
+ compile_parser
+ else
+ puts "Jison is not installed. Trying `npm install jison`."
+ sh "npm install jison -g"
+ compile_parser
end
end
diff --git a/bin/handlebars b/bin/handlebars
index 5e9dba8..2c03b0b 100755
--- a/bin/handlebars
+++ b/bin/handlebars
@@ -121,7 +121,7 @@ argv._.forEach(function(template) {
// Output the content
if (!argv.simple) {
- output.push('})()');
+ output.push('})();');
}
output = output.join('');
diff --git a/lib/handlebars/base.js b/lib/handlebars/base.js
index c51dafb..60c2fcf 100644
--- a/lib/handlebars/base.js
+++ b/lib/handlebars/base.js
@@ -1,7 +1,7 @@
// BEGIN(BROWSER)
var Handlebars = {};
-Handlebars.VERSION = "1.0.beta.2";
+Handlebars.VERSION = "1.0.beta.4";
Handlebars.helpers = {};
Handlebars.partials = {};
@@ -86,6 +86,10 @@ Handlebars.registerHelper('with', function(context, options) {
return options.fn(context);
});
+Handlebars.registerHelper('log', function(context) {
+ Handlebars.log(context);
+});
+
// END(BROWSER)
module.exports = Handlebars;
diff --git a/lib/handlebars/compiler/compiler.js b/lib/handlebars/compiler/compiler.js
index c7d051e..039a34e 100644
--- a/lib/handlebars/compiler/compiler.js
+++ b/lib/handlebars/compiler/compiler.js
@@ -104,7 +104,8 @@ Handlebars.JavaScriptCompiler = function() {};
'each': true,
'if': true,
'unless': true,
- 'with': true
+ 'with': true,
+ 'log': true
};
if (knownHelpers) {
for (var name in knownHelpers) {
@@ -335,6 +336,8 @@ Handlebars.JavaScriptCompiler = function() {};
initializeBuffer: function() {
return this.quotedString("");
},
+
+ namespace: "Handlebars",
// END PUBLIC API
compile: function(environment, options, context, asObject) {
@@ -405,8 +408,9 @@ Handlebars.JavaScriptCompiler = function() {};
var out = [];
if (!this.isChild) {
- var copies = "helpers = helpers || Handlebars.helpers;";
- if(this.environment.usePartial) { copies = copies + " partials = partials || Handlebars.partials;"; }
+ var namespace = this.namespace;
+ var copies = "helpers = helpers || " + namespace + ".helpers;";
+ if(this.environment.usePartial) { copies = copies + " partials = partials || " + namespace + ".partials;"; }
out.push(copies);
} else {
out.push('');
@@ -461,8 +465,6 @@ Handlebars.JavaScriptCompiler = function() {};
params.push("depth" + this.environment.depths.list[i]);
}
- if(params.length === 4 && !this.environment.usePartial) { params.pop(); }
-
if (asObject) {
params.push(this.source.join("\n "));
@@ -522,7 +524,7 @@ Handlebars.JavaScriptCompiler = function() {};
+ " || "
+ this.nameLookup('depth' + this.lastContext, name, 'context');
}
-
+
toPush += ';';
this.source.push(toPush);
} else {
@@ -727,7 +729,7 @@ Handlebars.JavaScriptCompiler = function() {};
};
var reservedWords = ("break case catch continue default delete do else finally " +
- "for function if in instanceof new return switch this throw " +
+ "for function if in instanceof new return switch this throw " +
"try typeof var void while with null true false").split(" ");
var compilerWords = JavaScriptCompiler.RESERVED_WORDS = {};
@@ -756,10 +758,21 @@ Handlebars.precompile = function(string, options) {
Handlebars.compile = function(string, options) {
options = options || {};
- var ast = Handlebars.parse(string);
- var environment = new Handlebars.Compiler().compile(ast, options);
- var templateSpec = new Handlebars.JavaScriptCompiler().compile(environment, options, undefined, true);
- return Handlebars.template(templateSpec);
+ var compiled;
+ function compile() {
+ var ast = Handlebars.parse(string);
+ var environment = new Handlebars.Compiler().compile(ast, options);
+ var templateSpec = new Handlebars.JavaScriptCompiler().compile(environment, options, undefined, true);
+ return Handlebars.template(templateSpec);
+ }
+
+ // Template is only compiled on first use and cached after that point.
+ return function(context, options) {
+ if (!compiled) {
+ compiled = compile();
+ }
+ return compiled.call(this, context, options);
+ };
};
// END(BROWSER)
diff --git a/package.json b/package.json
index b026238..8ce7097 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "handlebars",
"description": "Extension of the Mustache logicless template language",
- "version": "1.0.2beta",
+ "version": "1.0.4beta",
"homepage": "http://www.handlebarsjs.com/",
"keywords": "handlebars mustache template html",
"repository": {
@@ -12,8 +12,8 @@
"node": ">=0.4.7"
},
"dependencies": {
- "optimist": "~0.2",
- "uglify-js": "~1.0"
+ "optimist": "~0.3",
+ "uglify-js": "~1.2"
},
"devDependencies": {},
"main": "lib/handlebars.js",
diff --git a/spec/acceptance_spec.rb b/spec/acceptance_spec.rb
index 842722b..d896417 100644
--- a/spec/acceptance_spec.rb
+++ b/spec/acceptance_spec.rb
@@ -64,7 +64,8 @@ Module.new do
result = js_context.eval("$$RSPEC1$$ == $$RSPEC2$$")
- message ||= "#{first} did not == #{second}"
+ additional_message = "#{first.inspect} did not == #{second.inspect}"
+ message = message ? "#{message} (#{additional_message})" : additional_message
unless result
backtrace = js_backtrace(js_context)
diff --git a/spec/qunit_spec.js b/spec/qunit_spec.js
index 3e66a9b..62c76c3 100644
--- a/spec/qunit_spec.js
+++ b/spec/qunit_spec.js
@@ -576,7 +576,11 @@ test("Invert blocks work in knownHelpers only mode", function() {
equal(result, "bar", "'bar' should === '" + result);
});
-module("built-in helpers");
+var teardown;
+module("built-in helpers", {
+ setup: function(){ teardown = null; },
+ teardown: function(){ if (teardown) { teardown(); } }
+});
test("with", function() {
var string = "{{#with person}}{{first}} {{last}}{{/with}}";
@@ -608,6 +612,19 @@ test("each", function() {
"each with array argument ignores the contents when empty");
});
+test("log", function() {
+ var string = "{{log blah}}"
+ var hash = { blah: "whee" };
+
+ var logArg;
+ var originalLog = Handlebars.log;
+ Handlebars.log = function(arg){ logArg = arg; }
+ teardown = function(){ Handlebars.log = originalLog; }
+
+ shouldCompileTo(string, hash, "", "log should not display");
+ equals("whee", logArg, "should call log with 'whee'");
+});
+
test("overriding property lookup", function() {
});
diff --git a/src/handlebars.l b/src/handlebars.l
index 57ebb90..71c023e 100644
--- a/src/handlebars.l
+++ b/src/handlebars.l
@@ -27,7 +27,7 @@
<mu>"true"/[}\s] { return 'BOOLEAN'; }
<mu>"false"/[}\s] { return 'BOOLEAN'; }
<mu>[0-9]+/[}\s] { return 'INTEGER'; }
-<mu>[a-zA-Z0-9_$-]+/[=}\s/.] { return 'ID'; }
+<mu>[a-zA-Z0-9_$-]+/[=}\s\/.] { return 'ID'; }
<mu>\[.*\] { yytext = yytext.substr(1, yyleng-2); return 'ID'; }
<mu>. { return 'INVALID'; }