Single page applications pose a unique and interesting set of problems. Libraries that offer two-way binding like KnockoutJS and AngularJS make it trivial to completely redraw a screen with new data delivered from the server via AJAX calls. However, what happens when you want to maintain a native feel and support editing and tabbing through fields that are dynamically updating as their change events fire? Answer, things get complicated. Perhaps this applies to you:
- You’ve created a flashy, ultra-responsive new single page application (SPA)
- The app saves to the server via AJAX calls when any input field changes (typically when user tabs out of the input).
- You want to maintain the ability to tab through fields, even though the entire page is “rebound” when each AJAX save completes. This “rebind” of the page (using KnockoutJS in my case) is necessary because fields on the page are interrelated – changes to one creates complex corresponding changes that in other fields on the page. In my case, these changes require complex calculations that must be performed on the server and change many other fields on the page. Hence, the AJAX call and complete rebind. Being surgical and updating values of individual fields one by one isn’t practical.
That last one is a tricky bugger. Keeping track of focus isn’t easy.
I currently use KnockoutJS with Durandal for SPAs, and when it rebinds data, input field focus is lost when tabbing through in the scenario above. It’s as though the page was reloaded without an actual postback.
The solution isn’t trivial, but it does involve many simple steps. At a high level:
- Pause a moment after a field changes to allow the browser to complete changing focus to the new field.
- Store the CSS Selector for the newly focused field. I used this simple recursive script to get the CSS selector and store it in the viewmodel.
- When the AJAX call that was triggered on change in step #1 completes, wait a moment for the new data to bind/redraw the UI. A simple setTimeout in JavaScript does the trick.
- Use the CSS selector that you stored in step #2 to restore focus to the input and select all text in the input to mirror native browser input tabbing behavior. This simple line of jQuery does the trick:
$(cssSelector).focus().select();
The focus method sets focus, and the select method selects all text within the input (this mimics the default tabbing behavior of browsers). Voila!
A few tips:
- Set an ID whenever possible on elements. This will speed the creation of CSS selectors as well as the restoration of focus since ID is the most efficient DOM element selector.
- For IE8 and IE9, you must return true in the change handler so the tab event will fire. This is why it’s important to call setTimeout in the change handler and immediately return true so that tabbing to the next field is instantaneous.
The result is a highly interactive application that masks the rebinding by maintaining focus.