4
votes

TAB navigation breaks because render replaces the DOM element.

The backbone render method is designed to work with markup of the entire view and not only changed stuff...

Given the following - I set the tabindex correctly on the html tags to specify tab order. - I navigate from field to field by using TAB on the keyboard. - I bind to the change change event calling render when ever the model state changes as so :- this.model.bind('change', this.render); - I change something in a field and tab to the next (which triggers a model change event)

Has anyone a solution to this without explicit code checking all the changed properties, and without replacing backbone(as this is not an option on the current project)

Example:

Launch the TODO app creating 2 TODOs, tab to the first TODO and press space to mark is as done. Then try to tab to the next field, instead of going to the next TODO you're back to the what needs to be done input :(

1
The problem is that the checkbox loses focus after it's updated; you'd just need to refocus the changed item.Mathletics
Don't call render on a change event. Create a new function (update?) that doesn't replace the HTML entirelyCory Danielson
@CoryDanielson But then I have to manipulate the DOM myself, which is the problem backbone is trying to solve...James Kyburz
well, I would think you'd be able to watch for the change event on the model, pluck the ID, and focus from that. I don't think you'd need additional data; lastFocused can be deduced from the existing data, no? If you post some of your current code, I'd be happy to put together an example.Mathletics
Backbone doesn't aim to simplify DOM interaction. It's more of a code organization/flow library. Ember might be correct for you if you don't want to touch the DOM. There are plenty of ways to solve this problem though. You could stash a value to know what TODO you were focused on, re-render the "done" TODO and then re-focus on it using the stashed value. You should create a template that includes a text-box for the TODO as well as the done template hidden offscreen. When you mark it as done, you're just toggling a class on the same TODO item. A form tag around your TODO's might also be useful.Cory Danielson

1 Answers

2
votes

Here is the solution I ended up with:-

It adds a generated id for all elements that don't have an id, these ids will be the same for the model in the same state.

render: function(x) {
  var html = this.options.template(this.model.toJSON());
  var focused = window.document.activeElement.id; //Get the focused element
  this.el.innerHTML = html;
  this.decorate();
  if (focused) $('#' + focused).focus(); //Focus if previously focused prior to innerHTML
  return this;
},

decorate: function() {
  var i = 0;
  var idPrefix = 'ENTER PREFIX HERE'
  this.$el.find('*').each(function() {
    if (!this.id) this.id = idPrefix + ((++i).toString(36)); //Add id if none exists
  });
}