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}”).


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.
I am trying to implement this example in ADF w/ Facelets. So the few changes I had to make are as follows:
Registering the view handler is via ALTERNATEVIEWHANDLER within the web.xml (Instead of faces-config.xml)
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?
Probably better to use the decorater pattern rather than tie it to a facelet implementation. This approach breaks the JSF pluggability architecture.
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. ;-)
(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); } }
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:)
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
Hi,
Consider replacing the XML configuration files with a database-driven menu system.