- {{...">
Meteor: until all patterns are displayed
I have the following template code
<template name="home"> <div class="mainBox"> <ul class="itemList"> {{#each this}} {{> listItem}} {{/each}} </ul> </div> </template> <template name="listItem"> <li class="item"> {{username}} </li> </template> And I would like to execute the code as soon as ALL of the "listItem" are rendered. There are about 100 of them. I tried the following
Template.home.rendered = function() { // is this called once all of its 'subviews' are rendered? }; But this does not wait for the download of all views.
What's the best way to find out when all subtask templates load?
This is how I continue:
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 indicate that we want to display our list of positions only if the publication is ready, so by this time the data will be available, and the correct number of li elements will be displayed in the callback processed by the list.
Now the same with the iron:router waitOn :
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 elegantly solves the general pattern: we no longer need the itemsReady helper, the home pattern will only be displayed when the WaitList returned by waitOn is globally ready.
Do not forget to add both the download template and set the default "download", otherwise it will not work.
I had the same problem when I had to wait for all my subtopics to load before calling the JavaScript plugin for the carousel (or any cool JavaScript plugin like charts or graphs that need the whole dataset loaded in the DOM before call him).
I solved this by simply comparing the rank of the subtopic with the total counter that should be returned for any request I made. Once the rank is equal to the score, you can call your plugin from the subtemplate.rendered , because all subtopics 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 the rank, which is simple 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 }); } } method performed in this way
This callback is called once when the Template.myTemplate instance is mapped to DOM nodes and placed into the document for the first time.
therefore, when rendering, you do not have a reactive variable in this case.
// this would sufficient Template.listItem.helpers = function() { username:function(){ return ... } }; I would 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 under the assumption that essentially all instances of the templates will be created before the first ones are displayed, otherwise your code will be run before it is executed. I think this should be the case, but you will have to try, since I do not have time to run the test on more than 100 templates. If this is not so, then I canβt understand how you can achieve this behavior without knowing in advance how many subatmaps will be created.
If you know how much will be, then the code above can be simplified to a counter, which decreases every time rendered is rendered , and this is easy.
unrendered = [number of listitems]; Template.listItem.rendered = function () { unrendered--; if (!unrendered) { // WHATEVER NEEDS DOING WHEN THEY'VE ALL RENDERED } }; In addition, you may need meteor add random , but I think this package is now included in the kernel.
There are apparently various ways to deal with your situation. You can 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>