Reputation:
The problem I am having is that my a parent view's render() method is returning before its child render() method completes.
So I have these calls in a Backbone router:
this.viewState.get('headerView').render();
this.viewState.get('mainView').render();
this.viewState.get('footerView').render(); //this function starts (and completes) before the above completes
the problem is the footer renders before the mainView renders, because the main view render() function fully returns before the mainView's child views finish rendering to the DOM. So the screen flickers as the footer is inserted into the DOM and then the footer gets pushed down when the mainView actually renders.
I like the paradigm of "return this" in the render() function of a Backbone view, but the only way I can think of to solve this is to chain the render() functions using the async library or something, using asynchronous/CPS callbacks.
So, I have this piece of code as part of my render function for a Backbone View:
//here the parent renders it's child views:
function renderChildren($template) {
var ret = EJS.render($template, {
users: data
});
this.$el.html(ret); //***
this.childLoginView = new LoginView({el: this.$('#child-view-login-container')});
//console.log('this.childLoginView.$el', self.childLoginView.$el);
this.childLoginView.render();
this.childLoginView.delegateEvents();
this.childRegisteredUsersView = new RegisteredUsersView({el: this.$('#child-view-registered-users-container')});
//console.log('this.childRegisteredUsersView.$el', self.childRegisteredUsersView.$el);
this.childRegisteredUsersView.render();
this.childRegisteredUsersView.delegateEvents();
console.log('IndexView rendered');
}
I can tell you for a fact that "IndexView rendered" is logged before the child views actually render. This means of course that the child render functions are async and it also means that the web page flickers as the child views are rendered only AFTER the the parent view's render method completes.
Like I said, this suggests the render() methods are indeed asynchronous. So I would like to render one view at a time so the page doesn't flicker - is this a reasonable thing to want to do? Hopefully there will be less flickering on the web view as things are only rendered in series, but I am not sure how to do this yet.
Regarding the line with ***. Is there a way to define the template for the parent view, and only after the child views render, only then insert the template into the DOM with .html()? this might require callbacks and in this case it would be impossible to "return this" from the parent view's render() function.
Upvotes: 0
Views: 318
Reputation: 7133
You probably have some asynchronous operation in your view.render()
which causes the race condition.
Use $.Deferred();
:
render: function () {
var self = this,
dfd = $.Deferred();
this.model.fetch({
succcess: function (response) {
// do here the operations on your cached DOM:
self.$el.append(response.model.models);
dfd.resolve({template: self.$el});
}
});
return dfd.promise();
}
Now you can control the order of the render methods:
$.when(viewState.get('headerView').render()).then(function(result) {
$('body').append(result.template);
viewState.get('mainView').render().then(function (result) {
$('body').append(result.template);
});
});
This will render one piece at a time. The users will see new parts of the screen appearing as the promises are resolved.
See this fiddle: https://jsfiddle.net/h0h888rm/2/
If you want to wait until every render is completed and reload the parentView only once then consider this alternative solution:
If you want your renders to run asynchronously but do the parentView rendering only when all of them is completed do this:
$.when(
viewState.get('headerView').render(),
viewState.get('mainView').render()
).then(function (result1, result2) {
$('body').append(result1.template)
.append(result2.template);
});
fiddle: https://jsfiddle.net/szLcfbne/
Upvotes: 0