Order of onRendered execution in parent-children templates


#1

Excluding special cases like the one highlighed here , can be generally stated that onRendered on children templates gets executed before the parent template one?


#2

If the data is on the client then yes. If you’re using data from a subscription then you’ll most likely get the parent rendered first (without children) and then the children (once the data is on the client).


#3

So in case 2, you said “likely”, so I suppose that the only certain sequence of execution I can base my code on to correlate parent and child actions is:

  1. Intercept when a subscription gets ready from the parent
  2. then store some data in the parent context space
  3. so when the child execute its onRendered I can retrieve parent’s data stored previously and use it to change ─ in my case ─ an attribute on the screen.

I tried this pattern, but in the client onRendered callback the parent data context is null. I probably made some errors. This is the code:

In the parent template I have:

Template.todolistItems.onCreated(function(){
  var that = this;
  this.autorun(function(){
    if (that.subscriptionsReady()) {
      var item = TodolistItems.find({}, {limit : 1, fields : {_id : 1}});
      that.firstItemId = '#' + item.fetch()[0]._id;
    }
  });
});

in the client onRendered I have this:

Template.todolistItem.onRendered(function() {
  if (Template.parentData(1).firstItemId === Template.parentData(0)._id) {
    this.find('#' +Template.parentData(0)._id).focus();
  }
});

apparently Template.parentData(1) is null.

Can you see where I made a mistake?


#4

In your case I would do:

Template.todolistItems.onCreated(function(){
  var todolistItems = this;
  todolistItems.subscribe("TodolistItems", function(){
    Session.set("TodolistItemsReady", true);
  });
});
Template.todolistItem.onCreated(function(){
  var todolistItem = this;
  todolistItem.autorun(function(c) {
    if (Session.get("TodolistItemsReady")) {
      c.stop();
    } else {
      var firstId = TodolistItems.findOne({}, {limit : 1, fields : {_id : 1}})._id;
      if (firstId === Template.currentData()._id) {
        todolistItem.find('#' + firstId).focus();
      }
    }
  });
});

Edit: The session check and c.stop() might need to happen after the findOne and if (firstId ===...). I think you can have the case where the session is set to true before the item is focused.


#5

I didn’t actually pasted all the code in my parent on created. It was:

Template.todolistItems.onCreated(function(){
  var that = this;
  this.autorun(function(){
    if (Session.get('current-todolist')) {
      that.subscribe("todolist_items", Session.get('current-todolist')._id);
    }
  });
   this.autorun(function(){
    if (that.subscriptionsReady()) {
      var item = TodolistItems.find({}, {limit : 1, fields : {_id : 1}});
      that.firstItemId = '#' + item.fetch()[0]._id;
    }
  });
});

Sorry.
If I understand well you suggest to use a Session variable to synchronize child function execution after the parent did its job. But what’s “c” of c.stop() ?


#6

c is for computation. We stop it because once the subscription is ready and the session variable is true then it’s not going back to false. So there’s no point in keeping track of the session.


#7

Thanks, I didn’t know that autorun function can take a parameter. After your reply I went back to Meteor Documentation and I found exactly that:

The autorun function is called with one argument: the Computation object that represents this particular autorun.

https://manual.meteor.com/#deps-accessingcurrentcomputation

Anyhow I really appreciated your reply and I modified my code to apply a pattern similar to the one you proposed. On the other hand part of my initial question is still open and I really appreciated your opinion.

What did I do wrong in my first sample code?
I mean, how can I use parent context data space to pass data to a child? I’m not a real fan of Session variables unless you really need to force reactivity in a template helper (for example). In my knowledge in Meteor there isn’t a global data space for user session data exchange among templates helpers/events. I could create my own global object to get a session like behavior without reactivity plays any role…
Parent-children data context can be a possible and even interesting solution, but as you saw I’m not clear about how to make it working.
What pattern do you use for non-reactive parent-child data exchange on the client?
Thanks


#8

I suggest look on this
https://www.discovermeteor.com/blog/meteor-components-template-controller-pattern/

If I wanna resolve onRender problem, I delegate function to every child template to “checkIn” when rendered.
So when executed from child, I have info that they already finished.

Argument to the function would be some uniq identifier for given subTemplate, maybe even just increasing counter would work if you know exact count of child template in parent controller.

And in reactive computation just monitor how many of them checked in and if count is reached, or all we are interested in checked in, than execute something final.

All the logic/monitoring would be in parent template/controller. And you can also pass result of allLoaded helper to child template so they also know when is the moment everything is loaded.


#9

Just set reactive vars and methods on the parent and call them from the child using parentTemplate: http://stackoverflow.com/questions/27949407/how-to-get-the-parent-template-instance-of-the-current-template