当所有模板项目在Meteor中完成渲染时回调?

时间:2013-01-27 14:52:25

标签: meteor publish-subscribe

我正在获取一组记录并将它们放在一个模板中,让它们呈现{{#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帮助程序中查询会话变量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将来会更加统一和全球支持检查这个状态。

7 个答案:

答案 0 :(得分:6)

将项目模板嵌套在当前模板中可能实际上有帮助吗?

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

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

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

答案 1 :(得分:4)

我发现当等待模板中的元素写入DOM时,JQuery的livequery插件不起作用。这是因为插件侦听了追加等JQuery方法,但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;
            }
        });
    }
}

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

答案 2 :(得分: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很好地集成,但我发现这两个人在很多情况下非常有用。

答案 3 :(得分:2)

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

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

服务器:

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何时完成呈现是两个不同的问题。

答案 4 :(得分:2)

有几个选择:

  1. 正如您在最终评论中所提到的,您可以创建您的反应性上下文并在那里处理更改。
  2. 您可以创建一个特殊值,即。 _id =“END”并在模板渲染中查找该值。
  3. 我的最爱:不是使用集合,而是根据Meteor.callMeteor.methods来电对填充模板。您还可以将结果放入反应变量中,以便模板自动重新渲染。
  4. 正如您可能已经发现的那样,onReady事件subscribe()会在发布方法调用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和你自己的对象。

答案 5 :(得分:0)

我是另一种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感到好奇,以及是否有某种方法可以使用它来产生同样的效果。

答案 6 :(得分:0)

我发现在下一个模板的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中。