#each 完成后如何执行回调?

How to execute a callback after an #each is done?

我在 #each 完成后遇到回调问题。我有一个名为 "content":

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

起初我等待订阅,当它可用时,我用 {{#each}} 遍历我的 Collection 并附加 div。我需要的是 for-each 循环完成时的一种回调(换句话说 DOM 就绪)。

Template.content.onRendered()

-> 提前触发

我还尝试在 {{each}} 之后附加一张图片并在其 onload 中触发一个函数,如下所示:

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

-> 有时确实有效但不知何故不可靠

有没有办法得到这个回调?如果能带来解决方案,我不怕更改此模板的结构。

当空格键 {{#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));
});

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

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

考虑一种可能更简单的方法 - 在您的 #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!");
});

我遇到了类似的问题,经过大量搜索后找到了以下解决方案。我尝试使用 TrackeronRendered 和其他技巧,其中 none 有效。这可以被认为更像是一种 hack,但有效。不幸的是不记得我最初是在哪里找到这个解决方案的。

从您的模板开始,但在您的 each 之后添加一个模板标签。

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

然后定义一个returns null.

的助手
Template.content.helpers({
  doneTrigger: function() {
    Meteor.defer(function() {
      // do what you need to do
    });
    return null;
  }
});

你可以阅读更多关于Meteor.defer() here,但它相当于使用0毫秒setTimeout