Matt Fletcher
Matt Fletcher

Reputation: 9220

Backbone view events losing delegation on parent view re-render

I've seen this issue crop up a few times but haven't been able to solve it myself- or I don't quite understand the answers. Many things talk about using .empty().append() rather than .html() and stuff about making sure to always refer to the view's $el value rather than directly accessing the DOM. Still no dice.

If you take a look at this jsbin, you'll see that changing the select box value re-renders the wrapper view the first time, but then does nothing on subsequent changes- it loses its events.

http://jsbin.com/fuvoba/7/edit (apologies to any coffeescript haters)

Any idea why this could be happening?

Many thanks and internet pints in advance, as always :)

Edit- thought I'd include the code inline too:

class ArchivesDropdownView extends Backbone.View
  template: '<select><option>One</option><option>Two</option></select>'
  initialize: () ->
    console.log 'Initialised the select dropdown'
  render: () ->
    this.$el.find('#dropdown').empty().append @template
    console.log 'Rendered the select dropdown'
    return this
  events:
    'change select': 'updateSelect'
  updateSelect: () ->
    console.log 'Updated select dropdown.'
    wrapperView.render()


class WrapperView extends Backbone.View
  template: 'Please select: <div id="dropdown"></div><div id="random"><%=randomNum%></div>'
  render: () ->
    console.log 'Rendered the wrapper'

    this.$el.empty().append _.template(@template)(randomNum: Math.random())

    if !this.archivesDropdownView
      this.archivesDropdownView = (new ArchivesDropdownView().render())

    this.archivesDropdownView.render()

    return this


wrapperView = new WrapperView()
$('body').empty().append wrapperView.render().$el

Upvotes: 1

Views: 249

Answers (2)

Eugene Glova
Eugene Glova

Reputation: 1553

Add this.archivesDropdownView.delegateEvents() after appeding view $el to the other view.

  this.$el.find('#dropdown').empty().append this.archivesDropdownView.$el
  this.archivesDropdownView.delegateEvents()

But I don't like your architecture, you should use global reference from child view to render parent view. Use events instead, for example share the model between parent and child views and trigger event from child view to re-render parent view.

Here is a working example http://jsbin.com/dehujecejape/1/edit

Upvotes: 3

Eternal1
Eternal1

Reputation: 5625

You have this kind of problem because you don't do things the way they are supposed to be done. Why would you re-render entire container when one of its components change? Of course you will have all kinds of troubles with this approach.

I'd recommend you create a third component, and update it on select box change:

class ArchivesDropdownView extends Backbone.View      
  template: '<select><option>One</option><option>Two</option></select>'
  initialize: (opts)->
    console.log 'Initialised the select dropdown'    
    @listener = opts.listener
  render: ->
    console.log 'Rendered the select dropdown'
    @$el.html @template    
  events:
    'change': 'updateSelect'
  updateSelect: ->
    @listener.render()

class RandomGeneratorView extends Backbone.View
  template: '<%=randomNum%>'
  render: ->
    @$el.html _.template(@template)(randomNum: Math.random())    

class WrapperView extends Backbone.View
  template: 'Please select: <div id="dropdown"></div><div id="random"></div>'
  initialize: ->
    console.log "wrapper init"
    randomElement = new RandomGeneratorView()
    @randomGeneratorView = randomElement.render()
    @archivesDropdownView = (new ArchivesDropdownView(listener: randomElement)).render()

  render: ->
    @$el.html @template  
    @randomContainer = @$el.find "#random"
    @dropdownContainer = @$el.find "#dropdown"
    @dropdownContainer.html @archivesDropdownView
    @randomContainer.html @randomGeneratorView
    @$el

$('body').html (new WrapperView()).render()

Here's a modified JSBin

Upvotes: 1

Related Questions