How to call back after completing #each?

I am having problems with a callback after completing #each . I have a template called "content":

 <template name="content"> {{#if Template.subscriptionsReady}} {{#each currentData}} <div data-cid="{{this._id}}"></div> {{/each}} {{else}} Loading... {{/if}} </template> 

First, I wait for a subscription, when it is available, I {{#each}} over my collection using {{#each}} and add a div . I need some kind of callback when the for-each loop is executed (in other words, the DOM is ready).

 Template.content.onRendered() 

-> early triggers

I also tried adding an image after {{each}} and running the function onload as follows:

 <img style="height:0;width:0" src="*mysource*" onload="callback()"> 

-> worked sometimes, but was not sure in any way

Is there any way to get this callback? I am not afraid to change the structure of this template if it brings a solution.

+6
source share
4 answers

I had a similar problem, and after many searches, the following solution was found. I tried using Tracker , onRendered and other tricks, none of them worked. This can be considered rather a hack, but it works. Unfortunately, I can’t remember where I found this solution initially.

Start with the template, but add the template tag after each .

 <template name="content"> {{#if Template.subscriptionsReady}} {{#each currentData}} <div data-cid="{{this._id}}"></div> {{/each}} {{doneTrigger}} {{else}} Loading... {{/if}} </template> 

Then define a helper element that returns null.

 Template.content.helpers({ doneTrigger: function() { Meteor.defer(function() { // do what you need to do }); return null; } }); 

More on Meteor.defer() here , but this is equivalent to using 0 milliseconds setTimeout .

+1
source

There is no easy way to get notified when the Spacebars {{#each}} block rendered in the DOM, every element that has been renamed.

The best solution is to use another reactive computation ( Tracker.autorun ) to monitor your (reactive) current data.

Each time your current data (probably the cursor) changes, you can run arbitrary code after performing all other reactive calculations that perform any of your work using Tracker.afterFlush .

The {{#each}} block is one of those calculations whose role is to listen to the source of reactive data that you pass to it as an argument, and repeat it Template.contentBlock as many times as there are elements extracted from the original iteration using the current element as the current data context.

Listening to the same reactive data source as the auxiliary block {{#each}} , and running your code AFTER completing its own reactive calculation, you can get the actual requested behavior without relying on some weird tricks.

Here is the full implementation of this template:

Js

 Template.content.helpers({ currentData: function(){ return Template.currentData(); } }); Template.content.onRendered(function(){ this.autorun(function(){ var cursor = Template.currentData(); // we need to register a dependency on the number of documents returned by the // cursor to actually make this computation rerun everytime the count is altered var count = cursor.count(); // Tracker.afterFlush(function(){ // assert that every items have been rendered console.log(this.$("[data-cid]") == count); }.bind(this)); }.bind(this)); }); 
+3
source

You can also use subpatterns and count the number of subpatterns. If this number is the number of elements in the collection, then all of them are displayed.

 <template name="content"> {{#if Template.subscriptionsReady}} {{#each currentData}} {{> showData}} {{/each}} {{else}} Loading... {{/if}} </template> <template name="currentData"> <div data-cid="{{this._id}}"></div> </template> 

Initialize the reactive variable and track it:

 var renderedCount = new ReactiveVar(0); Tracker.autorun(function checkIfAllRendered() { if(renderedCount.get() === currentData.count() && renderedCount.get() !== 0) { //allDataRendered(); } }); 

When the currentData template currentData displayed, increase and decrease it when it is destroyed.

 Template.currentData.onRendered(function() { renderedCount.set(++renderedCount.curValue); }); Template.currentData.onDestroyed(function() { renderedCount.set(--renderedCount.curValue); }); 
0
source

Perhaps a simpler approach to consider is to create a template around your #each block, and then get the onRendered event afterwards:

HTML:

 <template name="content"> {{#if Template.subscriptionsReady}} {{> eachBlock}} {{else}} Loading... {{/if}} </template> <template name="eachBlock"> {{#each currentData}} <div data-cid="{{this._id}}"></div> {{/each}} </template> 

JS:

 Template.eachBlock.onRendered(function(){ console.log("My each block should now be fully rendered!"); }); 
-1
source

All Articles