在Meteor模板和模板助手中访问父级上下文

44
我遇到了一个模板上下文的情况,我很难找到一个解决办法。


这是相关的模板:

{{#each votes}}
    <h3>{{question}}</h3>

    <ul>
        {{#each participants}}
            <li>
                <p>{{email}}</p>
                <select name="option-select">
                    {{#each ../options}}
                    <option value="{{option}}" class="{{is_selected_option}}">{{option}}</option>
                    {{/each}}
                </select>
            </li>
        {{/each}}
    </ul>
</div>
{{/each}}


这里是一份投票文件的示例:

{
    _id: '32093ufsdj90j234',
    question: 'What is the best food of all time?'
    options: [
        'Pizza',
        'Tacos',
        'Salad',
        'Thai'
    ],
    participants: [
        {
            id: '2f537a74-3ce0-47b3-80fc-97a4189b2c15'
            vote: 0
        },
        {
            id: '8bffafa7-8736-4c4b-968e-82900b82c266'
            vote: 1
        }
    ]
}


这里出现了问题...

当模板进入参与者的#each时,它不再具有访问vote上下文的权限,因此无法访问每个投票的可用选项。

我可以通过使用../options handlebars路径来跳回到父级上下文中来解决这个问题,但这不会影响模板助手的上下文,所以Template.vote.is_selected_option 中的this指的是当前的participant,而不是当前的voteoption,并且无法知道我们目前正在迭代哪个option

有什么建议可以解决这个问题,而不需要借助DOM操作和jQuery伎俩?

这是一个我遇到过多次的模板问题。我们需要一种正式的方法,在模板、模板助手和模板事件中可以向上访问模板上下文层次结构。

9个回答

84

3
@opyh,你能否在Template.parentData(numLevels)中添加参考文献?我认为这会进一步改善这个答案。 - Adam Monsen
5
这个方法对我来说可行。在 Meteor 1.0 及以上版本中使用: - Michael
只需注意,在任何块中都可以访问父上下文,并且您可以像这样引用属性 {{#if ../someProperty}}。 - Little Brain

5

这并不是特别美观,但我已经做了类似这样的事情:


<template name='forLoop'>
{{#each augmentedParticipants}}
{{> participant }}
{{/each}}
</template>

<template name='participant'>
...
Question: {{this.parent.question}}
...
</template>


// and in the js:
Template.forLoop.helpers({
    augmentedParticipants: function() {
        var self = this;
        return _.map(self.participants,function(p) {
            p.parent = self;
            return p;
        });
    }
});

与AVGP建议的方法类似,但它在帮助器级别上增强了数据而不是数据库级别,我认为它更轻量级。

如果您想要高级一些,可以尝试编写一个Handlebars块助手eachWithParent来抽象此功能。 Meteor对handlebars的扩展在这里记录:https://github.com/meteor/meteor/wiki/Handlebars


zorlak:我最终做的事情与你的示例非常相似:创建了一个增强的participants_with_vote_info集合,其中包括我需要的所有内容,并使用#each迭代该集合,以便我不需要跳回到父级。感谢你的想法! - cmal
对于深度嵌套的模板布局,这真是一件很烦人的事情。 - Tiberiu C.

2

只需注册全局模板帮助器:

Template.registerHelper('parentData',
    function () {
        return Template.parentData(1);
    }
);

并将其用于您的HTML模板中,如下所示:

{{#each someRecords}}
  {{parentData.someValue}}
{{/each}}

对于 Meteor 1.2+,您应该使用以下内容:
UI.registerHelper('parentData', function() {
  return Template.parentData(1);
});

1
但是在你的例子中,“someRecords”是什么?肯定是来自parentData上下文 - 但是当你尝试在#each子句中访问parentData时,你的解决方案似乎不起作用。 - Andy Lorenz

2

我不知道是否存在正式的方法,但为了解决你的问题,我会像这样将参与者与父级ID链接:

{
    _id: "1234",
    question: "Whats up?",
    ...
    participants: [
      {
        _id: "abcd",
        parent_id: "1234",
        vote: 0
      }
    ]
}

使用findOne来查找parent_id,并在helpers、events等中使用它,以便跳回到父级。 这显然是一个次优的解决方案,但只要没有办法引用父上下文,这就是我想到的最简单的方法。 也许有一种方法,但它非常隐藏在Meteor的内部工作中,而且在文档中没有提到,如果有的话:如果您找到了,请更新此问题。


你说得对,这样做可能不是最优的,但它可以工作。一个类似的替代方案是使用单独的用户集合,每个用户存储其相关的投票ID。如果有其他更好的方法,请务必告诉我。 - cmal
我应该补充一点,这两种解决方案都无法解决最大的问题,即在 {{#each ../options}} 中的每个 Template.votes.option 都不知道它实际上是指哪个选项!!! - cmal

2

这应该会让生活变得更容易。

// use #eachWithParent instead of #each and the parent._id will be passed into the context as parent.
Handlebars.registerHelper('eachWithParent', function(context, options) {

    var self = this;
    var contextWithParent = _.map(context,function(p) {
        p.parent = self._id;
        return p;
    });

    var ret = "";

    for(var i=0, j=contextWithParent.length; i<j; i++) {
        ret = ret + options.fn( contextWithParent[i] );
    }
    return ret;
});

请继续进行更改

p.parent = self._id;

访问父级上下文中的任何内容。

修复后:

// https://github.com/meteor/handlebars.js/blob/master/lib/handlebars/base.js

// use #eachWithParent instead of #each and the parent._id will be passed into the context as parent.
Handlebars.registerHelper('eachWithParent', function(context, options) {
    var self = this;
    var contextWithParent = _.map(context,function(p) {
        p.parent = self._id;
        return p;
    });

    return Handlebars._default_helpers.each(contextWithParent, options);
});

这个可以正常工作 :) 没有错误


当我尝试更改使用eachWithParent渲染的内容时,会出现“Exception from Deps afterFlush function: Error: Can't create second landmark in same branch”错误。 - Chet

2

虽然可能性不大,但也许这个方法可行:

{{#with ../}}
  {{#each options}}
    {{this}}
  {{/each}}
{{/with}}

0

{{#each parent}}

{{#each child}}

<input type="hidden" name="child_id" value="{{_id}}" />

<input type="hidden" name="parent_id" value="{{../_id}}" />

{{/each}}

{{/each}}

_id 不是该物品的 _did,而是父级的 id!


0

"click .selected":function(e){
    var parent_id = $(e.currentTarget).parent().attr("uid");
    return parent_id
  },
<td id="" class="staff_docs" uid="{{_id}}">
                                        {{#each all_req_doc}}
                                                <div class="icheckbox selected "></div>
  {{/each}}
  </td>


0
我曾经遇到类似的问题,发现其他答案中建议使用Template.parentData()方法的方式在事件处理程序中无法正常工作(请参见https://github.com/meteor/meteor/issues/5491)。用户Lirbank发布了这个简单的解决方法:
将外部上下文中的数据传递给内部上下文中的HTML元素,在同一模板中实现:
{{#each companies}}
  {{#each employees}}
    <a href="" companyId="{{../id}}">Do something</a>
  {{/each}}
{{/each}}

现在可以通过类似以下方式从事件处理程序中访问公司ID:

$(event.currentTarget).attr('companyId')

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接