diff options
-rw-r--r-- | Gemfile | 2 | ||||
-rw-r--r-- | Gemfile.lock | 24 | ||||
-rw-r--r-- | README.markdown | 190 | ||||
-rw-r--r-- | Rakefile | 18 | ||||
-rwxr-xr-x | bin/handlebars | 2 | ||||
-rw-r--r-- | lib/handlebars/base.js | 6 | ||||
-rw-r--r-- | lib/handlebars/compiler/compiler.js | 35 | ||||
-rw-r--r-- | package.json | 6 | ||||
-rw-r--r-- | spec/acceptance_spec.rb | 3 | ||||
-rw-r--r-- | spec/qunit_spec.js | 19 | ||||
-rw-r--r-- | src/handlebars.l | 2 |
11 files changed, 198 insertions, 109 deletions
@@ -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 @@ +[](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 ----------- @@ -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'; } |