如何在 #each 循环完成后执行回调函数?

6

我在#each循环结束后遇到了回调问题。我有一个名为“content”的模板:

<template name="content">
{{#if Template.subscriptionsReady}}
    {{#each currentData}}
        <div data-cid="{{this._id}}"></div>
    {{/each}}
{{else}}
    Loading...
{{/if}}
</template>

首先我等待一个订阅,当它可用时,我使用 {{#each}} 迭代我的集合并附加 div。我需要的是一种回调函数,用于在 for-each 循环完成后(换句话说,DOM 已准备就绪)。

Template.content.onRendered()

-> 触发过早

我还尝试在 {{each}} 后面添加一个图像,并在其 onload 中触发函数,代码如下:

<img style="height:0;width:0" src="*mysource*" onload="callback()">

-> 有时候工作,但不太可靠

是否有一种方法可以获取这个回调?如果这能解决问题,我不介意改变模板的结构。


我不知道这是否是一个重复的问题,但它肯定有与这个问题相同的感觉。 - David Weldon
4个回答

3

当Spacebars模板中的{{#each}}块完成对每个迭代项的DOM渲染时,没有一种简单的方法来通知您。

最佳解决方案是使用另一个响应式计算(Tracker.autorun)观察您的(响应式)当前数据。

每次修改当前数据(可能是一个游标)后,您可以在其他所有响应式计算执行完其工作后运行任意代码,使用Tracker.afterFlush

{{#each}}块是这些计算之一,其作用是监听您给定的响应式数据源并重新呈现其Template.contentBlock,以当前项作为当前数据上下文,重复多次获取到的源项目。

通过侦听与{{#each}}块助手相同的响应式数据源,并在它完成自己的响应式计算后运行您的代码,您可以获得实际请求的行为,而不必依赖某些奇怪的技巧。

这是该模式的完整实现:

JS

Template.content.helpers({
  currentData: function(){
    return Template.currentData();
  }
});

Template.content.onRendered(function(){
  this.autorun(function(){
    var cursor = Template.currentData();
    // we need to register a dependency on the number of documents returned by the
    // cursor to actually make this computation rerun everytime the count is altered
    var count = cursor.count();
    //
    Tracker.afterFlush(function(){
      // assert that every items have been rendered
      console.log(this.$("[data-cid]") == count);
    }.bind(this));
  }.bind(this));
});

我喜欢这个解决方案,而且我认为它很容易使用,所以谢谢!我已经决定在{{#each}}内部使用模板,因为我意识到每个项目后面的回调对于我的问题已经足够了。 - ant45de

1
我遇到过类似的问题,在经过大量搜索后找到了以下解决方案。我尝试使用 TrackeronRendered 和其他技巧,但都没有成功。这可能被认为是更多的一个黑客方法,但它有效。不幸的是我无法记得最初在哪里找到这个解决方案。
从你的模板开始,但在你的 each 后面添加一个模板标签。
<template name="content">
{{#if Template.subscriptionsReady}}
    {{#each currentData}}
        <div data-cid="{{this._id}}"></div>
    {{/each}}
    {{doneTrigger}}
{{else}}
    Loading...
{{/if}}
</template>

然后定义一个返回 null 的辅助函数。
Template.content.helpers({
  doneTrigger: function() {
    Meteor.defer(function() {
      // do what you need to do
    });
    return null;
  }
});

您可以在这里了解更多关于Meteor.defer()的内容,但它等同于使用0毫秒的setTimeout

它正在使用#each的同步特性。有趣,但可能无法在Blaze的进一步更新中生存! - Kyll
确实,正如所提到的,我认为这只是一个临时的解决方案。虽然它能够完成工作,但需要谨慎使用。 - Kassym Dorsel

0

您还可以使用子模板并计算呈现的子模板数量。如果此数字等于集合中的项目数,则所有项目都将被呈现。

<template name="content">
{{#if Template.subscriptionsReady}}
    {{#each currentData}}
        {{> showData}}
    {{/each}}
{{else}}
    Loading...
{{/if}}
</template>

<template name="currentData">
  <div data-cid="{{this._id}}"></div>
</template>

有了那个,初始化一个响应式变量并跟踪它:

var renderedCount = new ReactiveVar(0);

Tracker.autorun(function checkIfAllRendered() {
  if(renderedCount.get() === currentData.count() && renderedCount.get() !== 0) {
    //allDataRendered();
  }
});

在渲染currentData模板时,将其递增,并在其销毁时将其递减。

Template.currentData.onRendered(function() {
  renderedCount.set(++renderedCount.curValue);
});

Template.currentData.onDestroyed(function() {
  renderedCount.set(--renderedCount.curValue);
});

-1
一个可能更简单的方法是创建一个围绕你的#each块的模板,然后在之后获取一个onRendered事件:
html:
<template name="content">
{{#if Template.subscriptionsReady}}
    {{> eachBlock}}
{{else}}
    Loading...
{{/if}}
</template>

<template name="eachBlock">
{{#each currentData}}
    <div data-cid="{{this._id}}"></div>
{{/each}}
</template>

js:

Template.eachBlock.onRendered(function(){
  console.log("My each block should now be fully rendered!");
});

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