I have the following template code
{{#each
the method rendered works of this way
This callback is called once when an instance of Template.myTemplate is rendered into DOM nodes and put into the document for the first time.
so, when is rendered you doesn't have variable reactive in this case.
// this would sufficient
Template.listItem.helpers = function() {
username:function(){
return ...
}
};
I had this same problem with needing to wait on all my subtemplates to load before calling a slick JavaScript carousel plugin (or any cool JavaScript plugin like charts or graphs that need your whole data set loaded in the DOM before calling it).
I solved it by simply comparing the rank of the subtemplate to the overall count that should be returned for whatever query I was doing. Once the rank is equal to the count, you can call your plugin from the subtemplate.rendered
helper because all the subtemplates have been inserted into the DOM. So in your example:
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.
});
}
}
Then you just need your helper for listItems
to return a rank, which is easy enough:
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
});
}
}
Apparently there are various ways to handle your situation. You could easily use template subscriptions.
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>
This is how I proceed :
client/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();
}
});
We specify that we want to render our list of items only when the publication is ready, so by that time data is available and the correct number of li
elements will get displayed in the list rendered callback.
Now the same using iron:router
waitOn
feature :
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>
Using iron:router
is probably better because it solves a common pattern elegantly : we don't need the itemsReady helper anymore, the home template will get rendered only when the WaitList
returned by waitOn will be globally ready.
One must not forget to add both a loading template and setup the default "loading" hook otherwise it won't work.
I'd suggest something like:
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
}
};
CAVEAT
This works on the assumption that essentially all template instances will be created before they first ones have been rendered, otherwise your code will run before it should. I think this should be the case, but you'll have to try it out as I don't really have time to run a 100+ sub-template test. If it's not the case, then I can't see how you can achieve this behavior without knowing in advance exactly how many sub-templates will be created.
If you do know how many there will be then the code above can be simplified to a counter that decrements every time rendered
runs, and it's easy.
unrendered = [number of listitems];
Template.listItem.rendered = function () {
unrendered--;
if (!unrendered) {
// WHATEVER NEEDS DOING WHEN THEY'VE ALL RENDERED
}
};
Also, you may need to meteor add random
, but I think this package is now included in core.