Oracle® Application Development Framework Case Manual
10g Release 2 (10.1.2) B19163-01 |
|
Previous |
Next |
This chapter contains the following sections:
Section 9.4, "Analyzing the Binding Container for the Edit Account Page"
Section 9.7, "Using EL to Work with Labels, Tooltips, and Other Metadata"
Section 9.9, "Alternative to a Databound Poplist Using Custom Renderer"
In the previous lesson, we created the account object for a new user by generating a data entry form, using a generic, metadata-driven approach. In Lesson 6, we examine another way to display a data entry form, this time to edit user account information: the editexistingaccount.jsp
page uses the traditional JSP page layout approach of placing each control inside an HTML form.
The /EditAccountAction
data action sets up the model layer in its initializeModelForPage()
method. It calls the custom service method prepareToEditAccountInfoFor()
on the ToyStoreService
interface, passing in the name of the current user as an argument. The implementation of this method in the ToyStoreServiceImpl
class looks like this:
/* From: toystore.model.services.ToyStoreServiceImpl */ public boolean prepareToEditAccountInfoFor(String username) { Key k = new Key(new Object[] { username }); ViewObject vo = getAccounts(); /* * We don't want the view object to execute any other query * than the one row we will be finding by key, so we mark * its max fetch size to zero. */ vo.setMaxFetchSize(0); Row[] r = vo.findByKey(k, 1); if (r.length < 1) { return false; } Row rowFound = r[0]; /* * Set the row we found as the current row in the VO */ vo.setCurrentRow(rowFound); return true; }
The above service method performs the following three basic steps:
It creates an oracle.jbo.Key
object based on the current user's name passed in.
It looks up an existing row in the Accounts
view object by passing this key to the findByKey()
method on the view object.
It sets that row as the current row in the view object.
In the corresponding JSP page, named editexistingaccount.jsp
, we use the <html:form>
tag from the Struts HTML tag library to implement the postback pattern by having its action post back to the data page like this:
<html:form action="editaccount.do" method="post">
At runtime, the Struts <html:form>
tag sees the action attribute value of /updateaccount.do
and uses it, along with its action mapping information, to determine that the form bean named DataForm
is the one that should be used to render this form. The DataForm
form bean is defined in struts-config.xml
to use our Oracle ADF BindingContainerActionForm
class.
Since we're rendering the data entry form for just a single "row" of user account information, we don't need to use the JSTL <c:forEach>
tag in this page and we don't need a range binding in our binding container. We simply format the individual fields in the form, using normal HTML table tags to get the prompts and controls to line up nicely. As this page shows off several different techniques in use, the following section will highlight each of the important ones.
Figure 9-1, "The UI Model Tab Shows the Binding Container for the Edit Account Page", shows the binding container for the edit account page. Notice that we have basic attribute bindings for all of the Accounts
object attributes except for Country
, which is a list binding (its icon represents a poplist). We have two iterator bindings: AccountsIterator
for the main account information we're editing, and CountryListIterator
to supply a poplist with the valid country names from which the user can choose for the Country
attribute.
We also have an action binding named save
that is bound to the built-in Commit
operation on the ToyStoreService
data control.
The following sample shows the tags used by the page to output the HTML table row containing the prompt and data for the username field.
<%-- Username field --%> <tr> <th align="right" title="<bean:message key="account.username.tooltip"/>"> <bean:message key="account.username.label"/> </th> <td title="<bean:message key="account.username.tooltip"/>"> <c:out value="${bindings.Username}"/> </td> </tr>
Since username
in this application is not updateable once it has been created, we don't need to render an HTML form control for the data. Using the <c:out>
tag, we just output the value of the field for display using its corresponding binding object. The <bean:message>
tags handle outputting translatable strings from the default ToyStoreResources.properties
file to display the tooltip and label for the username.
When data needs to be entered or edited, you can use a number of other tags in the Struts HTML library to render databound controls. The following code uses the <html:password>
tag to show the Password
property:
<%-- Password field --%> <tr> <th align="right" title="<bean:message key="account.password.tooltip"/>"> <bean:message key="dataentryform.mandatory"/> <bean:message key="account.password.label"/> </th> <td title="<bean:message key="account.password.tooltip"/>"> <html:password property="Password" size="25" maxlength="30"/> </td> <td><html:errors property="Password"/></td> </tr>
Recall that the Oracle ADF BindingContainerActionForm
presents Struts (in this case, tags from the Struts HTML tag library) with a DynaActionForm
bean having properties that are named for, and wired to, the bindings in your current binding container. So, when the <html:password>
tag gets and sets the value of the Password
property on this form bean, behind the scenes Oracle ADF is coordinating the properties of that form bean with the corresponding binding objects.
The above sample also illustrates using the Struts HTML tag <html:errors>
to display any validation errors that are specific to the Password
attribute. Of course, when the form is first rendered there won't be any validation errors, so this table cell will be empty. However, if the user submits the form and the model layer throws validation errors in, when this page is rendered again any errors related to the password will show up next to the password field on the screen.
Also, since the password field is mandatory, we've included a <bean:write>
tag to show the string corresponding to the key dataentryform.mandatory
as a visual marker for the user that the field is required. By default, we render an asterisk.
Oracle ADF entity object and view object components have a number of built-in features that allow developers to define control hints like locale-sensitive labels, tooltips, and format masks. The Oracle ADF binding layer exposes this metadata directly on the binding objects for convenient access by your view layer pages. The <c:out>
tags shown below illustrate the EL expressions for the tooltip and label information that have been associated with the business object attributes or the view object attributes. If an entity object has defined a tooltip for one of its attributes named Firstname
, for example, then this tooltip is inherited by any view objects that include Firstname
. Of course, the view object can also override these control hints if necessary.
<%-- Firstname field --%> <tr> <th align="right" title="<c:out value='${bindings.Firstname.tooltip}'/>"> <c:if test="${bindings.Firstname.mandatory}"> <bean:message key="dataentryform.mandatory"/> </c:if> <c:out value="${bindings.Firstname.label}"/> </th> <td> <html:text property="Firstname" size="30" maxlength="35"/> </td> <td><html:errors property="Firstname"/></td> </tr>
Each binding object exposes runtime metadata about the objects to which it is bound that you can access at runtime using EL expressions. For example, the control value binding for an attribute exposes information about the underlying attribute in the model layer. The above sample shows an example of using this metadata to detect at runtime whether a given attribute, like Firstname
, is mandatory or not. We can use this information, combined with the JSTL <c:if>
tag, to conditionally output the mandatory marker on a required field.
<c:if test="${bindings.Firstname.mandatory}"> <bean:message key="dataentryform.mandatory"/> </c:if>
JDeveloper Tip: To get a quick review of all the available properties on a binding object, just click on the binding in the UI Model tab of the Structure window and press the F1 key. The online help topic for the appropriate binding object appears in an IDE window for your reference. |
Finally, we look at an example of a more sophisticated databound form control, such as a poplist showing the country of residence for a user. There are two dimensions to the poplist control:
The value of the underlying Country
binding, reflected by the selection in the list
The list of all available country names to chose from
Figure 9-2, "The Poplist Displays the Current Value and Valid Values List", illustrates the poplist.
Oracle ADF provides more sophisticated binding objects to handle controls like this one, which have multiple facets to their data binding requirements. The Oracle ADF list binding caters specifically to poplist-type controls that need to manage both a current bound attribute value and a list of valid choices to present to the user. Oracle ADF also supplies a tree binding object that is useful for displaying hierarchical data.
When you click the UI Model tab of the Structure window with editexistingaccount.jsp selected in the Application Navigator, you'll see the bindings described above. Now, select CountryListIterator and open the Property Inspector: you'll see that the RangeSize property has a value of -1
. This value indicates that you want the range of the iterator to include all rows in the list of countries, rather than only a partial set.
Best Practice Tip: The iterator binding range size defaults to 10. For iterators driving the list of choices in a list binding, you will nearly always want to set the range size to be -1 as we've done here. |
Right-click the Country binding and choose Edit from the context menu. You will see the List Binding Editor shown in the screenshot below. The editor allows you to see the binding metadata required to support the Country poplist:
CountryListIterator
, the datasource for the list of available choices.
AccountsIterator
, the iterator whose current row will be used both to determine the current value of the binding and to update the binding when the user selects a new item from the list.
Code
and Country
, the source LOV and target attribute pairs, show that the value of the Code
property from the selected row in CountryList
will be set on the Country
property on the current row of the target AccountsIterator
.
If you click the LOV Display Attributes tab, you can observe that the Description
attribute from the CountryListIterator
is indicated as the value to be displayed to the user in the list. Figure 9-3, "The List Binding Editor for the Country List Binding", shows the editor.
The example below shows how to use <html:select>
and <html:optionsCollection>
to leverage the Country
list binding and display the poplist in our page. The <html:select>
element is bound to the Country
property of the form bean, which corresponds to our list binding object. The <html:optionsCollection>
element gets its data from the nested, list-valued displayData
property of that same Country
binding. The beans in this display data collection each have a prompt
and an index
property, so in the following code we use those as the label and value (respectively) for each option in the list:
<%-- Country field --%> <tr> <th align="right" title="<c:out value='${bindings.Country.tooltip}'/>"> <c:if test="${bindings.Country.mandatory}"> <bean:message key="dataentryform.mandatory"/> </c:if> <c:out value="${bindings.Country.label}"/> </th> <td> <html:select property="Country" > <html:optionsCollection label="prompt" value="index" property="Country.displayData" /> </html:select> </td> <td><html:errors property="Country"/></td> </tr>
Note: For bandwidth optimization, the Oracle ADF binding layer expects the nonvisible values of a list binding to be the zero-basedindex number in their displayData collection. The Oracle ADF list binding handles translating the underlying Country value (like IT , for example) into an index position (like 86 ) in the list of values on both read and write of the binding value.
|
In contrast to the above approach, which relied on the control to render the poplist in the form, we can accomplish the same thing in a more generic, metadata-driven way by performing these steps:
Create a custom renderer class to determine how to handle the country list.
Set a custom property on the attribute of the Oracle ADF Business Components to specify the custom renderer.
Work with the <adf:inputrender>
tag in the JSP page to apply the custom renderer.
Note: The<adf:inputrender> tag implementation consults the value of the EditRenderer attribute property to determine whether the attribute has specified a custom renderer. If none is specified, Oracle ADF picks an appropriate control to render the data.
|
This use of the custom renderer is demonstrated by the formControl.jsp
page introduced in Lesson 5, and described in more detail below.
In the case of the formControl.jsp
page, we've specified the class name toystore.fwk.view.ListBindingPoplistRenderer
, which implements a customized poplist renderer for Oracle ADF list bindings. This custom field renderer extends the default oracle.jdeveloper.html.StaticPickList
renderer to populate some of its properties based on information it can retrieve from the list binding object. The source code for the custom renderer (from the FwkExtensions
project) is shown below:
package toystore.fwk.view; import java.util.List; import java.util.Map; import oracle.jbo.Row; import oracle.jbo.html.BindingContainerDataSource; import oracle.jbo.uicli.binding.JUControlBinding; import oracle.jbo.uicli.binding.JUCtrlListBinding; import oracle.jdeveloper.html.StaticPickList; /** * Extends the oracle.jdeveloper.html.StaticPickList renderer to drive * off of a list binding. */ public class ListBindingPoplistRenderer extends StaticPickList { /** * Overrides renderToString() in StaticPickList */ public String renderToString(Row row) { BindingContainerDataSource ds = (BindingContainerDataSource)getDatasource(); JUControlBinding b = ds.getControlBinding(); String[] labels = null; String[] values = null; if (b instanceof JUCtrlListBinding) { JUCtrlListBinding listBinding = (JUCtrlListBinding)b; List valueList = listBinding.getDisplayData(); int size = valueList.size(); values = new String[size]; labels = new String[size]; for (int z = 0; z < size; z++) { labels[z] = (String)((Map)valueList.get(z)).get("prompt"); values[z] = Integer.toString(z); } setValue(Integer.toString(listBinding.getSelectedIndex())); } setDataSource(labels,values); return super.renderToString(row); } }
You can see that the code accesses the JUControlBinding
object from the datasource, that it checks to be sure the object is a JUCtrlListBinding
object, and that it calls the getDisplayData()
method on the list binding to access the list display data. In order to populate the String[]
variables for the labels and the values, it iterates over the display data collection and adds the prompt
attribute from each bean in the collection to the label
array. Since the Oracle ADF binding layer will expect the value returned from the page to be the numerical row number, we populate the values
array by converting the loop variable z
to a string on each iteration. The net effect is that when our generic formControl.jsp
"component" page renders an HTML form for the bindings in the current binding container, the Country
binding will be rendered as a databound poplist populated from the display data collection named CountryList
.
If you adopt a generic data form rendering technique like the one employed by the formControl.jsp
page in your applications, you can more easily ensure that all data entry forms in your application look and act similarly.
The following hands-on shows how changing the iterator rangesize
property controls the display range.
In the Application Navigator, locate editexistingaccount.jsp in the WEB-INF/jsp folder and select it so that it appears highlighted.
Open the Structure window and select the UI Model tab to view the list of binding definitions in the WEB_INF_jsp_editexistingaccountUIModel definition file.
In the Structure window, double-click CountryListIterator and observe that CountryListIterator is bound to the CountryList data collection. Click Cancel to exit the editor.
With CountryListIterator selected, open the Property Inspector and locate the RangeSize property with the value -1
.
In the Property Inspector, change the RangeSize property value to 10
and press Enter. This will limit the display list to just ten objects from the bound data collection.
Right-click the home.do action and choose Run to launch the application. Click the Login icon and enter the J2EE
/J2EE
username and password. Click the Edit Account icon and display the dropdown list for the Country field. The list should be limited to just ten rows.
The iterator binding is a runtime object that your application creates to access the Oracle ADF binding context. The iterator binding holds references to the bound data collection, it accesses the collection, and it iterates over its data objects. You can set the number of data objects to be fetched from the bound data collection, such that only the number is displayed on the page. The range you specify defines a window you can use to access a subset of the data objects in the collection. By default, the range size is set to just ten data objects.