Problem

The ADF Faces UI components sometimes misses some basic features. In this case it is the af:inputtext component which misses an default way to handle tab order and initial focus properties to handle both bahaviours as easy as possible in every fragment. We want to place the logic of this in a central way to avoid duplication of code.

Solution – Custom declarative component

To abstract these functionalities in ADF it is a good idea to create an own custom declarative component to extend functionalities of an existing UI component. So we used an own inputtext component like this

<?xml version='1.0' encoding='UTF-8'?>
<af:componentDef xmlns:af="http://xmlns.oracle.com/adf/faces/rich" var="attrs" componentVar="comp" definition="private"
                 xmlns:afc="http://xmlns.oracle.com/adf/faces/rich/component" xmlns:h="http://java.sun.com/jsf/html">
    <af:xmlContent>
        <afc:component>
            <afc:description/>
            <afc:display-name>v7InputText</afc:display-name>
            <afc:component-class>de.virtual7.libs.view.components.V7InputText</afc:component-class>
            <afc:attribute>
                <afc:attribute-name>initialfocus</afc:attribute-name>
                <afc:attribute-class>java.lang.Boolean</afc:attribute-class>
                <afc:default-value>false</afc:default-value>
            </afc:attribute>
            <afc:attribute>
                <afc:attribute-name>tabindex</afc:attribute-name>
                <afc:attribute-class>java.lang.Integer</afc:attribute-class>
            </afc:attribute>
            <afc:attribute>
                <afc:attribute-name>label</afc:attribute-name>
                <afc:attribute-class>java.lang.String</afc:attribute-class>
            </afc:attribute>
            <afc:attribute>
                <afc:attribute-name>value</afc:attribute-name>
                <afc:attribute-class>java.lang.String</afc:attribute-class>
            </afc:attribute>
            <afc:attribute>
                <afc:attribute-name>helpTopicId</afc:attribute-name>
                <afc:attribute-class>java.lang.String</afc:attribute-class>
            </afc:attribute>
            <afc:attribute>
                <afc:attribute-name>placeholder</afc:attribute-name>
                <afc:attribute-class>java.lang.String</afc:attribute-class>
            </afc:attribute>
            <afc:attribute>
                <afc:attribute-name>contentStyle</afc:attribute-name>
                <afc:attribute-class>java.lang.String</afc:attribute-class>
            </afc:attribute>
            <afc:attribute>
                <afc:attribute-name>labelStyle</afc:attribute-name>
                <afc:attribute-class>java.lang.String</afc:attribute-class>
            </afc:attribute>
            <afc:attribute>
                <afc:attribute-name>styleClass</afc:attribute-name>
                <afc:attribute-class>java.lang.String</afc:attribute-class>
            </afc:attribute>
            <afc:attribute>
                <afc:attribute-name>inlineStyle</afc:attribute-name>
                <afc:attribute-class>java.lang.String</afc:attribute-class>
            </afc:attribute>
            <afc:attribute>
                <afc:attribute-name>partialTrigger</afc:attribute-name>
                <afc:attribute-class>java.lang.String</afc:attribute-class>
            </afc:attribute>
            <afc:attribute>
                <afc:attribute-name>shortDesc</afc:attribute-name>
                <afc:attribute-class>java.lang.String</afc:attribute-class>
            </afc:attribute>
            <afc:attribute>
                <afc:attribute-name>maximumLength</afc:attribute-name>
                <afc:attribute-class>java.lang.Integer</afc:attribute-class>
            </afc:attribute>
            <afc:attribute>
                <afc:attribute-name>columns</afc:attribute-name>
                <afc:attribute-class>java.lang.Integer</afc:attribute-class>
            </afc:attribute>
            <afc:attribute>
                <afc:attribute-name>rows</afc:attribute-name>
                <afc:attribute-class>java.lang.Integer</afc:attribute-class>
            </afc:attribute>
            <afc:attribute>
                <afc:attribute-name>secret</afc:attribute-name>
                <afc:attribute-class>java.lang.Boolean</afc:attribute-class>
                <afc:default-value>false</afc:default-value>
            </afc:attribute>
            <afc:attribute>
                <afc:attribute-name>autoSubmit</afc:attribute-name>
                <afc:attribute-class>java.lang.Boolean</afc:attribute-class>
                <afc:default-value>false</afc:default-value>
            </afc:attribute>
            <afc:attribute>
                <afc:attribute-name>showRequired</afc:attribute-name>
                <afc:attribute-class>java.lang.Boolean</afc:attribute-class>
                <afc:default-value>false</afc:default-value>
            </afc:attribute>
            <afc:attribute>
                <afc:attribute-name>required</afc:attribute-name>
                <afc:attribute-class>java.lang.Boolean</afc:attribute-class>
                <afc:default-value>false</afc:default-value>
            </afc:attribute>
            <afc:attribute>
                <afc:attribute-name>readOnly</afc:attribute-name>
                <afc:attribute-class>java.lang.Boolean</afc:attribute-class>
                <afc:default-value>false</afc:default-value>
            </afc:attribute>
            <afc:attribute>
                <afc:attribute-name>disabled</afc:attribute-name>
                <afc:attribute-class>java.lang.Boolean</afc:attribute-class>
                <afc:default-value>false</afc:default-value>
            </afc:attribute>
            <afc:attribute>
                <afc:attribute-name>immediate</afc:attribute-name>
                <afc:attribute-class>java.lang.Boolean</afc:attribute-class>
                <afc:default-value>false</afc:default-value>
            </afc:attribute>
            <afc:attribute>
                <afc:attribute-name>simple</afc:attribute-name>
                <afc:attribute-class>java.lang.Boolean</afc:attribute-class>
                <afc:default-value>false</afc:default-value>
            </afc:attribute>
            <afc:component-extension>
                <afc:component-tag-namespace>v7.components</afc:component-tag-namespace>
                <afc:component-taglib-uri>/v7ComponentLib</afc:component-taglib-uri>
                <afc:method-attribute>
                    <afc:attribute-name>valueChangeListener</afc:attribute-name>
                    <afc:method-signature>void method(javax.faces.event.ValueChangeEvent)</afc:method-signature>
                </afc:method-attribute>
            </afc:component-extension>
        </afc:component>
    </af:xmlContent>
    <af:panelGroupLayout id="dc_pgl1">
        <af:inputText shortDesc="#{attrs.shortDesc}" maximumLength="#{attrs.maximumLength}" label="#{attrs.label}"
                      id="focus" clientComponent="true" value="#{attrs.value}" columns="#{attrs.columns}"
                      rows="#{attrs.rows}" secret="#{attrs.secret}" helpTopicId="#{attrs.helpTopicId}"
                      placeholder="#{attrs.placeholder}" contentStyle="#{attrs.contentStyle}"
                      labelStyle="#{attrs.labelStyle}" styleClass="#{attrs.styleClass}"
                      inlineStyle="#{attrs.inlineStyle}" partialTriggers="#{attrs.partialTrigger}"
                      valueChangeListener="#{comp.handleValueChangeListener}" showRequired="#{attrs.showRequired}"
                      autoSubmit="#{attrs.autoSubmit}" required="#{attrs.required}" readOnly="#{attrs.readOnly}"
                      disabled="#{attrs.disabled}" immediate="#{attrs.immediate}" simple="#{attrs.simple}"/>
        <af:poll id="p1" immediate="true" interval="100" pollListener="#{comp.focusPollListener}"
                 rendered="#{attrs.initialfocus}"/>
        <af:poll id="p2" immediate="true" interval="100" pollListener="#{comp.tabindexPollListener}"
                 rendered="#{attrs.tabindex > '-1'}"/>
    </af:panelGroupLayout>
</af:componentDef>

Next to different properties for the default bahaviour of an input text, we added also two new:

  1. initialfocus
  2. tabindex

Both trigger an af:poll component to render on rendering of the component itself. These are the hooks to take care of the functionality behind.

The next code shows the java side of the logic implementation.

Both poll listener fire only one time and disable the poll component afterwards via the interval of -1. With the help of that we reach an execution of the logic shortly after rendering of the component so javascript can be used. Both solution need to have the component with “ClientComponent=true” to be able to find the component on javascript side.

The initial focus JS call takes care of the javascript representation of the ADF component to set the focus based on the ID.

The tab order JS call is doing the same but with tabindex.

    /**
     * Handle initial focus logic
     * @param pollEvent
     */
    public void focusPollListener(PollEvent pollEvent) {
        RichPoll poll = (RichPoll) pollEvent.getComponent();
        focus(poll.getParent().getChildren().get(0).getClientId());
        poll.setInterval(-1);
        AdfFacesContext.getCurrentInstance().addPartialTarget(poll);
    }
    
    private void focus(String clientId) {
        runScript("var comp = AdfPage.PAGE.findComponent('" + clientId + "'); " + "comp.focus();");
    }
    
    /**
     * Handle tab order logic
     * @param pollEvent
     */
    public void tabindexPollListener(PollEvent pollEvent) {
        RichPoll poll = (RichPoll) pollEvent.getComponent();
        UIXDeclarativeComponent _this = (UIXDeclarativeComponent) JSFUtils.resolveExpression("#{comp}");
        tabindex(poll.getParent().getChildren().get(0).getClientId(),
                 (Integer) JSFUtils.resolveExpression("#{attrs.tabindex}"));
        poll.setInterval(-1);
        AdfFacesContext.getCurrentInstance().addPartialTarget(poll);
    }

    private void tabindex(String clientId, Integer tabindex) {
        runScript("document.getElementById(\"" + clientId + "::content\").tabIndex = \"" + tabindex + "\";");
    }
    
    /**
     * Run JS dynamically
     * @param script
     */
    private void runScript(String script) {
        log.finest("Skript: " + script);
        FacesContext fctx = FacesContext.getCurrentInstance();
        ExtendedRenderKitService erks = Service.getRenderKitService(fctx, ExtendedRenderKitService.class);
        erks.addScript(fctx, script);
    }

Where to put the component?

The best place to store custom declarative components is an ADF library. Here we need to register the new component in the declarativecomp-metadata.xml in ViewController/src/META-INF folder.

<?xml version="1.0" encoding="windows-1252" ?>
<declarativeCompDefs xmlns="http://xmlns.oracle.com/adf/faces/rich/declarativecomp">
    <declarativecomp-jsp-ui-def>/components/v7InputText.jsf</declarativecomp-jsp-ui-def>
    <declarativecomp-taglib>
        <taglib-name>V7 Components</taglib-name>
        <taglib-uri>/v7ComponentLib</taglib-uri>
        <taglib-prefix>v7</taglib-prefix>
    </declarativecomp-taglib>
</declarativeCompDefs>

After the exposure of this ViewController project to an ADF library JAR file we can consume it in another ViewController project and the new component should be available in the components window.

You can simply use it by drag and drop like the following:

<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE html>
<f:view xmlns:f="http://java.sun.com/jsf/core" xmlns:af="http://xmlns.oracle.com/adf/faces/rich"
        xmlns:c="http://java.sun.com/jsp/jstl/core" xmlns:v7="/v7ComponentLib">
    <af:document title="#{stammdatentaskflowviewcontrollerBundle.STAMMDATEN}" id="d1">
        <af:messages id="m1"/>
        <af:form id="f1" usesUpload="true">
            <v7:v7InputText id="v71" initialfocus="false" tabindex="2"/>
            <v7:v7InputText id="v72" initialfocus="true" tabindex="1"/>
            <v7:v7InputText id="v73" initialfocus="false" tabindex="3"/>
        </af:form>
    </af:document>
</f:view>

Example

In this scenario we added 3 inputtext fields based on our own component. The second will have initial focus enabled. If you tab to the next field the first one is focused. If you tab again the last own gets focused finally!

 

demo