How to make JSF navigation rules more dynamic

Posted by Dominik Wed, 24 Jan 2007 13:22:00 GMT

Consider the following situation:

You do a post request from some form in an JSF application and want to decide on the page to redirect to dynamically. No problem, just use different “navigation-case” entries and go to different “to-view-id”s depending on the “from-outcome”.

<navigation-rule>
    <from-view-id>/pages/organisation/*</from-view-id>
    <navigation-case>
        <from-action>#{OrganisationBean.select}</from-action>
        <from-outcome>to-user</from-outcome>
        <to-view-id>/pages/user/details.jsf</to-view-id>
        <redirect/>
    </navigation-case>
    <navigation-case>
        <from-action>#{OrganisationBean.select}</from-action>
        <from-outcome>to-project</from-outcome>
        <to-view-id>/pages/project/details.jsf</to-view-id>
        <redirect/>
    </navigation-case>
    <navigation-case>
        <from-action>#{OrganisationBean.select}</from-action>
        <from-outcome>to-whatever</from-outcome>
        <to-view-id>/pages/whatever/details.jsf</to-view-id>
        <redirect/>
    </navigation-case>
</navigation-rule>

Sure, this works. It’s not exactly what I would call DRY, but it works. But what if you need to be more dynamic then that?

Just use a framework that’s more dynamic I hear you say.

Sure, I’d love to. But what if that is not an option?

I found this, this and this on the web, and putting these together gave me the following solution which I wanted to share with you:

Step 1:

Create a ViewHandler, in my case a subclass of com.sun.facelets.FaceletViewHandler and overwrite getActionURL():

@Override
public String getActionURL(FacesContext context, String viewId)
{
    String result = viewId;
    if(Util.isVBExpression(viewId))
    {
        ValueBinding vb = context.getApplication().createValueBinding(viewId);
        result = vb.getValue(context).toString();
    }
        result = super.getActionURL(context, value);
    int queryStart = value.indexOf("?");
    if((queryStart > 0) && (result.indexOf("?") == -1))
    {
        result = result + value.substring(queryStart);
    }
    return result;
}

This enables you to use EL in the to-view-id entry of a navigation case.

Step 2:

Register your view handler with the application, in my case:

<application>
    ...
    <view-handler>com.interactive_objects.jsf.crud.generic.DynamicViewHandler</view-handler>
</application>

Step 3:

Now you can do things like:

<navigation-rule>
    <from-view-id>/pages/organisation/*</from-view-id>
    <navigation-case>
        <from-action>#{OrganisationBean.select}</from-action>
        <from-outcome>selected</from-outcome>
        <to-view-id>#{OrgansiationBean.computedRedirect}</to-view-id>
        <redirect/>
    </navigation-case>
</navigation-rule>

(You can also use compound expressions like “/path/to/page.jsf?foo=#{ManagedBean.bar}”).

Tags ,  | 11 comments | 5 trackbacks

Comments

  1. Avatar CF said 16 days later:

    Hi Dominik,

    Your solution works like a charm. The only pitfall can be using prefix mapping /faces/ vs. extension mapping .faces. The latter can wreck your url using the super.getActionURL() as adds (via repleacment) teh “.faces” but this can be eliminated easily.

    Coincidentally I had read all the solution articles you had mentioned myself, but yours gives the perfect sumary.

    Thanks.

    CF

    PS: It’s a shame that JSF does not provide this by default. This solution can even be used passing parameters between pages when using navigation rules with as you are able to add the much needed parameters to your “redirect-GET-request”. JSF should just let request scoped beans survive a redirect since this is semantically one JSF request and only technically two (thanks to HTTP). JSF still has sooo much to be fixed before it really can be used productivly.

  2. Avatar Justin said 23 days later:

    I am trying to implement this example in ADF w/ Facelets. So the few changes I had to make are as follows:

    1. Registering the view handler is via ALTERNATEVIEWHANDLER within the web.xml (Instead of faces-config.xml)

    2. getActionURL override had to be a little different as the default suffix we are defining seems to get slapped onto the end of the expression before it is passed to the method. So just a little trimming was needed.

    Well, the viewHandler is being called and the expression is sucesfully converted from expression to path. However, after it leaves the overridden method I get a 404 Not Found for the given path. Any ideas?

  3. Avatar Raghavan said about 1 month later:

    Probably better to use the decorater pattern rather than tie it to a facelet implementation. This approach breaks the JSF pluggability architecture.

  4. Avatar Dominik said about 1 month later:

    Hi Raghavan,

    sure, the code above is a hack and we were already forced to use a decorator because we switched the implementations for testing. ;-)

  5. Avatar Fredrik said about 1 month later:

    (You can also use compound expressions like “/path/to/page.jsf?foo=#{ManagedBean.bar}”).

    I can’t get this to work. If I try to this I only get redirected to /path/to/page.jsf

    All the paramterers are gone.

    I use facelets and wonder if I impored the right ViewHandler in my class that overrides the getActionURL

    I get two suggestions when fixing imports in netBeans. javax.faces.application.ViewHandler; javax.faces.context.FacesContext ;

    Witch one should I use?

    Here is my class. —— 8< —- import com.sun.facelets.tag.jsf.core.ViewHandler; import com.sun.faces.util.Util; import javax.faces.application.ViewHandler; //import javax.faces.context.FacesContext; import javax.faces.el.ValueBinding;

    public class DynamicViewHandler extends com.sun.facelets.FaceletViewHandler {

    public DynamicViewHandler(ViewHandler parent) { super(parent); }

    public String getActionURL(FacesContext context, String viewId) { String result = viewId; if(Util.isVBExpression(viewId)) { ValueBinding vb = context.getApplication().createValueBinding(viewId); result = vb.getValue(context).toString(); } return super.getActionURL(context, result); } }

  6. Avatar Wojtek said 6 months later:

    Hello Dominik. I’ve tried it and it works cute. But what about situation, when we return null from backing bean method (there is no navigation used and page is re-rendered), and we’d like to keep request parameter for that page. I need it for my jsp-jsf application and I’d very grateful for any your suggestion:)

  7. Avatar Richard Corsale said 8 months later:

    I have a problem, the default web.xml puts the JSF pages at /faces

    the login.jsp page below simply does not register the /faces/login.jsp I also tried it with /login.jsp , same thing. any Ideas?

    faces-config:

    login ignite.Login request

    <navigation-rule>
        <from-view-id>/faces/login.jsp</from-view-id>    
        <navigation-case> 
                <from-action>#{login.loginAction}</from-action>
                <from-outcome>sucess</from-outcome>
                   <to-view-id>/faces/validate.jsp</to-view-id>
            </navigation-case>
            <navigation-case> 
              <from-action>#{login.loginAction}</from-action>
                   <to-view-id>/faces/login.jsp</to-view-id>
            </navigation-case>
    </navigation-rule>
    

  8. Avatar Dave said about 1 year later:

    Hi,

    Consider replacing the XML configuration files with a database-driven menu system.

  9. Avatar espilce said over 3 years later:

    hi, at the begining you say: “sure, this works.” i want to write what i do:

    i wrote sth like these in faces-config.xml:

    * #{myBean.ifLoggedIn} no /login.jsp

    i write this in a forum.jsp:

    my ifLoggedIn method is like:

    public String ifLoggedIn(){ return loggedIn; }

    i see “no” in the page forum.jsp.

    but should not i redirect to login.jsp if the loggedIn value is “no”?

    what is your “select” method is like?

    how should my ifLoggedIn method be?

    thanks

  10. Avatar espilce said over 3 years later:

    sorry it deletes some chars. my faces-config has lines like

    navigation-rule from-view-id * from-view-id navigation-case from-action

    {myBean.ifLoggedIn}

    from-action from-outcome no from-outcome to-view-id /login.jsp to-view-id navigation-case navigation-rule

  11. Avatar Cross said over 3 years later:

    Hi, I tried your solution and I can’t made it work with tomahawk, so I found a different solution you could try: http://javaxcross.blogspot.com/2009/10/dynamic-navigation-in-jsf.html

Trackbacks

Use the following link to trackback from your own site:
http://www.ars-subtilior.com/typo/trackbacks?article_id=how-to-make-jsf-navigation-rules-more-dynamic&day;=24&month;=01&year;=2007

  1. From Confluence: Architecture and functionality
    web frmk - refactoring to eliminate dups for types of device
    Hi, I refactored a substantial amount of code to eliminate duplications between: an80i PMP and PTP and some of the an100u. Much remains to be done but at this point: 1) We can use the same outcome names for same ... well ......
  2. From JIRA: jsf
    [JSF-27] Try to make JSF navigation rules more dynamic
    Investigate possibility of using dynamic navigation rules. http://typo.ars-subtilior.com/articles/2007/01/24/how-to-make-jsf-navigation-rules-more-dynamic
  3. From Fred the dude!
    Wow strategy guide
    Great blog post, keep up the good work as its rare to find good posts latelly due to all the damn spam etc, thanks for a great read and take care!
  4. From Benny's Blog
    Bookmarkable and SEO Friendly JSF
    Yes, it is possible. Yes, it requires hacking JSF with filters and/or phaselisteners and/or custom servlets for encoding/decoding serializing/deserializing GET parameters and force redirects upon JSF view resolvers. The good news however: it’...
  5. From Benny's Blog
    Bookmarkable and SEO Friendly JSF
    Yes, it is possible. Yes, it requires hacking JSF with filters and/or phaselisteners and/or custom servlets for encoding/decoding serializing/deserializing GET parameters and force redirects upon JSF view resolvers. The good news however: it’...

(leave url/email »)

   Comment Markup Help Preview comment