|
|
 |
Oracle ADF Faces - Communicating Between Pages: processScope
Overview
One of the most common questions about application development
in JavaServer Faces (JSF) is how to communicate values between pages. For example,
how do I pick one row out of a table, and show it on a second
page to start editing it? How do I take search criteria entered
on one page, and show the results on a second? Both of these
(and many other common web application scenarios) require some
mechanism to pass a value from one page to another. The two
common solutions in JSF have been storing values on the
request or on the session. Both can be made to work, but
both have major limitations. ADF Faces introduces a new "processScope"
that seeks to offer the best of both. (This support is built entirely
off of existing, specified hooks in the Faces specification, and
is not based on proprietary extensions. If there
is interest from the rest of the Faces expert group, we would be
very interested in contributing this back into the specification.)
When using a request-scope value, an action listener might take
the ID of that table row, or the collection of search results,
and place that object into request scope, either directly:
FacesContext context = FacesContext.getCurrentInstance();
context.getExternalContext().getRequestMap().put("search", criteria);
... or indirectly, by storin
g it as a property of a request-scoped
<
managed-bean
>
. This works, but has some significant drawbacks:
-
The
<
navigation-case
>
used for page navigation can't
specify
<
redirect/
>
, because that would lead to
a client-side redirect, which would mean the second page
gets rendered on a new request.
<
redirect/
>
is very
useful for supporting bookmarking and better Back button
support.
-
Even if you don't redirect, the second page still has the
problem of making sure that this request-scoped value is
still available for its own purposes when it posts back.
To avoid these problems, developers might use session-scoped
variables instead. This fixes both of those problems, but
adds new ones:
-
A single user cannot have two windows open simultaneously; session-scoped
variables are global to the user. So, for instance, a user could
not work with two different search results simultaneously.
-
Back button support is highly limited, since navigating back
can't magically restore the session to its old state.
Finally, both session- and request-scoped parameters make bookmark
support completely hopeless; similarly, they make it very difficult
if you need to support emailing a link to a page. The URL does not
and cannot contain enough information on its own to show a target page.
ADF Faces offers a new scope - "processScope" - that aims to solve
all of these problems. It's a very new and experimental feature,
and we're interested in feedback on how well it addresses the
problem (it's not a panacea - some limitations are described below).
processScope
In addition to the standard JSF scopes - applicationScope,
sessionScope, and requestScope - ADF Faces adds a new scope,
processScope. Values added to this scope will automatically continue
to be
available as the user continues navigating from one page to
another. This is true even if you use
<
redirect/
>
.
But unlike session scope, these values are only visible in
the current "process". If the user opens up a new window
and starts navigating, that series of windows will have
their own process, and values stored in each window will
remain independent. And clicking the Back button will
automatically reset the process scope to its original
state.
From the JSF EL, it looks just like any other scope:
<h:outputText value="#{processScope.someKey}"/>
Note:
As you can see in this example, "processScope"
is supported for
all
JSF components, not just ADF Faces
components.
From Java code, you can access the process scope as a
java.util.Map
off of the
AdfFacesContext
API. (Despite its name, this class does not extend
FacesContext
, but it is a similar idea.)
import oracle.adf.view.faces.context.AdfFacesContext;
public class BackingBean
{
public String myAction()
{
Object someValue = ...;
AdfFacesContext afContext = AdfFacesContext.getCurrentInstance();
afContext.getProcessScope().put("someKey", someValue);
return "myOutcome";
}
}
Example:
Let's start with an
<
h:dataTable
>
showing some data:
<h:dataTable var="employee" value="#{....}">
...
<h:column>
<h:outputText value="#{employee.name}"/>
<h:column>
<h:column>
<h:outputText value="#{employee.id}"/>
<h:column>
</h:dataTable>
Now, we want to show more information about that employee on
another detail page. We'll add a commandButton, and tie it
to a "showEmployee" action in our backing bean:
<h:dataTable var="employee" value="#{....}">
...
<h:column>
<h:outputText value="#{employee.name}"/>
<h:column>
<h:column>
<h:outputText value="#{employee.id}"/>
<h:column>
<h:column>
<h:commandButton value="Show more"
action="#{backingBean.showEmployee}"/>
<h:column>
</h:dataTable>
Now all we've got to do is write the code for
showEmployee()
.
First, we'll find the current employee, and then we'll put it onto
the process scope.
import javax.faces.context.FacesContext;
import oracle.adf.view.faces.context.AdfFacesContext;
public class BackingBean
{
public String showEmployee()
{
// Find the current employee. I'll just look on the VariableResolver.
// (A lot of code out on the web manually creates a ValueBinding
// for "#{employee}" and executes it - this is a much simpler approach!
FacesContext context = FacesContext.getCurrentInstance();
Employee emp = (Employee)
context.getVariableResolver().resolveVariable(context, "employee");
if (emp == null)
return "error";
AdfFacesContext afContext = AdfFacesContext.getCurrentInstance();
afContext.getProcessScope().put("detailEmployee", emp);
// Navigate to whatever page handles the "showEmployee" outcome
return "showEmployee";
}
}
If you owned the code for "Employee", you might
consider moving
showEmployee()
directly onto
Employee
, in which case the code would simply be:
import oracle.adf.view.faces.context.AdfFacesContext;
public class Employee
{
...
public String showEmployee()
{
AdfFacesContext afContext = AdfFacesContext.getCurrentInstance();
// No need to find the employee - it's "this"
afContext.getProcessScope().put("detailEmployee", this);
// Navigate to whatever page handles the "showEmployee" outcome
return "showEmployee";
}
}
... but as you'll see below, there's an even easier way.
Now, on our detail page, we can just refer to the
"processScope.detailEmployee" object:
<h:panelGrid columns="2">
<h:outputText value="Name:"/>
<h:outputText value="#{processScope.detailEmployee.name}"/>
<h:outputText value="Employee ID:"/>
<h:outputText value="#{processScope.detailEmployee.id}"/>
<h:outputText value="Salary"/>
<h:outputText value="#{processScope.detailEmployee.salary}">
<f:convertNumber type="currency"/>
<h:outputText>
</h:panelGrid>
That's all there is to it. The detail page does need to know where to
look for the incoming value. The
"detailEmployee" object also persists automatically at processScope
if there were a "Show Even More Details" button on this detail page.
Limitations of processScope
Before moving on, there are, however, a number of limitations of processScope.
First, since processScope is not part of the standard JSF
specification, a couple of the niceties of standard scopes
can't be supported:
-
EL expressions do not automatically look into processScope; if you wish to locate a process-scoped value, you must
include "processScope." (For instance, in the previous example, we couldn't write "#{employeeDetail}" - we had to write "#{processScope.employeeDetail}".)
-
"process" cannot be used as a
<
managed-bean-scope
>
. (But
the
<
value
>
of a
<
managed-property
>
can refer to
process-scoped values.)
Second, because the original and detail pages have to agree on
the name of the process-scoped variable, they a
re more tightly
coupled than is ideal.
Finally, processScope never empties itself; the only way
to clear processScope is to manually force it to clear:
AdfFacesContext afContext = AdfFacesContext.getCurrentInstance();
afContext.getProcessScope().clear();
af:setActionListener
This code is easy, but to make coding even simpler, we provide a
new
ActionListener
tag that lets you code this style
without writing
any
Java code. The
<
af:setActionListener
>
tag has two properties,
"from" and "to", and simply takes a value from the "from" attribute
and puts it where "to" says to put it. Let's recode the last example
with this tag:
<h:dataTable var="employee" value="#{....}">
...
<h:column>
<h:outputText value="#{employee.name}"/>
<h:column>
<h:column>
<h:outputText value="#{employee.id}"/>
<h:column>
<h:column>
<h:commandButton value="Show more" action="showEmployee">
<af:setActionListener from="#{employee}"
to="#{processScope.employeeDetail}"/>
</h:commandButton>
<h:column>
</h:dataTable>
And that's it! No code is required in your backing bean at all
to implement this pattern. Let's walk through how this page works
when the button is clicked:
-
An
ActionEvent
fires on the commandButton. Since
it's inside a table row, the "employee" EL variable is pointing at
the current row.
-
ActionListeners
execute before any
"action"
excute, so
<
af:setActionListener
>
executes.
-
<
af:setActionListener
>
ret
rieves the
employee using the "#{employee}" EL expression.
-
<
af:setActionListener
>
stores the
employee into process scope with the "#{processScope.employeeDetail}"
EL expression.
-
Finally, the "action" executes with a static outcome - always
show the employee. No backing bean is needed.
Some may point out that this tag amounts to putting code in the UI,
and that is actually quite true. It is a matter of personal taste
whether this style of coding is acceptable or not, but used sparingly,
it can greatly simplify reading and understanding page logic. We
would never recommend using
<
af:setActionListener
>
to write values into a true model object. For example, you
could
write:
<h:dataTable var="employee" value="#{....}">
...
<h:column>
<h:outputText value="#{employee.name}"/>
<h:column>
<h:column>
<h:outputText value="#{employee.id}"/>
<h:column>
<h:column>
<h:commandButton value="Give Raise">
<af:setActionListener from="#{employee.salary + 500}"
to="#{employee.salary}"/>
</h:commandButton>
<h:column>
</h:dataTable>
... but this style of coding mixes application logic directly
into your page. This is all well and good for quick prototyping, but
is very dangerous for writing production code that has to be maintained.
Bookmarking support
The current implementation of processScope adds a single query
parameter to your URL, for example, "?_afPfm=4". This token points into
internal structures stored by ADF Faces at session scope. The
strategy allows processScope to store objects of any type, but does
nothi
ng to help with bookmarking (the tokens are not persistent across
requests or users). In future versions of ADF Faces, we plan to
support an augmented strategy that will detect when processScope
contains nothing but primitive objects - such as strings,
java.lang.Integers
- and automatically store
these values directly on the URL. This can directly and
transparently enable bookmarkability (especially if you use
<
redirect/
>
in
<
navigation-case
>
)
for web developers using processScope with that restriction.
|
Copyright © 2003-2006, Oracle Corporation. All Rights Reserved.
|
|
|
|