Anyone who has developed JSF 2 applications for any length of time, will have come across the issue that element focus may get lost when AJAX rendering is preformed.
So what is the cause of this? I have constructed a simple webapp to demonstrate the issue and show a potential solution.
One of the simplest possible scenario is a simple value change event that triggers a render on the following field:
[xhtml]
<h:outputLabel value="Input field: " />
<h:inputText id="firstValue" value="#{sessionBean.firstValue}" tabindex="1">
<f:ajax event="change" render="secondValue thirdValue" />
</h:inputText>
<h:outputLabel value="Render to Upper field: " />
<h:inputText id="secondValue" value="#{sessionBean.secondValue}" tabindex="2" />
[/xhtml]
On the server side we are simply preforming a toUpper on the firstInput and assigning it to the secondValue. In this case implemented as a simple session scoped CDI bean, the underlying backing bean infrastructure has no influence on the response that JSF produces or how it is treated by the client.
[java]
@Named
@SessionScoped
public class SessionBean implements Serializable {
…
public void setFirstValue(String firstValue) throws InterruptedException {
Thread.sleep(2400);
this.firstValue = firstValue;
this.secondValue = firstValue != null ? firstValue.toUpperCase() : "";
}
…
}
[/java]
To understand what is happening we have to take a look at the response the client browser receives:
[xml]
<?xml version=’1.0′ encoding=’UTF-8′?>
<partial-response>
<changes>
<update id="frmFieldRender:secondValue">
<![CDATA[
<input id="secondValue" … value="ASDF" tabindex="2" />
]]>
</update>
…
</changes>
</partial-response>
[/xml]
As can be seen, although only the value was updated on the server side, the entire element is written into the response (specified in JSR314 JSF 2.0 Chapter 13.4.4 Sending The Response to The Client). This is used by the client sided JavaScript to switch out the element in the DOM tree.
Interestingly this does not result in the browser loosing focus, all modern browsers that I tried (Firefox 30. Chrome 35, IE 10) were able to preform direct replacement of simple input element whilst keeping focus. This only happens when JSF AJAX render requests are preformed on elements that nest input elements or when render is preformed on entire sections.
However even when dealing with JSF AJAX re-render on simple input elements you have to be aware of another related issue. Depending on processing time and network latency, the user may already have started entering data into the input field that is about to be replaced. All inputs made will be overwritten as soon as the response is received and processed by the browser. To simulate this I have added a Thread.sleep(2400) to the setter method in the backing bean.
So we have yet another issue with the way that JSF AJAX render works. Not only do you have to deal with the focus disappearing, but also data being overwritten by server responses.
JSF 2 allows us to register JavaScript listeners on JSF AJAX events (see JsDoc, specified in JSR-314 JSF 2.0 Chapter 14.4.1 Request/Response Event Handling).
[javascript]
var ajaxStatus = {busy: false};
jsf.ajax.addOnEvent(function onStatusChange(data) {
var status = data.status;
if (status && status !== ‘complete’ && status !== ‘success’) {
ajaxStatus.busy = true;
} else if (status === ‘success’) {
ajaxStatus.busy = false;
}
});
$(document).on(‘keydown’, function(event) {
if (ajaxStatus.busy) {
event.preventDefault();
}
});
[/javascript]
This simple bit of JavaScript, with the help of jQuery, allows us to prevent further user input until the render is completed. This is sufficient to support re-render on simple input fields.
However as soon as you preform the render operation on more complex objects or entire sections, the browser will lose focus. To demonstrate this behavior I am preforming a render on a panelGroup that is nesting an input element.
[xhtml]
<h:outputLabel value="Input field: " />
<h:inputText id="firstValue" value="#{sessionBean.firstValue}" tabindex="4">
<f:ajax event="change" render="nestingElement" />
</h:inputText>
<h:outputLabel value="Nested input field: " />
<h:panelGroup id="nestingElement">
<h:inputText id="nestedInputField" value="#{sessionBean.secondValue}" tabindex="5"/>
</h:panelGroup>
[/xhtml]
In this case we have to keep track of the focused element ourself and restore the focus once the ajax render request is complete.
We can do this by extending the keydown listener to register Tab, Shift+Tab and click events and store the resulting tabindex of the focused element.
[javascript]
$(document).on(‘keydown’, function(event) {
if (ajaxStatus.busy) {
event.preventDefault();
} else {
if (!event.shiftKey && keyCode === KEY_CODE.TAB) {
registerTabForward();
}
else if (event.shiftKey && keyCode === KEY_CODE.TAB) {
registerTabBackward();
}
}
});
$(document).click(function(event) {
if (ajaxStatus.busy) {
event.preventDefault();
} else {
registerClick();
}
});
function registerClick() {
focus.tabIndex = document.activeElement.getAttribute(‘tabindex’);
focus.forward = false;
focus.backward = false;
}
function registerTabForward() {
focus.tabIndex = document.activeElement.getAttribute(‘tabindex’);
focus.forward = true;
focus.backward = false;
}
function registerTabBackward() {
focus.tabIndex = document.activeElement.getAttribute(‘tabindex’);
focus.forward = false;
focus.backward = true;
}
[/javascript]
You may be wondering why we’re storing the tabindex instead of the reference to the actual element? As already explained, JSF AJAX render request cause the replacement of entire elements (including nested elements). Hence a direct reference would disappear when the element is switched out.
Finally we need to set the focus when the ajax render request is completed.
[javascript]
jsf.ajax.addOnEvent(function onStatusChange(data) {
…
} else if (status === ‘success’) {
refocus();
ajaxStatus.busy = false;
}
});
function refocus() {
var targetTabIndex = focus.tabIndex;
if(focus.forward) {
targetTabIndex = parseInt(targetTabIndex) + 1;
} else if(focus.backward) {
targetTabIndex = parseInt(targetTabIndex) – 1;
}
$(‘[tabindex=’+ targetTabIndex +’]’).focus();
}
[/javascript]
Since the tabindex was stored on keyDown event, the tabindex of the AJAX request triggering element was stored and not the target elements, hence in the case of Tab or Shift+Tab we have to select the following or prior element.
Please note that the refocus method has been kept simple to illustrate the essence of the implementation required for the desired behaviour.
As a bare minimum this method has to be extended to cover disabled and hidden fields. It also may be desirable to allow for non linear tabindex progression (e.g. 1,5,8,12,…). This is particular useful for modular web page layouts so that a tabindex range can be assigned to the different sections of the page (e.g. the customer input fields have a range starting at 100 where the navigation buttons at the end always start at 1000).
You can find a sample application demonstrating the issue and solution here.
Related resources:
JSR-314 JavaServer Faces 2.0 Final Release
I have custom generated tabindex values in my application so a tweaked your solution a little bit. I found a simpler way of handling the focus. The idea is that I save the active tabindex on ajax complete event and refocus on the saved tabindex on ajax success event.
This solves the non-linear tabindex progression and disabled/hidden field problems.
The example:
var focus = {tabIndex: 0, forward: false, backward: false};
jsf.ajax.addOnEvent(function onStatusChange(data) {
var status = data.status;
if (status && status === 'complete') {
focus.tabIndex = document.activeElement.getAttribute('tabindex');
} else if (status === 'success') {
refocus();
}
});
function refocus() {
var targetTabIndex = focus.tabIndex;
if (targetTabIndex && targetTabIndex !== 0) {
var target = $('[tabindex='+ targetTabIndex +']');
if (target != null) {
$(target).focus();
}
focus.tabIndex = 0;
}
}
I like your solution.
I would be interested to find out how you went about the tabindex generation. Do you use something like a @RequestScoped TabIndexController that hands out tabindex to all visible active fields?
@Named
@RequestScoped
public class TabIndexController {
int tabIndex = 0:
public int getTabIndex() {
return tabIndex++;
}
}
<h:inputText id="foo" tabindex="#{foo.disabled || foo.hidden ? '' : tabIndexController.tabIndex}" ... />
Don’t forget to block user entry and further AJAX calls whilst waiting for a response. Otherwise you may encounter overwritten values and/or BusyConversationException.
The tabindex generation is pretty specific to the page where I use it. The page is all dynamic with multiple nested a4j:repeats. The tabindex is computed based on the nest level and the iteration var. The backing bean is conversation scoped.
I will make some tests to see how it affects usability. Thanks!