【问题标题】:Callback when all Template items finished rendering in Meteor?当所有模板项目在 Meteor 中完成渲染时回调?
【发布时间】:2013-01-10 23:56:11
【问题描述】:

我正在获取记录集合并将它们放置在模板中,让它们呈现{{#each}},并且我想显示一个加载图标,直到呈现最后一个 DOM 节点。

我的问题是我还没有找到一种方法来查询状态/在最后一个渲染的项目上触发回调,也就是要更新/重新绘制的最后一个 DOM 节点。

在我的 HTML 文件中看起来有点像这样:

<template name="stuff">
    {{#each items}}
        <div class="coolView">{{cool_stuff}}</div>
    {{/each}}
</template>

并在客户端JS文件中:

// returns all records of Stuff belonging to the currently logged-in user
Template.stuff.items = function () {
    Session.set('loading_stuff', true);
    return Items.find({owner: Meteor.userId()}, {sort: {created_time: -1}});
};

Template.stuff.rendered = function() {
    // every time something new is rendered, there will have been loading of the DOM. 
    // wait a short while after the last render to clear any loading indication.
    Session.set('loading_stuff', true);
    Meteor.setTimeout(function() {Session.set('loading_stuff', false);}, 300);
};

在 Handlebars 帮助器中查询 Session 变量 loading_stuff,如果它是 true,则返回加载类的名称(带有加载器图标 GIF)。

我之所以使用尴尬的Meteor.setTimeout 是因为Template.rendered 在被渲染到模板中的每个项目之后被调用。所以我需要重新确认它仍在加载,但给它一些时间来加载下一个或完成全部渲染,稍等片刻,直到它将loading_stuff 设置为false

如何在所有 DOM 更新(针对特定模板或整个页面)结束时以正确的 Meteor 方式可靠地查询/回调?

非常感谢。

编辑

使用subscribe() onReady() 回调的解决方案仅部分有效:

在渲染开始之前,模板似乎被多次调用 (2-3) 次,但 之后,数据已从模板所依赖的渲染服务器返回。这意味着它已经“完成”了加载数据,但 DOM 仍在渲染中。我将与Meteor.autorun() 一起玩,看看我是否可以在某个地方找到一个可靠的钩子来正确地做到这一点。与此同时,我想我原来的问题仍然存在:

我如何知道整个模板/页面何时完成渲染?

最终编辑:

归根结底,根据 DOM 的结构,开发人员需要拥有坚如磐石的 DOM,并尽可能将适当的回调附加到他/她想要检查状态的元素上.这似乎是一个反气候的明智之举,但对我来说,知道模板的最终元素被渲染的唯一方法是在模板的最后呈现一个具有特殊 id 的元素,这标志着结束渲染并附加一个.livequery 回调给它。我希望 Meteor 将来会为检查这种状态提供更统一和全球性的支持。

【问题讨论】:

标签: meteor publish-subscribe


【解决方案1】:

在当前模板中嵌套一个项目模板可能真的有帮助吗?

<template name="stuff">
   {{#each items}}
     {{> itemTemplate}}
   {{/each}}
</template>

<template name="itemTemplate">
   <div class="coolView">{{cool_stuff}}</div>
</template>

现在,Template.itemTemplate.rendered 上的回调可能会在每次将新项目更新到 DOM 时运行。

【讨论】:

    【解决方案2】:

    我发现 JQuery 的 livequery 插件在等待模板中的元素写入 DOM 时不起作用。这是因为该插件侦听 JQuery 方法(如 append),但 Meteor 不使用 JQuery。它使用一个名为 LiveRange 的对象来写入 DOM。我修改了我的 livequery JavaScript 文件以使用它。

    在文件底部附近,有一行调用registerPlugin()。我将其修改为如下所示:

    $.livequery.registerPlugin([
        {object: $.fn, methods: ['append', 'prepend', 'after', 'before', 'wrap', 'attr',    'removeAttr', 'addClass', 'removeClass', 'toggleClass', 'empty', 'remove', 'html']},
        {object: LiveRange.prototype, methods: ['insertBefore', 'insertAfter']} 
    ]);
    

    然后我将 registerPlugin() 方法修改为如下所示:

    registerPlugin: function(registration) {
        for (var i = 0; i < registration.length; i++) {
            var object = registration[i].object;
            var methods = registration[i].methods;
    
            $.each( methods, function(i,n) {
                // Short-circuit if the method doesn't exist
                if (!object[n]) return;
    
                // Save a reference to the original method
                var old = object[n];
    
                // Create a new method
                object[n] = function() {
                    // Call the original method
                    var r = old.apply(this, arguments);
    
                    // Request a run of the Live Queries
                    $.livequery.run();
    
                    // Return the original methods result
                    return r;
                }
            });
        }
    }
    

    当在LiveRange 对象上调用insertBefore()insertAfter() 时,这将检查元素是否存在,并且仍然可以像以前一样使用JQuery。

    【讨论】:

    • 好东西,我现在时间很少,昨晚我尝试玩 livequery,但很快就放弃了,因为事情根本没有洞察力,所以很高兴看到我尝试时并没有完全迟钝。我将尝试您建议的修改并尽快回复您,希望在这个周末。干杯!
    • 很高兴我能够减轻您对智力的担忧!希望这对您的 Meteor 问题也有帮助。
    • 我发现无论出于何种目的和目的,livequery 基本上都做了Template.rendered 已经做的事情;根据您的模板和其中的 div 的结构,它会触发与呈现的新实例一样多的次数。归根结底,Meteor 似乎经常触发重新渲染,有时基于 DOM 的结构是不可预测的,因此开发人员有一个坚如磐石的 DOM 并尽可能地将适当的回调附加到他/她想要检查的元素。
    【解决方案3】:

    您可以使用像 livequery 这样的 jquery 插件来跟踪特定选择器的 dom 插入:

    $('.my-rendered-element').livequery(function () {
        console.log('I've been added to the DOM');
    });
    

    如果您还需要确保图像已加载并且所有内容都很好地呈现,您可以使用其他一些实用程序,例如其他插件 imagesLoaded,并执行以下操作:

    $('.my-rendered-element').livequery(function () {
        $(this).imagesLoaded(function() {
            console.log('My images have been loaded');
        });
    });
    

    这是一种解决方法,它可能无法与 Meteor 很好地集成,但我发现这两个家伙在很多情况下都非常有用。

    【讨论】:

      【解决方案4】:

      就我而言,部分解决方案是这样的:

      onComplete() 回调添加到我的Items 集合的订阅中,这是任何加载过程的正式结束。因此,在Template.rendered 中不再使用hacky setTimeouts,只需在查询数据时(在Template.stuff.items 中)将loading_stuff 设置为true,然后在订阅函数的onComplete() 回调中设置为false

      服务器:

      Meteor.publish('items', function (some_param) {
          return Items.find({some_field: some_param});
      });
      

      和客户:

      Meteor.subscribe('items', some_param, function onComplete() {
          Session.set('loading_stuff', false);
      });
      

      ...

      Template.stuff.items = function () {
          Session.set('loading_stuff', true);
          return Items.find({owner: Meteor.userId()}, {sort: {created_time: -1}});
      };
      

      这是一个部分解决方案,因为知道数据何时从服务器到达以及 DOM 何时完成渲染是两个独立的问题。

      【讨论】:

      • 对于那些想要做更多有趣事情的人,Meteor.autorun 提供了一种在客户端中定义自己的反应式上下文的方法,因此您可以在处理数据/事件/渲染时做一些非常高级的事情。 docs here
      • 旧答案,但这不是一个好的解决方案,因为如果 items 被添加或删除,Meteor.subscribe 不会重新运行。因此,这将适用于初始加载,但不适用于反应性。请参阅下面的答案。
      【解决方案5】:

      有几个选项:

      1. 正如您在最后评论中提到的,您可以创建反应式上下文并在那里处理更改。
      2. 您可以创建一个特殊值,即。 _id = "END" 并在模板渲染中查找该值。
      3. 我的最爱:不使用集合,而是通过 Meteor.callMeteor.methods 调用对填充模板。您还可以将结果放入反应变量中,以便模板自动重新渲染。

      您可能已经发现,subscribe()onReady 事件是在发布方法调用 ready() 时触发的,而不是在所有数据传送过来时触发。

      这是上面 #3 的一个简单示例:

      在客户端:

        Meteor.call('get_complete_email',id, function(err,doc) {
          if (err === undefined) {
            // Note current_compose is reactive
            current_compose.set(new _ComposePresenter(action, doc));
          } else {
            log_error('compose','get complete email ',err);
          }
        }); 
      

      在服务器中:

       Meteor.methods({
         'get_complete_email': function(id) {
           return Emails.findOne({_id:id, user_id:Meteor.userId}, body_message_fields);
         }
       });
      

      在您的演示者或查看者代码中:(data 临时变量可以被删除 - 它是遗留的,尚未被重构)。

      Template.compose_to_cc_bcc_subject.prefilled = function(part) {
        if (Current && Current.compose()) { 
          var data = Current.compose();
          if (data == undefined || data[part] == undefined) { return; }
          return data[part]; 
        } else {
          return;
        }
      }  
      

      显然,您需要将 current_compose、Current 和您自己的对象连接起来稍有不同。

      【讨论】:

        【解决方案6】:

        我用另一种 DIY 方式来做。将索引/排名映射到光标上,然后根据 .rendered 回调中相同查询的计数检查该索引:

        items: function() {   
           return Items.find({whatever: whatever}).map(function(item, index) {
              item.rank = index + 1;
              return item;
           });
        }
        
        Template.stuff.rendered = function() { 
          if(this.data.rank === Items.find({whatever: this.data.whatever}).count()) {
             //  Do something cool like initializing a sweet
             //  JS plugin now that ALL your template instances 
             //  are all rendered
             $("#coolplugindiv").coolJSPluginInit();
          }
        }
        

        我敢肯定这对服务器的负担会稍微重一些,但它确实有效。我想我很好奇template.lastNode 以及是否有某种方法可以使用它来产生相同的效果。

        【讨论】:

          【解决方案7】:

          我发现在下一个模板的 onRendered 块中运行回调很有帮助:

          <template name="stuff">
            {{#each items}}
              <div class="coolView">{{cool_stuff}}</div>
            {{/each}}
            {{> didMyStuffLoad}}
          </template>
          

          然后:

          Template.didMyStuffLoad.rendered = function () {
            Session.set('loading_stuff', false);
          }
          

          在这个 onRendered 块执行之前,所有的东西都会被加载到 DOM 中。

          【讨论】:

            猜你喜欢
            • 2014-09-22
            • 2014-05-09
            • 1970-01-01
            • 2014-12-08
            • 1970-01-01
            • 2014-12-12
            • 2015-06-16
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多