Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using Backbone to render a form on model change causes form UI errors

I have a simple view that binds itself to redraw when the model changes as most guides indicate:

this.model.bind('change', this.render, this);

This view has a form with an input box and a submit button. I am binding to the 'change' event of the input box to change the related item on the model. (I can do this manually or with the ModelBinder project, works the same either way.)

A user changes the value in the form input box, then clicks on the submit button. The model is updated, the view and form are re-rendered. The submit button's click event is squashed.

I'm using Backbone's events property:

events : {
    'submit form' : 'save'
},

I know that the event is getting ignored because the DOM element that was clicked on is no longer there. I can place a small setTimeout inside of render() to prevent the HTML from being swapped out and things work as expected, but this requires a wait.

I can't be the first person to struggle with this - what's the standard way of capturing form 'change' events, updating the model, and redrawing the view without losing some key click or keypress information?

Similarly if I have several form items the user is unable to tab between the items after changing the contents as the form is redrawn.

Update 1 - 3 Days later

Still trying to find a good solution. Things I've tried:

  • moving or cloning the contents of the view to a different area on the page. The click event is still never received.
  • registering the click event with $(document).on or $(document).live instead of the standard view events object
  • separating out the form so that the entire form (inputs and buttons) stays together without being redrawn. Redraw parent elements (which rely on form values) and re-insert already drawn form. This fixes the related issue of being unable to tab across an element, but doesn't fix click events.
  • works as desired in firefox 4, but not ie9 or chrome. *

Update 2 - With example code

One of the comments asked for some code. I've massively simplified the code into one page and included it below. The actual application is much more complicated. With code as simple below I could just manually re-render parts of the page on change. In the actual application I'm using dustjs templates and even if I don't re-render the form, but do re-render elements that contain the form I have issues clicking on the submit. I'm hoping for a 'pattern' that is typical for backbone applications, including complicated pages and models and forms.

Most of the 'demo' apps and even the sites that I've seen using backbone appear to be mostly presentation focused apps that don't actually gather a lot of input from the user. If you know of a good data-collection focused application/demo based on backbone that would be helpful.

Code:

<!doctype html>
<html lang="en">
<head>
    <script src="libs/jquery.js"></script>
    <script src="libs/underscore.js"></script>
    <script src="libs/backbone.js"></script>
</head>
<body>
<div id="container"></div>
<script type="text/template" id="edit_user_template">
    <h3>Edit User <%=id%> : <%=name%></h3>
    <form>
        Name: <input type="text" name="name" value="<%=name%>"><br/>
        Email: <input type="text" name="email" value="<%=email%>"><br/>
        <input type="submit">
    </form>
</script>
<script>
    var UserModel = Backbone.Model.extend({});
    var model = new UserModel({id: '1', name:'Joe', email:'[email protected]'});
    var UserView = Backbone.View.extend({
        events : {
            'submit form' : 'save',
            'change input' : 'inputChange'
        },
        initialize: function(){
            this.model.bind('change', this.render, this);
        },
        render: function(){
            var template = _.template($('#edit_user_template').html(), this.model.toJSON());
            this.$el.html(template);
        },
        inputChange : function(evt){
            var target = $(evt.currentTarget);
            this.model.set(target.attr('name'), target.val());
        },
        save : function(event){
            console.log('saving');
            event.preventDefault();
            //this.model.save();
        }
    });
    var view = new UserView({model: model, el: '#container'});
    view.render();
</script>
</body>
</html>
like image 481
Will Shaver Avatar asked Aug 22 '12 20:08

Will Shaver


1 Answers

You ask for a pattern in backbone. The pattern is to use models to hold form info (like you are doing) and then update specific elements on the page instead of re-rendering everything every time the model changes. ModelBinder offers a nice way to do all of that (https://github.com/theironcook/Backbone.ModelBinder).

I don't think you are going to find a way around not being able to submit a form that doesn't exist anymore. You need to accomplish what you want in a different way.

like image 155
DrewB Avatar answered Sep 28 '22 04:09

DrewB