8

I have a view called DashboardView that instantiates multiple WidgetViews. Each widget needs to have its own event bindings. So far as I can tell, these bindings get lost when the view is rendered and added to the parent view, i.e.:

class DashboardView extends Backbone.View
  constructor: -> 
    context = @
    _.each @collection, (w)->
      dv = new app.WidgetView(model: w)
      context.$el.append(dv.render()) 

class WidgetView extends Backbone.View
  events: 
     "click .config" : "config_widget"

  render: ->
      _.template($("#widget-template").html(), @model)

Doing it this way, the click events on the .config element of the widget are now lost. Is there a better way of mixing the nested views into the parent while ensuring that the event handlers on the child view are channelled correctly?

One solution I have seen to this problem comes in this article. This looks about right, but I'm curious if there is a more elegant way of solving this.

mu is too short
  • 426,620
  • 70
  • 833
  • 800
picardo
  • 24,530
  • 33
  • 104
  • 151

2 Answers2

10

Try this:

class DashboardView extends Backbone.View
  constructor: -> 
    @collection.each ( w ) =>
      dv = new app.WidgetView( model: w )
      @$el.append dv.render().el // Append widget's @el explicitly

class WidgetView extends Backbone.View
  tagName: 'div' // or whatever your view's root element is

  template: _.template $( "#widget-template" ).html() // pre-compile template

  events: 
    "click .config": "config_widget"

  render: ->
    @$el.html @template @model.toJSON() // append template to @el
    return this // return view

So, the idea is this:

(1) Inside the WidgetView's render method, you populate @el (the root element of the view) with its model's data via the template. (And notice how I compile the template only once - there is no need to compile the template on each render operation.)

(2) Inside the DashboardView, you append the widget's root element - @el - to the DOM.

The thing is that the view's events delegate to its root element - @el. Therefore, you want to work with the root element explicitly: inside render, you populate it, and then you append it to the DOM.

Šime Vidas
  • 182,163
  • 62
  • 281
  • 385
  • 4
    You could avoid the `context` stuff by using [`(w) =>`](http://coffeescript.org/#fat_arrow). And `@collection` should be Underscore-ified already so `@collection.each (w) =>` is another option. – mu is too short Mar 04 '12 at 01:16
  • @mu Cool `:)` Didn't know about the fat arrow. – Šime Vidas Mar 04 '12 at 02:23
1

Your issue is that delegateEvents expects a single, non-changing element for your view. Because your render function creates a new element every time, the bindings made by delegateEvents are never fired when you click on the element generated by render.

Luckily the current version of Backbone offers a setElement method that will reassign your element with the argument you provide, and then it will automatically call delegateEvents.

Mark Rushakoff
  • 249,864
  • 45
  • 407
  • 398
  • One thing I should have made clear is I have multiple widgets instantiated in one dashboard. So reassigning the element is not exactly what I want, I think. – picardo Mar 03 '12 at 22:11