Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Re-rendering view causes input to lose focus

Tags:

backbone.js

I keep running into this problem over and over. I have a view with an input and I want to set and update things on every keyUp event. The problem is when set is called it triggers a change event which re-renders the view which causes the input to lose focus. So after the user types one character the input loses focus and they can't type anymore.

Another case where this happens is when the user clicks on an input I want to add a class to the div around the input so that it changes color. This of course causes the view to re-render and the input loses focus. I can't simply make a separate view for the input because the input is inside the div I want to re-render.

Here's a simple example.

itemView = Backbone.View.extend({
events: {
    "keyup .itemInput": "inputKeyUp"
}
initialize: function(){
    this.model.view = this;
    this.bind('change', this.render());
    this.render();
},
render: function(){
    $(this.el).html( $(ich.itemView( this.model.toJSON() )) );
    return this;
},
inputKeyUp: function(e) {
    this.model.set({name: $(this.view.el).find('input[type=text]').first().val()});
},
});

So far I've gotten around it by using {silent:true} and updating things manually but this creates a mess.

like image 208
Dan Avatar asked Jan 06 '12 21:01

Dan


1 Answers

You're basically getting yourself into a sort of infinite loop situation where you're binding your view too tightly to your model, and they're feeding back into each other.

When a user types into a browser text intput, they're already "updating the view". The view already represents the extra text.

So, when you update the model with those changes, you don't need the view to update AGAIN, as it already represents the current state.

So, in these cases, you really do want to use "silent", as you're just syncing the model with the current state of the UI, and don't need the model to inform the view to update.

As to how often to do this, I'm suspecting on keyup is probably excessive. You may want to do it on blur or, even, on some sort of "save" action.

As far as the other issue, I'm not sure why adding a class to an element would cause the view to re-render. Are you simply doing something like

this.$('input[type="text"]').addClass('active')

This shouldn't trigger your model's change event and cause render to run again.

Post comment:

You need to get more granular then.

In terms of rendering, break the individual rendering/updating of elements of the view into separate functions.

Bind property-specific change events ("change:name") to those more granular rendering functions so that they update the part of the view that you wish to change, but do not update the text input.

itemView = Backbone.View.extend({
events: {
    "keyup .itemInput": "inputKeyUp"
}
initialize: function(){
    this.model.view = this;
    this.bind('change:name', this.update_other_stuff());
    this.bind('change:selected', this.add_class());
    this.render();
},
update_other_stuff: function(){
    this.$('.some_other_thing').html("some desired change");
    return this;
},
add_class: function(){
    this.$('input[type=text]').first().addClass('active');
    return this;
},
render: function(){
    $(this.el).html( $(ich.itemView( this.model.toJSON() )) );
    return this;
},
inputKeyUp: function(e) {
    this.model.set({name: $(this.view.el).find('input[type=text]').first().val()});
},
});
like image 154
Edward M Smith Avatar answered Nov 15 '22 12:11

Edward M Smith