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:
findModeSingleIterfindModeSingleIter.jspDeptView collection in the Data
Control PaletteDeptView
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.jspfixedCriteria.jspdynamicCriteria.jspstickyDynamicCriteria.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.