What I want to achieve is that on form changes, the whole view should be re-rendered. This is to provide a preview of the data just edited, and to hide certain elements in the form when check boxes are ticked.
When the user edits the field and clicks on the button without leaving the filed first two events are fired at the same time: change, click. The change handler first updates the model, which triggers a re-render of the form. When it’s the click events turn, nothing happens. I guess it has to do with the re-render because when I comment out the
@model.on 'change', @render, @
Both event handlers are executed as it should be.
Maybe the click handler is not executed because the click target has been removed from dom and a new button has been added? How would I fix this? I was thinking the code I wrote was ‘idiomatic’ Backbone.js, but I’m still learning 🙂
Here is a simplified version of my code showing the problem:
jsbin
Let us add a few things so that we can see what’s going on. First we’ll mark the Save button with a unique ID:
And then we can see which button was hit:
We’ll also add a global click handler to watch the
<button>outside of the Backbone stuff:Live version: http://jsbin.com/oviruz/6/edit
Play around with that version a bit and you might see what is going on:
<input>.<input>loses focus, the change event is triggered.fieldChangedwhich does@model.set(...).@model.setcall triggers Backbone’s events, in particular, the@model.on(...)from the view’sinitialize.renderwhich does a@$el.html(...)which replaces both the<input>and the<button>.htmlcall kills all the DOM elements inside the view’sel. But, and this is a big but, the browser needs to get control again before this process finishes.<button>we’re clicking is a zombie as the browser’s work queue looks like this: deal with the click event, replace the DOM elements from 3.4. Here the work from 3.4 isn’t complete so the<button>that you’re clicking is half in the DOM and half dead and won’t respond to any events.You have two event queues fighting each other; your Backbone events are changing the DOM behind the browser’s back and, since JavaScript is single threaded, the browser is losing and getting confused.
If you delay the
@$el.htmlcall long enough to let the browser catch up:You’ll get the behavior you’re expecting. But that’s an awful, horrific, nasty, and shameful kludge.
Messing around with the DOM while you’re still processing events on those DOM elements is fraught with danger and is little more than a complicated way to hurt yourself.
If you want to validate the field when it changes and bind the view’s
renderto"change"events on the model, then I think you’ll have to do the validation by hand and use a silentsetcall:If you do a
@model.save()in the Save button’s callback, the silent changes will be validated en mass and sent to the server. Something like this: http://jsbin.com/oviruz/7/editOr you skip the
@model.setinsidefieldChangedand just use@model.validate:and leave all the setting stuff for
save:Something like this: http://jsbin.com/oviruz/8/edit