From c1bcd72eb141931e04caec688c6e8a7f5f2ec6e3 Mon Sep 17 00:00:00 2001 From: Alan Johnson Date: Fri, 24 Sep 2010 22:26:10 -0400 Subject: [PATCH] Reworked the way that expression work a good bit. They're a lot less error prone now. --- lib/handlebars.js | 60 +++++++++++++++++++++++++++------------------- test/handlebars.js | 5 ++++ 2 files changed, 40 insertions(+), 25 deletions(-) diff --git a/lib/handlebars.js b/lib/handlebars.js index e50677ea..f2d87ae3 100644 --- a/lib/handlebars.js +++ b/lib/handlebars.js @@ -78,23 +78,28 @@ var Handlebars = { return compiled; }, + evalExpression: function(path, context, stack) { + 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 && context !== undefined; i++) { + context = context[parts[i]]; + } + + return context; + }, + buildContext: function(context, stack) { var ContextWrapper = function(stack) { this.__stack__ = stack.slice(0); this.__get__ = function(path) { - var context = this; - var parsedPath = Handlebars.parsePath(path); - var depth = parsedPath[0]; - var parts = parsedPath[1]; - if (depth > 0) { - context = this.__stack__[this.__stack__.length - depth]; - } - - for (var i = 0; i < parts.length; i++) { - context = context[parts[i]]; - } - - return context; + return Handlebars.evalExpression(path, this, this.__stack__); }; }; @@ -102,13 +107,18 @@ var Handlebars = { return new ContextWrapper(stack); }, - // Returns a two element array containing the numbers of contexts to back up the stack and + // spot to memoize paths to speed up loops and subsequent parses + pathPatterns: {}, + + // returns a two element array containing the numbers of contexts to back up the stack and // the properties to dig into on the current context // - // For example, if the path is "../../alan/name", the result will be [2, ["alan", "name"]]. + // for example, if the path is "../../alan/name", the result will be [2, ["alan", "name"]]. parsePath: function(path) { if (path == null) { return [0, []]; + } else if (Handlebars.pathPatterns[path] != null) { + return Handlebars.pathPatterns[path]; } var parts = path.split("/"); @@ -134,8 +144,10 @@ var Handlebars = { dig.push(parts[i]); } } - - return [depth, dig]; + + var ret = [depth, dig]; + Handlebars.pathPatterns[path] = ret; + return ret; }, isEmpty: function(value) { @@ -180,6 +192,7 @@ var Handlebars = { handleExpression: function(lookup, context, arg, isEscaped) { var out = ""; + if (Handlebars.isFunction(lookup)) { out = out + Handlebars.filterOutput(lookup.call(context, arg), isEscaped); } else if(!Handlebars.isEmpty(lookup)) { @@ -330,15 +343,12 @@ Handlebars.Compiler.prototype = { var depth = parsed[0]; var parts = parsed[1]; - var paramExpr = ""; - for(var i = 0; i < parts.length; i++) { - paramExpr += "['" + parts[i] + "']"; - } - - if (depth > 0) { - return "( stack[stack.length - " + depth + "]" + paramExpr + ")"; + if (depth > 0 || parts.length > 1) { + return "(Handlebars.evalExpression('" + param + "', context, stack))"; + } else if (parts.length == 1) { + return "(context['" + parts[0] + "'] || fallback['" + parts[0] + "'])"; } else { - return "( context" + paramExpr + " != null ? context" + paramExpr + " : fallback" + paramExpr + " )"; + return "(context || fallback)"; } }, diff --git a/test/handlebars.js b/test/handlebars.js index 3229625a..0fc09c03 100644 --- a/test/handlebars.js +++ b/test/handlebars.js @@ -103,6 +103,11 @@ test("bad idea nested paths", function() { shouldCompileTo(string, hash, "world world world ", "Same context (.) is ignored in paths"); }); +test("complex but empty paths", function() { + shouldCompileTo("{{person/name}}", {person: {name: null}}, ""); + shouldCompileTo("{{person/name}}", {person: {}}, ""); +}); + test("this keyword in paths", function() { var string = "{{#goodbyes}}{{this}}{{/goodbyes}}"; var hash = {goodbyes: ["goodbye", "Goodbye", "GOODBYE"]};