diff --git a/lib/handlebars/helpers/each.js b/lib/handlebars/helpers/each.js index c1fa20f39..201e593d0 100644 --- a/lib/handlebars/helpers/each.js +++ b/lib/handlebars/helpers/each.js @@ -3,7 +3,8 @@ import { blockParams, createFrame, isArray, - isFunction + isFunction, + isMap } from '../utils'; import Exception from '../exception'; @@ -33,7 +34,7 @@ export default function(instance) { data = createFrame(options.data); } - function execIteration(field, index, last) { + function execIteration(field, value, index, last) { if (data) { data.key = field; data.index = index; @@ -47,12 +48,9 @@ export default function(instance) { ret = ret + - fn(context[field], { + fn(value, { data: data, - blockParams: blockParams( - [context[field], field], - [contextPath + field, null] - ) + blockParams: blockParams([value, field], [contextPath + field, null]) }); } @@ -60,9 +58,15 @@ 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, context[i], i, i === context.length - 1); } } + } else if (isMap(context)) { + const j = context.size - 1; + for (const [key, value] of context) { + execIteration(key, value, i, i === j); + i++; + } } else if (global.Symbol && context[global.Symbol.iterator]) { const newContext = []; const iterator = context[global.Symbol.iterator](); @@ -71,7 +75,7 @@ export default function(instance) { } context = newContext; for (let j = context.length; i < j; i++) { - execIteration(i, i, i === context.length - 1); + execIteration(i, context[i], i, i === context.length - 1); } } else { let priorKey; @@ -81,13 +85,13 @@ 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, context[priorKey], i - 1); } priorKey = key; i++; }); if (priorKey !== undefined) { - execIteration(priorKey, i - 1, true); + execIteration(priorKey, context[priorKey], i - 1, true); } } } diff --git a/lib/handlebars/runtime.js b/lib/handlebars/runtime.js index 67a500d79..142af9c78 100644 --- a/lib/handlebars/runtime.js +++ b/lib/handlebars/runtime.js @@ -127,6 +127,9 @@ export function template(templateSpec, env) { return obj[name]; }, lookupProperty: function(parent, propertyName) { + if (Utils.isMap(parent)) { + return parent.get(propertyName); + } let result = parent[propertyName]; if (result == null) { return result; diff --git a/lib/handlebars/utils.js b/lib/handlebars/utils.js index 6fa43b23c..3b5a9e416 100644 --- a/lib/handlebars/utils.js +++ b/lib/handlebars/utils.js @@ -57,6 +57,12 @@ export const isArray = : false; }; +export const isMap = function(value) { + return value && typeof value === 'object' + ? toString.call(value) === '[object Map]' + : false; +}; + // Older IE versions do not directly support indexOf so we must implement our own, sadly. export function indexOf(array, value) { for (let i = 0, len = array.length; i < len; i++) { diff --git a/spec/basic.js b/spec/basic.js index 65d228729..c5900a910 100644 --- a/spec/basic.js +++ b/spec/basic.js @@ -394,6 +394,14 @@ describe('basic context', function() { ); }); + it('nested paths with Map', function() { + var map = new Map(); + map.set('expression', 'beautiful'); + expectTemplate('Goodbye {{foo/expression}} world!') + .withInput({ foo: map }) + .toCompileTo('Goodbye beautiful world!'); + }); + it('nested paths with empty string value', function() { shouldCompileTo( 'Goodbye {{alan/expression}} world!', diff --git a/spec/builtins.js b/spec/builtins.js index c74092e1b..d8b961edc 100644 --- a/spec/builtins.js +++ b/spec/builtins.js @@ -551,6 +551,27 @@ describe('builtin helpers', function() { ); }); } + + it('each on Map and @key', function() { + var goodbyes = new Map(); + goodbyes.set('first', 'goodbye'); + goodbyes.set('second', 'Goodbye'); + goodbyes.set('last', 'GOODBYE'); + var goodbyesEmpty = new Map(); + var string = + '{{#each goodbyes}}{{@key}}. {{.}}! {{/each}}cruel {{world}}!'; + var hash = { goodbyes: goodbyes, world: 'world' }; + + expectTemplate(string) + .withInput(hash) + .toCompileTo( + 'first. goodbye! second. Goodbye! last. GOODBYE! cruel world!' + ); + + expectTemplate(string) + .withInput({ goodbyes: goodbyesEmpty, world: 'world' }) + .toCompileTo('cruel world!'); + }); }); describe('#log', function() {