Does onRendered work? (in a useful way...)

Let me explain…

I’m not talking about post a subscription.isready, or are elements of templates loaded (obviously important) - but rather are ALL elements of the DOM actually rendered?

What I’ve found w/ onRendered and working with dynamic templates / adds, etc. the OnRendered will fire long before the elements (particularly img) have actually rendered. Effect being that any jquery type of control (e.g., DataTables, pick your carousel, etc.) don’t initialize properly. Same can be said for any dynamically applied css.

As a temporary work around, I’ve wrapped onRendered logic in a Meteor.setTimeout - but that isn’t sustainable (given connectivity, mobility delays, etc.) - but it does make things work at least in the laboratory.

My ask - is there a way to actually detect when ALL elements of the DOM have been loaded and are ready?

3 Likes

I’ve had this question as well, and solved it in the same way. Part of the problem is reactivity, because if the data changes, then so will the UI and what it attaches to, and subscriptions (being reactive) may not be fully loaded before the jQuery runs. So you could run your code in a tracker.autorun statement, and reference the subscribed elements, but I don’t really know the best pattern here. I think react handles this well.

Glad to know I’m not the only one! Tried the autorun in the onCreated - that part works fine. As far as the DOM and rendering - this may specifically be an issue tied to ? I can get text to refire, but it’s anything to the images possibly?

You can’t easily tell when all injected images are loaded etc if that is your problem.
If your problem is simple race condition then a good way to somehow overcome is to use requestAnimationFrame to actually wait for the next frame. setTimeout can be fired too early.
Even all mighty React has same issue when firing componentDidMount.

The thing is you should think about the pieces of your app like components. If you are rendering the entire app in one template and that template has each loops and if statements within it those things may not be rendered when onRendered is called because they are relient of some sort of reactive data. However everything did render above it correct? So the way you can fix this is by making smaller templates an within those templates use their on rendered to bind your jQuery libs. for example lets use a jQuery Calendar plugin such as this one https://atmospherejs.com/drewy/datetimepicker if I create a component/template named DateTimePicker then I know that I can isolate the loading of the jQuery lib like so.

Blaze

Template.DateTimePicker.onRendered(function () {
   this.$('input').datetimepicker()
});

Blaze Template

<Template name="DateTimePicker">
   <input type="text" />
</Template>

ParentTemplate

<Template name="parent">
  <div>
  {{#if something}}
    {{> DateTimePicker}}
  {{/if}}
  </div>
</Template>

Now that the child component has been isolated the data doesnt interfere with the onRendered and the jQuery lib can be isolated to the loading of the component.

5 Likes

Thanks Patrick,

Let’s take this conversation in two different directions. One to DataTables and one 's.

DataTables:
With DataTables what I’ve seen is when dynamically adding data to the server source of the table things freak out. Hidden cols become visible. New rows are added… but the DataTable doesn’t recognize the value when sorting, etc.

I’m guessing the analogy to the above here would be to breakout the

's into their own template. Something like…

Parent Template

{{# each myrows}} {{> childrow}} {{/each}}
(...)

Parent .js
OnRendered(
$.(#myDataTable).DataTables(options);
);

Child Template
<-tr>
<-td>{{value1}}<-/td>
<-td>{{value2}}<-/td>
<-td->{{value3}}<-/td>

<-/tr->

child.js
would we move the DataTable init here? It will get initialized on EVERY pass?

Carousels:
Here I’ve seen the suggestion to render the carousel in the child records before. My experience has been, however, that the carousel arrow controls don’t render in the right place, the action of the carousel is off / jagged awkward transitions, and if you reload the image set (say while switching between two different products in a retail catalog) you end up w/ phantom, empty slides from the previous product. The model very closely mirrors what you have:

Parent.html

{{each product}} ... item details ...}} {{> child}} {{/each}}

child.html

child.js
$.(#myCarousel).carousel(options);

Would we maybe destroy the carousel at the parent level everytime?

See if plugins support some kind of refresh functionality. If so, use that to update view. Otherwise go with your idea of destroying carousel instance and initing a new one. But I’m agains that as on bigger DOM it will have performance cost.

I would suggest you to drop plugins and implement your own solution. Filtering and sorting table data is really easy.
As for carousel, it’s not that difficult to implement ii as well.

1 Like

@andrejsm is correct you need to see if the jQuery plugins offer support for refresh or adding objects to their current instance. Packages like these have already taken advantage of this.

https://atmospherejs.com/ephemer/reactive-datatables

https://atmospherejs.com/jasonford/reactive-carousel

If you are interested in how they did so you should check out their source. A lot of times they are using either a tracker to check for changes or observing the cursor http://docs.meteor.com/#/full/observe_changes

1 Like

ugh - I was afraid someone may say that. Ripping out DataTables is going to be… painful… kind of like that Vin Diesel movie where he breaks his leg in the beginning and sticks the bone back in? (or Kevin Costner in Dances w/ wolves for an OLDER generation).

1 Like

lol Well it either that or replicating what they have done over and over again xD so id say its more or less like ripping off a bandaid…slowly…

I recently ran into the same issue and fixed it like so

imgOnload = function() {
  // do stuff with image
};

and in the template

          <img src="{{image.url}}" onload=imgOnload()>

I am sure there are better ways to handle the scope, that is associate the imgOnload to the template somehow, but I wasn’t able to get to the template context for a function …

1 Like

Hi Jam,

I had definitely thought in this direction too. May I ask, what particularly is in your “// do stuff with image”? What I had been thinking about was trying to use a carousel “add method / slide” (presuming they had one) to manually add a slide with each image load.

  • MRT

I am initializing cropper.js during int the image load.

Jan

I’d try recreating the control after the data is updated and see how slow it is before ripping out everything.

The trick is not to recreate it while it’s still updating all the data / computing the reactive changes. Use something like:
Tracker.afterFlush() - it’s a handy function which has saved me a lot of timing headaches. It sometimes can be a bit naff in terms of how the partial rendering looks to the user but you could always cheat and grey it out till it’s loaded.

var scheduled = false;
Template.myTemplate.onRendered(function () {
    var self = this;
    this.autorun(()=> {
        if (!self.subscriptionsReady()) //you can use this or self as it's an arrow function, but this is being explicit
            return;
        self.data.theCursor.getData(); //a hacky way to be added to the reactive dependants of the cursor
        if (!scheduled) {
            Tracker.afterFlush(() => myCreateFunc(self));
            scheduled = true;
        }
    });
});

myCreateFunc won’t be able to call Template.instance() etc (which caught me out a few times), hence passing the reference.

Yeah this is soooo true!!

In my case i was trying to grab a container via $('#container .element'), BUT the #container was actually OUTSIDE of the template-scope. This is way $-jquery was NOT able to grab the element within onRendered().

A simple $('.element')} did the trick

I don’t know if this is still relevant for anyone here, but component-based programming solves this issue in a very elegant way. The UI component in charge of detecting when the onRendered event is triggered is the very same component that is being rendered, higher level components (parents) should not be aware of the rendering process of their children.

In this scenario and architecture pattern, each smart UI component should be responsible for its own state and life cycle. This allows us to create more complex and modular UIs and apps.

If you think about how Meteor works, you realize that async programming is at its core, and therefore the state of a UI portion that is bound to a dynamic resource is not determined by higher level wrappers or traditional checkers (onload event for the window object), but by the resource and the state of it. This reactivity comes as a constant flow of information through a pipe connection between the client and the server.

This is based upon event-driven programming, which is at the core of NodeJS (another technology used by Meteor for the backend or server side) and the DDP (Distributed Data Protocol).

All of this responds to an underlying architecture based upon the single responsibility principle (one of the five SOLID principles).

Template.imageContent.onRendered( () => {
    window.requestAnimationFrame(function() { // wait a frame so that any images are rendered into the DOM
        window.requestAnimationFrame(function() { // it's tied to requestAnimationFrame() so that it will actually wait until the next frame
            // do stuff now that content is actuallly rendered in the DOM
            createCarousel();
        });
    });
});