Blaze: How to execute code after a reactive re-rendering is completed?


#1

I am following the recommendation to do subscriptions on the template level. Of course, the transfer of the data to the client may take some time. Also, I have to do some additional calculations until everything is ready for rendering.

Hence, I am using a {{#with}} statement to trigger re-rendering of my data once everything is ready, like this:

{{#with something}}
   <div id="description">{{description}}</div>
{{/with}}

In this case, ‘description’ is some text property of the something object.

Inside an .autorun(), I am waiting for the data, and as soon as everything is ready, I assign this data to a ReactiveVar, which is then returned as ‘something’ by a helper like this:

something: function() {
  return Template.instance().something.get();
}

This works so far, everything is rendered as expected as soon as the data becomes available.

The problem occurs if I have to also initialize a jQuery plugin to modify the DOM after the rendering. In my example, I have a plugin that shortens the description text to a certain limit of characters.

I assumed that I could trigger this jQuery plugin in an .autorun() like this:

Template.myTemplate.onRendered(function() {
  var instance = this;
  instance.autorun(function() {
    if (instance.something.get()) {
        // do the jQuery call
    }
  });
});

In fact, this .autorun() will be triggered, but it runs too early, i.e. before Blaze has actually rendered the {{description}}.

Hence, I changed my HTML to this:

{{#with something}}
   <div id="description">{{description}}</div>
   {{trigger}}
{{/with}}

and put the jQuery initialization into this trigger helper like this:

trigger: function() {
  var something = Template.instance().something.get();
   if (something) {
   ... do the jQuery call
   }
}

but also in this case, the jQuery call is done too early.

So, is there any way to run a DOM manipulation after all data on the page has actually being rendered completely?


#2

I can’t be sure since I don’t have a full picture of what you are doing, but I really think you are doing this in a very over complicated and unintended way.


#3

Better describe why you need to manipulate DOM and maybe there will be better way.

Still we are using templates inside {{ #with }} and triggering it in theirs onRendered.
That would be 1 workaround.


#4

Ok. Let me try to put it in simple words:

  1. The template subscribes to data and has to wait for the data to arrive (using autorun)
  2. Once the data is ready, the data is being enriched (also inside this autorun)
  3. Once this preparation is complete, rendering shall take place
  4. After the the rendering is finished, a jQuery plugin shall modify the DOM

How do I do this correctly?

(PS: I am using FlowRouter and following it’s recommendation to prepare the data on template level instead of the router level. Everything would be of course a lot easier if the template would only be called after the data was ready completely.)


#5

The DOM manipulation is for shortening the text. This is just a simple example that might be easily done in a helper instead. But another sample would be a more complex component (for instance, I am using the rating component of Semantic UI on the same page), that really requires to use the jQuery plugin instead of re-inventing the wheel on helper level.


#6

For that rating component you can prepare specific template which will not have any spacebars iterators and will have the component html in it. And there in that template when onRendered run, html for component is 100% ready.

Cause it is static.


#7

Thanks for your reply. Yes, I already used this nested approach in another situation, where I had to initialize a gallery plugin. But this forces me to use a lot of sub-templates (even for something simple like responsively shortening a text), so I was wondering if there is any other way to wait until rendering has finished completely inside the main template?


#9

@waldgeist, can you give me an example of the more complicated use case that you couldn’t accomplish using a helper?


#10

I came to same multiline ellipse usecase, but after some thinking it could be done by checking parent container width/height and running that js calculation inside helper


#11

@copleykj: Sure.

One use-case is: adaptively shorten a multi-line text in a DIV to exactly 2 lines, using ellipses. In a responsive way, of course, i.e. reacting on window resizes. I wouldn’t see a way to do this in a helper.

My second use-case is a pre-existing user interface widget, based on Semantic UI or Bootstrap. Often, these plugins also require some initialization to be done once the DOM is up and running.

And of course, I do not want to program all this functionality myself (in a helper), if there’s already a good plugin available.


#12

@shock: This might work for the initial calculation. But how to you manage to react on window size changes then? Of course, I could attach events and program this myself. But I’d rather use an existing jQuery plugin instead, to save development time.


#13

The JS I have seen was not long, but we removed it from MVP todo list for now :smiley:


#14

So you’re in the lean startup business, too? :wink:


#15

Unfortunately in these cases your best bet is going to be breaking your template into a sub template. The problem is that there is no way to determine when the data is finished loading because you could get a batch of 10 records and then 200ms later get another batch of 100 records, and more could come in at any point. The only way to be sure that you’ll have the DOM nodes you need at the time of render is to break things down to the document level where the template corresponds 1:1 with the document.


#16

Thanks for clarifying this. I already built a sub-template, and now it works as a charm. And I have to admit that the code even looks better now, as it is cleaner this way.