Developer Tools
JDeveloper
Creating Search Pages with Both Fixed and Dynamic Criteria
Author: Steve Muench, Oracle ADF Development TeamAbstract
Oracle ADF makes it simple to implement search pages that include query-by-example criteria for a fixed set of data collection attributes. After first reviewing the role of ADF iterator bindings and their "find mode" behavior, this article explains how to implement a search page where the user dynamically selects which attribute in the data collection they want to search against, by picking from a drop-down list of available search attributes. While first explaining how to implement using a formal Struts layer, the last section covers how to implement the same functionality using a "Model One" JSP approach, without Struts as well.
| NOTE: |
You can download the
|
| NOTE: |
It will be helpful if you've read the ADF Data Binding Primer and ADF/Struts Overview before reading this HowTo article. |
Contents
ADF Iterator BindingsADF Iterator Bindings
The ADF Bindings layer provides iterator bindings to work with collections of data from a data control. As its name implies, an iterator binding is a binding object related to an iterator. This iterator provides access to the different instances in the data collection, keeping track of its current position in the collection.
While the data collection can contain simple scalar values, typically it comprises a collection of JavaBeans: each instance having a set of well-defined properties. When a collection contains JavaBeans whose properties hold the column values of a database query result, it's convenient to think of these JavaBeans as rows in a row set.
Following this convention, regardless of the actual backend model layer implementation, Oracle ADF provides developers the consistent
RowSetIterator and
Row interfaces to work programmatically with data from collections provided by a data control. In essence, an ADF iterator binding is simply a helper object in the binding layer that works with an underlying
RowSetIterator to access the
Rows in a row set.
As shown in
Figure 1
, control value bindings are related to a particular iterator binding and expose the data from its underlying collection. Some control value bindings like the three Attribute Bindings shown in the figure are "wired" to a particular attribute of the current row in their related iterator binding. Accordingly, since the current row in the iterator binding is the
first row — indicated by the arrow representing the position of its underlying
RowSetIterator — the related control value bindings display their respective attribute from that first row. Other control bindings like the Range Binding expose all of the rows and columns in the iterator's data collection for display in a table.
As shown in
Figure 2
, if you create multiple iterator bindings for the same collection, by
default they will be bound to the same underlying default
RowSetIterator for the collection. As the current row is changed in the single, underlying
RowSetIterator that both of these iterator bindings share, the control bindings related to
both iterator bindings will continue to see the same data.
On the other hand, if at design time you set the
Rowset Iterator Name property of an iterator binding to some non-empty value (e.g. "
Second", "
RSI2", or whatever), then at runtime that iterator binding will create and use a separate, named
RowSetIterator over its underlying data collection. As shown in
Figure 3
, this allows the multiple iterator bindings over the same collection to navigate independently. In other words, the default
RowSetIterator and the secondary, named
RowSetIterators keep track of their current row in an independent manner.
Accordingly, this allows control value bindings that are bound to these separate iterator bindings to show data from different rows in the same underlying collection.
Iterator Binding Find Mode
In addition to its basic data iteration functionality, the iterator binding also cooperates with its underlying data collection to simplify implementing query-by-example capability for your application data by providing:
In Oracle ADF, each data collection has an associated
ViewCriteria collection of zero or more
ViewCriteriaRows. Each
ViewCriteriaRow has the same attribute structure as a
Row in its related data collection, except that the attribute values are all treated as a
String data type so the user can enter query criteria containing comparison operators and wildcard characters.
For example, to indicate you want to find all departments whose department number is greater than 5 and whose department name matches the string 'ACC%', you would fill in the attributes of a
ViewCriteriaRow related to a
DeptView collection (based on a query over the familiar
DEPT table in the
SCOTT schema) like this:
| ViewCriteriaRow Attribute Name | Criteria Value |
|---|---|
Deptno
|
> 5
|
Dname
|
ACC%
|
The iterator binding's
Find Mode functionality makes it easy to create search pages that populate the attributes of the
ViewCriteria collection for query-by-example functionality. As shown in
Figure 4
, when an iterator binding is set to work in Find Mode, it switches to use a different
RowSetIterator over the related
ViewCriteria collection instead. This means that when Find Mode is set to
true, control bindings related to that iterator binding display and update attributes in the current
ViewCriteriaRow. Likewise, a Range Binding over an iterator binding in find mode allows you to render a table of current query-by-example
ViewCriteria rows.
When Find Mode is set back to
false, the iterator binding switches back to work with its
RowSetIterator over the data collection. This can be done explicitly by calling the iterator binding's
setFindMode() method, or implicitly by calling its
executeQuery() method.
| NOTE: |
By calling the
|
While the ADF iterator binding provides its Find Mode functionality independent of the kind of backend data control you choose, currently only the ADF Business Components data control makes
automatic use of the
ViewCriteria collection of
ViewCriteriaRows at runtime. It delegates the iterator binding's
ViewCriteria functionality to the underlying ADF view object, which implements the query-by-example criteria by automatically building appropriate SQL
WHERE clause predicates based on the
ViewCriteriaRows.
Other data control types would currently require a subclassed data control implementaiton containing some custom coding to read the query-by-example criteria from the
ViewCriteria collection and translate them into an appropriate runtime search implementation.
Building a Search Page with Fixed Search Attributes
Given what we've reviewed above about iterator bindings and Find Mode, we can easily build a search page using ADF and ADF Business Components. The Model project in the workspace provided is a simple ADF application module named
test.model.TestModule exposing a single view object instance named
DeptView in its data model. The underlying view object is related to a
Dept entity object and both are related to the familiar
DEPT table in the
SCOTT schema.
The
/findModeSingleIter data page in the
ViewController project illustrates a page that I created by performing the following steps in the JDeveloper 10g IDE:
findModeSingleIter
findModeSingleIter.jsp
DeptView collection in the
Data Control Palette
DeptView data collection onto the empty JSP page.
DeptView collection in the
Data Control Palette
and dropped four built-in actions —
Find,
Execute,
Next, and
Previous — as Button onto the JSP page, taking care to drop them inside the dotted line box representing the HTML form.
| NOTE: |
The default, declarative
|
To further polish this default page, I switched to source mode and added some conditional logic — using the JSTL Core tag library
<c:choose>
and
<c:if>
— based on evaluating the EL expression
${bindings.DeptViewIterator.findMode} to detect whether the
DeptViewIterator iterator binding is in find mode or not. I added a section at the top of the page to conditionally set the value two page-local variables named
findButtonName and
executeButtonName like this:
<c:choose>
<c:when test="${bindings.DeptViewIterator.findMode}">
<c:set var="findButtonName" value="Cancel Search"/>
<c:set var="executeButtonName" value="OK"/>
</c:when>
<c:otherwise>
<c:set var="findButtonName" value="Search"/>
<c:set var="executeButtonName" value="Refresh Data"/>
</c:otherwise>
</c:choose>
Later in the page I refer to the value of the
findButtonName variable to show the user either a
(Search)
or a
(Cancel Search)
button depending on whether the
DeptViewIterator is in find mode like this:
<input type="submit"
name="event_Find"
value="<c:out value='${findButtonName}'/>"/>
and likewise refer to the value of the executeButtonName variable to show the user either a (Refresh Data) button or an (OK) button.
| NOTE: |
The curious reader might wonder why I use the
The answer is that the in-line
|
I added one final conditional behavior to only show the
(Previous)
and
(Next)
buttons when not in find mode.
Figure 5
shows what the data page looks like when you run it. If the user clicks the
(Search)
button, the page toggles to display the current
ViewCriteriaRow attributes. Of course, these query-by-example criteria are initially blank, waiting to be filled-in by the end-user.
While in Find Mode — since the
(OK)
button's name is
event_Execute — clicking it will trigger the
Execute action as part of the default event-handling behavior of the ADF page lifecycle. During the
processUpdateModel phase of the lifecycle — which occurs
before the
processComponentEvents phase — the user's query criteria values are updated into the respective
Deptno,
Dname, and
Loc control value binding objects. Recall that since find mode is still
true when this occurs, their related iterator binding is still switched to work with the
ViewCriteria collection and these user-supplied attribute values update their corresponding attributes in the current
ViewCriteriaRow.
When the built-in
Execute action is invoked later during the
processComponentEvents phase of the lifecycle, it calls
executeQuery() method on the iterator binding. This, in turn, executes its underlying view object's query. The view object automatically augments its query to include the user-entered query-by-example criteria to produce the filtered results.
If the user clicks the
(Cancel Search)
button while in find mode, this executes the built-in Find action which toggles the iterator binding out of Find Mode and empties the
ViewCriteria collection.
Showing Search Criteria and Results With Multiple Iterator Bindings
Depending on your application, toggling the same set of user interface controls between Find Mode and regular mode as we did in the previous section may not be exactly that you want. There are two possible usability drawbacks to the approach above. The first issue is that the user sees the results only one row at a time, having to scroll next and previous through them to understand if their query produced the expected results. The second and more serious issue is that they cannot see their query-by-example criteria once they have toggled out of Find Mode. This could be confusing to many users.
Luckily, it's easy to have our cake and eat it too. In the sections above, we learned that we can use multiple iterator bindings in a binding container bound to the same data collection. Since the ADF Find Mode is a boolean property of each iterator binding, all we need to do is:
DeptView collection
Using this technique, we can allow the user to enter and continue to see their query-by-example criteria while viewing the
results of the search in the same page. The
fixedCriteria DataPage in the sample project adopts this approach to produce a page that looks like
Figure 6
.
Inside JDeveloper 10g, if you click on the
fixedCriteria DataPage and then select the
UI Model
tab of the
Structure Window
, you'll see that the binding objects in its binding container are setup as shown in
Figure 7
. The
DeptView range binding, related to the
DeptViewIterator iterator binding, lets us show a table displaying the search results. We've named the second iterator binding
DeptViewCriteriaIterator to make it clear that we created it for the purpose of working with the query-by-example criteria. This is the one we will force to always be in Find Mode, and with which the three TextField Bindings — to display
Deptno,
Dname, and
Loc query criteria — are related.
To force the
DeptViewCriteriaIterator to always be in Find Mode, we've created a custom
FixedCriteriaAction DataForwardAction class for the
fixedCriteria DataPage. We can do this by clicking on the DataPage and selecting
Go to Code
from the right-mouse menu. If no custom DataForwardAction class exists yet, a dialog appears giving you a chance to change the default class and package names.
In this class, we've overridden the
prepareModel() phase of the ADF page lifecycle to add one line of code that calls
setFindMode(true) on this iterator binding like this:
// From class test.controller.FixedCriteriaAction
/*
* Force DeptViewCriteriaIterator to always be in Find Mode
*/
protected void prepareModel(DataActionContext ctx) throws Exception {
super.prepareModel(ctx);
ctx.getBindingContainer()
.findIteratorBinding("DeptViewCriteriaIterator").setFindMode(true);
}
To handle the
Search event that the user will trigger when they press the
(Search)
button, we've written an appropriate
onSearch() method with the correct signature in the same class like this:
// From class test.controller.FixedCriteriaAction
/*
* Handle the "Search" event.
*
* Reapply the ViewCriteria and re-execute the iterator binding's query.
*/
public void onSearch(DataActionContext ctx) {
DCBindingContainer bc = ctx.getBindingContainer();
DCIteratorBinding deptIter = bc.findIteratorBinding("DeptViewIterator");
deptIter.getViewObject().applyViewCriteria(deptIter.getViewCriteria());
deptIter.executeQuery();
}
Notice that for a nice additional touch, we've included the conditional display of a
No department matches your search criteria
message in case the user's query criteria returns no rows. To implement it, we again employ the familiar
<c:choose>
tag — with its nested (possibly multiple)
<c:when>
tags and one
<c:otherwise>
tag — to obtain an
if/
then/
else style of display. The code excerpt from the
fixedSearch.jsp page looks like this:
<c:choose>
<c:when test="${empty bindings.DeptView.rangeSet}">
No department matches your search criteria.
</c:when>
<c:otherwise>
<%--
Show the table of results using the DeptView range binding
--%>
</c:otherwise>
</c:choose>
Building a Simple Dynamic Search Page
We've seen that Find Mode makes building search pages easy when the set of query-by-example attributes is fixed. But what if we want to provide a user interface like the one shown in Figure 8 where the user picks one attribute to search against from a drop-down list showing all of the possible searchable attribute names?
In this section, we'll see that using the same basic concepts we've leveraged above, creating the above user interface is again straightforward.
The
dynamicCriteria DataPage in the sample project illustrates a basic implementation of the approach.
Figure 9
shows the simple binding container for this page containing a
DeptViewIterator iterator binding, and a related
DeptView range binding.
To create the drop-down list in the
dynamicCriteria.jsp page, we use the JSTL
<c:forEach>
tag to iterate over the
attributeNames collection exposed by the
DeptView range binding. For our particular example here, the list will include the attribute names
Deptno,
Dname,
Loc. Since most end-users will prefer to see more friendly names in the drop-down list, we use the UI label for each option value, instead of the actual attribute name. We accomplish this by using the
value of the
c:forEach loop variable
attrName in the EL expression:
${bindings.DeptView.labels[attrName]}
as a key into the
labels map of attribute UI labels exposed by the
DeptView range binding. The part of the
dynamicCriteria.jsp that creates the
SearchField drop-down list, the
SearchValue text field, and the
event_Search
(Search)
button looks like this:
<form method="post" action="dynamicCriteria.do">Search
<select name="SearchField">
<option value=""><Choose Field></option>
<c:forEach var="attrName" items="${bindings.DeptView.attributeNames}">
<option value="<c:out value='${attrName}'/>"><c:out
value='${bindings.DeptView.labels[attrName]}'
/></option>
</c:forEach>
</select>
for
<input type="text" size="10" name="SearchValue"/>
<input type="submit" value="Search" name="event_Search"/>
</form>
To handle the
Search event triggered when the Search button is pressed, we've written an appropriate
onSearch() method in the
DynamicCriteriaAction class. Since the user is
dynamically picking the attribute name to target for the search, we cannot rely on the automatic setting of the
ViewCriteriaRow attributes as we did in previous examples when the search fields were a fixed set and Find Mode took care of the details. We need our onSearch() method to:
ViewCriteria collection from the
DeptViewIterator binding
ViewCriteriaRow and add it to the collection
ViewCriteriaRow attribute whose name is provided in the
SearchField parameter to the value provided in the
SearchValue parameter.
To code to accomplish the above looks like this:
// From Class test.controller.DynamicCriteriaAction
public void onSearch(DataActionContext ctx) {
HttpServletRequest request = ctx.getHttpServletRequest();
DCBindingContainer bc = ctx.getBindingContainer();
DCIteratorBinding deptIter = bc.findIteratorBinding("DeptViewIterator");
ViewCriteria criteria = deptIter.getViewCriteria();
if (criteria.size() > 0) criteria.clear();
String searchField = request.getParameter("SearchField");
if (searchField != null && !searchField.equals("")) {
Row criteriaRow = criteria.createViewCriteriaRow();
criteria.add(criteriaRow);
criteriaRow.setAttribute(searchField,request.getParameter("SearchValue"));
}
deptIter.getViewObject().applyViewCriteria(criteria);
deptIter.executeQuery();
}
The reason we clear out the
ViewCriteria collection on each search is to avoid successive searches on different attributes from being cumulative. Suppose the user first searches for rows with a department
Name
matching "
ACC%" and then changes the drop-down list to search for rows with a
Location
of "
%N%". If we did not clear the
ViewCriteria collection each time, then the second query would end up trying to find rows matching both of these criteria, instead of just the second criteria.
| NOTE: |
The reason that the user-supplied query-by-example criteria "hang around" between HTTP page requests in the
|
To see the results of our work, just run the
dynamicCriteria data page in the
ViewController project and try out a few queries.
Making the Search Criteria "Sticky"
After trying out the previous page, you might find it frustrating that the
SearchField drop-down list does not show the user's selection and the
SearchValue text field does not show the query criteria entered. The table of results reflect the user's attribute selection and search criteria, but a page like this that surprises the end-user, leaving them wondering if they might have made a mistake, has obvious potential usability issues. In this section, we'll evolve the example above to make the drop-down list and text field be "sticky" to maintain the user's context by continuing to show them their selected attribute and query-by-example criteria upon clicking the
(Search)
button.
As they did in the
fixedSearch example page, multiple iterator bindings and Find Mode will figure into our solution. As shown in
Figure 10
, you'll notice we've added two new binding objects to this page's binding container, compared to the example above. The
DeptViewCriteriaIterator is related to the same data collection as the original
DeptViewIterator, and again we'll force this second iterator binding to always be in Find Mode as we did in a previous example.
| NOTE: |
I created the additional iterator binding and range binding in the explicit way described in the ADF Data Binding Primer, using the options on the right-mouse menu in the UI Model tab of the Structure Window . |
With the additional
DeptViewCriteria range binding, related to
DeptViewCriteriaIterator, we'll be able to iterate over the
ViewCriteriaRows in JSTL to determine the current name of the non-empty ViewCriteriaRow attribute, and its current query-criteria value. We'll use this information to keep the
SearchField and
SearchValue UI controls "sticky".
In the
stickyDynamicCriteria.jsp page, we've slightly modified the logic from the simpler
dynamicCriteria.jsp above in order to mark one of the options in the
SearchField drop-down list of attributes as
SELECTED. We accomplish this using the following nested
<c:forEach>
loop at the top of the page:
<c:forEach var="Row" items="${bindings.DeptViewCriteria.rangeSet}">
<c:forEach var="attrName" items="${bindings.DeptViewCriteria.attributeNames}">
<c:if test="${not empty Row[attrName]}">
<c:set var="curCriteriaAttr" value="${attrName}"/>
<c:set var="curCriteriaVal" value="${Row[attrName]}"/>
</c:if>
</c:forEach>
</c:forEach>
Since we force the
DeptViewCriteriaIterator iterator binding to always be in Find Mode, the rangeSet from the related
DeptViewCriteria range binding will be the set of
ViewCriteriaRows instead of a set of data rows. Inside this loop, we perform a nested loop over all the attribute names exposed by the
DeptViewCriteria range binding. When we find a non-empty
ViewCriteriaRow attribute, we set the page local variables
curCriteriaAttr and
curCriteriaVal to capture the current query criteria attribute name and value for use later in the page.
As you can see in the modified form code below, we use the
<c:if>
tag to test whether the current attribute name matches the value of the
curCriteriaAttr we calculated above, and if so output the additional
SELECTED tag attribute. For the SearchValue field, it's even easier. We just use the value of the
curCriteraVal variable to provide the value of the
<input>
tag's
value attribute.
<form method="POST" action="stickyDynamicCriteria.do">Search
<select name="SearchField">
<option value=""><Choose Field></option>
<c:forEach var="attrName" items="${bindings.DeptViewCriteria.attributeNames}">
<option value="<c:out value='${attrName}'/>"
<c:if test="${curCriteriaAttr == attrName}">SELECTED</c:if>
><c:out value='${bindings.DeptViewCriteria.labels[attrName]}'/></option>
</c:forEach>
</select>
for
<input type="text" size="10"
value="<c:out value='${curCriteriaVal}'/>" name="SearchValue"/>
<input type="submit" value="Search" name="event_Search"/>
</form>
The code in the accompanying
StickyDynamicCriteriaAction serves the same purpose as the code we saw in the
DynamicCriteriaAction. The only thing different is that we've chosen to highlight an alternative coding technique in the
onSearch() method for manually populating the
ViewCriteria collection.
This alternative implementation leverages the fact that an iterator binding in find mode is iterating over the
ViewCriteriaRows. Since we are only searching on one field at a time, here we iterate over the attribute names of the
DeptViewCriteriaIterator — which again we force to always be in find mode in
prepareModel() — and set every attribute in the view criteria row to some value. If the current attribute name in the loop matches the value of the
SearchField parameter passed in, then we set
its value to the value of the
SearchValue parameter, otherwise we set it to
null. This again insures that successive searches against different user-selected attributes to not end up being undesirably cumulative. The code for this
onSearch() method in
StickyDynamicCriteriaAction looks like this:
public void onSearch(DataActionContext ctx) {
HttpServletRequest request = ctx.getHttpServletRequest();
DCBindingContainer bc = ctx.getBindingContainer();
DCIteratorBinding deptIter = bc.findIteratorBinding("DeptViewIterator");
DCIteratorBinding critIter = bc.findIteratorBinding("DeptViewCriteriaIterator");
AttributeDef[] attrDefs = critIter.getAttributeDefs();
Row criteriaRow = critIter.getCurrentRow();
String searchField = request.getParameter("SearchField");
for (int z = 0, attrCount = attrDefs.length; z < attrCount; z++) {
String curAttrName = attrDefs[z].getName();
criteriaRow.setAttribute(curAttrName, (curAttrName.equals(searchField) ?
request.getParameter("SearchValue") : null));
}
deptIter.getViewObject().applyViewCriteria(deptIter.getViewCriteria());
deptIter.executeQuery();
}
Supporting Case-Insensitive Queries
A frequently-asked question on the subject of searching is, "How can I get my queries to be case-insensitive?" Using ADF Business Components, the answer is easy. Each
ViewCriteriaRow in your
ViewCriteria collection of query-by-example records supports a boolean-valued
upperColumns property. If you set the property to
true by calling the
vcRow.setUpperColumns(true) API, then the view object will generate
UPPER(
COLUMN_NAME)
instead of just
COLUMN_NAME in the WHERE clause fragment created for the non-null, character-typed criteria attributes in that
ViewCriteriaRow.
We can create a custom extension of the base
ViewObjectImpl framework that simplifies enabling case-insensitive querying for any view object we might need it on in the future.
Example 1
shows the source code for our
CustomViewObjectImpl class that does just that. This class extends
ViewObjectImpl and overrides the framework method
executeQueryForCollection() to conditionally set the
upperColumns property to true on any
ViewCriteriaRows in the
ViewCriteria collection. In addition, it forces the query criteria values to also be in upper case. This generic code decides whether or not to perform the case-insensitive adjustment step based on the presence of a custom view object property named
CaseInsensitiveViewCriteria.
package test.fwkext;
|
Figure 12
illustrates how you define a new view object to extend from your
CustomViewObjectImpl class instead of using the default
ViewObjectImpl class. By visiting the
Java
panel of the View Object Editor, you click on the
Class Extends
button to see the
Extends
dialog. You can see here that the DeptView view object in our example project uses the test.fwkext.CustomViewObjectImpl class as its view object base class.
Figure 12
shows how to set a custom VO property with the name
CaseInsensitiveViewCriteria. By visiting the Custom Properties panel of the View Object Editor, you can simply type the name of any custom property and the value you'd like to assign it.
Our generic
CustomViewObjectImpl class will invoke the additional logic to turn
ViewCriteriaRows to be treated case-insensitively on any view object which:
CaseInsensitiveViewCriteria (with any value).
You can try out this case-insensitive query-by-example functionality in the example application by setting a custom
CaseInsensitiveViewCriteria property — as shown in
Figure 12
—on the
test.model.DeptView view object in the
Model project.
ADF Support for "Classic" JSP Development Without Struts
What happens if you want to implement search pages with a fixed or dynamic set of criteria attributes, but don't plan to use a formal Struts controller layer? Is there hope for the "classic" JSP page developers who aren't yet ready to include Struts in their application technology stack?
Why of course!
The Oracle ADF framework has been carefully designed to work well both with
and without Struts. In fact, as the UML diagram in
Figure 13
illustrates, the ADF
PageLifecycle object is the one that implements the default page-handling "controller layer" behavior. Each method in the
PageLifecycle class that implements an overridable phase of page-handling lifecycle gets passed a
LifecycleContext object as an argument. The method's implementation uses this object to reference and modify key request-specific, page lifecycle context information.
For the Struts developer, ADF provides the
DataAction and
DataForwardAction classes whose overridable lifecycle methods mirror the ones available on
PageLifecycle. By default, each one delegates to the analogous method on the
PageLifecycle object being used by the current request. Each
DataAction lifecycle method is passed a
DataActionContext object, which extends the base
LifecycleContext to add a few additional Struts-specific pieces of information.
| NOTE: |
The PageLifecycle object used for each request is created by a PageLifecycle factory object. For non-Struts environment, the default page lifecycle factory is
In case you ever want to customize the default page lifecycle factory, here are the details for accomplishing that. To customize the default page lifecycle factory for non-Struts JSP development — to return your own custom subclass of PageLifecycle — set the servlet initialization parameter named
|
This architecture allows developers building JSP pages without a Struts controller layer to customize the page handling lifecycle for each page — including writing ADF named event handler methods — by simply writing a class that extends
PageLifecycle and associating it with the page. The ADF tag library provides the
<adf:uimodelreference>
tag that is used for this purpose.
Let's assume you are working with a JSP page named
mypage.jsp, and you have created a class
myapp.MyPageHandler that extends
PageLifecycle:
package myapp;
import oracle.adf.controller.lifecycle.LifecycleContext;
import oracle.adf.controller.lifecycle.PageLifecycle;
public class MyPageHandler extends PageLifecycle {
// page handler code goes here.
}
By declaring the ADF tag library in your JSP page:
<%@ taglib uri="http://xmlns.oracle.com/adf/ui/jsp/adftags" prefix="adf"%>
and including an
<adf:uimodelreference>
tag like this:
<adf:uimodelreference model="myPageUIModel" lifecycle="myapp.MyPageHandler" />
You indicate the name of your page's binding container and the custom page lifecycle class that will be used to override any lifecycle handling behavior and/or write its event handler methods. Of course, if a given page needs no custom code in its
PageLifecycle class, then you can leave out the
lifecycle attribute like this:
<adf:uimodelreference model="myPageUIModel"/>
and ADF will use the default
PageLifecycle object instead.
This approach promotes a best practice coding style for "Model One" JSP development by neatly factoring the page's "controller layer" logic into a separate class instead of mixing it unmaintainably into the JSP page itself.
Implementing Search Pages Without a Struts Controller Layer
The
DynamicCriteria workspace for this article comes with both a Struts-based
ViewController project, as well as
ViewNoController project that help to illustrate the points made in the previous section. The four JSP pages in the
ViewNoController project:
findModeSingleIter.jsp
fixedCriteria.jsp
dynamicCriteria.jsp
stickyDynamicCriteria.jsp
implement identical functionality to the pages of the same names in the Struts-based
ViewController project. In this section, we'll examine the minor differences between the two to highlight how the ADF concepts are exactly the same and coding techniques are nearly identical.
You'll notice that each JSP page in the
ViewNoController project has a binding container that is setup with exactly the same binding objects as the ones used in the Struts-based project. Each page contains the
<adf:uimodelreference>
tag at the top to indicate which binding container the page is using. Except for the
findModeSingleIter.jsp that required no custom code to implement, the remaining three JSP pages each indicate the name of their custom
PageLifecycle class using the lifecycle attribute on their
<adf:uimodelreference>
tag.
| NOTE: |
Recall that in the Struts-based ADF environment, the binding container name and custom DataAction class are bits of metadata that are associated to the action in the
|
For example, the
dynamicCriteria.jsp page includes a tag like this:
<adf:uimodelreference model="dynamicCriteriaUIModel"
lifecycle="test.DynamicCriteriaPageHandler" />
The
DynamicCriteriaPageHandler class contains exactly the same code as the Struts-based
DynamicCriteriaAction class does, except for one minor difference: the method signatures of the overridden lifecycle methods and event handlers take an argument of type
LifecycleContext instead of
DataActionContext. So, for example, the
DynamicCriteriaPageHandler class looks like this:
package test;
// other imports here
import oracle.adf.controller.lifecycle.LifecycleContext;
import oracle.adf.controller.lifecycle.PageLifecycle;
public class DynamicCriteriaPageHandler extends PageLifecycle {
/*
* Handle the "Search" event.
*/
public void onSearch(LifecycleContext ctx) {
// Otherwise exactly the same code as the DynamicCriteriaAction
// from the previous second.
}
}
Another small difference you can observe in the JSP pages with data-bound fields in the
ViewNoController project is how these HTML controls are named. In the ADF/Struts environment, recall that ADF provides a multi-purpose
BindingContainerActionForm, which is a Struts
DynaActionForm that exposes the bindings in the current binding container as
ActionForm dynaproperties. With Struts and this action form in the picture, data-bound fields are named exactly the same as the bindings. For example, a data-bound text field showing the value of the Deptno binding is realized with the tag:
<html:text property="Deptno"/>
With no Struts in the picture, it's obvious that there won't be a Struts
ActionForm involved in handling the request, and therefore neither will we use the Struts HTML tag library, which depends on having an ActionForm. Outside the Struts environment, the ADF
PageLifecycle uses a slightly different approach to recognize the names of bound fields in submitted web forms. Each binding supports a
path property whose
value represents the "path name" that ADF will use to recognize a submitted value for that binding. Accordingly, a data bound text field looks like this in a Model-One style JSP page:
<input name="<c:out value='${bindings.Deptno.path}'/>"
value="<c:out value='${bindings.Deptno}'/>">
Other than these minor differences, you can see by running any of the JSP pages in the
ViewNoController project that they offer the same functionality as their Struts-powered "twins" in the
ViewController project.
Conclusion
In this article we've explored implementing simple search pages using both a fixed as well as a dynamic set of search criteria attributes. We learned about iterator bindings and find mode simplify the process, and how the concepts and implementation are virtually identical in both Model One and Model Two web development. Hopefully after going through this example, you'll find your next search page easier to tackle.