Add fallback hash (useful for a helper Hash).

* available globally per top-level function regardless of iteration
This commit is contained in:
wycats
2010-08-01 03:18:41 -07:00
parent 8714a581e9
commit ae98f233f1
2 changed files with 104 additions and 26 deletions
+20 -11
View File
@@ -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;
+84 -15
View File
@@ -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}}<p>{{name}}</p>{{/form}}"
var template = Handlebars.compile(string);
result = template({form: function(fn) { return "<form>" + fn(this) + "</form>" }, name: "Yehuda"});
equal(result, "<form><p>Yehuda</p></form>");
});
test("block helper passing a new context", function() {
var string = "{{#form yehuda}}<p>{{name}}</p>{{/form}}"
var template = Handlebars.compile(string);
result = template({form: function(fn) { return "<form>" + fn(this) + "</form>" }, yehuda: {name: "Yehuda"}});
equal(result, "<form><p>Yehuda</p></form>");
});
test("nested block helpers", function() {
var string = "{{#form yehuda}}<p>{{name}}</p>{{#link}}Hello{{/link}}{{/form}}"
var template = Handlebars.compile(string);
result = template({form: function(fn) { return "<form>" + fn(this) + "</form>" }, yehuda: {name: "Yehuda", link: function(fn) { return "<a href='" + this.name + "'>" + fn(this) + "</a>"; }}});
equal(result, "<form><p>Yehuda</p><a href='Yehuda'>Hello</a></form>");
});
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");