From ae98f233f134ba6ae8ffe985b692a5a034dab5f8 Mon Sep 17 00:00:00 2001 From: wycats Date: Sun, 1 Aug 2010 03:18:41 -0700 Subject: [PATCH] Add fallback hash (useful for a helper Hash). * available globally per top-level function regardless of iteration --- lib/handlebars.js | 31 +++++++++------ test/handlebars.js | 99 +++++++++++++++++++++++++++++++++++++++------- 2 files changed, 104 insertions(+), 26 deletions(-) diff --git a/lib/handlebars.js b/lib/handlebars.js index f38e4139..32b11250 100644 --- a/lib/handlebars.js +++ b/lib/handlebars.js @@ -2,7 +2,7 @@ Handlebars = { compile: function(string) { var compiler = new Handlebars.Compiler(string); var result = compiler.compile(); - return new Function("context", result); + return new Function("context", "fallback", "fallback = fallback || {}; " + result); }, isFunction: function(fn) { @@ -20,7 +20,7 @@ Handlebars.Compiler = function(string) { this.comment = false; } -Handlebars.catchAll = function(object, fn) { +Handlebars.helperMissing = function(object, fn) { var ret = ""; if(object === true) { @@ -74,17 +74,21 @@ Handlebars.Compiler.prototype = { if(this.text) { this.fn = this.fn + "out = out + '" + this.text + "'; "; this.fn = this.fn + this.newlines; - this.newlines = "" - this.text = "" + this.newlines = ""; + this.text = ""; } }, addExpression: function(mustache) { - this.fn = this.fn + "out = out + context['" + mustache + "']; "; + this.fn += "out = out + " + this.lookupFor(mustache) + "; "; + }, + + lookupFor: function(param) { + return param ? "( context['" + param + "'] || fallback['" + param + "'] )" : "context"; }, parseMustache: function() { - var chr, mustache; + var chr, part, mustache, param; var next = this.peek(); @@ -101,7 +105,10 @@ Handlebars.Compiler.prototype = { while(chr = this.getChar()) { if(this.mustache && chr === "}" && this.peek() === "}") { - mustache = this.mustache.trim(); + var parts = this.mustache.trim().split(/\s+/); + mustache = parts[0]; + param = this.lookupFor(parts[1]); + this.mustache = false; this.getChar(); if(this.comment) { @@ -110,13 +117,15 @@ Handlebars.Compiler.prototype = { } else if(this.openBlock) { var compiler = new Handlebars.Compiler(this.string.slice(this.pointer + 1, -1)); - // compile with a custom EOF instruction + // sub-compile with a custom EOF instruction var result = compiler.compile(function(compiler) { if(compiler.peek(mustache.length + 5) === "{{/" + mustache + "}}") { compiler.getChar(mustache.length + 5); return true; } }); + + // move the pointer forward the amount of characters handled by the sub-compiler this.pointer += compiler.pointer + 1; // each function made internally needs a unique IDs. These are locals, so they @@ -124,9 +133,9 @@ Handlebars.Compiler.prototype = { var fnId = "fn" + this.pointer.toString(); this.fn += "var " + fnId + " = function(context) {" + result + "}; "; - this.fn += "lookup = context['" + mustache + "']; "; - this.fn += "if(Handlebars.isFunction(lookup)) out = out + " + fnId + "(lookup); " - this.fn += "else if(typeof lookup !== 'undefined') out = out + Handlebars.catchAll(lookup, " + fnId + "); " + this.fn += "lookup = " + this.lookupFor(mustache) + "; "; + this.fn += "if(Handlebars.isFunction(lookup)) out = out + lookup.call(" + param + ", " + fnId + "); " + this.fn += "else if(typeof lookup !== 'undefined') out = out + Handlebars.helperMissing.call(" + param + ", lookup, " + fnId + "); " this.openBlock = false; return; diff --git a/test/handlebars.js b/test/handlebars.js index 5ad9267d..1ce2dc4a 100644 --- a/test/handlebars.js +++ b/test/handlebars.js @@ -1,28 +1,97 @@ module("basic context"); -test("compiling with a basic context", function() { - var string = "Goodbye\n{{cruel}}\n{{world}}!"; +var shouldCompileTo = function(string, hash, result, message) { var template = Handlebars.compile(string); + var params = toString.call(hash) === "[object Array]" ? hash : [hash, undefined]; + equal(template.apply(this, params), result, message); +} - result = template({cruel: "cruel", world: "world"}); - equal(result, "Goodbye\ncruel\nworld!", "it works if all the required keys are provided"); +test("compiling with a basic context", function() { + shouldCompileTo("Goodbye\n{{cruel}}\n{{world}}!", {cruel: "cruel", world: "world"}, "Goodbye\ncruel\nworld!", + "It works if all the required keys are provided"); }); test("comments", function() { - var string = "{{! Goodbye}}Goodbye\n{{cruel}}\n{{world}}!"; - var template = Handlebars.compile(string); - - result = template({cruel: "cruel", world: "world"}); - equal("Goodbye\ncruel\nworld!", result, "it works if all the required keys are provided"); + shouldCompileTo("{{! Goodbye}}Goodbye\n{{cruel}}\n{{world}}!", + {cruel: "cruel", world: "world"}, "Goodbye\ncruel\nworld!", + "comments are ignored"); }); test("boolean", function() { - var string = "{{#goodbye}}GOODBYE {{/goodbye}}cruel {{world}}!" + var string = "{{#goodbye}}GOODBYE {{/goodbye}}cruel {{world}}!"; + shouldCompileTo(string, {goodbye: true, world: "world"}, "GOODBYE cruel world!", + "booleans show the contents when true"); + + shouldCompileTo(string, {goodbye: false, world: "world"}, "cruel world!", + "booleans do not show the contents when false"); +}); + +module("blocks"); + +test("array", function() { + var string = "{{#goodbyes}}{{text}}! {{/goodbyes}}cruel {{world}}!" + var hash = {goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}], world: "world"}; + shouldCompileTo(string, hash, "goodbye! Goodbye! GOODBYE! cruel world!", + "Arrays iterate over the contents when not empty"); + + shouldCompileTo(string, {goodbyes: [], world: "world"}, "cruel world!", + "Arrays ignore the contents when empty"); +}); + +test("nested iteration", function() { + +}); + +test("block helper", function() { + var string = "{{#goodbyes}}{{text}}! {{/goodbyes}}cruel {{world}}!"; var template = Handlebars.compile(string); - result = template({goodbye: true, world: "world"}); - equal("GOODBYE cruel world!", result, "booleans work when true"); - - result = template({goodbye: false, world: "world"}); - equal("cruel world!", result, "booleans work when true"); + result = template({goodbyes: function(fn) { return fn({text: "GOODBYE"}); }, world: "world"}); + equal(result, "GOODBYE! cruel world!"); }); + +test("block helper staying in the same context", function() { + var string = "{{#form}}

{{name}}

{{/form}}" + var template = Handlebars.compile(string); + + result = template({form: function(fn) { return "
" + fn(this) + "
" }, name: "Yehuda"}); + equal(result, "

Yehuda

"); +}); + +test("block helper passing a new context", function() { + var string = "{{#form yehuda}}

{{name}}

{{/form}}" + var template = Handlebars.compile(string); + + result = template({form: function(fn) { return "
" + fn(this) + "
" }, yehuda: {name: "Yehuda"}}); + equal(result, "

Yehuda

"); +}); + +test("nested block helpers", function() { + var string = "{{#form yehuda}}

{{name}}

{{#link}}Hello{{/link}}{{/form}}" + var template = Handlebars.compile(string); + + result = template({form: function(fn) { return "
" + fn(this) + "
" }, yehuda: {name: "Yehuda", link: function(fn) { return "" + fn(this) + ""; }}}); + equal(result, "

Yehuda

Hello
"); +}); + +module("fallback hash"); + +test("providing a fallback hash", function() { + shouldCompileTo("Goodbye {{cruel}} {{world}}!", [{cruel: "cruel"}, {world: "world"}], "Goodbye cruel world!", + "Fallback hash is available"); + + shouldCompileTo("Goodbye {{#iter}}{{cruel}} {{world}}{{/iter}}!", [{iter: [{cruel: "cruel"}]}, {world: "world"}], + "Goodbye cruel world!", "Fallback hash is available inside other blocks"); +}); + +test("in cases of conflict, the explicit hash wins", function() { + +}); + +test("the fallback hash is available is nested contexts", function() { + +}); + +module("partials"); + +