Meteor:等待所有的模板都渲染完毕

6
我有以下模板代码。
<template name="home">
    <div class="mainBox">
        <ul class="itemList">
            {{#each this}}
                {{> listItem}}
            {{/each}}
        </ul>
    </div>
</template>

<template name="listItem">
    <li class="item">
        {{username}}
    </li>
</template>

我希望在所有的“listItem”都渲染完毕后执行一段代码。这里大约有100个“listItem”。我尝试了以下方法:

Template.home.rendered = function() {
   // is this called once all of its 'subviews' are rendered?
};

但是它不会等待所有视图都加载完毕。 如何最好地知道所有子视图模板何时加载完毕?

你在某个地方订阅了数据吗?你正在使用Iron Router吗? - Peppe L-G
listItem 模板从一个可能是响应式集合的数据中获取数据,因此您永远无法确定是否已经渲染了所有数据。您可以使用一些技巧来在绘制初始集时获得通知。但是,您可能正在尝试以不符合“Meteor方式”的方式进行操作,因此我建议您寻找更好的解决方案。您想要实现什么目标? - Hubert OG
5个回答

3
这是我的处理方式:

客户端/views/home/home.html

<template name="home">
  {{#if itemsReady}}
    {{> itemsList}}
  {{/if}}
</template>

<template name="itemsList">
  <ul>
    {{#each items}}
      {{> item}}
    {{/each}}
  </ul>
</template>

<template name="item">
  <li>{{value}}</li>
</template>

client/views/home/home.js

Template.home.helpers({
  itemsReady:function(){
    return Meteor.subscribe("items").ready();
  }
});

Template.itemsList.helpers({
  items:function(){
    return Items.find();
  }
});

Template.itemsList.rendered=function(){
  // will output 100, once
  console.log(this.$("li").length);
};

lib/collections/items.js

Items=new Mongo.Collection("items");

server/collections/items.js

insertItems=function(){
  var range=_.range(100);
  _.each(range,function(index){
    Items.insert({value:"Item "+index});
  });
};

Meteor.publish("items",function(){
  return Items.find();
});

server/startup.js

Meteor.startup(function(){
  Items.remove({});
  if(Items.find().count()===0){
    insertItems();
  }
});

我们指定只有在发布准备就绪时才渲染项目列表,因此在此之前数据可用,并且正确数量的
  • 元素将在列表呈现回调中显示。
    现在使用iron:router的waitOn功能进行相同操作:
    client/views/home/controller.js
    HomeController=RouteController.extend({
      template:"home",
      waitOn:function(){
        return Meteor.subscribe("items");
      }
    });
    

    client/lib/router.js

    Router.configure({
      loadingTemplate:"loading"
    });
    
    Router.onBeforeAction("loading");
    
    Router.map(function(){
      this.route("home",{
        path:"/",
        controller:"HomeController"
      });
    });
    

    client/views/loading/loading.html

    <template name="loading">
      <p>LOADING...</p>
    </template>
    

    使用iron:router可能更好,因为它优雅地解决了一种常见的模式:我们不再需要itemsReady助手,只有当由waitOn返回的WaitList全局准备就绪时,主页模板才会被渲染。

    不要忘记添加一个加载模板并设置默认的“loading”钩子,否则它将无法工作。


  • 只是补充一下@saimeunt的答案 - 在我的路由中,我有一个"data:"函数,但是数据函数本身在"subscription"完成之前就运行了,所以你需要添加"this.ready()"条件检查来确保所有数据都被返回 - https://github.com/EventedMind/iron-router/issues/265 - ericbae

    1

    我曾遇到同样的问题,需要等待所有子模板加载完毕后才能调用一个流畅的JavaScript轮播插件(或任何需要整个数据集在DOM中加载后才能调用的酷炫JavaScript插件,如图表或图形)。

    我通过比较子模板的排名和应该返回的总数来解决这个问题。一旦排名等于计数,您就可以从subtemplate.rendered助手调用插件,因为所有子模板都已插入到DOM中。所以在你的例子中:

    Template.listItem.rendered = function() {
    
        if(this.data.rank === ListItems.find({/* whatever query */}).count()) {
           console.log("Last item has been inserted into DOM!");
           //  Call your plugin
           $("#carousel").owlCarousel({
              //  plugin options, etc.
           });
        }
    }
    

    然后你只需要让listItems的助手返回一个等级,这很容易:

    Template.home.helpers({
    
        listItems: function() {         
            return ListItems.find({/* whatever query */}).map(function(listItem, index) {
               listItem.rank = index + 1;  //  Starts at 1 versus 0, just a preference
            });
        }
    }
    

    0

    这个方法的工作方式如下

    当 Template.myTemplate 的一个实例被渲染为 DOM 节点并首次放入文档中时,将调用此回调函数。

    因此,在此情况下,渲染完成后您不会拥有响应式变量。

    // this would sufficient
    Template.listItem.helpers = function() {
       username:function(){
          return ...
       }
    };
    

    0
    我建议使用以下内容:
    var unrendered = [];
    
    Template.listItem.created = function () {
      var newId = Random.id();
      this._id = newId;
      unrendered.push(newId);
    };
    
    Template.listItem.rendered = function () {
      unrendered = _.without(unrendered, this._id);
      if (!unrendered.length) {
        // WHATEVER NEEDS DOING WHEN THEY'VE ALL RENDERED
      }
    };
    

    注意

    这个假设是基于所有的模板实例在第一次渲染之前都已经被创建,否则你的代码将会在它应该运行之前运行。我认为这应该是正确的,但你需要试一下,因为我没有时间运行100多个子模板测试。如果不是这样的话,那么我看不出你如何在不事先知道有多少子模板将被创建的情况下实现这种行为。

    如果你知道将会有多少个子模板,那么上面的代码可以简化为一个计数器,每次rendered运行时递减,这很容易实现。

    unrendered = [number of listitems];
    
    Template.listItem.rendered = function () {
      unrendered--;
      if (!unrendered) {
        // WHATEVER NEEDS DOING WHEN THEY'VE ALL RENDERED
      }
    };
    

    另外,你可能需要meteor add random,但我认为此包现在已被包含在核心中。


    0

    显然有多种处理您的情况的方法。您可以轻松使用模板订阅。

    Template.myView.onCreated(function() {
        var self = this;
    
        self.autorun(function(){
            self.mySub = self.subscribe('mySubscription');
        });
    
        if(self.mySub.ready()) {
            // my sweet fancy code...
        }
    });
    
    
    <template name="myTemplate">
        <ul>
            {{#if Template.subscriptionsReady}}
               {{#each items}}
                   <li>{{item}}</li>
               {{/each}}
            {{else}}
              <div class="loading">Loading...</div>
            {{/if}}
        </ul>
    </template>
    

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