7.7. Navigation
You can use standard JSF navigation rules defined in
faces-config.xml
in a Seam application. However, these rules have several limitations:
- It is not possible to specify that request parameters are used when redirecting.
- It is not possible to begin or end conversations from a rule.
- Rules work by evaluating the return value of the action method; it is not possible to evaluate an arbitrary EL expression.
Another problem is that "orchestration" logic is scattered between
pages.xml
and faces-config.xml
. It is better to unify this logic under pages.xml
.
This JSF navigation rule:
<navigation-rule> <from-view-id>/editDocument.xhtml</from-view-id> <navigation-case> <from-action>#{documentEditor.update}</from-action> <from-outcome>success</from-outcome> <to-view-id>/viewDocument.xhtml</to-view-id> <redirect/> </navigation-case> </navigation-rule>
Can be rewritten as follows:
<page view-id="/editDocument.xhtml"> <navigation from-action="#{documentEditor.update}"> <rule if-outcome="success"> <redirect view-id="/viewDocument.xhtml"/> </rule> </navigation> </page>
However, this method pollutes
DocumentEditor
with string-valued return values (the JSF outcomes). Instead, Seam lets us write:
<page view-id="/editDocument.xhtml"> <navigation from-action="#{documentEditor.update}" evaluate="#{documentEditor.errors.size}"> <rule if-outcome="0"> <redirect view-id="/viewDocument.xhtml"/> </rule> </navigation> </page>
Or even:
<page view-id="/editDocument.xhtml"> <navigation from-action="#{documentEditor.update}"> <rule if="#{documentEditor.errors.empty}"> <redirect view-id="/viewDocument.xhtml"/> </rule> </navigation> </page>
The first form evaluates a value binding to determine the outcome value used by the subsequent rules. The second approach ignores the outcome and evaluates a value binding for each possible rule.
When an update succeeds, we probably want to end the current conversation, like so:
<page view-id="/editDocument.xhtml"> <navigation from-action="#{documentEditor.update}"> <rule if="#{documentEditor.errors.empty}"> <end-conversation/> <redirect view-id="/viewDocument.xhtml"/> </rule> </navigation> </page>
Since the conversation has ended, any subsequent requests will not know which document we are interested in. We can pass the document ID as a request parameter, which also makes the view bookmarkable:
<page view-id="/editDocument.xhtml"> <navigation from-action="#{documentEditor.update}"> <rule if="#{documentEditor.errors.empty}"> <end-conversation/> <redirect view-id="/viewDocument.xhtml"> <param name="documentId" value="#{documentEditor.documentId}"/> </redirect> </rule> </navigation> </page>
Null outcomes are a special case in JSF, and are interpreted as instructions to redisplay the page. The following navigation rule matches any non-null outcome, but not the null outcome:
<page view-id="/editDocument.xhtml"> <navigation from-action="#{documentEditor.update}"> <rule> <render view-id="/viewDocument.xhtml"/> </rule> </navigation> </page>
To perform navigation when a null outcome occurs, use the following:
<page view-id="/editDocument.xhtml"> <navigation from-action="#{documentEditor.update}"> <render view-id="/viewDocument.xhtml"/> </navigation> </page>
The view ID can be given as a JSF EL expression:
<page view-id="/editDocument.xhtml"> <navigation> <rule if-outcome="success"> <redirect view-id="/#{userAgent}/displayDocument.xhtml"/> </rule> </navigation> </page>