making block helpers work

This commit is contained in:
David Greenspan
2013-12-03 14:23:18 -08:00
parent c876ff3c47
commit ccd3e185fc
4 changed files with 146 additions and 11 deletions

View File

@@ -1384,6 +1384,14 @@ var optimize = function (tree) {
return optTree;
};
var builtInComponents = {
'content': '__content',
'elseContent': '__elseContent',
'if': 'UI.If2',
'unless': 'UI.Unless2',
'with': 'UI.With2'
};
var replaceSpecials = function (node) {
if (UI.isComponent(node)) {
return node;
@@ -1416,8 +1424,15 @@ var replaceSpecials = function (node) {
var path = tag.path;
var compCode = codeGenPath2(path);
if (path.length === 1)
compCode = '(Template[' + toJSLiteral(path[0]) + '] || ' + compCode + ')';
if (path.length === 1) {
var compName = path[0];
if (builtInComponents.hasOwnProperty(compName)) {
compCode = builtInComponents[compName];
} else {
compCode = ('(Template[' + toJSLiteral(path[0]) +
'] || ' + compCode + ')');
}
}
var includeArgs = codeGenInclusionArgs(tag);
@@ -1437,6 +1452,17 @@ var replaceSpecials = function (node) {
var codeGenInclusionArgs = function (tag) {
var args = null;
if ('content' in tag) {
args = (args || {});
args.__content = (
'UI.block(' + Spacebars.compile2(tag.content) + ')');
}
if ('elseContent' in tag) {
args = (args || {});
args.__elseContent = (
'UI.block(' + Spacebars.compile2(tag.elseContent) + ')');
}
_.each(tag.args, function (arg) {
var argType = arg[0];
var argValue = arg[1];
@@ -1466,8 +1492,9 @@ var codeGenInclusionArgs = function (tag) {
if (arg.length > 2) {
// keyword argument (represented as [type, value, name])
var name = arg[2];
args = (args || {});
args[toJSLiteral(arg[2])] = argCode;
args[toJSLiteral(name)] = argCode;
} else {
// positional argument
// XXX deal with >1 posArgs for #foo helpers
@@ -1483,6 +1510,7 @@ var codeGenInclusionArgs = function (tag) {
};
Spacebars.include = function (kindOrFunc, args) {
args = args || {};
if (typeof kindOrFunc === 'function') {
// function block helper
var func = kindOrFunc;
@@ -1495,13 +1523,27 @@ Spacebars.include = function (kindOrFunc, args) {
}
}
var result;
if ('data' in args) {
var data = args.data;
data = (typeof data === 'function' ? data() : data);
return func(data, { hash: hash });
result = func(data, { hash: hash });
} else {
return func({ hash: hash });
result = func({ hash: hash });
}
// In `{{#foo}}...{{/foo}}`, if `foo` is a function that
// returns a component, attach __content and __elseContent
// to it.
if (UI.isComponent(result) &&
(('__content' in args) || ('__elseContent' in args))) {
var extra = {};
if ('__content' in args)
extra.__content = args.__content;
if ('__elseContent' in args)
extra.__elseContent = args.__elseContent;
result = result.extend(extra);
}
return result;
} else {
// Component
var kind = kindOrFunc;
@@ -1677,7 +1719,7 @@ var codeGenMustache = function (tag, mustacheType) {
(argCode ? ', ' + argCode.join(', ') : '') + ')';
};
Spacebars.compile2 = function (input) {
Spacebars.compile2 = function (input, options) {
var tree;
// Accept string or output of Spacebars.parse
@@ -1690,7 +1732,19 @@ Spacebars.compile2 = function (input) {
tree = replaceSpecials(tree);
var code = '(function () { var self = this; return ';
// is this a template, rather than a block passed to
// a block helper, say
var isTemplate = (options && options.isTemplate);
var code = '(function () { var self = this; ';
if (isTemplate) {
// support `{{> content}}` and `{{> elseContent}}` with
// lexical scope by creating a local variable in the
// template's render function.
code += 'var __content = self.__content, ' +
'__elseContent = self.__elseContent; ';
}
code += 'return ';
code += UI.toCode(tree);
code += '; })';

View File

@@ -150,10 +150,9 @@ html2_scanner = {
var renderFuncCode = Spacebars.compile2(
contents, {
sourceName: 'Template "' + name + '"',
// XXX MESSY HACK - make only Templates expose
// `content` and `elseContent`
preamble: '\n var _local_content = this.content = this.__content;\n var _local_elseContent = this.elseContent = this.__elseContent' });
isTemplate: true,
sourceName: 'Template "' + name + '"'
});
results.js += "\nTemplate[" + JSON.stringify(name) +
"] = UI.Component.extend({kind: " +

View File

@@ -56,6 +56,84 @@ UI.If = Component.extend({
}
});
UI.If2 = Component.extend({
kind: 'If',
init: function () {
// XXX this probably deserves a better explanation if this code is
// going to stay with us.
this.condition = this.data;
// content doesn't see the condition as `data`
delete this.data;
// XXX I guess this means it's kosher to mutate properties
// of a Component during init (but presumably not before
// or after)?
},
render: function (buf) {
var self = this;
return function () {
var condition = getCondition(self);
// `__content` and `__elseContent` are passed by
// the compiler and are *not* emboxed, they are just
// Component kinds.
return condition ? self.__content : self.__elseContent;
};
}
});
// Acts like `!! self.condition()` except:
//
// - Empty array is considered falsy
// - The result is Deps.isolated (doesn't trigger invalidation
// as long as the condition stays truthy or stays falsy
var getCondition = function (self) {
return Deps.isolateValue(function () {
// `condition` is emboxed; it is always a function,
// and it only triggers invalidation if its return
// value actually changes. We still need to isolate
// the calculation of whether it is truthy or falsy
// in order to not re-render if it changes from one
// truthy or falsy value to another.
var cond = self.condition();
// empty arrays are treated as falsey values
if (cond instanceof Array && cond.length === 0)
return false;
else
return !! cond;
});
};
UI.Unless2 = Component.extend({
kind: 'Unless',
init: function () {
this.condition = this.data;
delete this.data;
},
render: function (buf) {
var self = this;
return function () {
var condition = getCondition(self);
return (! condition) ? self.__content : self.__elseContent;
};
}
});
UI.With2 = Component.extend({
kind: 'With',
init: function () {
this.condition = this.data;
},
render: function (buf) {
var self = this;
return function () {
var condition = getCondition(self);
return condition ? self.__content : self.__elseContent;
};
}
});
UI.Unless = Component.extend({
kind: 'Unless',
init: function () {

View File

@@ -693,3 +693,7 @@ UI.body2 = UI.Component.extend({
// XXX revisit how body works.
INSTANTIATED: false
});
UI.block = function (renderFunc) {
return UI.Component.extend({ render: renderFunc });
};