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:- About Using the JSF Lifecycle with ADF Faces, section 5.1 in Developing Web User Interfaces with Oracle ADF Faces.
- Using the ADF Optimized Lifecycle, section 5.3 in Developing Web User Interfaces with Oracle ADF Faces.
- 18 Invaluable Lessons About ADF-JSF Interaction, slides 1-8, presentation by me (Steven Davelaar). The remaining slides of this presentation discuss a number of development issues that are now easily resolved using the af:target tag as will be explained in the next sections of this article.
- Avoiding JSF and ADF Lifecyle Frustrations, ADF insider video by ADF Product Manager Frank Nimphius which is a recording of a shortened version of my presentation mentioned in the previous bullet.
Use Case 1: Avoiding premature validation when clicking on a button
This use case is illustrated by the following screen shot.
<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
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.
<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.
<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.
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".
All content listed on this page is the property of Oracle Corp. Redistribution now allowed without written permission