mirror of
https://github.com/meteor/meteor.git
synced 2026-05-02 03:01:46 -04:00
fix component invocations, make dynamic (wip)
TODO next: - fix keyword args when foo's a function - pass them to function not template - evaluate them - back-compat block helper calling convention
This commit is contained in:
@@ -7,6 +7,10 @@
|
||||
<template name="spacebars_template_test_bbb">
|
||||
bbb
|
||||
</template>
|
||||
|
||||
<template name="spacebars_template_test_bracketed_this">
|
||||
[{{this}}]
|
||||
</template>
|
||||
<!-- -->
|
||||
|
||||
|
||||
@@ -30,3 +34,15 @@
|
||||
<template name="spacebars_template_test_triple">
|
||||
{{{html}}}
|
||||
</template>
|
||||
|
||||
<template name="spacebars_template_test_inclusion_args">
|
||||
{{> foo bar}}
|
||||
</template>
|
||||
|
||||
<template name="spacebars_template_test_inclusion_args2">
|
||||
{{> foo bar baz}}
|
||||
</template>
|
||||
|
||||
<template name="spacebars_template_test_inclusion_args3">
|
||||
{{> foo bar q=baz}}
|
||||
</template>
|
||||
|
||||
@@ -104,3 +104,67 @@ Tinytest.add("spacebars - templates - triple", function (test) {
|
||||
test.equal(span.className, 'hi');
|
||||
test.equal(span.innerHTML, 'blah');
|
||||
});
|
||||
|
||||
Tinytest.add("spacebars - templates - inclusion args", function (test) {
|
||||
var tmpl = Template.spacebars_template_test_inclusion_args;
|
||||
|
||||
var R = ReactiveVar(Template.spacebars_template_test_aaa);
|
||||
tmpl.foo = function () { return R.get(); };
|
||||
|
||||
var div = renderToDiv(tmpl);
|
||||
// `{{> foo bar}}`, with `foo` resolving to Template.aaa,
|
||||
// which consists of "aaa"
|
||||
test.equal(div.innerHTML, 'aaa');
|
||||
R.set(Template.spacebars_template_test_bbb);
|
||||
Deps.flush();
|
||||
test.equal(div.innerHTML, 'bbb');
|
||||
|
||||
////// Ok, now `foo` *is* Template.aaa
|
||||
tmpl.foo = Template.spacebars_template_test_aaa;
|
||||
div = renderToDiv(tmpl);
|
||||
test.equal(div.innerHTML, 'aaa');
|
||||
|
||||
////// Ok, now `foo` is a template that takes an argument; bar is a string.
|
||||
tmpl.foo = Template.spacebars_template_test_bracketed_this;
|
||||
tmpl.bar = 'david';
|
||||
div = renderToDiv(tmpl);
|
||||
test.equal(div.innerHTML, '[david]');
|
||||
|
||||
////// Now `foo` is a template that takes an arg; bar is a function.
|
||||
tmpl.foo = Template.spacebars_template_test_bracketed_this;
|
||||
R = ReactiveVar('david');
|
||||
tmpl.bar = function () { return R.get(); };
|
||||
div = renderToDiv(tmpl);
|
||||
test.equal(div.innerHTML, '[david]');
|
||||
R.set('avi');
|
||||
Deps.flush();
|
||||
test.equal(div.innerHTML, '[avi]');
|
||||
});
|
||||
|
||||
Tinytest.add("spacebars - templates - inclusion args 2", function (test) {
|
||||
///// `foo` is a function in `{{> foo bar baz}}`.
|
||||
// `bar` and `baz` should be called and passed as an arg to it.
|
||||
var tmpl = Template.spacebars_template_test_inclusion_args2;
|
||||
tmpl.foo = function (x, y) {
|
||||
return y === 999 ? Template.spacebars_template_test_aaa :
|
||||
Template.spacebars_template_test_bracketed_this.withData(x + y);
|
||||
};
|
||||
var R = ReactiveVar(3);
|
||||
tmpl.bar = 4;
|
||||
tmpl.baz = function () { return R.get(); };
|
||||
var div = renderToDiv(tmpl);
|
||||
test.equal(div.innerHTML, '[7]');
|
||||
R.set(11);
|
||||
Deps.flush();
|
||||
test.equal(div.innerHTML, '[15]');
|
||||
R.set(999);
|
||||
Deps.flush();
|
||||
test.equal(div.innerHTML, 'aaa');
|
||||
});
|
||||
|
||||
Tinytest.add("spacebars - templates - inclusion args 2", function (test) {
|
||||
// `{{> foo bar q=baz}}`
|
||||
var tmpl = Template.spacebars_template_test_inclusion_args3;
|
||||
|
||||
// XXX
|
||||
});
|
||||
|
||||
@@ -662,19 +662,8 @@ Spacebars.compile = function (inputString, options) {
|
||||
}
|
||||
} else {
|
||||
// positional argument
|
||||
if (forComponent) {
|
||||
// for Components, only take one positional
|
||||
// argument, and call it `data`
|
||||
if (i === 0) {
|
||||
options = (options || {});
|
||||
options[toJSLiteral('data')] = argCode;
|
||||
} else {
|
||||
error("Only one positional argument is allowed");
|
||||
}
|
||||
} else {
|
||||
args = (args || []);
|
||||
args.push(argCode);
|
||||
}
|
||||
args = (args || []);
|
||||
args.push(argCode);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -683,9 +672,8 @@ Spacebars.compile = function (inputString, options) {
|
||||
options = (options || {});
|
||||
options[toJSLiteral(k)] = v;
|
||||
});
|
||||
|
||||
// components get one argument, the options dictionary
|
||||
args = [options ? makeObjectLiteral(options) : '{}'];
|
||||
// put options as dictionary at beginning of args for component
|
||||
args.unshift(options ? makeObjectLiteral(options) : 'null');
|
||||
} else {
|
||||
// put options as dictionary at end of args
|
||||
if (options) {
|
||||
@@ -698,26 +686,31 @@ Spacebars.compile = function (inputString, options) {
|
||||
};
|
||||
|
||||
var codeGenComponent = function (path, args, funcInfo,
|
||||
compOptions) {
|
||||
compOptions, isBlock) {
|
||||
|
||||
var nameCode = codeGenPath(path, funcInfo);
|
||||
var argCode = (args.length || compOptions) ?
|
||||
codeGenArgs(args, funcInfo, compOptions || {})[0] : null;
|
||||
codeGenArgs(args, funcInfo, compOptions || {}) : null;
|
||||
|
||||
// XXX provide a better error message if
|
||||
// `foo` in `{{> foo}}` is not found?
|
||||
// Instead of `null`, we could evaluate to the path
|
||||
// as a string, and then the renderer could choke on
|
||||
// that in a way where it ends up in the error message.
|
||||
|
||||
var compFunc = 'function () { return (Spacebars.call(' + nameCode +
|
||||
') || null); }';
|
||||
var comp = nameCode;
|
||||
|
||||
if (path.length === 1)
|
||||
compFunc = 'Template[' + toJSLiteral(path[0]) + '] || ' + compFunc;
|
||||
comp = '(Template[' + toJSLiteral(path[0]) + '] || ' + comp + ')';
|
||||
|
||||
return '{kind: ' + compFunc + (argCode ? ', props: ' + argCode : '') +
|
||||
'}';
|
||||
// XXX For now, handle the calling convention for `{{> foo}}` and `{{#foo}`
|
||||
// using a wrapper component, which processes the arguments based
|
||||
// on the type of tag and the type of `foo` (component or function).
|
||||
// If `foo` changes reactively, the wrapper component is invalidated.
|
||||
//
|
||||
// This should be cleaned up to make the generated code cleaner and
|
||||
// to not have all the extra components and DomRanges hurting
|
||||
// peformance and showing up during debugging.
|
||||
return '{kind: UI.DynamicComponent, props: {' +
|
||||
(isBlock? 'isBlock: true, ' : '') + 'compKind: ' + comp +
|
||||
(argCode ? ', compArgs: [' + argCode.join(', ') + ']': '') + '}}';
|
||||
};
|
||||
|
||||
var codeGenBasicStache = function (tag, funcInfo) {
|
||||
@@ -829,7 +822,7 @@ Spacebars.compile = function (inputString, options) {
|
||||
renderables.push(codeGenComponent(
|
||||
block.openTag.path,
|
||||
block.openTag.args,
|
||||
funcInfo, extraArgs));
|
||||
funcInfo, extraArgs, true));
|
||||
} else {
|
||||
switch (tag.type) {
|
||||
case 'INCLUSION':
|
||||
|
||||
@@ -80,4 +80,51 @@ UI.With = Component.extend({
|
||||
render: function (buf) {
|
||||
buf.write(this.content);
|
||||
}
|
||||
});
|
||||
|
||||
var callIfFunction = function (value) {
|
||||
return (typeof value === 'function') ? value() : value;
|
||||
};
|
||||
|
||||
UI.DynamicComponent = Component.extend({
|
||||
kind: 'DynamicComponent',
|
||||
render: function (buf) {
|
||||
var kind = this.compKind;
|
||||
var args = this.compArgs;
|
||||
|
||||
var kwArgs = args && args.length && args[0];
|
||||
var posArgs = args && args.length > 1 && args.slice(1);
|
||||
|
||||
var props = _.extend({}, kwArgs);
|
||||
if (typeof kind === 'function') {
|
||||
// Calling a helper function as a template. Evaluate the
|
||||
// arguments and pass them to the function to get back
|
||||
// a component. Completely different use of args than
|
||||
// when calling a bare component like `Template.foo` or
|
||||
// a "helper" that is a constant component (in which case
|
||||
// the args are used to extend the component).
|
||||
//
|
||||
// `kind` should be already bound with a `this`, so it
|
||||
// doesn't matter what we pass in for the first argument
|
||||
// to `apply`. Same with arguments.
|
||||
if (posArgs) {
|
||||
for (var i = 0; i < posArgs.length; i++)
|
||||
posArgs[i] = callIfFunction(posArgs[i]);
|
||||
}
|
||||
kind = kind.apply(null, posArgs || []);
|
||||
} else {
|
||||
if (posArgs && posArgs.length) {
|
||||
if (posArgs.length > 1)
|
||||
throw new Error("Can't have more than one argument to a template");
|
||||
|
||||
if (posArgs.length) {
|
||||
props.data = posArgs[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (kind) {
|
||||
buf.write({kind: kind, props: props});
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -12,8 +12,8 @@ var DomRange = UI.DomRange;
|
||||
// - String (any raw HTML substring with no limitations)
|
||||
// - Component (a kind to instantiate and insert)
|
||||
// - function returning Component (reactive kind)
|
||||
// - { kind: functionOrComponent, ... } (reactive kind, instantiated
|
||||
// with props (the `...`)
|
||||
// - { kind: functionOrComponent, props: dict } (reactive kind, instantiated
|
||||
// with props
|
||||
// - { attrs: functionOrDictionary } - reactive dictionary of attributes,
|
||||
// only allowed inside an HTML tag. For example,
|
||||
// `buf.write("<div ", { attrs: { 'class': 'foo' } }, ">text</div>")`.
|
||||
|
||||
Reference in New Issue
Block a user