循环中的车把装饰器

Handlebars decorator in loop

这几天,我开始使用 Handlebars,目前正在尝试装饰器。当它保持简单时,我已经了解它是如何工作的: 例如:单独装饰名字:bill gaTes -> Mr Bill GATES

然后我尝试在 "each" 循环中使用装饰器,以执行与以前完全相同的操作,但使用的是人员列表而不是一个人。问题是:当我在装饰器函数中时,我需要知道我正在查看哪个元素(来自数组)。

所以我想在参数中给出 "each" 循环的索引(然后,因为我可以访问数据,我可以检索当前元素)或当前元素。

我尝试使用@index(通常效果很好),但在调试时我得到了undefined。 而且我找不到在装饰器中获取当前元素的方法。

代码如下:

index.html

<!doctype html>
<html>
    <head>  
        <title>Test Handlebars Decorators 4</title>
    </head>
    <body>
        <div id = "main"> </div>

        <script id = "rawTemplate" type = "text/x-handlebars-template">

            {{#each this}}
                Decorated: {{formatGenderHelper this}}
                {{* activateFormatter @index}}
                <br />
            {{/each}}

        </script>

        <script type = "text/javascript" src = "js/lib/handlebars.min-latest.js"></script>
        <script type = "text/javascript" src = "js/lib/jquery-2.2.4.min.js"></script>
        <script type = "text/javascript" src = "js/data.js"></script>
        <script type = "text/javascript" src = "js/index.js"></script>
    </body>
</html>

data.js

var data = [{
    "firstname": "Bill",
    "lastname": "Gates",
    "gender": "MALE"
}, {
    "firstname": "Hillary",
    "lastname": "Clinton",
    "gender": "FEMALE"
}];

function formatMale(author) {
    return "M. " + author.firstname + " " + author.lastname.toUpperCase();
}
function formatFemale(author) {
    return "Mme " + author.firstname + " " + author.lastname.toUpperCase();
}

Handlebars.registerDecorator('activateFormatter', function(program, props, container, context) {
    var genderHelper;
    var gender = context.args[0] || context.data.root[0].gender;

    switch(gender) {
        case "MALE":
            genderHelper = formatMale;
            break;
        case "FEMALE":
            genderHelper = formatFemale;
            break;
        default:
            console.log('Gender format not set. Please set before rendering template.');
            genderHelper = function() {};
    }

    container.helpers = {
        formatGenderHelper: genderHelper
    };
});

index.js

// Get the template
var rawTemplate = $('#rawTemplate').html();

// Compile the template
var compiledTemplate = Handlebars.compile(rawTemplate);

// Apply the template on the data
var content = compiledTemplate(data);

// Finally, re-inject the rendered HTML into the DOM
$('#main').html(content);

如果您需要更多信息,请告诉我。

感谢您的帮助:)

有两个问题导致您的示例失败。一个是您的代码的一个小问题,另一个是装饰器似乎工作的方式,它基本上只是 "not in loops"(除非您使用的是部分,请参阅此答案的最后一部分)。


首先,您没有告诉装饰器如何处理您传递给它的 @index。您可以更改装饰器函数,但由于装饰器位于 #each 块中,因此 this 与传递给 formatGenderHelper 的相同,这意味着我们可以将装饰器 this.gender 仅解析为性别字符串。这意味着模板的编写者确切地知道他们传递给装饰器的是什么,并且逻辑不会试图猜测模板告诉它的是什么。

index.html

...
{{#each this}}
    Decorated: {{formatGenderHelper this}}
    {{* activateFormatter this.gender}}
    <br />
{{/each}}
...

Also, I figure you are basing your example on Ryan Lewis's sitepoint demo. One problem/limitation his code has that isn't immediately obvious (because in the simple case it isn't even an issue), is that his decorator overwrites of all of the available helpers except for the formatting one it provides. To avoid "Error(s): TypeError: Cannot read property 'call' of undefined" errors when doing things that are a little bit more complex, I recommend using this in your decorator function.

data.js

  Handlebars.registerDecorator('activateFormatter', function(program, props, container, context) {
        // leaving out the code here for brevity
        container.helpers = Object.assign({}, container.helpers, {
            formatGenderHelper: genderHelper
        });
    });

第二个问题是在循环中使用装饰器的一般问题,这只是 Handlebars 使用装饰器的方式的结果。根据 decorators API doc,

Decorators are executed when the block program is instantiated and are passed (program, props, container, context, data, blockParams, depths).

这意味着(afaict)当块的模板首次实例化时,这意味着在块上需要 运行 的任何其他助手或程序之前,它会执行装饰器,并且在执行创建块的程序(在本例中为 #each) 之前,装饰器会进行所需的修改。这意味着要让装饰器以不同方式区分 #each 的每次迭代,它需要在 #each 的孙块中 运行,因此它在 [=15= 之后执行] 但在 formatGenderHelper 之前,但是,如果您这样做并根据迭代上下文在每次迭代中重新定义装饰助手,您最好只注册一个具有性别格式化逻辑的助手英寸


但是,这是一个无法回答的问题。所以,为了回答你的问题,我发现你 可以 让车把做你想做的事,但这有点像破解。由于诀窍是您需要从 #each 的子子块渲染带有装饰器的块,我们可以部分渲染它。为此,我们可以将其粘贴到另一个文件中并将其注册为部分文件,但这并不是很方便,因此我们有两个更好的选择。 1) 我们可以将代码包装在一个未定义的部分块中,并让 Handlebars 在该部分失败时将其呈现为回退块,或者(更灵活的选项)2) 我们可以使用提供的内置装饰器 #*inline 来定义一个内联部分。

  1. 故障转移部分块方法

    index.html

    ...
    {{#each this}}
       {{#>nothing}}
           Decorated: {{formatGenderHelper this}}
           {{* activateFormatter this.gender}}
           <br />
       {{/nothing}}
    {{/each}}
    ...
    
  2. #*inline方法

    index.html

    ...
    {{#*inline "formattedAuthor"}}
        Decorated: {{formatGenderHelper this}}
        <br />
    {{/inline}}
    {{#each this}}
       {{#>formattedAuthor}}
           {{* activateFormatter this.gender}}
       {{/formattedAuthor}}
    {{/each}}
    ...
    

这里的关键是我们使用我们的 *activeFormatter 装饰器在每次调用时动态地重新配置部分。第二个内联示例更清楚地说明了这一点。当然可能还有其他好的用例,但这是我看到装饰器真正闪耀的地方,即允许我们动态地重新配置部分的逻辑或助手 就在我们调用它们的地方 .

但是,有一个警告:如果我们的部分使用仅在从该部分定义外部调用的装饰器中提供的助手(就像我们在上面的例如 2) 如果装饰器没有在正确的地方被调用,部分将无法找到助手。这也是为什么最好使用上述两种方法中的任何一种来提供部分:我们将部分的定义与装饰器调用保存在同一个文件中,因此我们可以知道助手只是动态提供的,而不是全局注册的.