using a single div as a container for multiple backbone views losing event bindings
backbone-events, backbone.js, jquery
Solution
When you use `.html(dom_object)`:
$(this.el).find("#current_tab").html(this.widgets_view.el);
You're actually doing this:
$(this.el).find('#current_tab').empty().append(this.widgets_view.el);
and `empty`:
removes other constructs such as data and event handlers from the child elements before removing the elements themselves [To avoid memory leaks]
So once you've added, say, `widgets_view` to `#current_tab` and then `.html(this.foobars_view.el)`, the events on `widgets_view.el` are gone. Your `.html` call works fine the first time because there's nothing in `#current_tab` so there aren't any events to unbind.
You can use your "reuse the views" approach by adding simple `delegateEvents` calls into the mix to rebind the events:
activateWidgets: function(){
if(!(this.widgets_view)){
this.widgets_view = new WidgetsView();
this.widgets_view.render();
}
$(this.el).find("#current_tab").html(this.widgets_view.el);
this.widgets_view.delegateEvents(); // <--------------- you need this
}
While I'm here, you can use `this.$('#current_tab')` instead of `$(this.el).find('#current_tab')` and `this.$el` instead of `$(this.el)`.
Demo: http://jsfiddle.net/ambiguous/75KuW/1/
Note that switching tabs in the above preserves the content of the `<input type="text">` elements. If you don't need that sort of behavior then you're probably better off removing and instantiating views as needed instead of holding all the views and swapping them in and out.
Also, if your `WidgetsView` (and friends) have other Backbone views inside them, you'll have to call `delegateEvents` on the contained views all the way down. In this case, some sort of `bind_events` method on the views would make sense:
this.widgets_view.rebind_events(); // delegateEvents() all the way down this view subtree
Problem
Ok, so I have a navigation and the concept of a dashboard. In my router when I click on a navigation link, I'm calling on my dashboard view to set the active tab. For example: NAV ``` <a href="#" class="dashabord_tab" id="widgets">Widgets</a> <a href="#" class="dashabord_tab" id="Foobars">Foobars</a> ``` dashboard_container.jst.tpl ``` <div id="nav"></div> <div id="current_tab"></div> ``` DASHABORDVIEW ``` DashboardView = Backbone.View.extend({ template:JST.dashboard_container, render: function(){ $(this.el).html(this.template()); }, activateWidgets: function(){ if(!(this.widgets_view)){ this.widgets_view = new WidgetsView(); this.widgets_view.render(); } $(this.el).find("#current_tab").html(this.widgets_view.el); }, activateFoobars: function(){ if(!(this.foobars_view)){ this.foobars_view = new FooBarsView(); this.foobars_view.render(); } $(this.el).find("#current_tab").html(this.foobars_view.el); } }); ``` the problem: So the first time I switch to either widgets_tab or foobar_tab the tab loads as expected. However if I switch to a tab, switch out of it, then switch back to it, all the events in my view are no longer bound. Clicking, hoevering, none of the views inside foobars_view for example are clickable, hoverable, etc. In the past, I have been making a wrapper for each one of the child views like so: past solution, pita: ``` activateFoobars: function(){ $('.tab_wrapper').hide(); if($(this.el).find("#foobars_wrapper").length < 1){ $(this.el).append("<div class='tab_wrapper' id='foobars_wrapper'></div>"); this.foobars_view = new FooBarsView(); $(this.el).find("#foobars_wrapper").html(this.foobars_view.render().el); } $('#foobars_wrapper').show(); } ``` I'd rather not do it the way I just showed above, and I really like the idea of just putting the view's el in an active_tab div that always stays open... If I can't do it the way I'd like to, is there an alternative to the way I just showed above? Thanks!