diff --git a/core/server/helpers/foreach.js b/core/server/helpers/foreach.js index 5b86fb0811..20eb6f9140 100644 --- a/core/server/helpers/foreach.js +++ b/core/server/helpers/foreach.js @@ -3,6 +3,7 @@ // // Block helper designed for looping through posts var hbs = require('express-hbs'), + _ = require('lodash'), errors = require('../errors'), hbsUtils = hbs.handlebars.Utils, @@ -15,9 +16,10 @@ foreach = function (context, options) { var fn = options.fn, inverse = options.inverse, - i = 0, columns = options.hash.columns, - ret = '', + length = _.size(context), + limit = options.hash.limit || length, + output = '', data, contextPath; @@ -50,53 +52,32 @@ foreach = function (context, options) { } } - ret = ret + fn(context[field], { + output = output + fn(context[field], { data: data, blockParams: hbsUtils.blockParams([context[field], field], [contextPath + field, null]) }); } - function iterateArray(context) { - var j; - for (j = context.length; i < j; i += 1) { - execIteration(i, i, i === context.length - 1); - } - } + function iterateCollection(context) { + var count = 1; - function iterateObject(context) { - var priorKey, - key; - - for (key in context) { - if (context.hasOwnProperty(key)) { - // We're running the iterations one step out of sync so we can detect - // the last iteration without have to scan the object twice and create - // an itermediate keys array. - if (priorKey) { - execIteration(priorKey, i - 1); - } - priorKey = key; - i += 1; + _.each(context, function (item, key) { + if (count <= limit) { + execIteration(key, count - 1, count === length); } - } - if (priorKey) { - execIteration(priorKey, i - 1, true); - } + count += 1; + }); } if (context && typeof context === 'object') { - if (hbsUtils.isArray(context)) { - iterateArray(context); - } else { - iterateObject(context); - } + iterateCollection(context); } - if (i === 0) { - ret = inverse(this); + if (length === 0) { + output = inverse(this); } - return ret; + return output; }; module.exports = foreach; diff --git a/core/server/helpers/tags.js b/core/server/helpers/tags.js index 419530e4d4..8310b9b83a 100644 --- a/core/server/helpers/tags.js +++ b/core/server/helpers/tags.js @@ -16,28 +16,33 @@ tags = function (options) { options = options || {}; options.hash = options.hash || {}; - var autolink = options.hash && _.isString(options.hash.autolink) && options.hash.autolink === 'false' ? false : true, - separator = options.hash && _.isString(options.hash.separator) ? options.hash.separator : ', ', - prefix = options.hash && _.isString(options.hash.prefix) ? options.hash.prefix : '', - suffix = options.hash && _.isString(options.hash.suffix) ? options.hash.suffix : '', + var autolink = !(_.isString(options.hash.autolink) && options.hash.autolink === 'false'), + separator = _.isString(options.hash.separator) ? options.hash.separator : ', ', + prefix = _.isString(options.hash.prefix) ? options.hash.prefix : '', + suffix = _.isString(options.hash.suffix) ? options.hash.suffix : '', + limit = options.hash.limit ? parseInt(options.hash.limit, 10) : undefined, output = ''; function createTagList(tags) { - var tagNames = _.pluck(tags, 'name'); - if (autolink) { return _.map(tags, function (tag) { return utils.linkTemplate({ url: config.urlFor('tag', {tag: tag}), text: _.escape(tag.name) }); - }).join(separator); + }); } - return _.escape(tagNames.join(separator)); + return _(tags).pluck('name').each(_.escape); } if (this.tags && this.tags.length) { - output = prefix + createTagList(this.tags) + suffix; + output = createTagList(this.tags); + + if (limit) { + output = output.slice(0, limit); + } + + output = prefix + output.join(separator) + suffix; } return new hbs.handlebars.SafeString(output); diff --git a/core/test/unit/server_helpers/foreach_spec.js b/core/test/unit/server_helpers/foreach_spec.js index 9d9652e984..26329a77ce 100644 --- a/core/test/unit/server_helpers/foreach_spec.js +++ b/core/test/unit/server_helpers/foreach_spec.js @@ -261,6 +261,15 @@ describe('{{#foreach}} helper', function () { }); describe('(compile)', function () { + var objectHash = {posts: { + first: {title: 'first'}, second: {title: 'second'}, third: {title: 'third'}, fourth: {title: 'fourth'}, fifth: {title: 'fifth'} + }}, + arrayHash = {posts: [ + {title: 'first'}, {title: 'second'}, {title: 'third'}, {title: 'fourth'}, {title: 'fifth'} + ]}, + arrayHash2 = {goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}], world: 'world'}, + objectHash2 = {goodbyes: {foo: {text: 'goodbye'}, bar: {text: 'Goodbye'}, baz: {text: 'GOODBYE'}}, world: 'world'}; + function shouldCompileToExpected(templateString, hash, expected) { var template = handlebars.compile(templateString), result = template(hash); @@ -269,92 +278,97 @@ describe('{{#foreach}} helper', function () { } /** Many of these are copied direct from the handlebars spec */ - it('foreach with object and @key', function () { + it('object and @key', function () { var templateString = '', - hash = {posts: {first: {title: 'first'}, second: {title: 'second'}}}, - expected = ''; + expected = ''; - shouldCompileToExpected(templateString, hash, expected); + shouldCompileToExpected(templateString, objectHash, expected); }); - it('foreach with @index', function () { + it('@index', function () { var templateString = '', - hash = {posts: [{title: 'first'}, {title: 'second'}]}, - expected = ''; + expected = ''; - shouldCompileToExpected(templateString, hash, expected); + shouldCompileToExpected(templateString, arrayHash, expected); + shouldCompileToExpected(templateString, objectHash, expected); }); - it('foreach with @number', function () { + it('@number', function () { var templateString = '', - hash = {posts: [{title: 'first'}, {title: 'second'}]}, - expected = ''; + expected = ''; - shouldCompileToExpected(templateString, hash, expected); + shouldCompileToExpected(templateString, arrayHash, expected); + shouldCompileToExpected(templateString, objectHash, expected); }); - it('foreach with nested @index', function () { + it('nested @index', function () { var templateString = '{{#foreach goodbyes}}{{@index}}. {{text}}! {{#foreach ../goodbyes}}{{@index}} {{/foreach}}After {{@index}} {{/foreach}}{{@index}}cruel {{world}}!', - hash = {goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}], world: 'world'}, expected = '0. goodbye! 0 1 2 After 0 1. Goodbye! 0 1 2 After 1 2. GOODBYE! 0 1 2 After 2 cruel world!'; - shouldCompileToExpected(templateString, hash, expected); + shouldCompileToExpected(templateString, arrayHash2, expected); + shouldCompileToExpected(templateString, objectHash2, expected); }); - it('foreach with block params', function () { + it('array block params', function () { var templateString = '{{#foreach goodbyes as |value index|}}{{index}}. {{value.text}}! {{#foreach ../goodbyes as |childValue childIndex|}} {{index}} {{childIndex}}{{/foreach}} After {{index}} {{/foreach}}{{index}}cruel {{world}}!', - hash = {goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}], world: 'world'}, - expected = '0. goodbye! 0 0 0 1 After 0 1. Goodbye! 1 0 1 1 After 1 cruel world!'; + expected = '0. goodbye! 0 0 0 1 0 2 After 0 1. Goodbye! 1 0 1 1 1 2 After 1 2. GOODBYE! 2 0 2 1 2 2 After 2 cruel world!'; - shouldCompileToExpected(templateString, hash, expected); + shouldCompileToExpected(templateString, arrayHash2, expected); }); - it('foreach with @first', function () { + it('object block params', function () { + var templateString = '{{#foreach goodbyes as |value index|}}{{index}}. {{value.text}}! {{#foreach ../goodbyes as |childValue childIndex|}} {{index}} {{childIndex}}{{/foreach}} After {{index}} {{/foreach}}{{index}}cruel {{world}}!', + expected = 'foo. goodbye! foo foo foo bar foo baz After foo bar. Goodbye! bar foo bar bar bar baz After bar baz. GOODBYE! baz foo baz bar baz baz After baz cruel world!'; + + shouldCompileToExpected(templateString, objectHash2, expected); + }); + + it('@first', function () { var templateString = '{{#foreach goodbyes}}{{#if @first}}{{text}}! {{/if}}{{/foreach}}cruel {{world}}!', - hash = {goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}], world: 'world'}, expected = 'goodbye! cruel world!'; - shouldCompileToExpected(templateString, hash, expected); + shouldCompileToExpected(templateString, arrayHash2, expected); + shouldCompileToExpected(templateString, objectHash2, expected); }); - it('foreach with nested @first', function () { + it('nested @first', function () { var templateString = '{{#foreach goodbyes}}({{#if @first}}{{text}}! {{/if}}{{#foreach ../goodbyes}}{{#if @first}}{{text}}!{{/if}}{{/foreach}}{{#if @first}} {{text}}!{{/if}}) {{/foreach}}cruel {{world}}!', - hash = {goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}], world: 'world'}, expected = '(goodbye! goodbye! goodbye!) (goodbye!) (goodbye!) cruel world!'; - shouldCompileToExpected(templateString, hash, expected); + shouldCompileToExpected(templateString, arrayHash2, expected); + shouldCompileToExpected(templateString, objectHash2, expected); }); - it('foreach object with @first', function () { - var templateString = '{{#foreach goodbyes}}{{#if @first}}{{text}}! {{/if}}{{/foreach}}cruel {{world}}!', - hash = {goodbyes: {foo: {text: 'goodbye'}, bar: {text: 'Goodbye'}}, world: 'world'}, - expected = 'goodbye! cruel world!'; - - shouldCompileToExpected(templateString, hash, expected); - }); - - it('foreach with @last', function () { + it('@last', function () { var templateString = '{{#foreach goodbyes}}{{#if @last}}{{text}}! {{/if}}{{/foreach}}cruel {{world}}!', - hash = {goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}], world: 'world'}, expected = 'GOODBYE! cruel world!'; - shouldCompileToExpected(templateString, hash, expected); + shouldCompileToExpected(templateString, arrayHash2, expected); + shouldCompileToExpected(templateString, objectHash2, expected); }); - it('foreach object with @last', function () { - var templateString = '{{#foreach goodbyes}}{{#if @last}}{{text}}! {{/if}}{{/foreach}}cruel {{world}}!', - hash = {goodbyes: {foo: {text: 'goodbye'}, bar: {text: 'Goodbye'}}, world: 'world'}, - expected = 'Goodbye! cruel world!'; - - shouldCompileToExpected(templateString, hash, expected); - }); - - it('foreach with nested @last', function () { + it('nested @last', function () { var templateString = '{{#foreach goodbyes}}({{#if @last}}{{text}}! {{/if}}{{#foreach ../goodbyes}}{{#if @last}}{{text}}!{{/if}}{{/foreach}}{{#if @last}} {{text}}!{{/if}}) {{/foreach}}cruel {{world}}!', - hash = {goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}], world: 'world'}, expected = '(GOODBYE!) (GOODBYE!) (GOODBYE! GOODBYE! GOODBYE!) cruel world!'; - shouldCompileToExpected(templateString, hash, expected); + shouldCompileToExpected(templateString, arrayHash2, expected); + shouldCompileToExpected(templateString, objectHash2, expected); + }); + + it('foreach with limit 1', function () { + var templateString = '', + expected = ''; + + shouldCompileToExpected(templateString, arrayHash, expected); + shouldCompileToExpected(templateString, objectHash, expected); + }); + + it('foreach with limit 3', function () { + var templateString = '', + expected = ''; + + shouldCompileToExpected(templateString, arrayHash, expected); + shouldCompileToExpected(templateString, objectHash, expected); }); }); }); diff --git a/core/test/unit/server_helpers/tags_spec.js b/core/test/unit/server_helpers/tags_spec.js index 3cb183fcf4..d3b13ce8f6 100644 --- a/core/test/unit/server_helpers/tags_spec.js +++ b/core/test/unit/server_helpers/tags_spec.js @@ -109,4 +109,15 @@ describe('{{tags}} helper', function () { String(rendered).should.equal('foo, bar'); }); + + it('can limit no. tags output to 1', function () { + var tags = [{name: 'foo', slug: 'foo-bar'}, {name: 'bar', slug: 'bar'}], + rendered = helpers.tags.call( + {tags: tags}, + {hash: {limit: '1'}} + ); + should.exist(rendered); + + String(rendered).should.equal('foo'); + }); });