I’ve been using Meteor for years and I finally found a Blaze autorun issue that has stumped me. The problem is waiting on multiple Collection updates in a single template before updating the UI.
Here’s an example illustrating the problem:
I’m working on a collaborative document editor. Think Google Docs. I have a Papers collection with a page field that holds the current page the group is on. At any time, any members of the group can update the paper to a the next/previous page.
When collaboratively working on a paper, any user can enable several different “info views” on the paper. One view, for example, is a “Author Information” view that kicks out a side-column to the right of the paper that has a bunch of meta-data about the authors of the paper. This data is in a Authors Collection and contains a showAuthors field in the Authors collection along with the data. There’s another view that can be enabled that shows the formulas that make up any embedded charts. That’s a collection called Formulas and has a showFormulas field along with the data.
Any time someone changes to a new page, the “Authors” and “Formulas” side-columns should automatically close and have to be reopened by someone on the new page of the paper.
So in the page viewing template there is an autorun that looks like this:
instance.autorun(function() {
let currentData = Template.currentData();
// Listen for page to update
instance.currentPage.set(currentData.page);
// Listen for views to update
instance.showAuthors.set(currentData.authors.showAuthors);
instance.showFormulas.set(currentData.formulas.showFormulas);
});
So the idea is that we’re reactively listening for anyone to enable any of the side-column views and also update the page. The documents Authors and Formulas object are loaded onto the main Papers document in the publication. SIDE NOTE: I’m using ReactiveVars to control reactivity because the user can put the paper into a “local editing mode” and control the page and views without updating everyone else or receiving the server state, so I don’t connect directly to the actual data fields in helpers. Instead I use currentData() in an autorun. I use intermediary ReactiveVars to control the UI. This doesn’t/shouldn’t affect this issue.
Whenever a page is changed by any user from the server, the two views need to disable. So on the server for the Papers collection I’m using matb33:meteor-collection-hooks and have a hook to hide the Authors and Formulas anytime a new page is updated:
Papers.before.update(function (userId, doc, fieldNames, modifier, options) {
if(modifier.$set && modifier.$set.page && modifier.$set.page != doc.page) {
Authors.update({paperId: doc.paperId}, {$set: {showAuthors: false}});
Formulas.update({paperId: doc.paperId}, {$set: {showFormulas: false}});
}
});
So what ends up happening is the above autorun is ran three times. Once for the Authors update, once for the Formulas update, and once for the Papers update.
The problem is in the UI this all happens in a glitchy linear order. First the “Authors” side-column hides, then the “Formulas” side-column hides, then the “Page” updates to the next page (I know that might sound cool - but in this case it looks really bad). What I’m trying to achieve is to somehow wait for all these updates to happen on the client then to a single update pass to the template. The above example is simplified and is actually more complex in my actual app. But this illustrates the issue.
I don’t need to use a loader because I actually load ten pages of the paper, the authors, and the formulas at a time. So there is no loading delay. All the data is already loaded. What I’m looking for is some way to not update the template until all three autoruns have completed. Even if I separated the above autorun into three separate autorun blocks, you’d still have the same problem. Because the template is sourced from three different Collections, each time you update each of those Collections, the template updates one by one. Is there any way to handle this in Meteor? I tried playing with Tracker.afterFlush but it always fires after each autorun fires. There doesn’t seem to be a way to wait for multiple autoruns to finish.
Trying to use some kind loader ReactiveVar hack doesn’t work either. As the above autorun has to also allow any users to show or hide any of the side-columns at anytime. So there’s no way to know if hiding a side-column is because a user directly wants to hide it or if it’s being hidden by the page hook because a new page is being updated. I even thought about using a setTimeout to wait on multiple autorun updates but again… too hacky.
I guess in this use-case need to know why data is being updated - not just the the update of the data. Then I could shut off all the views in one pass of the autorun but there’s no way of knowing why a view is being shut off.
I know one solution is to move the two fields over to the Papers collection, then the update could come in one fell swoop to a single collection. But I’m curious if there’s a solution for this type of data design. As this could happen a lot in my app.
