From 9df919083d4e25512fb6dc6fe1a050e63ad12c80 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Thu, 16 Jan 2014 13:59:02 -0600 Subject: [PATCH 1/4] Create track ids test stub --- spec/track-ids.js | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 spec/track-ids.js diff --git a/spec/track-ids.js b/spec/track-ids.js new file mode 100644 index 000000000..9d9847ced --- /dev/null +++ b/spec/track-ids.js @@ -0,0 +1,6 @@ +describe('track ids', function() { + it('should include argument ids'); + it('should include hash ids'); + it('should note ../ references'); + it('should update the path when using built helpers'); +}); From ace2896ec876104f5bd220744124e7370fe6b9a2 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Fri, 17 Jan 2014 19:14:36 -0600 Subject: [PATCH 2/4] Add trackIds compiler flag Allows helpers that care about where a particular field came from derive this data while maintaining backward compatibility with existing helpers. --- lib/handlebars/compiler/ast.js | 6 +- lib/handlebars/compiler/compiler.js | 4 + .../compiler/javascript-compiler.js | 39 ++++++- spec/track-ids.js | 110 +++++++++++++++++- 4 files changed, 151 insertions(+), 8 deletions(-) diff --git a/lib/handlebars/compiler/ast.js b/lib/handlebars/compiler/ast.js index 678988394..5a5f514db 100644 --- a/lib/handlebars/compiler/ast.js +++ b/lib/handlebars/compiler/ast.js @@ -149,7 +149,8 @@ var AST = { var original = "", dig = [], - depth = 0; + depth = 0, + depthString = ''; for(var i=0,l=parts.length; i Date: Fri, 17 Jan 2014 20:01:43 -0600 Subject: [PATCH 3/4] Add contextPath tracking in builtin helpers --- lib/handlebars/base.js | 34 +++++++++++++++++++++++-- lib/handlebars/utils.js | 4 +++ spec/track-ids.js | 55 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 91 insertions(+), 2 deletions(-) diff --git a/lib/handlebars/base.js b/lib/handlebars/base.js index 790228c1c..9dc49fc8e 100644 --- a/lib/handlebars/base.js +++ b/lib/handlebars/base.js @@ -70,12 +70,19 @@ function registerDefaultHelpers(instance) { return inverse(this); } else if (isArray(context)) { if(context.length > 0) { + options.ids = [options.name]; return instance.helpers.each(context, options); } else { return inverse(this); } } else { - return fn(context); + if (options.ids) { + var data = createFrame(options.data); + data.contextPath = Utils.appendContextPath(options.data.contextPath, options.name); + options = {data: data}; + } + + return fn(context, options); } }); @@ -89,6 +96,11 @@ function registerDefaultHelpers(instance) { var fn = options.fn, inverse = options.inverse; var i = 0, ret = "", data; + var contextPath; + if (options.ids) { + contextPath = Utils.appendContextPath(options.data.contextPath, options.ids[0]) + '.'; + } + if (isFunction(context)) { context = context.call(this); } if (options.data) { @@ -102,6 +114,10 @@ function registerDefaultHelpers(instance) { data.index = i; data.first = (i === 0); data.last = (i === (context.length-1)); + + if (contextPath) { + data.contextPath = contextPath + i; + } } ret = ret + fn(context[i], { data: data }); } @@ -112,6 +128,10 @@ function registerDefaultHelpers(instance) { data.key = key; data.index = i; data.first = (i === 0); + + if (contextPath) { + data.contextPath = contextPath + key; + } } ret = ret + fn(context[key], {data: data}); i++; @@ -147,7 +167,17 @@ function registerDefaultHelpers(instance) { instance.registerHelper('with', function(context, options) { if (isFunction(context)) { context = context.call(this); } - if (!Utils.isEmpty(context)) return options.fn(context); + var fn = options.fn; + + if (!Utils.isEmpty(context)) { + if (options.ids) { + var data = createFrame(options.data); + data.contextPath = Utils.appendContextPath(options.data.contextPath, options.ids[0]); + options = {data:data}; + } + + return fn(context, options); + } }); instance.registerHelper('log', function(context, options) { diff --git a/lib/handlebars/utils.js b/lib/handlebars/utils.js index 63148e460..f2f1a5470 100644 --- a/lib/handlebars/utils.js +++ b/lib/handlebars/utils.js @@ -75,3 +75,7 @@ export function isEmpty(value) { return false; } } + +export function appendContextPath(contextPath, id) { + return (contextPath ? contextPath + '.' : '') + id; +} diff --git a/spec/track-ids.js b/spec/track-ids.js index 6bd97d5b3..938f98bb5 100644 --- a/spec/track-ids.js +++ b/spec/track-ids.js @@ -105,4 +105,59 @@ describe('track ids', function() { equals(template(context, {helpers: helpers}), 'HELP ME MY BOSS 1'); }); + + describe('builtin helpers', function() { + var helpers = { + wycats: function(name, options) { + return name + ':' + options.data.contextPath + '\n'; + } + }; + + describe('#each', function() { + it('should track contextPath for arrays', function() { + var template = CompilerContext.compile('{{#each array}}{{wycats name}}{{/each}}', {trackIds: true}); + + equals(template({array: [{name: 'foo'}, {name: 'bar'}]}, {helpers: helpers}), 'foo:array.0\nbar:array.1\n'); + }); + it('should track contextPath for keys', function() { + var template = CompilerContext.compile('{{#each object}}{{wycats name}}{{/each}}', {trackIds: true}); + + equals(template({object: {foo: {name: 'foo'}, bar: {name: 'bar'}}}, {helpers: helpers}), 'foo:object.foo\nbar:object.bar\n'); + }); + it('should handle nesting', function() { + var template = CompilerContext.compile('{{#each .}}{{#each .}}{{wycats name}}{{/each}}{{/each}}', {trackIds: true}); + + equals(template({array: [{name: 'foo'}, {name: 'bar'}]}, {helpers: helpers}), 'foo:.array..0\nbar:.array..1\n'); + }); + }); + describe('#with', function() { + it('should track contextPath', function() { + var template = CompilerContext.compile('{{#with field}}{{wycats name}}{{/with}}', {trackIds: true}); + + equals(template({field: {name: 'foo'}}, {helpers: helpers}), 'foo:field\n'); + }); + it('should handle nesting', function() { + var template = CompilerContext.compile('{{#with bat}}{{#with field}}{{wycats name}}{{/with}}{{/with}}', {trackIds: true}); + + equals(template({bat: {field: {name: 'foo'}}}, {helpers: helpers}), 'foo:bat.field\n'); + }); + }); + describe('#blockHelperMissing', function() { + it('should track contextPath for arrays', function() { + var template = CompilerContext.compile('{{#field}}{{wycats name}}{{/field}}', {trackIds: true}); + + equals(template({field: [{name: 'foo'}]}, {helpers: helpers}), 'foo:field.0\n'); + }); + it('should track contextPath for keys', function() { + var template = CompilerContext.compile('{{#field}}{{wycats name}}{{/field}}', {trackIds: true}); + + equals(template({field: {name: 'foo'}}, {helpers: helpers}), 'foo:field\n'); + }); + it('should handle nesting', function() { + var template = CompilerContext.compile('{{#bat}}{{#field}}{{wycats name}}{{/field}}{{/bat}}', {trackIds: true}); + + equals(template({bat: {field: {name: 'foo'}}}, {helpers: helpers}), 'foo:bat.field\n'); + }); + }); + }); }); From 5a0bcb932de840b32910ebe65cb851b764425e66 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Fri, 17 Jan 2014 23:24:35 -0600 Subject: [PATCH 4/4] Fix handler execution in nondata/nonid mode --- lib/handlebars/base.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/handlebars/base.js b/lib/handlebars/base.js index 9dc49fc8e..2dc2fea6d 100644 --- a/lib/handlebars/base.js +++ b/lib/handlebars/base.js @@ -70,13 +70,16 @@ function registerDefaultHelpers(instance) { return inverse(this); } else if (isArray(context)) { if(context.length > 0) { - options.ids = [options.name]; + if (options.ids) { + options.ids = [options.name]; + } + return instance.helpers.each(context, options); } else { return inverse(this); } } else { - if (options.ids) { + if (options.data && options.ids) { var data = createFrame(options.data); data.contextPath = Utils.appendContextPath(options.data.contextPath, options.name); options = {data: data}; @@ -97,7 +100,7 @@ function registerDefaultHelpers(instance) { var i = 0, ret = "", data; var contextPath; - if (options.ids) { + if (options.data && options.ids) { contextPath = Utils.appendContextPath(options.data.contextPath, options.ids[0]) + '.'; } @@ -170,7 +173,7 @@ function registerDefaultHelpers(instance) { var fn = options.fn; if (!Utils.isEmpty(context)) { - if (options.ids) { + if (options.data && options.ids) { var data = createFrame(options.data); data.contextPath = Utils.appendContextPath(options.data.contextPath, options.ids[0]); options = {data:data};