Thursday May 07, 2009

JSF Actions on the URL

In a recent entry, I described a mechanism for passing a JSF view ID as a GET parameter to force JSF to a particular view. This entry will improve on that by allowing a JSF action to be specified a a GET parameter.

After writing the previous entry, I started thinking ... JSF already has a mapping from action to view ID, why not make use of that to simplify the URL? Instead of:

/faces/home.xhtml?viewId=/faces/other.xhtml

we have this,

/faces/home/xhtml?jsf.action=success

So, from the current view of /faces/home.xhtml, and action success, go to the view defined in faces-config.xml's navigation rules. For example,

     <navigation-rule>
        <from-view-id>/faces/summary.xhtml
        <navigation-case>
            <from-outcome>success</from-outcome>
            <to-view-id>/admin/facelet/finished.xhtml</to-view-id>
        </navigation-case>
        ...
     ...

If our current view is /faces/summary.xhtml, and the jsf.action=success parameter is present, then move to view ID /facelet/finished.xhtml. This is rather slick as it makes use of existing rules; no additional configuration is required. Anyway, here's the code:

public class ActionPhaseListener implements PhaseListener {

    public RedirectPhaseListener() {
    }

    public PhaseId getPhaseId() {
        return PhaseId.RESTORE_VIEW;
    }

    public void beforePhase(PhaseEvent phaseEvent) {
    }

    public void afterPhase(PhaseEvent phaseEvent) {
        if (navigationRules == null) {
            navigationRules = new NavigationRules();
        }

        FacesContext ctx = phaseEvent.getFacesContext();
        HttpServletRequest request =
                (HttpServletRequest) ctx.getExternalContext().getRequest();

        String action = request.getParameter("jsf.action");

        if (action != null) {
            String currentViewId = ctx.getViewRoot().getViewId();
            NavigationRule nr = navigationRules.getNavigationRules().get(currentViewId);
            if (nr == null) {
                nr = navigationRules.getNavigationRules().get(null);
            }
            NavigationCase nc = nr.getNavigationCases().get(action);
            if (nc != null) {
                String newViewId = nc.getToViewId();
                UIViewRoot page = ctx.getApplication().getViewHandler().createView(ctx, newViewId);
                ctx.setViewRoot(page);
                ctx.renderResponse();
            }
        }
    }

    private NavigationRules navigationRules = null;
} 

in the interest of not boring you, NavigationRules.java is linked. It reads faces-config.xml by using ServletContext.getResource() and parses it into bean-like objects using JDOM.

Alain commented on my previous post that the solution there was problematic as it allowed access to arbitrary view which may depend on objects being set up in other views (think of a work flow / wizard). This solution has no such problem, as you can only navigate according to the rules you've already defined in faces-config.xml.



Tuesday May 05, 2009

Poor Man's JSF Navigation By URL

Every wanted to access a JSF view by directly entering the view onto URL? This is a common request. In my case, I needed to link to specific places in my JSF app from a legacy non-JSF application. This is the type of thing you'd think would be straightforward, but JSF falls flat.

I scoured the web for solutions to this, and this is the simplest: implement a phase listener. In the interest of getting right to it, here it is:

public class RedirectPhaseListener implements PhaseListener {

    public RedirectPhaseListener() {
    }

    public PhaseId getPhaseId() {
        return PhaseId.RESTORE_VIEW;
    }

    public void afterPhase(PhaseEvent phaseEvent) {
    }

    public void beforePhase(PhaseEvent phaseEvent) {
        FacesContext ctx = phaseEvent.getFacesContext();
        HttpServletRequest request =
                (HttpServletRequest) ctx.getExternalContext().getRequest();

        String viewId = request.getParameter("viewId");

        if (viewId != null) {
            UIViewRoot page = ctx.getApplication().getViewHandler().createView(ctx, viewId);
            ctx.setViewRoot(page);
            ctx.renderResponse();

        }
    }
} 

The phase listener gets the request, and checks for a parameter. The parameter is the path to the view ID you want to visit. For example: /faces/someview.xhtml. With Facelets, these goto URLs end up looking funny, because the real view ID is in the URL also,

 /faces/home.xhtml?viewId=/faces/other.xhtml

Not nice, but it works. Other solutions I've seen try to get fancy by keeping a mapping a view+action result=new view. That's cleaner, and it's easy to do once you understand what's going on above.

About

jtb

Search

Categories
Archives
« April 2014
SunMonTueWedThuFriSat
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today