Hey @sashko, I found an issue with Blaze and I wondered how to go about it.
The short version is that Blaze.View.prototype.onViewReady uses Tracker.afterFlush to queue the rendered callbacks. This opens up the possibility of a parent’s rendered callback to run before a child’s one, even though the child already rendered.
This creates issues if you’re processing elements on the onRendered functions. The typical example is “locate all unprocessed elements and do something with them, then flag them as processed”. If onViewReady uses Tracker.afterFlush then a parent template can process an element before a child template can do it.
Here’s a repro: https://github.com/ManuelDeLeon/blaze-rendering-issue
The hierarchy is:
body
-> editContact
-> card
-> editContactForm
editContact
only displays card
if editContactForm
has rendered.
When you run it the output is:
editContact.onCreated
editContactForm.onCreated
editContactForm.onRendered
card.onCreated
editContact.onRendered
============= editContact rendered with a card! =============
card.onRendered
In other words, card
renders on the page but card.onRendered
is called after editContact.onRendered
(the parent).
The problem is that Tracker.afterFlush sets functions to run in the next cycle, but it allows other computations to be injected into the current queue while the queue is being processed .
The problem is fixed if you use a setTimeout instead of Tracker.afterFlush. The onRendered functions are still set to run on the next cycle but they don’t allow other functions to be injected in the queue before they’re processed.
So instead of:
Blaze.View.prototype.onViewReady = function (cb) {
var self = this;
var fire = function () {
Tracker.afterFlush(function () {
if (! self.isDestroyed) {
Blaze._withCurrentView(self, function () {
cb.call(self);
});
}
});
};
self._onViewRendered(function onViewRendered() {
if (self.isDestroyed)
return;
if (! self._domrange.attached)
self._domrange.onAttached(fire);
else
fire();
});
};
You would have:
Blaze.View.prototype.onViewReady = function (cb) {
var self = this;
var fire = function () {
setTimeout(function () {
if (! self.isDestroyed) {
Blaze._withCurrentView(self, function () {
cb.call(self);
});
}
}, 0);
};
self._onViewRendered(function onViewRendered() {
if (self.isDestroyed)
return;
if (! self._domrange.attached)
self._domrange.onAttached(fire);
else
fire();
});
};
(the only change is that Tracker.afterFlush
becomes setTimeout
)
What do you think?