Dan is an open source advocate, community catalyst, author and speaker. He's currently pursuing these interests as a Principal Software Engineer at Red Hat. In that role, he serves as a JBoss Community liaison, contributes to several JBoss Community projects, including Arquillian, ShrinkWrap, Seam 3 / DeltaSpike and JBoss Forge, and participates in the JCP on behalf of Red Hat. Dan is the author Seam in Action (Manning, 2008), writes for IBM developerWorks, NFJS magazine and JAXenter and is an internationally recognized speaker. He's presented at major software conference series including JavaOne, Devoxx, NFJS, JAX and Jazoon. After a long conference day, you'll likely find Dan enjoying tech talk with fellow community members over a Belgian Trappist beer. Dan has posted 5 posts at DZone. You can read more from them at their website. View Full User Profile

Fluent Navigation in JSF 2

11.02.2009
| 105670 views |
  • submit to reddit

In this article, the third in a series covering JavaServer Faces (JSF) 2.0 features contributed by Red Hat, or which Red Hat participated in extensively, you'll discover that getting around in a JSF 2 application is much simpler and requires less typing. With improved support for GET requests and bookmarkability, which the previous article covered, JSF 2 is decidely more nimble. But not at the cost of good design. JSF no longer has to encroach on your business objects by requiring action methods to return navigation outcomes, but can instead reflect on the state of the system when selecting a navigation case. This article should give you an appreciation for how intelligent the navigation system has become in JSF 2.

Read the other parts in this article series:
Part 1 - JSF 2: Seam's Other Avenue to Standardization
Part 2 - JSF 2 GETs Bookmarkable URLs 
Part 3 - Fluent Navigation in JSF 2
Part 4 - Ajax and JSF, Joined At Last
Part 5 - Introducing JSF 2 Client Behaviors

 

Three new navigation variants are going to be thrown at you in this article: implicit, conditional and preemptive. These new options are a sign that the JSF navigation system is becoming more adaptable to the real world. There's also a touch of developer convenience thrown in.

Implicit navigation is particularly useful for developing application prototypes, where navigation rules just get in the way. This style of navigation interprets navigation outcomes as view IDs. As you move beyond prototyping, conditional navigation removes the coupling between the web and transactional tier because the navigation handler pulls information from your business components to select a navigation case. Preemptive navigation, which you were introduced to in the last article, can use either implicit navigation or declarative navigation rules to produce bookmarkable URLs at render time. Leveraging the navigation system to generate bookmarkable URLs allows JSF to add GET support while maintaining consistent, centralized navigation rules.

Even with these new options, there's no telling what requirements your application might have for navigation. Thus, in JSF 2, you can finally query and modify the navigation cases; a new API has been introduced in JSF 2 that exposes the navigation rule set. Before we get into customizations, let's find out how these new variants make the navigation system more flexible and help prepare the user's next move. Hopefully you won't need those customizations after all.

Flexible navigation choices

The declarative navigation model in JSF was a move away from the explicit navigation "forward" selection by the action in Struts. Navigation transitions in JSF, which get matched based on current view ID, logical outcome and/or action expression signature, are described in the JSF descriptor (faces-config.xml) using XML-based rules. The matched transition indicates the next view to render and whether a client-side redirect should proceed rendering. Here's a typical example:

<navigation-rule>
<from-view-id>/guess.xhtml</from-view-id>
<navigation-case>
<from-action>#{numberGuessGame.guess}</from-action>
<from-outcome>correct</from-outcome>
<to-view-id>/gameover.xhtml</to-view-id>
</navigation-case>
</navigation-rule>

While the JSF navigation model is clearer and arguably more flexible than in Struts, two fundamental problems remain. First, the action method is still required to return a navigation directive. The directive just happens to be a more "neutral" string outcome rather than an explicit type (i.e., ActionForward), but the coupling is just as tight and you loose type safety in the process, so is it really an improvement? The other issue is that you must define a navigation case to match that outcome, even in the simplest cases, which can be really tedious. So you can't make the argument that the navigation model is less obtrusive or more convenient. It's just stuck somewhere in between.

To sum it up, the JSF navigation model is not flexible enough. It needs to accommodate different development styles better and it needs to be more self sufficient. On the one hand, your style or development phase may dictate waiving the declarative navigation rule abstraction. On the other hand, you may want to completely decouple your business objects from the navigation model, eradicating those arbitrary return value directives. JSF 2 gives you this broad range of options, and even let's you settle for a happy medium. The first option is provided by implicit navigation and the second conditional navigation. With implicit navigation, you can even use the current model without having to define the navigation rule right away. Let's unbox these two new alternatives, starting with implicit navigation.

Implicit navigation

JSF will post a form back to the current view (using the POST HTTP method) whenever the user performs an action, such a clicking a command button (hence the term "postback"). In the past, the only way to get JSF to advance to another view after the action is invoked (i.e., following the Invoke Application phase) was to define a navigation case in faces-config.xml. Navigation cases are matched based on the EL signature of the action method invoked and the method's return value converted to a string (the logical outcome). To cite an example, assume the user clicks on a button defined as follows:
<h:commandButton action="#{commandHandler.preview}" value="Preview"/>

The preview() method on the bean named commandHandler returns a value to indicate the outcome of processing:

public String preview() {
// tidy, translate and/or validate comment
return "success";
}

These two criteria are joined in a navigation case that dictates which view is to be rendered next.

<navigation-rule>
<from-view-id>/entry.xhtml</from-view-id>
<navigation-case>
<from-action>#{commentHandler.preview}</from-action>
<from-outcome>success</from-outcome>
<to-view-id>/previewComment.xhtml</to-view-id>
</navigation-case>
</navigation-rule>

If no navigation case can be matched, all JSF knows to do is render the current view again. So without a navigation case, there is no navigation.

A quick shorthand, which is present in Seam, is to have the action method simply return the target view ID directly. In this case, you're effectively treating the logical outcome value as a view ID. This technique has been adopted in JSF 2 as implicit navigation. It's improved since Seam because you can choose to drop the view extension (e.g., .xhtml) and JSF will automatically add it back on for you when looking for a view ID. Therefore, it's no more invasive than the string outcome values you are currently returning.

Implicit navigation comes into play when a navigation case cannot be matched using the existing mechanism. Here's how the logic outcome is processed in the implicit navigation case:

  1. Detect the presence of the ? character in the logical outcome
    1. If present, capture the query string parameters that follow it the ? character
    2. The special query string parameter faces-redirect=true indicates that this navigation should be issued using a client-side redirect
  2. If the logical outcome does not end with a file extension, append file extension of current view ID (e.g., .xhtml)
  3. If the logical outcome does not begin with a /, prepend the location of current view id (e.g., /, /admin/, etc.)
  4. Attempt to locate the template for the view ID
    1. If the template is found, create a virtual navigation case that targets the resolved view ID
    2. If the template is not found, skip implicit navigation
  5. Carry out the navigation case
    1. If the navigation case is not a redirect, build and render the target view in the same request
    2. If the navigation case is a redirect, build a redirect URL, appending the query string parameters captured earlier, then redirect to it

Implicit navigation can be leveraged anywhere a logical outcome is interpreted. That includes:

  • The return value of an action method
  • The action attribute of a UICommand component (e.g., <h:commandButton action="/entries.xhtml" ...>)
  • The outcome attribute of a UIOutcomeTarget (e.g., <h:link outcome="/entries.xhtml" ...>)
  • The handleNavigation() method of the NavigationHandler API

Here's an example of the navigation to the preview comment view translated into implicit navigation. The return value is automatically decorated with a leading / and a trailing .xhtml.

public String preview() {
// tidy, translate and/or validate comment
return "previewComment";
}


The /previewComment.xhtml view will be rendered in the same request. If you want to redirect first, add the following flag in the query string of the return value:

public String preview() {
// tidy, translate and/or validate comment
return "previewComment?faces-redirect=true";
}

You can accomplish any navigation scenario using implicit navigation that you can today with a formal navigation case defined in faces-config.xml. Implicit navigation is designed as the fall-through case (after the explicit navigation rules are consulted). If it fails (i.e., the template cannot be located), and the JSF 2 ProjectStage is set to development, a FacesMessage is automatically generated to warn the developer of a possible programming error.

Implicit navigation is great for prototyping and other rapid development scenarios. The major downside of implicit navigation is that you are further tying your business objects into the navigation model. Next we'll look conditional navigation, which provides an alternative that keeps your tiers loosely coupled.
 

Conditional navigation

Implicit navigation spotlights how invasive it is to put the onus on your business object to return a logic outcome just to make JSF navigation happy (and work). This coupling is especially problematic when you want to respond to user interface events using components in your business tier, a simplified architecture that is supported by both Seam and Java EE 6 to reduce the amount of glue code without increasing coupling.

What would be more "logical" is to invert the control and have the navigation handler consult the state of the bean to determine which navigation case is appropriate. The navigation becomes contextual rather than static. That's what conditional navigation gives you.

Conditional navigation introduces a condition as a new match criteria on the navigation case. It's defined in the <if> element as a child of <navigation-case> and expressed using an EL value expression. The value expression is evaluated each time the navigation case is considered. For any navigation case that matches, if a condition is defined, the condition must resolve to true for the navigation case to be considered a match.

Here's an example of a conditional navigation case:

<navigation-case>
<from-action>#{registration.register}</from-action>
<if>#{currentUser.registered}</if>
<to-view-id>/account.xhtml</to-view-id>
<redirect/>
</navigation-case>

As you can see, the condition doesn't necessarily have to reference a property on the bean that was invoked. It can be any state reachable by EL.

Conditional navigation solves a secondary problem with the JSF navigation model, one of those little annoyances in JSF that was tedious to workaround. In JSF 1.2 and earlier, if your action method is a void method or returns a null value, interpreted in both cases as a null outcome, the navigation is skipped entirely. As a result, the current view is rendered again. The only workaround is to override the navigation handler implementation and change the behavior. That really throws a wrench in being able to cut the glue code between your UI and transactional tier.

That changes with the introduction of conditional navigation. Since the condition provides either an alternative, or supplemental, match criteria to the logical outcome, navigation cases that have a condition are consulted even when the logical outcome is null or void. When the outcome is null, you can emulate switch statement to match a navigation case, switching on the condition criteria:
<navigation-case>
<from-action>#{identity.login}</from-action>
<if>#{currentUser.admin}</if>
<to-view-id>/admin/home.xhtml</to-view-id>
<redirect/>
</navigation-case>
<navigation-case>
<from-action>#{identity.login}</from-action>
<if>#{currentUser.vendor}</if>
<to-view-id>/vendor/home.xhtml</to-view-id>
<redirect/>
</navigation-case>
<navigation-case>
<from-action>#{identity.login}</from-action>
<if>#{currentUser.client}</if>
<to-view-id>/client/home.xhtml</to-view-id>
<redirect/>
</navigation-case>


If you intend to simply match the null outcome in any case, you can use a condition that is verily true (which, admittedly, could be improved in JSF 2.1):

<navigation-case>
<from-action>#{identity.logout}</from-action>
<if>#{true}</if>
<to-view-id>/home.xhtml</to-view-id>
<redirect/>
</navigation-case>

You can also use this fixed condition to provide a fall-through case.

But wait, there's more! Having to itemize all the possible routes using individual navigation cases causes death by XML (a quite painful death). What if you wanted to delegate the decision to a navigation helper bean or involve a scripting language? There's good news. You can! The target view ID can be resolved from an EL value expression. Let's return to the login example and use a helper bean to route the user using one navigation case:

<navigation-case>
<from-action>#{identity.login}</from-action>
<to-view-id>#{navigationHelper.userHomeViewId}</to-view-id>
<redirect/>
</navigation-case>

Oh my goodness, how much nicer is that? The navigation helper can encapsulate the logic of inspecting the currentUser bean and determining the correct target view ID.

In this section, we looked at two additional ways a navigation case is matched, increasing the overall flexibility of the navigation model. Implicit navigation maps logical outcomes directly to view IDs and conditional navigation reflects on contextual data to select a navigation case without imposing unnecessary coupling with the transactional tier. We're still looking at the same fundamental navigation model, though. In the next section, you'll see the navigation model used in a new role, and in a new place in the JSF life cycle, to generate bookmarkable links. 

Published at DZone with permission of its author, Dan Allen.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Comments

Adam Warski replied on Wed, 2010/03/10 - 6:06am

Unless I'm doing something wrong the element can't have any children or attributes, and you mention two:

  • view-param
  • include-view-params

Are you referring to some future version of JSF, or were these two removed from the final spec? Is there a way to include the view parameters after a POST?

Adam

Adam Warski replied on Wed, 2010/03/10 - 6:14am

Also, the ?faces-redirect=true parameter doesn't seem to have any effect.

Adam Warski replied on Wed, 2010/03/10 - 6:26am

Moreover any parameters included in seem to be stripped which leaves me with a good question on how to redirect after a POST to a page which has the parameters :).

Liezel Jandayan replied on Mon, 2012/05/21 - 9:52pm

First of all, you pass the string 'block' as an argument to the method render. In that method, that string is treated as an object.-James P. Stuckey

Jorge Muñoz replied on Sun, 2013/03/17 - 5:55pm

Great article covering JSF 2.0 navigation system. This has helped me a lot differentiating when each option is most appropiate and the idea of abstracting the navigation on a helper bean is very clever.Thanks for posting this

Tayo Koleosho replied on Mon, 2013/04/08 - 10:23am

 This is implicitly flawed navigation. Is there anything that stops me from navigating directly to /client/home.xhtml, completely bypassing the `if`

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.