即时计算Handlebars模板的值?

时间:2013-12-10 17:43:34

标签: javascript handlebars.js

有没有办法为Handlebars提供一个在需要时计算值的函数,而不是在上下文中预先提供所有值?

例如,假设我想用示例数据填写任何模板:

var exampleFunction = function (varName) {
    return "Example " + varName;
};

如果我提前知道Handlebars模板需要哪些变量,我可以使用此函数来组合上下文。但是,我真正想做的是:

var template = Handlebars.compile(templateString);
var html = template.fillFromFunction(exampleFunction);

这可能吗?如果没有,那么是否还有其他支持它的模板引擎?


奖金问题:这可以是异步的,例如:

var template = Handlebars.compile('{{foo.bar}}');
var dataFunction = function (path, callback) {
    setTimeout(function () {
        callback("Async " + path);
    }, 100);
};

1 个答案:

答案 0 :(得分:0)

简短版本:

这是一个黑客,但我有一个解决方法。

var oldNameLookup = handlebars.JavaScriptCompiler.prototype.nameLookup;
handlebars.JavaScriptCompiler.prototype.nameLookup = function (parent, name) {
    return '(typeof ' + parent + ' === "function" ? '
        + parent + '(' + JSON.stringify(name) + ') : '
        + oldNameLookup(parent, name) + ')';
}

用法:

var template = handlebars.compile('{{foo}} {{bar}}');
var dataFunction = function (key) {
    return 'data:' + key;
};
console.log(template(dataFunction));
// Outputs: "data:foo data:bar"

子属性:

要启用子属性(例如"{{foo.bar}}"),我们需要一个包装器方法:

function transformFunc(dataFunction) {
    return function (key) {
        if (typeof key !== 'string') {
            return dataFunction('');
        }
        var pointerKey = '/' + key.replace(/~/g, '~0').replace(/\//g, '~1');
        return transformFunc(function (path) {
            return dataFunction(pointerKey + path);
        });
    };
}

用法:

var template = handlebars.compile('{{foo}} {{foo.bar}}');
var dataFunction = transformFunc(function (path) {
    // path is a JSON Pointer path
    return "data:" + path;
});
console.log(template(dataFunction));
// Outputs: "data:/foo data:/foo/bar"

如果有人想要,我认为可以修改.compile().precompile(),以便在模板化时将transformFunc()应用于任何传入函数。

说明:

黑客攻击包括改变Handlebars的代码生成,但是这个code comment意味着这没有问题。我试图找到一种子类的方法,但看不到如何让thisthis使用它。

简短版本:覆盖nameLookup() method

此方法通常会生成JavaScript代码,例如depth0.foo(depth1 && depth1.bar)。我们正在扩展它,以便生成的代码首先检查parent以查看它是否是一个函数。

如果它是一个函数,则它以属性名称作为参数调用该函数。否则,它返回与先前相同的值。例如,它将生成如下内容:

(typeof depth0 === "function" ? depth0("foo") : (depth0 && depth0.foo))

对于简单变量(例如,仅"{{foo}}"),您现在可以只提供一个函数,并使用变量名调用它。

启用子属性

但是,对于嵌套属性(即"{{foo.bar.baz}}"),我们实际上需要我们的函数来返回另一个函数,该函数可以返回适当的数据,也可以使用另一个属性名称调用,具体取决于需要的。

所以:如果我们的转换函数没有给出一个字符串,那么假设我们处于终点(我们想要实际的数据,而不是子属性),所以我们只需要调用。

如果我们的转换函数被赋予一个字符串,那么它被假定为一个键,所以另一个(转换的)函数是返回,它返回数据函数,为参数添加适当的前缀。