A Hidden Gem of ADF Faces 12c: The <af:target> Tag

Introduction

In JDeveloper 12c, the <af:target> tag has been added, a very powerful new ADF Faces tag which can make your life much easier and more productive when building ADF Faces pages. This post discusses how you use this new tag, and explains how specific functional requirements that used to be mind-boggling to implement, are now easily and quickly implemented using this new tag.

Main Article

To really value the power of this new tag, it is important to understand the complex implementations often required to implement seemingly simple use cases before this tag existed. In this article, we will first discuss such use cases, then introduce the <af:target> tag, and finally we will see how the same use cases can be implemented smoothly using this tag.

Problematic Use Cases Before <af:target> Tag Existed

The development problems that we will discuss are all related to the ADF and JSF Lifecycle. So, it helps to have an understanding of the ADF/JSF Lifecycle and its phases that are executed when you submit a JSF page. It is beyond the scope of this article to explain these phases, so if you don’t feel comfortable yet with the ADF/JSF lifecycle, then first take a look at the following resources:

The presentation and ADF insider not only explain the JSF/ADF lifecycle, but also discuss in great detail the development problems and their (often complex) solutions that you might encounter when using an older ADF Faces version. In this article, we will use three of those use cases that were difficult (or impossible) to implement before this tag existed, and illustrate how easily they can be implemented using the new tag.

Use Case 1: Avoiding premature validation when clicking on a button

This use case is illustrated by the following screen shot.

Usecase1

When clicking the Suggest button, a suggested greeting should be displayed in the greeting field based on the name entered. The name that is entered must have at least 4 characters.

Here is the source code of the page:

<af:panelFormLayout id="pfl1">
  <af:inputText required="true" label="Name" id="it1" value="#{viewScope.HelloBeanSu.name}">
    <f:validateLength minimum="4"></f:validateLength>
  </af:inputText>
  <af:panelLabelAndMessage showRequired="true" label="Greeting" id="plam2">
    <af:inputText required="true" label="Greeting" id="it2" value="#{viewScope.HelloBeanSu.greeting}"
                  simple="true"/>
    <f:facet name="end">
      <af:button text="Suggest" id="cb3"
                        actionListener="#{viewScope.HelloBeanSu.suggestPreferredGreeting}"></af:button>
    </f:facet>
  </af:panelLabelAndMessage>
  <af:inputDate label="Date" required="true" id="id1" value="#{viewScope.HelloBeanSu.date}"/>
  <f:facet name="footer">
    <af:panelGroupLayout id="pgl3" layout="horizontal">
      <af:button text="Say Hello" id="cb1" action="#{viewScope.HelloBeanSu.sayHello}"/>
      <af:button text="Reset" id="cb2" immediate="true" actionListener="#{viewScope.HelloBeanSu.reset}"></af:button>
      <f:facet name="separator">
        <af:spacer width="10" height="10" id="s3"/>
      </f:facet>
    </af:panelGroupLayout>
  </f:facet>
</af:panelFormLayout>

Note: the code snippet uses the new 12c <af:button> tag, this is the renamed version of the now deprecated <af:commandButton> tag in ADF Faces 12c.

And this is the Java code implemented behind the suggestPreferredGreeting method:

public void suggestPreferredGreeting(ActionEvent event) {
  String greeting = getPreferredGreeting(getName());
  setGreeting(greeting);
}

private String getPreferredGreeting(String name)
{
  if ("Steven".equalsIgnoreCase(name)) {
    return "Goedendag";
  }
  else if ("Angela".equalsIgnoreCase(name)) {
    return "Gutentag";
  }
  else if ("Nathalie".equalsIgnoreCase(name)) {
    return "Bonjour";
  }
  else if ("Barack".equalsIgnoreCase(name)) {
    return "Hi";
  }
  else {
    return "Hello";
  }    
}

There are quite a few issues you will encounter when implementing this use case in JDeveloper 11.1.1.x or 11.1.2.x:

  • When clicking the suggest button, we get validation errors on the other fields: greeting and date
  • To avoid these validation errors, we can set the immediate property of the Suggest button to true. However, this will also skip the required validation of the name field which should have at least 4 characters.
  • Furthermore, by setting immediate=true, the setName method in the managed bean will no longer be called because the Update Model phase is also skipped. So, the suggestPreferredGreeting method can no longer use the getName method to get hold of the name just entered by the user.
  • We cannot use a valueChangeListener to set the name value in the managed bean, because a valueChangeListener is executed in the Process Validations phase which is also skipped when immediate is set to true on a command component. And if we set immediate=true on the name field to execute the valueChangeListener in the Apply Request Values phase, we hit the issues described below in use case 3….
  • And because immediate=true even if the greeting would have been set correctly in the managed bean, it would not display because the greeting inputText component will not check its underlying managed bean value when an immediate request has been sent to the server

To avoid all these issues, you do need to set immediate=true on the button, and write Java code to programmatically execute the validation on the name field, obtain the entered value for the name field directly from the inputText component using the binding property, and programmatically refresh (re-render) the greeting field. See the aforementioned presentation for implementation details.

Well, you have to admit, that is a lot of work for a seemingly simple requirement, right?

Use Case 2: Refreshing dependent fields when tabbing out an item

This use case is a variation of the first use case, rather than clicking on a button to suggest a preferred greeting, the suggested greeting will be displayed automatically when the user tabs out the name field.

Usecase2

The relevant page code snippet looks like this:

<af:inputText required="true" label="Name" id="it1"
              value="#{viewScope.HelloBeanAs.name}" autoSubmit="true"
              valueChangeListener="#{viewScope.HelloBeanAs.nameChanged}">
</af:inputText>
<af:inputText required="true" label="Greeting" id="it2"
              value="#{viewScope.HelloBeanAs.greeting}"/>

The Java code of the valueChangeListener method looks like this:

public void nameChanged(ValueChangeEvent valueChangeEvent) {
  String name = (String) valueChangeEvent.getNewValue();
  String greeting = getPreferredGreeting(name);      
  setGreeting(greeting);
}

What are the issues with this use case? Well, we do not (yet) have issues with premature validation because when auto-submitting an inputText component, the ADF optimized lifecycle kicks in and ensures that the JSF lifecycle is only executed on the component itself. The problem here is that we do not see the suggested value of the greeting field in the user interface because the ADF optimized lifecycle, by default, only re-renders the component that is auto-submitted. You might think the obvious solution is to add a partialTriggers property to the greeting field that references the name field. However, this will not work because of so-called Cross-Component-Refresh: the ADF optimized lifecycle will now also run the JSF lifecycle phases against the greeting field causing again premature validation leading to a “field is required” error on the greeting field when tabbing out the name field. The solution here is less elaborate than use case 1 but still requires Java coding: you need to programmatically refresh the greeting field using the AdfFacesContext.addPartialTarget API.  

Use Case 3: Implementing a cancel or reset button

If you want to abandon or reset a page that misses required data, or contains invalid data, then you typically do this by adding a Cancel button with immediate property set to true. However, this will only work when there are no input elements that also have the immediate property set to true. If you click a button with immediate set to true, and there are input elements which also have immediate set to true, then validation of these elements is executed in the Apply Request Values phase and might cause the cancel or reset action to fail, as illustrated by the screen shot shown below.

Usecase3

Source code of this page fragment:

<af:panelFormLayout id="pfl1">
  <af:inputText required="true" label="Name" id="it1" value="#{viewScope.HelloBeanAs.name}" immediate="true"
                autoSubmit="true" valueChangeListener="#{viewScope.HelloBeanAs.nameChanged}">
    <f:validateLength minimum="4"></f:validateLength>
  </af:inputText>
  <af:inputText required="true" label="Greeting" id="it2" value="#{viewScope.HelloBeanAs.greeting}"/>
  <af:inputDate label="Date" required="true" id="id1" value="#{viewScope.HelloBeanAs.date}"/>
  <f:facet name="footer">
    <af:panelGroupLayout id="pgl3" layout="horizontal">
      <af:button text="Say Hello" id="cb1" action="#{viewScope.HelloBeanAs.sayHello}"/>
      <af:button text="Reset" id="cb2" immediate="true" actionListener="#{viewScope.HelloBeanAs.reset}">
       <af:resetActionListener/>
      <af:button>
      <f:facet name="separator">
        <af:spacer width="10" height="10" id="s3"/>
      </f:facet>
    </af:panelGroupLayout>
  </f:facet>
</af:panelFormLayout>

And the reset method looks like this:

public void reset(ActionEvent event) {
  setName(null);
  setDate(null);
  setHelloMessage(null);
}

Prior to ADF Faces 12c, there is no solution for this problem other than just not setting immediate to true on input elements.

Introducing the <af:target> tag

The tag documentation states the following about the <af:target> tag: it provides a declarative way to allow a component to specify the list of targets it wants executed and rendered when an event (among the list of events) is fired by the component. You might wonder, is that all, what is the big deal here? Well, the great power of this tag is in two things:

  • You have complete control on which components the JSF lifeycle is executed, and which components are re-rendered (refreshed).
  • The list of components on which the JSF lifecycle will be executed, can be defined separately from the list of components that needs to be re-rendered. 

Before this tag existed, there was little control over which components were executed in the JSF lifecycle. Either the standard JSF lifecycle kicked in which processed all the components on a page, or the ADF optimized lifecycle kicked in which has its own algorithm for determining the boundary component on which the lifecycle is run. This list of components (boundary component and its children) can be extended, but cannot be reduced. Clicking a button inside an <af:region> will always run the JSF lifecycle on at least all components within the region, possibly causing premature validation issues as discussed in use case 1. Furthermore, with the optimized ADF lifecycle the components that are executed are also re-rendered. Adding a partialTrigger property to add a component to the re-render list also means that the component is added to the execute-lifecycle list, causing issues as mentioned in use case 2.

The <af:target> tag has the following attributes:

Name Description
events List of event names for which the target rules apply. The space delimited legal values are: @all, action, calendar, calendarActivity, calendarActivityDurationChange, calendarDisplayChange, carouselSpin, contextInfo, dialog, disclosure, focus, item, launch, launchPopup, poll, popupCanceled, popupFetch, query, queryOperation, rangeChange, regionNavigation, return, returnPopupData, returnPopup, rowDisclosure, selection, sort, and valueChange. The default value is @all.
execute Set of components that will be executed when one of the specified events is raised. If a literal is specified it must be a space delimited String of component identifiers and/or one of the keywords. Supported keywords are @this, @all and @default. The @default keword can be used to fall back to the ADF default behavior which is usually the behavior of the ADF optimized lifecycle. The default value is @default.
render Set of the components that will be re-rendered when one of the specified events is raised. If not specified the default behavior will apply. Supported keywords are @this, @all and @default. The keyword @default can be used to fall back to the ADF default behavior which is usually the behavior of the ADF optimized lifecycle. The default value is @default.

 

The <af:target> tag in action

In the first use case, avoiding premature validation when clicking a button, the added value of this tag is most visible. All issues with this use case as discussed above can be avoided by using the <af:target> tag as follows:

<af:inputText required="true" label="Name" id="it1" value="#{viewScope.HelloBeanSu.name}">
  <f:validateLength minimum="4"></f:validateLength>
</af:inputText>
<af:panelLabelAndMessage showRequired="true" label="Greeting" id="plam2">
  <af:inputText required="true" label="Greeting" id="it2" value="#{viewScope.HelloBeanSu.greeting}"
                simple="true"/>
  <f:facet name="end">
    <af:button text="Suggest" id="cb3"
                      actionListener="#{viewScope.HelloBeanSu.suggestPreferredGreeting}">
       <af:target execute="@this it1" render="it2"/>                
    </af:button>
  </f:facet>
</af:panelLabelAndMessage>

The execute attribute specifies that only the button itself and the name field should be executed as part of the lifecycle. This ensures that the setName method in the managed bean will be called in the Update Model phase, and the suggestPreferredGreeting method in the Invoke Application phase. No other components are processed so we don’t get premature validation errors. We only need to re-render the greeting field so the render attribute specifies the id of this field.

 The solution in the second use case, refreshing dependent items when tabbing out an item, is very similar to the first use case. Main difference is that we now add the <af:target> tag to the name inputText component:

<af:inputText required="true" label="Name" id="it1" value="#{viewScope.HelloBeanAs.name}"
              autoSubmit="true" valueChangeListener="#{viewScope.HelloBeanAs.nameChanged}">
  <f:validateLength minimum="4"></f:validateLength>
  <af:target execute="@this" render="it2"/>
</af:inputText>
<af:inputText required="true" label="Greeting" id="it2" value="#{viewScope.HelloBeanAs.greeting}"/>

The solution in the third use case, implementing a cancel or reset button, is to no longer use the immediate property on the button, and use the <af:target> tag as follows:

<af:button text="Reset" id="cb2" actionListener="#{viewScope.HelloBeanAs.reset}">
  <af:target execute="@this" render="@all"/>
</af:button>

You can argue how useful this is, as the <af:target> tag removes the need for using the immediate attribute on an input component all together as shown in the second use case. But at least it shows that we can completely avoid the use of the immediate property if desired. It also removes the need to use the <af:resetActionListener> tag, which is a poorly understand tag that many developers just add because they are told so, without really understanding what it is doing.

Final Observations

 The render attribute of the <af:target> tag can be seen as the inverse function of the partialTriggers property with one important difference: a component listed in the render attribute will NOT be executed as part of the JSF lifecycle. It also moves the responsibility of dependent components to refresh themselves when appropriate to the triggering component which is not necessarily a good thing as the triggering component now needs to be aware of all its dependent components. So, the partialTriggers property still has its place and can be used when there are no issues with the component also being added to the JSF lifecycle execute list. You can also use them in combination, but then the partialTriggers property “wins”. Let me explain what I mean by that with an example based on the second use case:

<af:inputText required="true" label="Name" id="it1" value="#{viewScope.HelloBeanAs.name}" autoSubmit="true"
              valueChangeListener="#{viewScope.HelloBeanAs.nameChanged}">
  <f:validateLength minimum="4"></f:validateLength>
  <af:target execute="@this" render="@this"/>
</af:inputText>
<af:inputText required="true" label="Greeting" id="it2" value="#{viewScope.HelloBeanAs.greeting}"
              partialTriggers="it1"/>

The render attribute no longer specifies the greeting field id, instead the greeting field “subscribes” itself to the “tab-out-name-field” event so it will be re-rendered. However, this will not work in this use case: while the <af:target> tag only specifies the name field itself to be executed in the JSF lifecycle, the greeting component will also be executed because of the partialTriggers property pointing to the name field.

  In our experience, the use of the immediate attribute, both on command components and input components, is an endless source of confusion, misunderstanding and utter frustration resulting in loss of productivity and buggy applications. With the availability of the <af:target> tag the immediate attribute has become redundant and you might want to consider to stop using this attribute in favor of the <af:target> tag. Developers will get a better understanding of the ADF/JSF Lifecycle when using this tag, and it will probably lead to more robust applications that are built faster.

More information: Section 8.3 Using the Target Tag To Execute PPR in the fusion middleware documentation “Developing Web User Interfaces with Oracle ADF“.

Add Your Comment