diff --git a/lib/handlebars/helpers/each.js b/lib/handlebars/helpers/each.js index 914928d6d..f2e935f50 100644 --- a/lib/handlebars/helpers/each.js +++ b/lib/handlebars/helpers/each.js @@ -19,12 +19,13 @@ export default function(instance) { data = createFrame(options.data); } - function execIteration(field, index, last) { + function execIteration(field, index, last, value) { if (data) { data.key = field; data.index = index; data.first = index === 0; data.last = !!last; + data.value = value; } ret = ret + fn(context[field], { @@ -37,7 +38,7 @@ export default function(instance) { if (isArray(context)) { for (let j = context.length; i < j; i++) { if (i in context) { - execIteration(i, i, i === context.length - 1); + execIteration(i, i, i === context.length - 1, context[i]); } } } else { @@ -49,14 +50,14 @@ export default function(instance) { // the last iteration without have to scan the object twice and create // an itermediate keys array. if (priorKey !== undefined) { - execIteration(priorKey, i - 1); + execIteration(priorKey, i - 1, false, context[priorKey]); } priorKey = key; i++; } } if (priorKey !== undefined) { - execIteration(priorKey, i - 1, true); + execIteration(priorKey, i - 1, true, context[priorKey]); } } } diff --git a/spec/builtins.js b/spec/builtins.js index dc1df0a3e..d620ddf09 100644 --- a/spec/builtins.js +++ b/spec/builtins.js @@ -115,6 +115,67 @@ describe('builtin helpers', function() { shouldCompileTo(string, {goodbyes: {}, world: 'world'}, 'cruel world!'); }); + it('each with an object and @key/@value', function() { + var string = '{{#each goodbyes}}{{@key}}. {{@value.text}}! {{/each}}cruel {{world}}!'; + + function Clazz() { + this['#1'] = {text: 'goodbye'}; + this[2] = {text: 'GOODBYE'}; + } + Clazz.prototype.foo = 'fail'; + var hash = {goodbyes: new Clazz(), world: 'world'}; + + // Object property iteration order is undefined according to ECMA spec, + // so we need to check both possible orders + // @see http://stackoverflow.com/questions/280713/elements-order-in-a-for-in-loop + var actual = compileWithPartials(string, hash); + var expected1 = '<b>#1</b>. goodbye! 2. GOODBYE! cruel world!'; + var expected2 = '2. GOODBYE! <b>#1</b>. goodbye! cruel world!'; + + equals(actual === expected1 || actual === expected2, true, 'each with object argument iterates over the contents when not empty'); + shouldCompileTo(string, {goodbyes: {}, world: 'world'}, 'cruel world!'); + }); + + it('each with an object and @key/@value that has key/value properties itself', function() { + var string = '{{#each goodbyes}}{{@key}}. [{{@value.key}}] {{@value.value}}! {{/each}}cruel {{world}}!'; + + function Clazz() { + this['#1'] = {value: 'goodbye', key: 'html'}; + this[2] = {value: 'GOODBYE', key: 'number'}; + } + Clazz.prototype.foo = 'fail'; + var hash = {goodbyes: new Clazz(), world: 'world'}; + + // Object property iteration order is undefined according to ECMA spec, + // so we need to check both possible orders + // @see http://stackoverflow.com/questions/280713/elements-order-in-a-for-in-loop + var actual = compileWithPartials(string, hash); + var expected1 = '<b>#1</b>. [html] goodbye! 2. [number] GOODBYE! cruel world!'; + var expected2 = '2. [number] GOODBYE! <b>#1</b>. [html] goodbye! cruel world!'; + equals(actual === expected1 || actual === expected2, true, 'each with object argument iterates over the contents when not empty'); + shouldCompileTo(string, {goodbyes: {}, world: 'world'}, 'cruel world!'); + }); + + it('each with @key', function() { + var string = '{{#each goodbyes}}{{@key}}. {{text}}! {{/each}}cruel {{world}}!'; + var hash = {goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}], world: 'world'}; + + var template = CompilerContext.compile(string); + var result = template(hash); + + equal(result, '0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!', 'The @key variable is used'); + }); + + it('each with @key/@value', function() { + var string = '{{#each goodbyes}}{{@key}}. {{@value.text}}! {{/each}}cruel {{world}}!'; + var hash = {goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}], world: 'world'}; + + var template = CompilerContext.compile(string); + var result = template(hash); + + equal(result, '0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!', 'The @key/@value variables are used'); + }); + it('each with @index', function() { var string = '{{#each goodbyes}}{{@index}}. {{text}}! {{/each}}cruel {{world}}!'; var hash = {goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}], world: 'world'};