Backbone.js Render Malfunctioning After Being Removed or Closed-Collection of common programming errors
I’ve extended Backbone’s View prototype to include a “close” function in order to “kill the zombies”, a technique I learned from Derrick Bailey’s blog
The code looks like this:
Backbone.View.prototype.close = function () {
this.remove();
this.unbind();
if (this.onClose) {
this.onClose();
}
};
Then I have a Router that looks (mostly) like this:
AppRouter = Backbone.Router.extend({
initialize: function() {
this.routesHit = 0;
//keep count of number of routes handled by your application
Backbone.history.on('route', function () { this.routesHit++; }, this);
},
back: function () {
if(this.routesHit > 1) {
//more than one route hit -> user did not land to current page directly
logDebug("going window.back");
window.history.back();
} else {
//otherwise go to the home page. Use replaceState if available so
//the navigation doesn't create an extra history entry
this.navigate('/', {trigger:true, replace:true});
}
},
routes: {
"": "showLoginView",
"login": "showLoginView",
"signUp": "showSignUpView"
},
showLoginView: function () {
view = new LoginView();
this.render(view);
},
showSignUpView: function () {
view = new SignUpView();
this.render(view);
},
render: function (view) {
if (this.currentView) {
this.currentView.close();
}
view.render();
this.currentView = view;
return this;
}
});
The render function of my LoginView looks like this:
render: function () {
$("#content").html(this.$el.html(_.template($("#login-template").html())));
this.delegateEvents();
return this;
}
The first time the LoginView is rendered, it works great. But if I render a different view (thereby calling “close” on my LoginView) and then try to go back to my LoginView, I get a blank screen. I know for a fact that the render on my LoginView fires the second time, but it seems that my “close” method is causing a problem. Any ideas?
EDIT After some feedback from Rayweb_on, it appears I should add more detail and clarify.
My HTML looks like this:
this is my header
I want my view to render in here
this is my footer
Then I have a login-template that looks like this (sort of):
...
I’m trying to get it so that the view always renders inside of that “content” div, but it appears that the call to “close” effectively removes the “content” div from the DOM. Hence the “blank” page. Any ideas?
EDIT 2 Here’s what my LoginView looks like, after some noodling:
LoginView = Backbone.View.extend({
events: {
"vclick #login-button": "logIn"
},
el: "#content",
initialize: function () {
_.bindAll(this, "logIn");
},
logIn: function (e) {
...
},
render: function () {
this.$el.html(_.template($("#login-template").html()));
this.delegateEvents();
return this;
}
});
I set the el to “#content” in the hopes that it would get recreated. But still no luck. In fact, now when I go to the next page it’s not there because #content is being removed right away.
I also tried:
LoginView = Backbone.View.extend({
events: {
"vclick #login-button": "logIn"
},
el: "#login-template",
initialize: function () {
_.bindAll(this, "logIn");
},
logIn: function (e) {
...
},
render: function () {
this.$el.html(_.template($("#login-template").html()));
this.delegateEvents();
return this;
}
});
But that doesn’t work at all. Any ideas?
-
It would appear that any elements included in the view get destroyed (or at least removed from the DOM) by my “close” function (thanks Rayweb_on!). So, if I reference the #content div in my view, I lose it after the view is closed. Therefore, I no longer reference #content anywhere in my view. Instead, I add my view’s default “el” to my #content element externally of my view.
In my case, I chose to do it in my router. The result looks like this:
ROUTER
AppRouter = Backbone.Router.extend({ initialize: function() { ... }, routes: { "" : "showLoginView", "login": "showLoginView", "signUp": "showSignUpView", }, showLoginView: function () { view = new LoginView(); this.render(view); }, showSignUpView: function () { view = new SignUpView(); this.render(view); }, render: function (view) { if (currentView) { currentView.close(); } $("#content").html(view.render().el); currentView = view; return this; } });
LOGIN VIEW
LoginView = Backbone.View.extend({ events: { "vclick #login-button": "logIn" }, initialize: function () { _.bindAll(this, "logIn"); }, logIn: function (e) { ... }, render: function () { this.$el.html(_.template($("#login-template").html())); this.delegateEvents(); return this; } });
This all works swimingly, but I am not certain it’s the best solution. I’m not even certain it’s a good solution, but it’ll get me moving for now. I’m certainly open to other options.
====
EDIT: Found a slightly cleaner solution.
I wrote a function called injectView that looks like this:
function injectView(view, targetSelector) { if (_.isNull(targetSelector)) { targetSelector = "#content"; } $(targetSelector).html(view.el); }
and I call it from each View’s render function, like this:
render: function () { this.$el.html(_.template($("#login-template").html())); this.delegateEvents(); injectView(this,"#content") return this; }
It works. I don’t know how good it is. But it works.
-
When you remove your view the first time you are removing its el, so this line
$("#content").html(this.$el.html(_.template($("#login-template").html())));
on your render function wont work. as this.$el. its undefined.
Originally posted 2013-11-09 21:40:15.