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 = '
{{#foreach posts}}- {{@key}} {{title}}
{{/foreach}}
',
- hash = {posts: {first: {title: 'first'}, second: {title: 'second'}}},
- expected = '';
+ expected = '- first first
- second second
- third third
- fourth fourth
- fifth fifth
';
- shouldCompileToExpected(templateString, hash, expected);
+ shouldCompileToExpected(templateString, objectHash, expected);
});
- it('foreach with @index', function () {
+ it('@index', function () {
var templateString = '{{#foreach posts}}- {{@index}} {{title}}
{{/foreach}}
',
- hash = {posts: [{title: 'first'}, {title: 'second'}]},
- expected = '';
+ expected = '- 0 first
- 1 second
- 2 third
- 3 fourth
- 4 fifth
';
- shouldCompileToExpected(templateString, hash, expected);
+ shouldCompileToExpected(templateString, arrayHash, expected);
+ shouldCompileToExpected(templateString, objectHash, expected);
});
- it('foreach with @number', function () {
+ it('@number', function () {
var templateString = '{{#foreach posts}}- {{@number}} {{title}}
{{/foreach}}
',
- hash = {posts: [{title: 'first'}, {title: 'second'}]},
- expected = '';
+ expected = '- 1 first
- 2 second
- 3 third
- 4 fourth
- 5 fifth
';
- 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 = '{{#foreach posts limit="1"}}- {{title}}
{{else}}not this{{/foreach}}
',
+ expected = '';
+
+ shouldCompileToExpected(templateString, arrayHash, expected);
+ shouldCompileToExpected(templateString, objectHash, expected);
+ });
+
+ it('foreach with limit 3', function () {
+ var templateString = '{{#foreach posts limit="3"}}- {{title}}
{{else}}not this{{/foreach}}
',
+ 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');
+ });
});