Fluent Navigation in JSF 2
Join the DZone community and get the full member experience.
Join For FreeIn 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 -
Part 4 -
Part 5 -
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:
- Detect the presence of the ? character in the logical outcome
- If present, capture the query string parameters that follow it the ? character
- The special query string parameter faces-redirect=true indicates that this navigation should be issued using a client-side redirect
- If the logical outcome does not end with a file extension, append file extension of current view ID (e.g., .xhtml)
- If the logical outcome does not begin with a /, prepend the location of current view id (e.g., /, /admin/, etc.)
- Attempt to locate the template for the view ID
- If the template is found, create a virtual navigation case that targets the resolved view ID
- If the template is not found, skip implicit navigation
- Carry out the navigation case
- If the navigation case is not a redirect, build and render the target view in the same request
- 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.
Anticipating the user's next move
Preemptive navigation
The spec defines preemptive navigation as a mechanism for determining the target URL at Render Response, typically for a hyperlink component. The current view ID and specified outcome are used to determine the target view ID, which is then translated into a bookmarkable URL and used as the hyperlink's target. This process happens, of course, before the user has activated the component (i.e., click on the hyperlink). In fact, the user may never activate the component. The idea is to marry the declarative (or implicit) navigation model with the support for generating bookmarkable links.Based on what was just described, you should now understand why you declare the target view ID in an attribute named outcome on the new bookmarkable component tags (and why those components inherit from a component class named UIOutcomeTarget). You are not targeting a view ID directly, but rather a navigation outcome which may be interpreted as a view ID if the matching falls through to implicit navigation.
Let's consider an example. Assume that you want to create a link to the home page of the application. You could define the link using one the new bookmarkable link component:
<h:link outcome="home" value="Home"/>
This definition would match the following navigation case if it existed:
<navigation-rule> <from-view-id>*</from-view-id> <navigation-case> <from-outcome>home</from-outcome> <to-view-id>/home.xhtml</to-view-id> </navigation-case> </navigation-rule>
Of course, with implicit navigation available, this navigation case would be redundant. We could exclude it and the result would be the same.
<a href="/app/home.jsf">Home</a>
As it turns out, you've already been using preemptive navigation when you explored bookmarkability in the last article. But there's a critical part of preemptive navigation that we haven't yet fully explored: the assembly of the query string. As it turns out, this topic also applies to redirect navigation rules. In a sense, preemptive navigation has the same semantics as redirect navigation rules because both produces URL that lead to a non-faces request. The only difference is that a bookmarkable URL is a deferred request, whereas a redirect happens immediately. In both cases, the payload in the query string is an essential part of the URLs identity.
Building the query string
As a result of the new GET support in JSF 2, there are now a plethora of ways to tack on values to the query string. Options can collide when heading into the navigation funnel. What comes out on the other side? There's a simple conflict resolution algorithm to find out. Each parameter source is given a precedence. When a conflict occurs, meaning two sources define the same parameter name, the parameter from the source with the highest precedence is used.The query string parameters are sourced using the following order of precedence, from highest to lowest:
- Implicit query string parameter (e.g., /blog.xhtml?id=3)
- View parameter (defined in the <f:metadata> of the target view ID)
- Nested <f:param> in UIOutcomeTarget (e.g., <h:link>) or UICommand component (e.g., <h:commandLink>)
- Nested <view-param> within the navigation case <redirect> element in faces-config.xml
Granted, this appears to be a lot of options. Don't worry, we'll walk you through the cases in which you would use each option in this article. We recommend you choose a single style of providing navigation parameters that best suits your architecture and keep the others in the back of your mind, so that when an edge case comes up, you can tap into their power.
In the last article, you learned that you can use view parameters to let JSF manage the query string for you. Instead of using view parameters, you could just tack on the query string yourself when building a link to a blog entry.
<h:link outcome="entry?id=#{blog.entryId}" value="Permalink"/This is really a prototyping case because tooling is going to struggle to locate and visualize parameters like this, and it makes refactoring difficult. It's far cleaner, and really the recommended approach, to use a nested parameter tag:
<h:link outcome="entry" value="Permalink"> <f:param name="id" value="#{blog.entryId}"/> </h:link>
You could even abstract the parameter away from the view and define it in the navigation case instead, but it again it presents a challenge to tooling:
<h:link outcome="permalink" value="Permalink"/> <navigation-case> <from-outcome>permalink</from-outcome> <to-view-id>/entry.xhtml?id=#{blog.entryId}</to-view-id> </navigation-case>
A nested <view-param> would also work here, especially if you want to centralized your parameters.
In terms of navigation, the most important point to emphasize here is that you can finally add query string parameters to a redirect URL in the navigation rules. This need likely appears in your existing applications. No longer do you have to resort to using the programmatic API to issue a redirect with a query string payload. Let's consider the case of posting a comment to an entry. This example demonstrates the case when you are submitting a form and want to redirect to a bookmarkable page which displays the result of submitting the form:
<navigation-case> <from-action>#{commentHandler.post}</from-action> <to-view-id>/entry.xhtml</to-view-id> <redirect> <view-param> <param-name>id</param-name> <param-value>#{blog.entryId}</param-value> </view-param> </redirect> </navigation-case>
Note: Don't confuse <view-param> with a UIViewParameter. Think of it more as a redirect parameter (the tag should probably be called <redirect-param> not <view-param>, something to address in JSF 2.1).
There are now plenty of options to pass the user along with the right information. But the spec can't cover everything. That's why you can now query the navigation rule base at runtime to do with it what you like.
Peeking into the navigation cases
Not that you necessarily want to make changes in production. Having a configurable navigation rule set means that you can incorporate a custom configuration scheme such as a DSL or even a fluent, type-safe navigation model from which rules can be discovered at deployment time. In short, the navigation rule set is pluggable, and it's up to you what to plug into it.
NavigationCase is the model that represents a navigation case in the JSF API. When JSF starts up, the navigation cases are read from the JSF descriptor, encapsulated into NavigationCase objects and registered with the ConfigurableNavigationHandler. You can retrieve one of the registered NavigationCase objects by the action expression signature and logical outcome under which it is registered.
NavigationCase case = navigationHandler.getNavigationCase( facesContext, "#{commandBoard.post}", "success");
You can also access the complete navigation rule set as a Map<String, Set<NavigationCase>>, where the keys are the <from-view-id> values.
Map<String, Set<NavigationCase>> cases = navigationHandler.getNavigationCases();
You can use this map to register your own navigation cases dynamically. For example, a framework might read an alternative navigation descriptor (such as Seam's pages descriptor) and contribute additional navigation cases. With an individual NavigationCase object in hand, you can either read its properties or use it to create an action or redirect URL, perhaps to feed into your own navigation handler. There are a lot of possiblities here.
The slightly awkward part is how you reference this new API (ConfigurableNavigationHandler). The default NavigationHandler implementation in a standard JSF implementation must implement this interface. But you still have to cast to it when you retrieve it from the Application object, as follows:
ConfigurableNavigationHandler nh = (ConfigurableNavigationHandler) FacesContext.getCurrentInstance() .getApplication().getNavigationHandler();
Obviously, something to revisit in JSF 2.1. Once you get a handle on it, the navigation model is your oyster. You can define new ways to navigate or use it to generate bookmarkable URLs in your own style.
Forging ahead
The JSF navigation model had the right idea in spirit, but lacked a couple of elements that would allow it to truly realize loose coupling, it's required use slowed down prototyping, and you had no control to query or modify the navigation rule set at runtime. Your going to find that in JSF 2, the navigation system is much more flexible. You could argue that it finally accomplishes its original goals.For prototype applications, you can get navigation working without touching the faces-config.xml descriptor with implicit navigation. Just use a view ID, with or without an extension, as the logical outcome and away you go. As the application matures, you can establish a clean separation between JSF and your transactional tier by using conditional navigation to select a navigation case. You can trim the number of navigation cases by defining the target view ID as a value expression and having JSF resolve the target view ID from a navigation helper bean. If the design of your application calls for bookmarkable support, you can leverage the navigation handler in its new role to produce bookmarkable URLs at render time.
In JSF 2, it's a lot easier to route the user around the application. While that may be good for some applications, other applications never advance the user beyond a single page. These single page applications transform in place using Ajax and partial page updates. The next article in this series will open your eyes to how well Ajax and JSF fit together, and what new Ajax innovations made their way into the spec.
Opinions expressed by DZone contributors are their own.
Comments