fix helpers, dynamic attributes; tests

This commit is contained in:
David Greenspan
2013-10-04 12:36:52 -07:00
parent 7e5dcedb30
commit 932075eacb
5 changed files with 94 additions and 14 deletions

View File

@@ -8,6 +8,7 @@ Package.on_test(function (api) {
api.use('underscore');
api.use('spacebars');
api.use('tinytest');
api.use('jquery');
api.use('test-helpers');
api.use('templating', 'client');

View File

@@ -18,3 +18,11 @@
<template name="spacebars_template_test_dynamic_template">
{{> foo}}
</template>
<template name="spacebars_template_test_interpolate_attribute">
<div class="aaa{{foo bar}}zzz"></div>
</template>
<template name="spacebars_template_test_dynamic_attrs">
<span {{{attrs1}}} {{{attrs2}}} {{k}}={{v}} {{x}} {{nonexistent}}>hi</span>
</template>

View File

@@ -1,3 +1,9 @@
var renderToDiv = function (comp) {
var div = document.createElement("DIV");
UI.insert(UI.render(comp), div);
return div;
};
Tinytest.add("spacebars - templates - simple helper", function (test) {
var tmpl = Template.spacebars_template_test_simple_helper;
tmpl.foo = function (x) {
@@ -6,8 +12,7 @@ Tinytest.add("spacebars - templates - simple helper", function (test) {
tmpl.bar = function () {
return 123;
};
var div = document.createElement("DIV");
UI.insert(UI.render(tmpl), div);
var div = renderToDiv(tmpl);
test.equal(div.innerHTML, "124");
});
@@ -20,9 +25,7 @@ Tinytest.add("spacebars - templates - dynamic template", function (test) {
tmpl.foo = function () {
return R.get() === 'aaa' ? aaa : bbb;
};
var div = document.createElement("DIV");
UI.insert(UI.render(tmpl), div);
var div = renderToDiv(tmpl);
test.equal(div.innerHTML, "aaa");
R.set('bbb');
@@ -30,3 +33,44 @@ Tinytest.add("spacebars - templates - dynamic template", function (test) {
test.equal(div.innerHTML, "bbb");
});
Tinytest.add("spacebars - templates - interpolate attribute", function (test) {
var tmpl = Template.spacebars_template_test_interpolate_attribute;
tmpl.foo = function (x) {
return x+1;
};
tmpl.bar = function () {
return 123;
};
var div = renderToDiv(tmpl);
test.equal($(div).find('div')[0].className, "aaa124zzz");
});
Tinytest.add("spacebars - templates - dynamic attrs", function (test) {
var tmpl = Template.spacebars_template_test_dynamic_attrs;
var R1 = ReactiveVar('');
var R2 = ReactiveVar('n=1');
var R3 = ReactiveVar('selected');
tmpl.attrs1 = function () { return R1.get(); };
tmpl.attrs2 = function () { return R2.get(); };
tmpl.k = 'x';
tmpl.v = 'y';
tmpl.x = function () { return R3.get(); };
var div = renderToDiv(tmpl);
var span = $(div).find('span')[0];
test.equal(span.innerHTML, 'hi');
test.equal(span.getAttribute('n'), "1");
test.equal(span.getAttribute('x'), 'y');
test.isTrue(span.hasAttribute('selected'));
R1.set('zanzibar="where the heart is"');
R2.set('');
R3.set('');
Deps.flush();
test.equal(span.innerHTML, 'hi');
test.isFalse(span.hasAttribute('n'));
test.isFalse(span.hasAttribute('selected'));
test.equal(span.getAttribute('zanzibar'), 'where the heart is');
});

View File

@@ -577,6 +577,12 @@ var makeObjectLiteral = function (obj) {
return buf.join('');
};
// Generates a render function (i.e. JS source code) from a template
// string or a pre-parsed template string. Consumes the AST from the
// parser, which consists of HTML tokens with embedded stache tags. A
// "block" (i.e. `{{#foo}}...{{/foo}}`) is represented as a single tag
// (always as part of an HTML "Characters" token), which has content
// that contains more HTML.
Spacebars.compile = function (inputString, options) {
var tree;
if (typeof inputString === 'object') {
@@ -641,15 +647,14 @@ Spacebars.compile = function (inputString, options) {
argCode = toJSLiteral(argValue);
break;
case 'PATH':
argCode = 'function () { return Spacebars.call(' +
codeGenPath(argValue, funcInfo) + '); }';
argCode = codeGenPath(argValue, funcInfo);
break;
default:
error("Unexpected arg type: " + argType);
}
if (arg.length > 2) {
// keyword argument
// keyword argument (represented as [type, value, name])
options = (options || {});
if (! (forComponentWithOpts &&
(arg[2] in forComponentWithOpts))) {
@@ -719,7 +724,7 @@ Spacebars.compile = function (inputString, options) {
var nameCode = codeGenPath(tag.path, funcInfo);
var argCode = codeGenArgs(tag.args, funcInfo);
return 'Spacebars.call(' + nameCode +
return 'Spacebars.mustache(' + nameCode +
(argCode ? ', ' + argCode.join(', ') : '') + ')';
};
@@ -861,6 +866,7 @@ Spacebars.compile = function (inputString, options) {
var name = kv.nodeName;
var value = kv.nodeValue;
if ((typeof name) === 'string') {
// attribute name has no tags
attrs = (attrs || {});
attrs[toJSLiteral(name)] =
interpolate(value, funcInfo,
@@ -870,6 +876,8 @@ Spacebars.compile = function (inputString, options) {
} else if (value === '' &&
name.length === 1 &&
name[0].type === 'TRIPLE') {
// attribute name is a triple-stache, no value, as in:
// `<div {{{attrs}}}>`.
renderables.push(
'{attrs: function () { return Spacebars.parseAttrs(' +
codeGenBasicStache(name[0], funcInfo) + '); }}');
@@ -877,7 +885,7 @@ Spacebars.compile = function (inputString, options) {
pairsWithReactiveNames.push(
interpolate(name, funcInfo,
INTERPOLATE_ATTR_VALUE),
interpolate(name, funcInfo,
interpolate(value, funcInfo,
INTERPOLATE_ATTR_VALUE));
isReactive = true;
}
@@ -977,6 +985,21 @@ Spacebars.call = function (value/*, args*/) {
return value.apply(null, args);
};
// Executes `{{foo bar baz}}` when called on `(foo, bar, baz)`.
// If `bar` and `baz` are functions, they are called. `foo`
// may be a non-function, in which case the arguments are
// discarded (though they may still be evaluated, i.e. called).
Spacebars.mustache = function (value/*, args*/) {
// call any arg that is a function (checked in Spacebars.call)
for (var i = 1; i < arguments.length; i++)
arguments[i] = Spacebars.call(arguments[i]);
var result = Spacebars.call.apply(null, arguments);
// map `null` and `undefined` to "", stringify anything else
// (e.g. strings, booleans, numbers including 0).
return String(result == null ? '' : result);
};
Spacebars.extend = function (obj/*, k1, v1, k2, v2, ...*/) {
for (var i = 1; i < arguments.length; i += 2)
obj[arguments[i]] = arguments[i+1];
@@ -996,8 +1019,8 @@ Spacebars.parseAttrs = function (attrs) {
if (tokens.length &&
tokens[0].type === 'StartTag') {
_.each(tokens[0].data, function (kv) {
if (UI.isValidAttributeName(kv[0]))
dict[kv[0]] = kv[1];
if (UI.isValidAttributeName(kv.nodeName))
dict[kv.nodeName] = kv.nodeValue;
});
}
return dict;

View File

@@ -34,10 +34,12 @@ AttributeManager = function (dictOrFunc) {
var handlers = self.handlers;
for (var attrName in dict) {
if (! attrName)
continue; // ignore empty attribute names
// perform a sanity check, since we'll be inserting
// attrName directly into the HTML stream
if (! isValidAttributeName(attrName))
throw new Error("Illegal HTML attribute name: '" + attrName + "'");
throw new Error("Expected single HTML attribute name, found: '" + attrName + "'");
handlers[attrName] = makeAttributeHandler(
attrName, dict[attrName]);
@@ -88,12 +90,14 @@ _extend(AttributeManager.prototype, {
h.update(element, oldValue, h.value);
}
for (var k in newDict) {
if (! k)
continue; // ignore empty attributes
if (! handlers.hasOwnProperty(k)) {
// need a new handler
var attrName = k;
if (! isValidAttributeName(attrName))
throw new Error("Illegal HTML attribute name: " + attrName);
throw new Error("Expected single HTML attribute name, found: " + attrName);
var h = makeAttributeHandler(
attrName, newDict[attrName]);