Learning JSF2: Navigation
This is a second post in Learning JSF 2 series. The first one on Managed Beans can be found here.
Before I start, thanks to Nick Belaevski (RichFaces Team Lead – Exadel) for reviewing this posting.
In JSF 1.2 all navigation rules are placed in JSF configuration file. Although you can still places navigation rules inside JSF configuration file, JSF 2 upgrades navigation by introducing implicit navigation and conditional navigation.
Implicit navigation
In JSF 1.2, navigating from one page to another required something like this:
<navigation-rule> <from-view-id>page1.xhtml</from-view-id> <navigation-case> <from-outcome>next</from-outcome> <to-view-id>/page2.xhtml</to-view-id> </navigation-case> </navigation-rule> |
JSF 2 now supports implicit navigation where you don’t need to define a navigation rules inside JSF configuration file. You can do this:
<h:commandButton action="page2" value="Submit" /> |
JSF will try to find a view named page2.xhtml in the current directory.
The following will also work:
<h:commandButton action="page2.xhtml" value="Submit" /> |
or
<h:commandButton action="page2.jsf" value="Submit" /> |
Note: this is assuming that JSF servlet is mapped to .jsf.
or using the new h:link (or h:button) tags in JSF 2 (I’ll cover these tags in a separate posting):
<h:button outcome="page2" value="Go There"/> |
Implicit navigation can also be used from within an action method:
public String next () { return "page2"; } |
The following will also work:
public String next () { return "page2.jsf"; } |
public String next () { return "page2.xhtml"; } |
All examples above implied that both pages (page1.xhtml and page2.xhtml) are in the same directory (notice there is no / before page name). If pages are in different directories, then full path has to be used:
<h:commandButton action="/shopping/page2" value="Submit" /> |
or
public String next () { return "/shopping/page2" ; } |
Conditional navigation
In JSF 1.2, methods in managed beans would return arbitrary string values which are passed into the navigation. Navigation couldn’t use application state to help determine what page to select. The most you could is something like this, you could check what button/link was clicked in addition to using the outcome:
<navigation-rule> <from-view-id>/pages/course.xhtml</from-view-id> <navigation-case> <from-action>#{bean.register}</from-action> <from-outcome>success</from-outcome> <to-view-id>/pages/registered.xhtml</to-view-id> </navigation-case> </navigation-rule> |
In JSF 2, you can now do this:
<navigation-rule> <from-view-id>/pages/course.xhtml</from-view-id> <navigation-case> <from-action>#{bean.register}</from-action> <if>#{bean.prerequisiteCompleted}</if> <to-view-id>/pages/registered.xhtml</to-view-id> </navigation-case> <navigation-case> <from-action>#{bean.register}</from-action> <if>#{bean.advisingHold}</if> <to-view-id>/pages/scheduleAdvisingSession.xhtml</to-view-id> </navigation-case> <navigation-case> <from-action>#{bean.register}</from-action> <if>#{not bean.payment}</if> <to-view-id>/pages/payForCourse.xhtml</to-view-id> </navigation-case> </navigation-rule> |
When #{bean.register} action is invoked, based on
Forward/Redirect
When navigating to another page, both JSF 1.2 and JSF 2 perform a forward (default behavior) to the new page (Seam, for example does a redirect by default). Because it’s a server forward (the browser is not aware that we are displaying a different page), you might have noticed that the page address in the URL is always one behind.
If defining navigation rules in JSF configuration file, then the same tag is used in JSF 1.2 and JSF 2:
<navigation-rule> <from-view-id>/bar/enter.xhtml</from-view-id> <navigation-case> <from-outcome>enterBar</from-outcome> <to-view-id>/bar/welcome.xhtml</to-view-id> <redirect/> </navigation-case> </navigation-rule> |
When using implicit navigation in JSF2, redirect is setup using faces-redirect=true request parameter:
<h:commandLink action="/bar/welcome?faces-redirect=true" value="Go to page 5"/> |
If returning an outcome from action method:
public String enter () { return "/bar/welcome?faces-redirect=true"; } |
Using EL in to-view-id
You can also use EL in
<navigation-rule> <from-view-id>/el/page1.xhtml</from-view-id> <navigation-case> <from-action>#{bean.navigate}</from-action> <from-outcome>success</from-outcome> <to-view-id>#{bean.page}</to-view-id> </navigation-case> </navigation-rule> |
In the managed bean:
… private String page; public String getPage () { return this.page; } public String navigate () { this.page = "/el/page3"; return "success"; } … |
A topic closely related to navigation is page parameters and how they are propagated, I will cover that in another posting.
Finally, one thing to be aware of. Suppose you have the following rule:
<navigation-rule> <from-view-id>/purchase.xhtml</from-view-id> <navigation-case> <from-action>#{bean.purchase}</from-action> <if>#{not bean.creditCardExpired}</if> <to-view-id>/confirmation.xhtml</to-view-id> </navigation-case> </navigation-rule> |
In managed bean:
public String purchase () { ... return "confirmation"; } |
Suppose creditCardExpired evaluates to true and thus making
I have been following the tutorials on JSF 2.0 on this blog.
I appreciate the effort that makes Max Katz to,
keep us informed of developments and of what can be
do with RichFaces. Apropósito the testing I’ve done in
NetBeans 6.8 and works great ….
————————————————
He estado siguiendo los tutoriales sobre JSF 2.0 en este blog. Quiero agradecer el esfuerzo que hace Max Katz, para, mantenernos informados de las novedades y de lo que se puede hacer con RichFaces. Apropósito las pruebas las he hecho en NetBeans 6.8 y funciona de maravilla….
JSF 2.0.0 FCS is out!!!!
It is funny to see that the configuration hell that existed ever since struts became popular, finally is starting to fade away and make room for practicality. I understand why having the navigation in a nicely formatted XML is very convenient and has is pluses, but it’s simply not simple. I’ve always refused to start projects on struts because of these complexities (as I did for EJB 2 and do for Spring).
Everything is a balance.
good.I am learning JSF2 now.
JSF 2.0 is cool. However, the spec lead and expert group can go one step further by standardizing components such as calendar, list shuttle, data table with complex header and pagination, tree, etc, etc, …
Hopefully, the next release will address these long-awaited items.
@Doug: I don’t believe the EG wants to be in the business of building components. The want to define a standard with basic core features and tags that can be easily extendable. It’s up to 3rd party vendors such as RichFaces to provide a rich component set.
I am using JSF 2.0 and implicit navigation in a new project I am working on. The one issue I am facing is that some action methods navigate to the outcome page based on the return string but some don’t. They don’t even given an error.
For instance – this method does not work. It goes to the return “home”; line but does not do anything – seems like returns back to the same page – even though the home.xhtml is present in the same directory.
public String login() {
user = getUser();
if (user != null) {
if (!user.getPassword().equals(password)) {
JsfUtil.addErrorMessage(“Login Failed!. The password specified is not correct.”);
return null;
}
FacesContext.getCurrentInstance().getExternalContext().getSessionMap().put(USER_SESSION_KEY, user);
System.out.println(“Login Successful. Going to the welcome page …”);
if (!user.getRoleCode().equals(admin_role)) {
return “home”;
}else{
return “administration/index”;
}
}else{
JsfUtil.addErrorMessage(“Login Failed! User with ‘” emailAddress “‘ does not exist.”);
return null;
}
}
While this method – almost similar seems to perform the navigation just fine – sends the user to the index.xhtml
public String logout() {
HttpSession session = (HttpSession) FacesContext.getCurrentInstance().getExternalContext().getSession(false);
if (session != null) {
session.invalidate();
}
return “index”;
}
There are couple of more instances where this is happening. In the same ManagedBean class one action method works but another does not. Any help would be appreciated.
@sam: implicit navigation takes precedence last, maybe you have another (defined) navigation rule that keeps you on the same page?
Thanks Max for a quick reply. Actually I don’t use the explicit rule definition in faces-config.xml at all. Also I did step-by-step debug through that and saw that it goes to a Method.invokeMethod() and just dies there.
Any idea what I might be missing?
@sam: does your method reach the return statement?
Max
Thanks a lot for responding to my problem and helping out.
Yes it does. the method goes to return statement and then steps into the Method.invokeMethod().
I changed the destination in the logout() to some other page in the same directory instead of index and it seemed to work fine.
The two places it does not work is places where I have some kinda put into session preceding the return like
FacesContext.getCurrentInstance().getExternalContext().getSessionMap().put(USER_SESSION_KEY, user);
Not sure why it should matter.
Another thing I wanted to point out is I have multiple url patterns in the web.xml (not sure if that should impact anything)
Faces Servlet
*.jsf
/faces/*
@sam: I’m not sure why it’s happening, try home.jsf or home.xhtml.
@sam: Also, Mojarra just released JSF version 2.0.3, see if it might solve your problem.
Thanks Max. I tried both of those options already and it does not seem to fix the issue. I have been banging my head on this for 3 days now
Appreciate your taking time so far to help.
@sam: have you tried Mojarra 2.0.3? I have no idea why it’s not working. What if you rename the page to home1.xhtml?
I tried Mojarra 2.0.3 and also renamed the file to homepage.xhtml and welcome.html to no avail. In fact it was called welcome.xhtml before and to debug this issue I renamed it to home.xhtml – maybe that left some reference to welcome.xhtml somewhere – don’t know where.
Surprisingly only two places this is happening. One is when I login and login validation is successful it is not going to homepage and the second is another object details page. Remaining 15 or so places it works just fine.
I looked at the com.sun.faces.application.NavigationHandlerImpl.java to see how it arrives at the destination to go and following the logic there it should find my page – but for some reason not.
Max, thanks for trying to help me. Finally nailed the sucker. I was using Primefaces instead and that was causing it to not work – yet to find out what it is. I am sure it is something stupid I have done. But changing it to made things work just fine.
Thanks again.
Interesting.. let me know when you find out.
Implicit Navigation is working fine -> shorter faces-config ->
But: Is there any command to stay on the same view using a new bean?
My problem is: I’ve got View-Scoped-ManagedBeans, have a link where I will stay in the same viewbut will simulate the “Reload” of this view using a new (clean) View-Scoped-ManagedBean … at the moment I solved this problem with some “reset”-navigation-rules in my faces-config which is working fine but I like to shorten my faces-config as much as possible …
Thanks in advance
@Matze: return null or make the action void – both will keep you on the same page.
Thank you
How can I navigate betwen views?
@Leany: I’m not sure I understand the question, this posts explains how to define navigation between views.
Very useful work, thank you
This gives: Not a Valid Method Expression
How to fix?
Sorry, next try,
About faces-redirect,
h:comandButton value=”Confirm” action=”#{userBean.addConfirmedUser}?faces-redirect=true”/>
This gives: Not a Valid Method Expression
How to fix?
@Eric: I’m not sure whether redirect can be added this way. You can add it in navigation rules with redirect/ tag. Or, you can add ‘faces-redirect=true’ flag to the string returned by addConfirmedUser.
Hi Max,
In my application, I have common file containing menu and I am using implicit navigation where I don’t have to define a navigation rules inside JSF configuration file.
I have following Dir structure
index.html home.xhtml Pages/user/admin_page1.xhtml Pages/admin/user_page1.xhtmlHere is MenuBean
@ManagedBean(name = "menuBean") @ApplicationScoped public class MenuBean { public String home() throws IOException { return "home"; } public String user_page1() throws IOException { return "user/user_page1"; } public String admin_page1() throws IOException { return "admin/admin_page1"; } }But I am getting following message :
Unable to find matching navigation case with from-view-id ‘/pages/user/user_page1.xhtml’ for action ‘#{menuBean.user_page1}’ with outcome ‘/admin/admin_page1′
Please note that even though named directories based on Role, no security is implemented.
Reagrds,
Shirish
@Shirish: the method you are clicking on is meneBean.user_page1 but the outcome based on error is /admin/admin_page1 — is that correct?
Hi, how do you map a navigation case for an action method that is parametised?
@Tumza: I think it would be the same way, just make sure the method returns a navigation outcome.