Question

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!

Was it helpful?

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
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top