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 .. condition (evaluates to true/false) in each case, it’s possible to navigate to three different pages. This allows to use application state to determine where to navigate. It’s not longer necessary to have model objects return arbitrary strings and thus could eliminate having your back-end know anything about navigation.

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 condition false. In such case you would think that navigation shouldn’t take place. However, you still navigate to confirmation.xhtml because implicit navigation is used. First, the above case is matched but not selected as .. evaluates to false. Navigation continues to look for a match and because purchase() method returned a string value of “confirmation” is used by implicit navigation which matches a page with such name. Implicit navigation is used last.

34 comments

  1. grafity

    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….

  2. Pingback: lost in thought » JSF2 Linksammlung
  3. Tbee

    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.

  4. Pingback: Learning JSF2: Ajax in JSF – using f:ajax tag | Maxa Blog
  5. Doug

    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.

  6. max

    @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.

  7. sam

    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.

  8. max

    @sam: implicit navigation takes precedence last, maybe you have another (defined) navigation rule that keeps you on the same page?

  9. sam

    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?

  10. sam

    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/*

  11. sam

    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.

  12. max

    @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?

  13. sam

    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.

  14. sam

    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.

  15. Pingback: Learning JSF2: Using Flash scope | Maxa Blog
  16. Matze

    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 :)

  17. Eric J. Van der Velden

    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?

  18. max

    @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.

  19. Shirish

    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.xhtml
    
    

    Here 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

  20. max

    @Shirish: the method you are clicking on is meneBean.user_page1 but the outcome based on error is /admin/admin_page1 — is that correct?

Leave a Reply