6

我正在获取一组记录并将它们放在模板中,让它们呈现{{#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);
};

loading_stuff在 Handlebars 助手中查询Session 变量,如果它是true.

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

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

非常感谢。

编辑

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

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

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

最终编辑:

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

4

7 回答 7

6

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

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

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

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

于 2015-02-17T14:44:01.977 回答
4

我发现 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;
            }
        });
    }
}

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

于 2013-01-30T03:51:59.127 回答
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 很好地集成,但我发现这两个家伙在很多情况下都非常有用。

于 2013-01-28T15:26:24.297 回答
2

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

onComplete()回调添加到我的收藏的订阅中Items,这是任何加载过程的正式结束。所以,没有更多的 hacky setTimeouts in Template.rendered,只需设置loading_stufftrue查询数据时(in Template.stuff.items),然后在 subscribe 函数falseonComplete()回调中设置为:

服务器:

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 何时完成渲染是两个独立的问题。

于 2013-01-27T16:14:06.600 回答
2

有几个选项:

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

正如您可能已经发现的那样,当调用 publish 方法时触发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 和您自己的对象稍微不同地连接起来。

于 2013-01-28T13:49:01.527 回答
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,是否有某种方法可以使用它来产生相同的效果。

于 2015-07-28T20:21:35.927 回答
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 中。

于 2016-04-28T13:25:24.470 回答