diff --git a/README.md b/README.md
index 95c92788b..93ba4a20b 100644
--- a/README.md
+++ b/README.md
@@ -43,7 +43,7 @@ There are several types of tags available in mustache.js.
### Variables
-The most basic tag type is a simple variable. A `{{name}}` tag renders the value of the `name` key in the current context. If there is no such key, nothing is rendered.
+The most basic tag type is a simple variable. A `{{name}}` tag renders the value of the `name` key in the context of its containing object. If there is no such key, nothing is rendered.
All variables are HTML-escaped by default. If you want to render unescaped HTML, use the triple mustache: `{{{name}}}`. You can also use `&` to unescape a variable.
@@ -170,7 +170,7 @@ Output:
* Porthos
* D'Artagnan
-If the value of a section variable is a function, it will be called in the context of the current item in the list on each iteration.
+If the value of a section variable is a function, it will be called in the context of the current item in the list on each iteration. The return value of the function will be rendered in the context of the current item.
View:
@@ -182,7 +182,9 @@ View:
{ "firstName": "Ringo", "lastName": "Starr" }
],
"name": function () {
- return this.firstName + " " + this.lastName;
+ return function () {
+ return "{{firstName}} {{lastName}}";
+ };
}
}
@@ -201,7 +203,7 @@ Output:
#### Functions
-If the value of a section key is a function, it is called with the section's literal block of text, un-rendered, as its first argument. The second argument is a special rendering function that uses the current view as its view argument. It is called in the context of the current view object.
+If the value of a section key is a function, it is called with the section's literal block of text, un-rendered, as its first argument. The second argument is a special rendering function that uses the current view as its view argument. It is called in the context of the current view object. The return value of the function will be rendered in the context of the current item.
View:
diff --git a/mustache.js b/mustache.js
index af48fd1c5..43697dc6b 100644
--- a/mustache.js
+++ b/mustache.js
@@ -141,35 +141,43 @@
};
Context.prototype.lookup = function (name) {
- var value = this._cache[name];
+ var value = this._cache[name], view = this.view;
if (!value) {
if (name == '.') {
- value = this.view;
+ value = view;
} else {
var context = this;
+ var names = name.split('.');
+ // Walk the context stack from top to bottom, finding the first
+ // context that contains the first part of the name
while (context) {
- if (name.indexOf('.') > 0) {
- value = context.view;
- var names = name.split('.'), i = 0;
- while (value && i < names.length) {
- value = value[names[i++]];
- }
- } else {
- value = context.view[name];
- }
+ view = context.view;
+ value = view[names[0]];
if (value != null) break;
context = context.parent;
}
+
+ var i = 1;
+ // If there are more name parts, resolve them in the context
+ // from the former resolution.
+ while (value && i < names.length) {
+ // Call methods in the context of their object
+ if (typeof value === 'function') value = value.call(view);
+ view = value;
+ value = view[names[i++]];
+ }
}
this._cache[name] = value;
}
- if (typeof value === 'function') value = value.call(this.view);
+ // Call methods in the context of their object and pass the lookup context
+ // as a parameter.
+ if (typeof value === 'function') value = value.call(view, this.view);
return value;
};
@@ -258,9 +266,9 @@
} else if (typeof value === 'function') {
var text = template == null ? null : template.slice(token[3], token[5]);
value = value.call(context.view, text, function (template) {
- return writer.render(template, context);
+ return writer.render('' + template, context);
});
- if (value != null) buffer += value;
+ if (value != null) buffer += writer.render('' + value, context);
} else if (value) {
buffer += renderTokens(token[4], writer, context, template);
}
@@ -282,10 +290,14 @@
break;
case '&':
value = context.lookup(tokenValue);
+ if (typeof value === 'function')
+ value = writer.render('' + value.call(context.view), context);
if (value != null) buffer += value;
break;
case 'name':
value = context.lookup(tokenValue);
+ if (typeof value === 'function')
+ value = writer.render('' + value.call(context.view), context);
if (value != null) buffer += exports.escape(value);
break;
case 'text':
diff --git a/test/_files/complex.js b/test/_files/complex.js
index 68a48093e..9d77916f9 100644
--- a/test/_files/complex.js
+++ b/test/_files/complex.js
@@ -7,8 +7,8 @@
{name: "green", current: false, url: "#Green"},
{name: "blue", current: false, url: "#Blue"}
],
- link: function () {
- return this["current"] !== true;
+ link: function (ctx) {
+ return ctx["current"] !== true;
},
list: function () {
return this.item.length !== 0;
diff --git a/test/_files/higher_order_sections.js b/test/_files/higher_order_sections.js
index bacb0a44a..bcd403c6e 100644
--- a/test/_files/higher_order_sections.js
+++ b/test/_files/higher_order_sections.js
@@ -3,7 +3,7 @@
helper: "To tinker?",
bolder: function () {
return function (text, render) {
- return text + ' => ' + render(text) + ' ' + this.helper;
+ return '' + render(text) + ' ' + this.helper;
}
}
})
diff --git a/test/_files/higher_order_sections.txt b/test/_files/higher_order_sections.txt
index be50ad764..9db786aad 100644
--- a/test/_files/higher_order_sections.txt
+++ b/test/_files/higher_order_sections.txt
@@ -1 +1 @@
-Hi {{name}}. => Hi Tater. To tinker?
+Hi Tater. To tinker?
diff --git a/test/_files/list_example.js b/test/_files/list_example.js
new file mode 100644
index 000000000..5ec34773d
--- /dev/null
+++ b/test/_files/list_example.js
@@ -0,0 +1,16 @@
+({
+ "beatles": [
+ { "firstName": "John", "lastName": "Lennon" },
+ { "firstName": "Paul", "lastName": "McCartney" },
+ { "firstName": "George", "lastName": "Harrison" },
+ { "firstName": "Ringo", "lastName": "Starr" }
+ ],
+ "name": function () {
+ return function () {
+ return "{{firstName}} {{lastName}}";
+ };
+ },
+ "name2": function (ctx) {
+ return ctx.firstName + " " + ctx.lastName + " " + this.beatles.length;
+ }
+})
diff --git a/test/_files/list_example.mustache b/test/_files/list_example.mustache
new file mode 100644
index 000000000..b3ecea725
--- /dev/null
+++ b/test/_files/list_example.mustache
@@ -0,0 +1,7 @@
+{{#beatles}}
+* {{name}}
+{{/beatles}}
+
+{{#beatles}}
+* {{name2}}
+{{/beatles}}
diff --git a/test/_files/list_example.txt b/test/_files/list_example.txt
new file mode 100644
index 000000000..0c7cdf287
--- /dev/null
+++ b/test/_files/list_example.txt
@@ -0,0 +1,9 @@
+* John Lennon
+* Paul McCartney
+* George Harrison
+* Ringo Starr
+
+* John Lennon 4
+* Paul McCartney 4
+* George Harrison 4
+* Ringo Starr 4
diff --git a/test/_files/method_interpolation.js b/test/_files/method_interpolation.js
new file mode 100644
index 000000000..57611d41f
--- /dev/null
+++ b/test/_files/method_interpolation.js
@@ -0,0 +1,15 @@
+({
+ a: function() { return {
+ b: function() { return {
+ c: function() { return 'a.b.c'; }
+ };}
+ };},
+ d: {
+ _p: 'd.e',
+ e: function() { return this._p; },
+ f: {
+ _p: 'd.f.g',
+ g: function() { return this._p; }
+ }
+ }
+})
\ No newline at end of file
diff --git a/test/_files/method_interpolation.mustache b/test/_files/method_interpolation.mustache
new file mode 100644
index 000000000..329b83ad7
--- /dev/null
+++ b/test/_files/method_interpolation.mustache
@@ -0,0 +1,3 @@
+a.b.c == {{a.b.c}}
+d.e == {{d.e}}
+d.f.g == {{d.f.g}}
diff --git a/test/_files/method_interpolation.txt b/test/_files/method_interpolation.txt
new file mode 100644
index 000000000..63e205a7f
--- /dev/null
+++ b/test/_files/method_interpolation.txt
@@ -0,0 +1,3 @@
+a.b.c == a.b.c
+d.e == d.e
+d.f.g == d.f.g
diff --git a/test/mustache-spec-test.js b/test/mustache-spec-test.js
index 1050ea43d..e38b6ae34 100644
--- a/test/mustache-spec-test.js
+++ b/test/mustache-spec-test.js
@@ -4,6 +4,10 @@ var fs = require('fs');
var path = require('path');
var specsDir = path.join(__dirname, 'spec/specs');
+// need to define globals used in ~lambdas - Interpolation - Multiple Calls
+// otherwise mocha complains
+g=null, calls=null;
+
var skipTests = {
comments: [
'Standalone Without Newline'
@@ -23,12 +27,6 @@ var skipTests = {
'Standalone Without Newline'
],
'~lambdas': [
- 'Interpolation',
- 'Interpolation - Expansion',
- 'Interpolation - Alternate Delimiters',
- 'Interpolation - Multiple Calls',
- 'Escaping',
- 'Section - Expansion',
'Section - Alternate Delimiters'
]
};