Skip Headers
Oracle® Application Development Framework Developer's Guide For Forms/4GL Developers
10g Release 3 (10.1.3.0)
B25947-01
  Go To Table Of Contents
Contents
Go To Index
Index

Previous
Previous
 
Next
Next
 

10.5 Application Module Databinding Tips and Techniques

{para}?>

10.5.1 How to Create a Record Status Display

When building pages you'll often want to display some kind of record status indicator like: "Record 5 of 25". If you display multiple rows on a page, then you may also want to display a variant like "Record 5-10 of 25". You can build a record indicator like this using simple text components, each of which displays an appropriate value from an iterator binding or table binding using an EL expression. The iterator binding's rangeSize property defines how many rows per page it makes available to display in the user interface. If your page definition contains either an iterator binding named SomeViewIter or a table binding named SomeView, you can reference the following EL expressions:

  • Number of Rows per Page

    #{bindings.SomeViewIter.rangeSize}

    #{bindings.SomeView.rangeSize}

  • Total Rows

    #{bindings.SomeViewIter.estimatedRowCount}

    #{bindings.SomeView.estimatedRowCount}

  • First Row on the Current Page

    #{bindings.SomeViewIter.rangeStart + 1}

    #{bindings.SomeView.rangeStart + 1}

  • Last Row on the Current Page

    #{bindings.SomeViewIter.rangeStart + bindings.SomeViewIter.rangeSize}

    #{bindings.SomeView.rangeStart + bindings.SomeView.rangeSize}

  • Current Row Number

    #{bindings.SomeViewIter.rangeStart + bindings.SomeViewIter.currentRowIndexInRange + 1}

    #{bindings.SomeView.currentRowIndex + 1}

10.5.2 How to Work with Named View Object Bind Variables

When a view object has named bind variables, an additional ExecuteWithParams operation appears in the corresponding data collection's Operations folder. As shown in Figure 10-9, this built-in operation accepts one parameter corresponding to each named bind variable. For example, the StaffListByEmailNameRole view object in the figure has four named bind variables — EmailAddress, Role, TheFirstName, and TheLastName — so the parameters appear for the ExecuteWithParams action having these same names. At runtime, when the ExecuteWithParams built-in operation is invoked by name by the data binding layer, each named bind variable value is set to the respective parameter value passed by the binding layer, and then the view object's query is executed to refresh its row set of results.

Figure 10-9 Work with Named Bind Variables Using Either a Built-in or Custom Operation

Image shows ways of working wtih named bind variables

An alternative approach, also shown in Figure 10-9, involves creating a custom view object "finder" method that accepts the arguments you want to allow the user to set and then including this method on the client interface of the view object in the View Object Editor. Example 10-1 shows what the code for such a custom method would look like. It is taken from the StaffListByEmailNameRole view object in the SRDemo application. Notice that due to the application module's active data model, the method does not need to return the data to the client. The method has a void return type, sets the bind variable values to the parameter values passed into the method using the generated bind variable accessor methods setEmailAddress(), setRole(), setTheFirstName(), and setTheLastName(). Then, it calls executeQuery() to execute the query to refresh the view object's row set of results.

Example 10-1 Custom View Object Method Sets Named Bind Variables and Executes Query

// From SRDemo Sample's StaffListByEmailNameRoleImpl.java
public void findStaffByEmailNameRole(String email,
                                     String firstName,
                                     String lastName,
                                     String role) 
{
  setEmailAddress(email);
  setRole(role);
  setTheFirstName(firstName);
  setTheLastName(lastName);
  executeQuery();
}

Both of these approaches accomplish the same end result. Both can be used with equal ease from the Data Control Palette to create a search form. The key differences between the approaches are the following:

Using the built-in ExecuteWithParams operation
  • You don't need to write code since it's a built-in feature.

  • You can drop the operation from the Data Control Palette to create an ADF Search Form to allow users to search the view object on the named bind variable values.

  • Your search fields corresponding to the named bind variables inherit bind variables UI control hints for their label text that you can define as part of the view object component.

Using the custom View Object "finder" operation
  • You need to write a little custom code, but you see a top-level operation in the Data Control Palette whose name can help clarify the intent of the find operation.

  • You can drop the custom operation from the Data Control Palette to create an ADF Search Form to allow users to search the view object on the parameter values.

  • Your search fields corresponding to the method arguments do not inherit label text UI control hints you define on the view object's named bind variables themselves. Instead, you need to define UI control hints for their label text by using the Properties... choice from the context menu of the page definition variable corresponding to the method argument as shown in Figure 10-10.

Figure 10-10 Accessing Properties of Page Definition Variables to Set UI Control Hints

Image of context menus in Application Navigator

In short, it's good to be aware of both approaches and you can decide for yourself which approach you prefer. As described in Section 10.6.5, "The SRStaffSearch Page", while the StaffListByEmailNameRole view object contains the above findStaffByEmailNameRole() custom method for educational purposes, the demo's JSF page uses the declarative ExecuteWithParams built-in action.

10.5.3 How to Use Find Mode to Implement Query-by-Example

In the ADF Model layer, an iterator binding supports a feature called "find mode" when working with data collections that support query-by-example. As cited in Section 5.8, "Filtering Results Using Query-By-Example View Criteria", view objects support query-by-example when view criteria row set of view criteria rows has been applied. The view criteria rows have the same structure as the rows in the view object, but the datatype of each attribute is String to allow criteria like "> 304" or "IN (314,326)" to be entered as search criteria.

In support of the find mode feature, an iterator binding has a boolean findMode property that defaults to false. As shown in Figure 10-11, when findMode is false the iterator binding points at the row set containing the rows of data in the data collection. In contrast, when you set findMode to true the iterator binding switches to point at the row set of view criteria rows.

Figure 10-11 When Find Mode is True, Iterator Binding Points at ViewCriteria Row Set Instead

Image describes ADF Find Mode feature

In the binding layer, action bindings that invoke built-in data control operations are associated to an iterator binding in the page definition metadata. This enables the binding layer to apply an operation like Create or Delete, for example, to the correct data collection at runtime. When an operation like Create or Delete is invoked for an iterator binding that has findMode set to false, these operations affect the row set containing the data. In contrast, if the operations are invoked for an iterator binding that has findMode set to true, then they affect the row set containing the view criteria row, effectively creating or deleting a row of query-by-example criteria.

The built-in Find operation allows you to toggle an iterator binding's findMode flag. If an iterator binding's findMode is false, invoking the Find operation for that iterator binding sets the flag to true. The UI components that are bound to attribute bindings related to this iterator switch accordingly to displaying the current view criteria row in the view criteria row set. If the user modifies the values of UI components while their related iterator is in find mode, the values are applied to the view criteria row in the view criteria row set. If you invoke the Execute or ExecuteWithParams built-in operation on an iterator that is in find mode, each will first toggle find mode to false, apply the find mode criteria, and refresh the data collection.

If an iterator binding's findMode is true invoking the Find operation sets it to false and removes the view criteria rows in the view criteria row set. This effectively cancels the query-by-example mode.

10.5.4 How to Customize the ADF Page Lifecycle to Work Programmatically with Bindings

The ADF Controller layer integrates the JSF page lifecycle with the ADF Model data binding layer. You can customize this integration either globally for your entire application, or on a per-page basis. This section highlights some tips related to how the SRDemo application illustrates using both of these techniques.

10.5.4.1 Globally Customizing the ADF Page Lifecycle

To globally customize the ADF Page Lifecycle, do the following:

  • Create a class that extends oracle.adf.controller.faces.lifecycle.FacesPageLifecycle

  • Create a class that extends ADFPhaseListener and overrides the createPageLifecycle() method to return an instance of your custom page lifecycle class.

  • Change your faces-config.xml file to use your subclass of ADFPhaseListener instead of the default ADFPhaseListener. As shown in Figure 10-12, you can do this on the Overview tab of the JDeveloper faces-config.xml editor, in the Life Cycle category.


Note:

Make sure to replace the existing ADFPhaseListener with your custom subclass of ADFPhaseListener, or everything in the JSF / ADF lifecycle coordination will happen twice!

Figure 10-12 Setting Up a Custom ADFPhaseListener To Install a Custom Page Lifecycle Globally

Image of faces-config.xml editor

The SRDemo application includes a SRDemoPageLifecycle class that globally overrides the reportErrors() method of the page lifecycle to change the default way that exceptions caught and cached by the ADF Model layer are reported to JSF. The changed implementation reduces the exceptions reported to the user to include only the exceptions that they can directly act upon, suppressing additional "wrapping" exceptions that will not make much sense to the end user.

10.5.4.2 Customizing the Page Lifecycle for a Single Page

You can customize the lifecycle of a single page setting the ControllerClass attribute of the pageDefinition to identify a class that either:

  • Extends oracle.adf.controller.v2.PageController class

  • Implements oracle.adf.controller.v2.PagePhaseListener interface

The value of the page definition's ControllerClass attribute can either be:

  • A fully qualified class name

  • An EL expression that resolves to a class that meets the requirements above

Using an EL expression for the value of the ControllerClass, it is possible to specify the name of a custom page controller class (or page phase listener implementation) that you've configured as a managed bean in the faces-config.xml file. This includes a backing bean for a JSF page, provided that it either extended PageController or implements PagePhaseListener.

Figure 10-13 illustrates how to select the root node of the page definition in the Structure window to set.

Figure 10-13 Setting the ControllerClass of a Page Definition

Image of setting a controller class in Property Inspector

Note:

When using an EL expression for the value of the ControllerClass attribute, the Structure window may show a warning, saying the "#{YourExpression}" is not a valid class. You can safely ignore this warning.

10.5.4.3 Using Custom ADF Page Lifecycle to Invoke an onPageLoad Backing Bean Method

The SRDemo application contains a OnPageLoadBackingBeanBase class in the oracle.srdemo.view.util package that implements the PagePhaseListener interface described above using code like what's shown in Example 10-2. The class implements the interface's beforePhase() and afterPhase() methods so that in invokes an onPageLoad() method before the normal ADF prepare model phase, and an onPagePreRender() method after the normal ADF prepare render phase.

Example 10-2 PagePhaseListener to Invoke an onPageLoad() and onPagePreRender() Method

// In class oracle.srdemo.view.util.OnPageLoadBackingBeanBase
/**
 * Before the ADF page lifecycle's prepareModel phase, invoke a
 * custom onPageLoad() method. Subclasses override the onPageLoad()
 * to do something interesting during this event.
 * @param event
 */
public void beforePhase(PagePhaseEvent event) {
  FacesPageLifecycleContext ctx =
    (FacesPageLifecycleContext)event.getLifecycleContext();
  if (event.getPhaseId() == Lifecycle.PREPARE_MODEL_ID) {
    bc = ctx.getBindingContainer();
    onPageLoad();
    bc = null;
  }
}
/**
 * After the ADF page lifecycle's prepareRender phase, invoke a
 * custom onPagePreRender() method. Subclasses override the onPagePreRender()
 * to do something interesting during this event.
 * @param event
 */    
public void afterPhase(PagePhaseEvent event) {
  FacesPageLifecycleContext ctx =
   (FacesPageLifecycleContext)event.getLifecycleContext();
  if (event.getPhaseId() == Lifecycle.PREPARE_RENDER_ID) {
    bc = ctx.getBindingContainer();
    onPagePreRender();
    bc = null;
  }
}
public void onPageLoad() {
  // Subclasses can override this.
}
public void onPagePreRender() {
  // Subclasses can override this.
}

If a managed bean extends the OnPageLoadBackingBeanBase class, then it can be used as an ADF page phase listener because it inherits the implementation of this interface from the base class. If that backing bean then overrides either or both of the onPageLoad() or onPagePreRender() method, that method will be invoked by the ADF Page Lifecycle at the appropriate time during the page request lifecycle. The last step in getting such a backing bean to work, is to tell the ADF page definition to use the backing bean as its page controller for that page. As described above, this is done by setting the ControllerClass attribute on the page definition in question to an EL expression that evaluates to the backing bean.

The SRMain page in the SRDemo application uses the technique described in this section to illustrate writing programmatic code in the onPageLoad() method of the SRMain backing bean in the oracle.srdemo.view.backing package. Since that backing bean is named backing_SRMain in faces-config.xml, the ControllerClass property of the SRMain page's page definition is set to the EL expression "#{backing_SRMain}".

10.5.5 How to Use Refresh Correctly for InvokeAction and Iterator Bindings

Figure 10-14 illustrates how the JSF page lifecycle relates to the extended page processing phases that the ADF page lifecycle adds. You can use the Refresh property on iterator bindings and invokeAction executables in your page definition to control when each are evaluated during the ADF page lifecycle, either during the prepareModel phase, the prepareRender phase, or both. Since the term "refresh" isn't exactly second-nature, this section clarifies what it means for each kind of executable and how you should set their Refresh property correctly to achieve the behavior you need.

Figure 10-14 How JSF Page Lifecycle and ADF Page Lifecycle Phases Relate

Image shows relationships between JSF and ADF lifecycles

10.5.5.1 Correctly Configuring the Refresh Property of Iterator Bindings

In practice, when working with iterator bindings for view object instances in an application module, you can simply leave the default setting of Refresh="ifNeeded". You may complement this with a boolean-valued EL expression in the RefreshCondition property to conditionally avoid refreshing the iterator if desired. However, you may still be asking yourself, "What does refreshing an iterator binding mean anyway?"

An iterator binding, as its name implies, is a binding that points to an iterator. For scalability reasons, at runtime the iterator bindings in the binding container release any reference they have to a row set iterator at the end of each request. During the next request, the iterator bindings are refreshed to again point at a "live" row set iterator that is tracking the current row of some data collection. The act of refreshing an ADF iterator binding during the ADF page lifecycle is precisely the operation of accessing the row set iterator to "reunite" the binding to the row set iterator to which it is bound.

If an iterator binding is not refreshed during the lifecycle, it is not pointing to any row set iterator for that request. This results in the value bindings related to that iterator binding not having any data to display. This can be a desirable result, for example, if you want a page like a search page to initially show no data. To achieve this, you can use the RefreshCondition of:

#{adfFacesContext.postback == true}

The adfFacesContext.postback boolean property evaluates to false when a page is first rendered, or rendered due to navigation from another page. It evaluates to true when the end user has interacted with some UI control on the page, causing a postback to that page to handle the event. By using this expression as the RefreshCondition for a particular iterator binding, it will refresh the iterator binding only when the user interacts with a control on the page.

The valid values for the Refresh property of an iterator binding are as follows. If you want to refresh the iterator during:

  • Both the prepareModel and prepareRender phases, use Refresh="ifNeeded" (default)

  • Just during the prepareModel phase, use Refresh="prepareModel"

  • Just during the prepareRender phase, use Refresh="renderModel"

If you only want the iterator binding to refresh when your own code calls getRowSetIterator() on the iterator binding, set Refresh="never". Other values of Refresh are either not relevant to iterator bindings, or reserved for future use.

10.5.5.2 Refreshing an Iterator Binding Does Not Forcibly Re-Execute Query

It is important to understand that when working with iterator bindings related to a view object instance in an application module, refreshing an iterator binding does not forcibly re-execute its query each time. The first time the view object instance's row set iterator is accessed during a particular user's unit of work, this will implicitly execute the view object's query if it was not already executed. Subsequent refreshing of the iterator binding related to that view object instance on page requests that are part of the same logical unit of work will only access the row set iterator again, not forcibly re-execute the query. Should you desire re-executing the query to refresh its data, use the Execute or ExecuteWithParams built-in operation, or programmatically call the executeQuery() method on the iterator binding.

10.5.5.3 Correctly Configuring Refresh Property of InvokeAction Executables

Several page definitions in the SRDemo application use the declarative invokeAction binding to trigger either built-in operations or custom operations during this extended ADF page processing lifecycle. Each invokeAction has an id property that give the binding a name, and then three other properties of interest:

  • The Binds property controls what the invokeAction will do if it fires

    Its value is the name of an action binding or method action binding in the same binding container.

  • The Refresh property controls when the invokeAction will invoke the action binding

    To have it fire during the ADF page lifecycle's:

    • prepareModel phase, use Refresh=prepareModel

    • prepareRender phase, use Refresh=renderModel

    • prepareModel and prepareRender phases, use Refresh=ifNeeded

  • The RefreshCondition property can be used to control whether it will fire at all

    Its value, if supplied, is a boolean-valued EL expression. If the expression evaluates to true when the invokeAction is considered during the page lifecycle, the related action binding is invoked. If it evaluates to false, then the action binding is not invoked.


Note:

Other values of the Refresh property not described here are reserved for future use.

Notice in Figure 10-14 that the key distinction between the ADF prepareModel phase and the prepareRender phase is that one comes before JSF's invokeApplication phase, and one after. Since JSF's invokeApplication phase is when action listeners fire, if you need your invokeAction to trigger after these action listeners have performed their processing, you'll want to use the Refresh="renderModel" setting on it.

If the invokeAction binds to a method action binding that accepts parameters, then two additional values can be supplied for the Refresh property: prepareModelIfNeeded and renderModelIfNeeded. These have the same meaning as their companion settings without the *IfNeeded suffix, except that they perform an optimization to compare the current set of evaluated parameter values with the set that was used to invoke the method action binding the previous time. If the parameter values for the current invocation are exactly the same as the ones used previously, the invokeAction does not invoke its bound method action binding.


Note:

The default value of the Refresh property is ifNeeded. This means that if you do not supply a RefreshCondition expression to further refine its firing, the related action binding will be invoked twice during each request. Oracle recommends either adding an appropriate RefreshCondition expression (if you want it evaluated during both phases) or changing the default Refresh setting for invokeAction bindings to either prepareModel or renderModel, depending on whether you want your invokeAction to occur before or after the JSF invokeApplication phase.

10.5.6 Understanding the Difference Between setCurrentRowWithKey and setCurrentRowWithKeyValue

You can call the getKey() method on any view row get a Key object that encapsulates the one or more key attributes that identify the row. As you've seen in various examples, you can also use a Key object like this to find a view row in a row set using the findByKey(). At runtime, when either the setCurrentRowWithKey or the setCurrentRowWithKeyValue built-in operations is invoked by name by the data binding layer, the findByKey() method is used to find the row based on the value passed in as a parameter before setting the found row as the current row.

Confusingly, as shown in Figure 10-15, the setCurrentRowWithKey and setCurrentRowWithKeyValue operations both expect a parameter named rowKey, but they differ precisely by what each expects that rowKey parameter value to be at runtime:

setCurrentRowWithKey

setCurrentRowWithKey expects the rowKey parameter value to be the serialized string representation of a view row key. This is a hexadecimal-encoded string that looks like this:

000200000002C20200000002C102000000010000010A5AB7DAD9

The serialized string representation of a key encodes all of the key attributes that might comprise a view row's key in a way that can be conveniently passed as a single value in a browser URL string or form parameter. At runtime, if you inadvertently pass a parameter value that is not a legal serialized string key, you may receive exceptions like oracle.jbo.InvalidParamException or java.io.EOFException as a result. In your web page, you can access the value of the serialized string key of a row by referencing the rowKeyStr property of an ADF control binding (e.g. #{bindings.SomeAttrName.rowKeyStr}) or the row variable of an ADF Faces table (e.g. #{row.rowKeyStr}).

setCurrentRowWithKeyValue

setCurrentRowWithKeyValue expects the rowKey parameter value to be the literal value representing the key of the view row. For example, it's value would be simply "201" to find service request number 201.

Figure 10-15 The setCurrentRowWithKeyValue Operation Expects a Literal Attribute Value as the Key

Image shows possible confusion with key values

Note:

If you write custom code in an application module class and need to find a Row based on a serialized string key passed from the client, you can use the getRowFromKey() method in the JboUtil class in the oracle.jbo.client package:
static public Row getRowFromKey(RowSetIterator rsi, String sKey)

Pass the view object instance in which you'd like to find the row as the first parameter, and the serialized string format of the Key as the second parameter.


10.5.7 Understanding Bundled Exception Mode

An application module provides a feature called bundled exception mode which allows web applications to easily present a maximal set of failed validation exceptions to the end user, instead of presenting only the first error that gets raised. By default, the ADF Business Components application module pool enables bundled exception mode for web applications.

You typically will not need to change this default setting. However it is important to understand that it is enabled by default since it effects how validation exceptions are thrown. Since the Java language and runtime only support throwing a single exception object, the way that bundled validation exceptions are implemented is by wrapping a set of exceptions as details of a new "parent" exception that contains them. For example, if multiple attributes in a single entity object fail attribute-level validation, then these multiple ValidationException objects will be wrapped in a RowValException. This wrapping exception contains the row key of the row that has failed validation. At transaction commit time, if multiple rows do not successfully pass the validation performed during commit, then all of the RowValException objects will get wrapped in an enclosing TxnValException object.

When writing custom error processing code, as illustrated by the overridden reportErrors() method in the SRDemoPageLifecycle class in the SRDemo application, you can use the getDetails() method of the JboException base exception class to recursively process the bundled exceptions contained inside it.


Note:

All the exception classes mentioned here are in the oracle.jbo package.