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:

<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" />

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.



@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() : ""; } ... }

To understand what is happening we have to take a look at the response the client browser receives:

<?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>

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).

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(); } });

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.

<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>

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.

$(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; }

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.

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(); }

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