【问题标题】:Handlebars decorator in loop循环中的车把装饰器
【发布时间】:2016-07-01 15:26:29
【问题描述】:

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

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

所以我想在参数中给出“每个”循环的索引(然后,由于我可以访问数据,我可以检索当前元素)或当前元素。

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

代码如下:

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);

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

感谢您的帮助:)

【问题讨论】:

  • 我认为这不是装饰器的正确用法。我认为装饰器定义了整个块的格式化功能,而不是单个项目。
  • 你能再详细解释一下你的意思吗?
  • @Barudar 装饰器与父块同时运行,而不是与装饰器所在的子块同时运行。通过这种方式,它在某种程度上打破了关于模板中块执行顺序的典型推理。实际上,对于上述情况,这意味着它运行一次以配置整个父 #each 块的格式化功能,而不是为 #each 迭代项目创建的每个子块。
  • @Joshua Skrzypek 好的,谢谢您的解释。但是,我仍然不明白装饰器的目的是什么。我的意思是,看起来我们可以用装饰器做的所有事情也可以用助手来完成。所以装饰器肯定有一些特定的用途,它们确实比助手更好。

标签: javascript templates handlebars.js decorator


【解决方案1】:

有两个问题使您的示例失败。一个是您的代码有一个小问题,另一个是装饰器的工作方式,本质上只是“不在循环中”(除非您使用的是部分,请参阅此答案的最后一部分)。


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

index.html

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

另外,我认为您的示例基于Ryan Lewis's sitepoint demo。他的代码存在的一个问题/限制不是立即显而易见的(因为在简单的情况下它甚至不是问题),是他的装饰器覆盖了所有可用的助手,除了它提供的格式化。为了避免在执行稍微复杂一点的操作时出现“错误:TypeError:无法读取未定义的属性‘调用’”错误,我建议在你的装饰器函数中使用它。

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

装饰器在块程序被实例化并被传递(程序、道具、容器、上下文、数据、块参数、深度)时执行。

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


但是,这有点无法回答。所以,为了回答你的问题,我发现你可以让车把做你想做的事,但这有点像黑客。由于技巧是您需要从#each 的子子块渲染带有装饰器的块,因此我们可以部分渲染它。为此,我们可以将其粘贴到另一个文件中并将其注册为部分文件,但这并不方便,因此我们有两个更好的选择。 1)我们可以将代码包装在一个未定义的部分块中,并让 Handlebars 在该部分失败时将其呈现为后备块,或者(slicker 选项)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 ) 如果没有在正确的位置调用装饰器,则部分将无法找到帮助器。这也是为什么最好使用上述两种方法中的任何一种来提供partial:我们将partial的定义与decorator调用保存在同一个文件中,这样我们就可以知道helper只是动态提供的,而不是全局注册的.

【讨论】:

  • 您好,您可能花了很长时间才写完所有这些。此外,这很好解释,谢谢。我更了解装饰器的工作原理。不过,我仍然需要更多地了解它们应该如何使用。你的两个例子都像一个魅力。所以也谢谢你!
  • 只有一个问题:在倒数第二段中,您说的是“*activeTranslator”。你的意思是“*activateFormatter”还是我错过了什么?
  • 哈!你抓住了我!我用一个活跃的翻译助手为我们的电子邮件模板进行国际化,为类似的问题找到了这个问题的答案:) 我将在我的答案中进行编辑
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-12-04
  • 2021-11-15
  • 2015-04-24
  • 2021-02-19
  • 1970-01-01
  • 1970-01-01
  • 2021-01-05
相关资源
最近更新 更多