diff --git a/lib/handlebars.js b/lib/handlebars.js index be38e19c..7486763f 100644 --- a/lib/handlebars.js +++ b/lib/handlebars.js @@ -5,7 +5,7 @@ var Handlebars = { if (Handlebars.compilerCache[string] == null) { var fnBody = Handlebars.compileFunctionBody(string); var fn = new Function("context", "fallback", "stack", "Handlebars", fnBody); - Handlebars.compilerCache[string] = + Handlebars.compilerCache[string] = function(context, fallback, stack) { return fn(context, fallback, stack, Handlebars); }; } @@ -16,12 +16,12 @@ var Handlebars = { var fnBody = Handlebars.compileFunctionBody(string); return "function(context, fallback, stack) { " + fnBody + "}"; }, - + compileFunctionBody: function(string) { var compiler = new Handlebars.Compiler(string); compiler.compile(); - return "fallback = fallback || {}; var stack = stack || [];" + compiler.fn; + return "var stack = stack || [];" + compiler.fn; }, isFunction: function(fn) { @@ -29,7 +29,7 @@ var Handlebars = { }, trim: function(str) { - return str.replace(/^\s+|\s+$/g, ''); + return str.replace(/^\s+|\s+$/g, ''); }, escapeText: function(string) { @@ -69,42 +69,20 @@ var Handlebars = { } else { compiled = Handlebars.compile(partial); } - + return compiled; }, - evalExpression: function(path, context, stack, fallback) { - fallback = fallback || {}; - - var parsedPath = Handlebars.parsePath(path); - var depth = parsedPath[0]; - var parts = parsedPath[1]; - if (depth > stack.length) { - context = null; - } else if (depth > 0) { - context = stack[stack.length - depth]; - } - - for (var i = 0; i < parts.length && typeof context !== "undefined" && context != null ; i++) { - context = context[parts[i]]; - } - - if (parts.length == 1 && typeof context === "undefined") { - return fallback[parts[0]]; - } - - return context; - }, - buildContext: function(context, stack) { - var ContextWrapper = function(stack) { + var ContextWrapper = function(stack) { + this.__context__ = context; this.__stack__ = stack.slice(0); this.__get__ = function(path) { - return Handlebars.evalExpression(path, this, this.__stack__); + return this.__context__.evalExpression(path, this.__stack__).data; }; }; - ContextWrapper.prototype = context; + ContextWrapper.prototype = context.data; return new ContextWrapper(stack); }, @@ -130,7 +108,7 @@ var Handlebars = { switch(parts[i]) { case "..": if (readDepth) { - throw new Handlebars.Exception("Cannot jump out of context after moving into a context."); + throw new Handlebars.Exception("Cannot jump out of context after moving into a context."); } else { depth += 1; } @@ -145,7 +123,7 @@ var Handlebars = { dig.push(parts[i]); } } - + var ret = [depth, dig]; Handlebars.pathPatterns["hbs" + path] = ret; return ret; @@ -167,66 +145,117 @@ var Handlebars = { // Escapes output and converts empty values to empty strings filterOutput: function(value, escape) { - + if (Handlebars.isEmpty(value)) { return ""; } else if (escape) { - return Handlebars.escapeExpression(value); + return Handlebars.escapeExpression(value); } else { return value; } }, handleBlock: function(lookup, context, arg, fn, notFn) { - var out = ""; - if (Handlebars.isFunction(lookup)) { - out = out + lookup.call(context, arg, fn); - if (notFn != null && Handlebars.isFunction(lookup.not)) { - out = out + lookup.not.call(context, arg, notFn); - } + var out = "", args; + originalArgs = arg.length ? arg : [null] + + if (Handlebars.isFunction(lookup.data)) { + args = originalArgs.concat(fn); + out = out + lookup.data.apply(context, args); + + if (notFn != null && Handlebars.isFunction(lookup.data.not)) { + args = originalArgs.concat(notFn); + out = out + lookup.data.not.apply(context, args); + } } else { - if (!Handlebars.isEmpty(lookup)) { - out = out + Handlebars.helperMissing.call(arg, lookup, fn); + if (!Handlebars.isEmpty(lookup.data)) { + // TODO: which case is this, and what does it mean for multiple args + out = out + Handlebars.helperMissing.call(arg[0], lookup, fn); } - + if (notFn != null) { - out = out + Handlebars.helperMissing.not.call(arg, lookup, notFn); + out = out + Handlebars.helperMissing.not.call(arg[0], lookup, notFn); } } - + return out; }, - handleExpression: function(lookup, context, arg, isEscaped) { + // lookup: The evaluated mustache, either function or value. A Handlebars.Context + // context: The wrapped context object + // args: The evaluated arguments to the mustache + // isEscaped: Is it escaped? + handleExpression: function(lookup, context, args, isEscaped) { var out = ""; - if (Handlebars.isFunction(lookup)) { - out = out + Handlebars.filterOutput(lookup.call(context, arg), isEscaped); - } else if(!Handlebars.isEmpty(lookup)) { - out = out + Handlebars.filterOutput(lookup, isEscaped); + if (Handlebars.isFunction(lookup.data)) { + out = out + Handlebars.filterOutput(lookup.data.apply(context, args), isEscaped); + } else if(!Handlebars.isEmpty(lookup.data)) { + out = out + Handlebars.filterOutput(lookup.data, isEscaped); } return out; }, - + + // lookup: The evaluated mustache, either function or value. A Handlebars.Context + // context: The context where this item occurred + // fn: The compiled body of this inverted section handleInvertedSection: function(lookup, context, fn) { var out = ""; - if(Handlebars.isFunction(lookup) && Handlebars.isEmpty(lookup())) { + if(Handlebars.isFunction(lookup.data) && Handlebars.isEmpty(lookup.data())) { out = out + fn(context); - } else if (Handlebars.isEmpty(lookup)) { + } else if (Handlebars.isEmpty(lookup.data)) { out = out + fn(context); } return out; } } +Handlebars.Context = function(context, fallback, path) { + if (context instanceof Handlebars.Context) { + this.data = context.data; + this.fallback = context.fallback; + this.path = context.path; + } else { + this.data = context; + this.fallback = fallback || {}; + this.path = path || ""; + } +}; +Handlebars.Context.prototype = { + // path: The path to evaluate + // stack: The stack to work with + evalExpression: function(path, stack) { + var newContext = new Handlebars.Context(this); + + var parsedPath = Handlebars.parsePath(path); + var depth = parsedPath[0]; + var parts = parsedPath[1]; + if (depth > stack.length) { + newContext.data = null; + } else if (depth > 0) { + newContext = new Handlebars.Context(stack[stack.length - depth]); + } + + for (var i = 0, j = parts.length; i < j && typeof newContext.data !== "undefined" && newContext.data !== null ; i++) { + newContext.data = newContext.data[parts[i]]; + } + + if (parts.length == 1 && typeof newContext.data === "undefined") { + newContext.data = newContext.fallback[parts[0]]; + } + + return newContext; + } +}; + Handlebars.Compiler = function(string) { this.string = string; this.pointer = -1; this.mustache = false; this.text = ""; - this.fn = "var out = ''; var lookup; "; + this.fn = "context = new Handlebars.Context(context, fallback); var out = ''; var lookup; "; this.newlines = ""; this.comment = false; this.escaped = true; @@ -251,17 +280,17 @@ Handlebars.SafeString.prototype.toString = function() { Handlebars.helperMissing = function(object, fn) { var ret = ""; - if(object === true) { + if(object.data === true) { return fn(this); - } else if(object === false) { + } else if(object.data === false) { return ""; - } else if(Object.prototype.toString.call(object) === "[object Array]") { - for(var i=0, j=object.length; i 0) { var out = "