Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dynamic partials and partial collections #242

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
76 changes: 76 additions & 0 deletions README.md
Expand Up @@ -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
Expand Down
24 changes: 22 additions & 2 deletions mustache.js
Expand Up @@ -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
Expand Down Expand Up @@ -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);
Expand All @@ -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);

Expand Down Expand Up @@ -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;
Expand Down
6 changes: 6 additions & 0 deletions test/_files/partial_dynamic_collection.js
@@ -0,0 +1,6 @@
({
items: [
{ content: 'Hello', partial: 'partial' },
{ content: 'Hello', partial: 'partial2' }
]
})
1 change: 1 addition & 0 deletions test/_files/partial_dynamic_collection.mustache
@@ -0,0 +1 @@
{{@items}}
1 change: 1 addition & 0 deletions test/_files/partial_dynamic_collection.partial
@@ -0,0 +1 @@
I am partial 1
1 change: 1 addition & 0 deletions test/_files/partial_dynamic_collection.partial2
@@ -0,0 +1 @@
I am partial 2
2 changes: 2 additions & 0 deletions test/_files/partial_dynamic_collection.txt
@@ -0,0 +1,2 @@
I am partial 1
I am partial 2
7 changes: 7 additions & 0 deletions test/_files/partial_dynamic_collection_implicit.js
@@ -0,0 +1,7 @@
({
title: 'Dynamic partial collection',
items: [
{ content: 'Hello', partial: 'partial' },
{ content: 'Hello', partial: 'partial2' }
]
})
7 changes: 7 additions & 0 deletions test/_files/partial_dynamic_collection_implicit.mustache
@@ -0,0 +1,7 @@
Header
{{#items}}
Before
{{>.}}
After
{{/items}}
Footer
1 change: 1 addition & 0 deletions test/_files/partial_dynamic_collection_implicit.partial
@@ -0,0 +1 @@
I am partial 1
1 change: 1 addition & 0 deletions test/_files/partial_dynamic_collection_implicit.partial2
@@ -0,0 +1 @@
I am partial 2
8 changes: 8 additions & 0 deletions 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
4 changes: 3 additions & 1 deletion test/parse_test.js
Expand Up @@ -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 = {};
Expand Down
11 changes: 8 additions & 3 deletions test/render_test.js
Expand Up @@ -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);
}
Expand Down