0

I'm new to Backbone and am using it with jQuery in Rails. In my view's render method, I use delegateEvents to bind an event handler to the "click" event of a button with id btn-go. This button is itself rendered by the view in question.

Clicking the button stores the form's values in an array and causes the view to re-render. This button is itself rendered by the view in question. This works the first time I click the button, but nothing happens the second time, even though the view does correctly re-render its template.

class MyApp.Views.ChainsNew extends Backbone.View

    # @template is defined by a conditional inside render()

    render: (step_number) ->
        window.model = @model
        @step_number = step_number

        @template = if @step_number is 1 then JST['chains/new'] else JST['chains/step']
        $(@el).html(@template())

        @delegateEvents({
            'click #btn-go': 'add_step'
        })
        @

    add_step: ->
        #divide array into arrays of steps before and after step being edited
        steps = @model.get('steps')
        array1 = steps.slice(0, @step_number - 1)
        array2 = steps.slice(@step_number)
        array1.push(@$el.find('textarea').val())
        newArray = array2.concat(array1)

        @model.set({
            steps: newArray
        })

The view's render method is called by the router. As you can see in the code below, the router is listening to the change event on the model. This causes it to update the URL, which in turn triggers the router's step method to be called, and it's within that method that the view's render method is finally called.

class MyApp.Routers.Chains extends Backbone.Router
    routes:
        'chains/new(/)': 'new'
        'chains/new/step/:step_number(/)': 'step'

    initialize: ->
        # Model
        @model = new MyApp.Models.Chain()
        @listenTo(@model, "change", ->
            @goto_step_number @model.get('steps').length + 1
        )

        # Views
        @view_new = new MyApp.Views.ChainsNew({
            model: @model
        })


    step: (url_step_number) ->
        # Before rendering the view, make sure URL & number of steps in model are correctly related
        url_step_number = parseInt url_step_number
        steps_entered = @model.get('steps').length

        if url_step_number > steps_entered + 1
            @goto_step_number steps_entered + 1
        else
            $('#main-container').html(@view_new.render(url_step_number).el)

    new: ->
        @goto_step_number 1

    goto_step_number: (step_number) ->
        @.navigate('chains/new/step/' + step_number, trigger: true)

Why doesn't anything happen the second time I click the button? I'm guessing that the event handler hasn't been correctly bound to the button, but I have no idea why.

4
  • Do both of your templates have the "*btn-go" button? Especially "chains/step" ? Commented Feb 4, 2014 at 14:45
  • @DavidSulc yes they do. Commented Feb 4, 2014 at 19:15
  • Works fine with the delegateEvents call (jsfiddle.net/ambiguous/2SnDn) or with just a plain events map (jsfiddle.net/ambiguous/8q8XH). How are you using this view? What triggers the render calls? Commented Feb 4, 2014 at 21:01
  • @muistooshort I have updated my post to show my router's code, along with an explanation of the method call sequence that results from clicking the go button. Commented Feb 4, 2014 at 21:53

1 Answer 1

1

Your problem is right here:

$('#main-container').html(@view_new.render(url_step_number).el)

From the fine manual:

.html( htmlString )
[...]
When .html() is used to set an element's content, any content that was in that element is completely replaced by the new content. Additionally, jQuery removes other constructs such as data and event handlers from child elements before replacing those elements with the new content.

Note the removes other constructs such as data and event handlers part. The sequence of events goes like this:

  1. You call render.
  2. render calls delegateEvents to attached a jQuery event delegator to the view's el.
  3. You call $x.html(view.el) but view.el is already there so jQuery detaches all the event bindings (including the one you just added in 2), clears out $x, and then puts view.el back into $x.

But when view.el is put back on the page, the events are already gone. This is roughly equivalent to what you're doing:

# In the view...
add_step: ->
    re_render(@step_number + 1)

and

v = new YourView
$('#main-container').append(v.render(1).el)
re_render = (step_number) ->
    $('#main-container').html(v.render(step_number).el)

Demo: http://jsfiddle.net/ambiguous/4rJyB/

You need to stop calling .html all the time. Once the view is on the page, you can simply tell it to re-render itself and that's all you need to do. So, if the view has been rendered once to get its el into #main-container, you just need to:

@view_new.render(url_step_number)

and that's it. Then you can remove the @delegateEvents call from render and use the usual events map on the view:

class MyApp.Views.ChainsNew extends Backbone.View
    events:
        'click #btn-go': 'add_step'
    render: (step_number) ->
        window.model = @model
        @step_number = step_number

        @template = if @step_number is 1 then JST['chains/new'] else JST['chains/step']
        @$el.html(@template())
        @
    #...
Sign up to request clarification or add additional context in comments.

2 Comments

Perfect, thanks! I was so caught up in looking at the Backbone-specific stuff that I overlooked the simple jQuery issue. One question though -- why did the button work at all for even the first click? Seems like my call to delegateEvents should never have worked since even the first time the view hadn't been inserted into the DOM yet.
I think the first .html call works because view.el isn't in #main-container yet so the first part of step 3 ("jQuery detaches all the event bindings") doesn't happen.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.