diff --git a/CHANGELOG.md b/CHANGELOG.md index adeb4369..f7ce2d20 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,13 @@ Changelog ========= +Unreleased +---------- + +* Add support for nested attributes on + [`sort` filter](https://mozilla.github.io/nunjucks/templating.html#sort-arr-reverse-casesens-attr); + respect `throwOnUndefined` if sort attribute is undefined. + 3.2.2 (Jul 20 2020) ------------------- diff --git a/nunjucks/src/filters.js b/nunjucks/src/filters.js index 1a99c1e1..40dce7b3 100644 --- a/nunjucks/src/filters.js +++ b/nunjucks/src/filters.js @@ -447,13 +447,21 @@ exports.sum = sum; exports.sort = r.makeMacro( ['value', 'reverse', 'case_sensitive', 'attribute'], [], - (arr, reversed, caseSens, attr) => { + function sortFilter(arr, reversed, caseSens, attr) { // Copy it let array = lib.map(arr, v => v); + let getAttribute = lib.getAttrGetter(attr); array.sort((a, b) => { - let x = (attr) ? a[attr] : a; - let y = (attr) ? b[attr] : b; + let x = (attr) ? getAttribute(a) : a; + let y = (attr) ? getAttribute(b) : b; + + if ( + this.env.opts.throwOnUndefined && + attr && (x === undefined || y === undefined) + ) { + throw new TypeError(`sort: attribute "${attr}" resolved to undefined`); + } if (!caseSens && lib.isString(x) && lib.isString(y)) { x = x.toLowerCase(); diff --git a/nunjucks/src/lib.js b/nunjucks/src/lib.js index 6e666219..376db95a 100644 --- a/nunjucks/src/lib.js +++ b/nunjucks/src/lib.js @@ -210,6 +210,8 @@ function getAttrGetter(attribute) { }; } +exports.getAttrGetter = getAttrGetter; + function groupBy(obj, val, throwOnUndefined) { const result = {}; const iterator = isFunction(val) ? val : getAttrGetter(val); diff --git a/nunjucks/src/runtime.js b/nunjucks/src/runtime.js index 7b0a4342..d57c741e 100644 --- a/nunjucks/src/runtime.js +++ b/nunjucks/src/runtime.js @@ -82,7 +82,7 @@ class Frame { } function makeMacro(argNames, kwargNames, func) { - return (...macroArgs) => { + return function macro(...macroArgs) { var argCount = numArgs(macroArgs); var args; var kwargs = getKeywordArgs(macroArgs); diff --git a/tests/filters.js b/tests/filters.js index 2c56bc7c..5ed78612 100644 --- a/tests/filters.js +++ b/tests/filters.js @@ -825,6 +825,35 @@ equal('{% for i in [ {n:3},{n:5},{n:2},{n:1},{n:4},{n:6}] | sort(attribute="n") %}{{ i.n }}{% endfor %}', '123456'); + const nestedAttributeSortTemplate = '{% for item in items | sort(attribute="meta.age") %}{{ item.name }}{% endfor %}'; + equal( + nestedAttributeSortTemplate, + { + items: [ + {name: 'james', meta: {age: 25}}, + {name: 'fred', meta: {age: 18}}, + {name: 'john', meta: {age: 19}} + ] + }, + 'fredjohnjames' + ); + + expect(function() { + render( + nestedAttributeSortTemplate, + { + items: [ + {name: 'james', meta: {age: 25}}, + {name: 'fred', meta: {age: 18}}, + {name: 'john', meta: {title: 'Developer'}} + ] + }, + { + throwOnUndefined: true + } + ); + }).to.throwError(/sort: attribute "meta\.age" resolved to undefined/); + finish(done); });