Oracle® Application Development Framework Developer's Guide For Forms/4GL Developers
10g Release 3 (10.1.3.0) B25947-01 |
|
![]() Previous |
![]() Next |
{para}?>
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}
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.
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:
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.
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.
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.
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.
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.
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.
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! |
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.
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.
Note: When using an EL expression for the value of theControllerClass attribute, the Structure window may show a warning, saying the "#{ YourExpr ession} " is not a valid class. You can safely ignore this warning.
|
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}
".
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.
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.
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.
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 theRefresh 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 theRefresh 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.
|
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
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
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
.
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 thegetRowFromKey() 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. |
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 theoracle.jbo package.
|