From 472773faadc6c3250dcf08591e1637492e2e503c Mon Sep 17 00:00:00 2001 From: Jamie Hill Date: Mon, 6 Aug 2012 23:34:35 +0100 Subject: [PATCH] Dynamic partials and partial collections --- README.md | 76 +++++++++++++++++++ mustache.js | 24 +++++- test/_files/partial_dynamic_collection.js | 6 ++ .../partial_dynamic_collection.mustache | 1 + .../_files/partial_dynamic_collection.partial | 1 + .../partial_dynamic_collection.partial2 | 1 + test/_files/partial_dynamic_collection.txt | 2 + .../partial_dynamic_collection_implicit.js | 7 ++ ...rtial_dynamic_collection_implicit.mustache | 7 ++ ...artial_dynamic_collection_implicit.partial | 1 + ...rtial_dynamic_collection_implicit.partial2 | 1 + .../partial_dynamic_collection_implicit.txt | 8 ++ test/parse_test.js | 4 +- test/render_test.js | 11 ++- 14 files changed, 144 insertions(+), 6 deletions(-) create mode 100644 test/_files/partial_dynamic_collection.js create mode 100644 test/_files/partial_dynamic_collection.mustache create mode 100644 test/_files/partial_dynamic_collection.partial create mode 100644 test/_files/partial_dynamic_collection.partial2 create mode 100644 test/_files/partial_dynamic_collection.txt create mode 100644 test/_files/partial_dynamic_collection_implicit.js create mode 100644 test/_files/partial_dynamic_collection_implicit.mustache create mode 100644 test/_files/partial_dynamic_collection_implicit.partial create mode 100644 test/_files/partial_dynamic_collection_implicit.partial2 create mode 100644 test/_files/partial_dynamic_collection_implicit.txt diff --git a/README.md b/README.md index 7f9fa987d..6036f0f04 100644 --- a/README.md +++ b/README.md @@ -328,6 +328,82 @@ In mustache.js an object of partials may be passed as the third argument to `Mustache.render`. The object should be keyed by the name of the partial, and its value should be the partial text. +### Dynamic Partials + +It is quite common to want to render a partial for each item in a collection. +Sometimes this will be a common partial but often it will be dependent on each +item in the collection. + +The implicit partial notation of `{{>.}}` renders a partial who's name comes +from the `partial` key of the current item. The current item becomes the +context of the partial. + +View: + + { + "beatles": [ + { "name": "John", "partial": "dead" }, + { "name": "Paul", "partial": "alive" }, + { "name": "George", "partial": "dead" }, + { "name": "Ringo", "partial": "alive" } + ] + } + +Template: + + base.mustache + {{#beatles}} + {{>.}} + {{/beatles}} + + alive.mustache + * Keep it up {{name}} + + dead.mustache + * Rest in peace {{name}} + +Output: + + * Rest in peace John + * Keep it up Paul + * Rest in peace George + * Keep it up Ringo + +#### Partial Collections + +As this is such a common pattern, there is a shortcut that renders the named +partial for each item in a collection. The same example can therefore also be +accomplished with the following: + +View: + + { + "beatles": [ + { "name": "John", "partial": "dead" }, + { "name": "Paul", "partial": "alive" }, + { "name": "George", "partial": "dead" }, + { "name": "Ringo", "partial": "alive" } + ] + } + +Template: + + base.mustache + {{@beatles}} + + alive.mustache + * Keep it up {{name}} + + dead.mustache + * Rest in peace {{name}} + +Output: + + * Rest in peace John + * Keep it up Paul + * Rest in peace George + * Keep it up Ringo + ### Set Delimiter Set Delimiter tags start with an equals sign and change the tag delimiters from diff --git a/mustache.js b/mustache.js index 4286635f8..6cc983096 100644 --- a/mustache.js +++ b/mustache.js @@ -48,7 +48,7 @@ var Mustache; var nonSpaceRe = /\S/; var eqRe = /\s*=/; var curlyRe = /\s*\}/; - var tagRe = /#|\^|\/|>|\{|&|=|!/; + var tagRe = /@|#|\^|\/|>|\{|&|=|!/; // Workaround for https://issues.apache.org/jira/browse/COUCHDB-577 // See https://github.com/janl/mustache.js/issues/189 @@ -300,7 +300,10 @@ var Mustache; }; Renderer.prototype._partial = function (name, context) { - var fn = this._partialCache[name]; + // If the partial name is a dot, render based on the 'partial' key of + // the current context, otherwise use default behaviour. + var cache = this._partialCache, + fn = (name === '.' ? cache[context.lookup("partial")] : cache[name]); if (fn) { return fn(context); @@ -309,6 +312,20 @@ var Mustache; return ""; }; + Renderer.prototype._partialCollection = function (name, context) { + var value = context.lookup(name), buffer = ""; + + // If we have an array, render the partial defined by the 'partial' + // key for each item in the array. + if (isArray(value)) { + for (var i = 0, len = value.length; i < len; ++i) { + buffer += this._partial('.', context.push(value[i])); + } + } + + return buffer; + }; + Renderer.prototype._name = function (name, context, escape) { var value = context.lookup(name); @@ -355,6 +372,9 @@ var Mustache; case ">": body.push("r._partial(" + quote(token.value) + ", c)"); break; + case "@": + body.push("r._partialCollection(" + quote(token.value) + ", c)"); + break; case "text": body.push(quote(token.value)); break; diff --git a/test/_files/partial_dynamic_collection.js b/test/_files/partial_dynamic_collection.js new file mode 100644 index 000000000..3781ed1d5 --- /dev/null +++ b/test/_files/partial_dynamic_collection.js @@ -0,0 +1,6 @@ +({ + items: [ + { content: 'Hello', partial: 'partial' }, + { content: 'Hello', partial: 'partial2' } + ] +}) diff --git a/test/_files/partial_dynamic_collection.mustache b/test/_files/partial_dynamic_collection.mustache new file mode 100644 index 000000000..33132ad96 --- /dev/null +++ b/test/_files/partial_dynamic_collection.mustache @@ -0,0 +1 @@ +{{@items}} \ No newline at end of file diff --git a/test/_files/partial_dynamic_collection.partial b/test/_files/partial_dynamic_collection.partial new file mode 100644 index 000000000..ee4fbe3df --- /dev/null +++ b/test/_files/partial_dynamic_collection.partial @@ -0,0 +1 @@ +I am partial 1 diff --git a/test/_files/partial_dynamic_collection.partial2 b/test/_files/partial_dynamic_collection.partial2 new file mode 100644 index 000000000..49416d593 --- /dev/null +++ b/test/_files/partial_dynamic_collection.partial2 @@ -0,0 +1 @@ +I am partial 2 diff --git a/test/_files/partial_dynamic_collection.txt b/test/_files/partial_dynamic_collection.txt new file mode 100644 index 000000000..804a78c9b --- /dev/null +++ b/test/_files/partial_dynamic_collection.txt @@ -0,0 +1,2 @@ +I am partial 1 +I am partial 2 diff --git a/test/_files/partial_dynamic_collection_implicit.js b/test/_files/partial_dynamic_collection_implicit.js new file mode 100644 index 000000000..8ca342196 --- /dev/null +++ b/test/_files/partial_dynamic_collection_implicit.js @@ -0,0 +1,7 @@ +({ + title: 'Dynamic partial collection', + items: [ + { content: 'Hello', partial: 'partial' }, + { content: 'Hello', partial: 'partial2' } + ] +}) diff --git a/test/_files/partial_dynamic_collection_implicit.mustache b/test/_files/partial_dynamic_collection_implicit.mustache new file mode 100644 index 000000000..58d74f075 --- /dev/null +++ b/test/_files/partial_dynamic_collection_implicit.mustache @@ -0,0 +1,7 @@ +Header +{{#items}} +Before +{{>.}} +After +{{/items}} +Footer \ No newline at end of file diff --git a/test/_files/partial_dynamic_collection_implicit.partial b/test/_files/partial_dynamic_collection_implicit.partial new file mode 100644 index 000000000..ee4fbe3df --- /dev/null +++ b/test/_files/partial_dynamic_collection_implicit.partial @@ -0,0 +1 @@ +I am partial 1 diff --git a/test/_files/partial_dynamic_collection_implicit.partial2 b/test/_files/partial_dynamic_collection_implicit.partial2 new file mode 100644 index 000000000..49416d593 --- /dev/null +++ b/test/_files/partial_dynamic_collection_implicit.partial2 @@ -0,0 +1 @@ +I am partial 2 diff --git a/test/_files/partial_dynamic_collection_implicit.txt b/test/_files/partial_dynamic_collection_implicit.txt new file mode 100644 index 000000000..860591899 --- /dev/null +++ b/test/_files/partial_dynamic_collection_implicit.txt @@ -0,0 +1,8 @@ +Header +Before +I am partial 1 +After +Before +I am partial 2 +After +Footer \ No newline at end of file diff --git a/test/parse_test.js b/test/parse_test.js index 8586a2524..8fa8bb5f5 100644 --- a/test/parse_test.js +++ b/test/parse_test.js @@ -49,7 +49,9 @@ var expectations = { "{{#a}}{{/a}}hi{{#b}}{{/b}}\n" : [ { type: '#', value: 'a', tokens: [] }, { type: 'text', value: 'hi' }, { type: '#', value: 'b', tokens: [] }, { type: 'text', value: '\n' } ], "{{a}}\n{{b}}\n\n{{#c}}\n{{/c}}\n" : [ { type: 'name', value: 'a' }, { type: 'text', value: '\n' }, { type: 'name', value: 'b' }, { type: 'text', value: '\n\n' }, { type: '#', value: 'c', tokens: [] } ], "{{#foo}}\n {{#a}}\n {{b}}\n {{/a}}\n{{/foo}}\n" - : [ { type: "#", value: "foo", tokens: [ { type: "#", value: "a", tokens: [ { type: "text", value: " " }, { type: "name", value: "b" }, { type: "text", value: "\n" } ] } ] } ] + : [ { type: "#", value: "foo", tokens: [ { type: "#", value: "a", tokens: [ { type: "text", value: " " }, { type: "name", value: "b" }, { type: "text", value: "\n" } ] } ] } ], + "{{>.}}" : [ { type: '>', value: '.' } ], + "{{@collection}}" : [ { type: '@', value: 'collection' } ] }; var spec = {}; diff --git a/test/render_test.js b/test/render_test.js index 9638638e6..e4f9a02d0 100644 --- a/test/render_test.js +++ b/test/render_test.js @@ -43,13 +43,18 @@ testNames.forEach(function (testName) { var template = getContents(testName, "mustache"); var expect = getContents(testName, "txt"); var partial = getContents(testName, "partial"); + var partial2 = getContents(testName, "partial2"); spec["knows how to render " + testName] = function () { Mustache.clearCache(); - var output; - if (partial) { - output = Mustache.render(template, view, {partial: partial}); + var output, partials = {}; + + if (partial) { partials['partial'] = partial; } + if (partial2) { partials['partial2'] = partial2; } + + if (partial || partial2) { + output = Mustache.render(template, view, partials); } else { output = Mustache.render(template, view); }