我正在获取一组记录并将它们放在一个模板中,让它们呈现{{#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将来会更加统一和全球支持检查这个状态。
答案 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)
有几个选择:
Meteor.call
和Meteor.methods
来电对填充模板。您还可以将结果放入反应变量中,以便模板自动重新渲染。正如您可能已经发现的那样,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中。