diff --git a/packages/handlebars/evaluate.js b/packages/handlebars/evaluate.js
index 7c5e28d37f..4064e338ce 100644
--- a/packages/handlebars/evaluate.js
+++ b/packages/handlebars/evaluate.js
@@ -136,7 +136,7 @@ Handlebars.evaluate = function (ast, data, options) {
var dataThis = stack.data;
var data;
- if (id[0] === 0 && (id[1] in helpers) && ! scopedToContext) {
+ if (id[0] === 0 && helpers.hasOwnProperty(id[1]) && ! scopedToContext) {
// first path segment is a helper
data = helpers[id[1]];
} else {
diff --git a/packages/templating/deftemplate.js b/packages/templating/deftemplate.js
index 42fedb6fcd..746791b439 100644
--- a/packages/templating/deftemplate.js
+++ b/packages/templating/deftemplate.js
@@ -70,7 +70,8 @@
// XXX forms hooks into this to add "bind"?
Meteor._template_decl_methods = {
- _tmpl_data: {}, // methods store data here (event map, etc.)
+ // methods store data here (event map, etc.). initialized per template.
+ _tmpl_data: null,
// these functions must be generic (i.e. use `this`)
events: function (eventMap) {
var events =
@@ -90,7 +91,7 @@
},
helpers: function (helperMap) {
var helpers =
- (this._tmpl_data.events = (this._tmpl_data.events || {}));
+ (this._tmpl_data.helpers = (this._tmpl_data.helpers || {}));
for(var h in helperMap)
helpers[h] = helperMap[h];
}
@@ -179,6 +180,7 @@
Template[name] = partial;
_.extend(partial, Meteor._template_decl_methods);
+ partial._tmpl_data = {};
Meteor._partials[name] = partial;
}
diff --git a/packages/templating/html_scanner.js b/packages/templating/html_scanner.js
index 238b291e91..d5ac1954ae 100644
--- a/packages/templating/html_scanner.js
+++ b/packages/templating/html_scanner.js
@@ -111,7 +111,8 @@ var html_scanner = {
_handleTag: function (results, tag, attribs, contents, parseError) {
- // trim the tag contents
+ // trim the tag contents.
+ // this is a courtesy and is also relied on by some unit tests.
contents = contents.match(/^[ \t\r\n]*([\s\S]*?)[ \t\r\n]*$/)[1];
// do we have 1 or more attribs?
diff --git a/packages/templating/templating_tests.html b/packages/templating/templating_tests.html
index 4b79606bf4..f6e9d05d8d 100644
--- a/packages/templating/templating_tests.html
+++ b/packages/templating/templating_tests.html
@@ -234,3 +234,35 @@
Foo Bar Baz
+
+
+ x
+ x
+ x
+ x
+ x
+ x
+ x
+ x
+ {{var}}
+
+
+
+ {{foo}}{{bar}}{{baz}}
+
+
+
+ {{name}}{{arity}}{{toString}}{{length}}{{var}}
+
+
+
+ {{name}}{{arity}}{{toString}}{{length}}{{var}}
+
+
+
+ foobarbaz
+
+
+
+ foobarbaz
+
diff --git a/packages/templating/templating_tests.js b/packages/templating/templating_tests.js
index a451fbfa21..11c5cd9718 100644
--- a/packages/templating/templating_tests.js
+++ b/packages/templating/templating_tests.js
@@ -619,3 +619,118 @@ Tinytest.add("templating - template arg", function (test) {
div.kill();
Meteor.flush();
});
+
+Tinytest.add("templating - preserve", function (test) {
+ var R = ReactiveVar('foo');
+
+ var tmpl = Template.test_template_preserve_a;
+ tmpl.preserve(['.b']);
+ tmpl.preserve(['.c']);
+ tmpl.preserve({'.d': true});
+ tmpl.preserve({'span': function (n) {
+ return _.contains(['e','f'], n.className) && n.className;
+ }});
+ tmpl.preserve(['span.a']);
+ tmpl['var'] = function () { return R.get(); };
+
+ var div = OnscreenDiv(Meteor.render(tmpl));
+ Meteor.flush();
+ test.equal(div.node().lastChild.nodeValue.match(/\S+/)[0], 'foo');
+ var spans1 = {};
+ _.each(DomUtils.findAll(div.node(), 'span'), function (sp) {
+ spans1[sp.className] = sp;
+ });
+
+ R.set('bar');
+ Meteor.flush();
+ test.equal(div.node().lastChild.nodeValue.match(/\S+/)[0], 'bar');
+ var spans2 = {};
+ _.each(DomUtils.findAll(div.node(), 'span'), function (sp) {
+ spans2[sp.className] = sp;
+ });
+
+ test.isTrue(spans1.a === spans2.a);
+ test.isTrue(spans1.b === spans2.b);
+ test.isTrue(spans1.c === spans2.c);
+ test.isTrue(spans1.d === spans2.d);
+ test.isTrue(spans1.e === spans2.e);
+ test.isTrue(spans1.f === spans2.f);
+ test.isFalse(spans1.y === spans2.y);
+ test.isFalse(spans1.z === spans2.z);
+
+ div.kill();
+ Meteor.flush();
+});
+
+Tinytest.add("templating - helpers", function (test) {
+ var tmpl = Template.test_template_helpers_a;
+
+ tmpl.foo = 'z';
+ tmpl.helpers({bar: 'b'});
+ // helpers(...) takes precendence of assigned helper
+ tmpl.helpers({foo: 'a', baz: function() { return 'c'; }});
+
+ var div = OnscreenDiv(Meteor.render(tmpl));
+ test.equal(div.text().match(/\S+/)[0], 'abc');
+ div.kill();
+ Meteor.flush();
+
+ tmpl = Template.test_template_helpers_b;
+
+ tmpl.helpers({
+ 'name': 'A',
+ 'arity': 'B',
+ 'toString': 'C',
+ 'length': 4,
+ 'var': 'D'
+ });
+
+ div = OnscreenDiv(Meteor.render(tmpl));
+ test.equal(div.text().match(/\S+/)[0], 'ABC4D');
+ div.kill();
+ Meteor.flush();
+
+ // test that helpers don't "leak"
+ tmpl = Template.test_template_helpers_c;
+ div = OnscreenDiv(Meteor.render(tmpl));
+ test.equal(div.text(), '');
+ div.kill();
+ Meteor.flush();
+});
+
+Tinytest.add("templating - events", function (test) {
+ var tmpl = Template.test_template_events_a;
+
+ var buf = [];
+
+ // old style
+ tmpl.events = {
+ 'click b': function () { buf.push('b'); }
+ };
+
+ var div = OnscreenDiv(Meteor.render(tmpl));
+ clickElement(DomUtils.find(div.node(), 'b'));
+ test.equal(buf, ['b']);
+ div.kill();
+ Meteor.flush();
+
+ ///
+
+ tmpl = Template.test_template_events_b;
+ buf = [];
+ // new style
+ tmpl.events({
+ 'click u': function () { buf.push('u'); }
+ });
+ tmpl.events({
+ 'click i': function () { buf.push('i'); }
+ });
+
+ var div = OnscreenDiv(Meteor.render(tmpl));
+ clickElement(DomUtils.find(div.node(), 'u'));
+ clickElement(DomUtils.find(div.node(), 'i'));
+ test.equal(buf, ['u', 'i']);
+ div.kill();
+ Meteor.flush();
+
+});