Callback when all template elements end up rendering in Meteor?

I get a collection of records and put them in the template, having them displayed {{#each}} , and I want to display the loading icon until the last DOM node is displayed.

My problem is that I did not find a way to request the callback status / fire for the last element that was added as the last DOM node to update / re-draw.

In my HTML file, it looks something like this:

 <template name="stuff"> {{#each items}} <div class="coolView">{{cool_stuff}}</div> {{/each}} </template> 

And in the client JS file:

 // 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); }; 

The session of the variable loading_stuff requested in the Handlebars helper, returning the name of the loading class (with the GIF loader icon), if true .

The reason I am making inconvenient Meteor.setTimeout is because Template.rendered is called after every single element that has been displayed in the Template. Therefore, I need to re-confirm that it is still loading, but prepare for a while to either download the next one or finish rendering them, with a slight pause, until it sets loading_stuff to false .

How can I reliably request / callback at the end of all DOM updates (for a specific template or for the whole page) the correct Meteor path?

Thank you very much.

EDIT

The subscribe() onReady() callback solution only works partially:

The templates are apparently called multiple (2-3) times before the start of rendering, but after that the data returned from the server on which the template for rendering is based. This means that it has โ€œcompletedโ€ the data loading, but the DOM is still displayed. I will play with Meteor.autorun() and see if I can find a reliable hook somewhere to do it right. In the meantime, I think my initial question still remains:

How to find out when the processing of the entire template / page has ended?

COMPLETION:

At the end of the day, based on how the DOM is structured, the developer has a built-in DOM and applies the appropriate callbacks as best as possible to the elements that he / she wants to check the state. This may seem anti-climatic without problems, but for me the only way to find out which the last element of the template rendered is to have an element with a special identifier displayed at the very end of the template that signals the completion of rendering and attach a .livequery callback to it. I hope that Meteor will include more uniform and global support for checking this condition in the future.

+6
source share
7 answers

I found that the LiveQuery JQuery plugin does not work when you expect the elements from the template to be written to the DOM. This is because the plugin listens for jQuery methods like append, but Meteor does not use jQuery. It uses an object called LiveRange to write to the DOM. I modified my real-time JavaScript file to work with this.

There is a line at the bottom of the file that calls registerPlugin() . I changed it like this:

 $.livequery.registerPlugin([ {object: $.fn, methods: ['append', 'prepend', 'after', 'before', 'wrap', 'attr', 'removeAttr', 'addClass', 'removeClass', 'toggleClass', 'empty', 'remove', 'html']}, {object: LiveRange.prototype, methods: ['insertBefore', 'insertAfter']} ]); 

Then I modified the registerPlugin () method to look like this:

 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; } }); } } 

This will check if the element exists when insertBefore() or insertAfter() is called in the LiveRange object and will work with JQuery, as it was before.

+4
source

Actually can be found the placement of the element template inside the current?

 <template name="stuff"> {{#each items}} {{> itemTemplate}} {{/each}} </template> <template name="itemTemplate"> <div class="coolView">{{cool_stuff}}</div> </template> 

Now the callback in Template.itemTemplate.rendered can be triggered every time a new item is updated in the DOM.

+6
source

You can use a jquery plugin like livequery for subsequent dom entries for a specific selector:

 $('.my-rendered-element').livequery(function () { console.log('I've been added to the DOM'); }); 

If you also need to make sure that the images are uploaded and everything is well displayed, you can use some other utility similar to this other imagesLoaded plugin and do something like:

 $('.my-rendered-element').livequery(function () { $(this).imagesLoaded(function() { console.log('My images have been loaded'); }); }); 

This is a workaround, and it may not integrate very well with Meteor, but I found that these 2 guys are very useful in many situations.

+3
source

In my case, the partial solution is this:

Add the onComplete() callback to the subscriptions of my Items collection, which is the official completion of any loading process. So there is no more hacky setTimeout in Template.rendered , just set loading_stuff to true when requesting data (in Template.stuff.items ) and then to false in the onComplete() callback in the subscription function:

Server:

 Meteor.publish('items', function (some_param) { return Items.find({some_field: some_param}); }); 

and client:

 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}}); }; 

This is a partial solution because knowing when the data comes from the server and when the DOM has finished rendering are two separate problems.

+2
source

There are several options:

  • As you mentioned in your last comment, you can create your own reactive context and process the changes there.
  • You can create a special value, i.e. _id = "END" and find this value in the template rendering.
  • My favorite: instead of using the collection, fill the template with a pair of Meteor.call and Meteor.methods . You can also put the result in a reactive variable so that the template automatically re-displays.

As you may have already discovered, the onReady subscribe() event fires when the publish method calls ready() , and not when all the data has been sent.

Here is a simple example number 3 above:

In the client:

  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); } }); 

On server:

  Meteor.methods({ 'get_complete_email': function(id) { return Emails.findOne({_id:id, user_id:Meteor.userId}, body_message_fields); } }); 

In the code of your presenter or viewer: (the temp data variable can be eliminated - it is outdated and has not yet been reorganized).

 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; } } 

Obviously, you need to connect current_compose, Current and your own objects a little differently.

+2
source

I do it another DIY way. Match the index / rank with your cursor, and then check that index against the same query in the .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(); } } 

I am sure there are a bit more taxes on the server, but it works. I think I'm curious about template.lastNode and if there is a way to use this to have the same effect.

0
source

It is useful for me to run a callback in the onRendered block of the following template:

 <template name="stuff"> {{#each items}} <div class="coolView">{{cool_stuff}}</div> {{/each}} {{> didMyStuffLoad}} </template> 

then

 Template.didMyStuffLoad.rendered = function () { Session.set('loading_stuff', false); } 

Everything will be loaded into the DOM before the onRendered block is executed.

0
source

All Articles