JSF 2 and bookmarkable URLs

With JEE6 and JSF2 we finally have the great feature of bookmarkable URLs. This fixes one of the most awful faults of JSF that pages were not bookmarkable. An application can now become bookmarkable with some modifications. (See details about bookrmarkble URLs in Ryan Lubke’s Blog)

In a JSF web application you will usually have a <h:command link> or <h:commandbutton> like this:

<h:commandLink action="#{workflowController.load()}">
    <h:outputTextvalue="My action link" />
</h:commandLink>

In this example the action method returns a navigation outcome. Since JSF2 this action outcome can be also a jsf page URL (e.g /page/my_page.xhml), so no more explicit navigation rule is necessary. But the result is not bookmarkable.

To replace this with a bookmarkable URL you can replace the JSF component with the new  <h:link>. See the following example:

<h:link outcome="#{workflowController.load()}">
    <f:param name="id" value="#{workitem.item['$uniqueid']}"/>
    <h:outputText value="My action link" />
</h:link>

The trick here is, that the link is extended with a URL query param ‘id’.  Now the HTML outcome of the JSF component will look like this.

<a href="http://localhost:8080/myapp/page/my_page.xhtml?id=1">My action Link</a>

The result is simmilar to a <h:commandLink> with the difference that the URL is now set to the browsers URL. So far this new link can be bookmarked, but it is not automatically working. To resolve this new URL you have to customize your jsf page to evaluate the new query param (in this example my_page.xhml).

You can add the following metadata tag to extract the ID.

<h:head>
 <f:metadata>
 <f:viewParam name="id" value="#{workflowController.deepLinkId}" />
 </f:metadata>
</h:head>

In my example I have a method ‘getDeepLinkId’ in my workflowController Bean. The method just loads the corresponding object into my controller.

public void setDeepLinkId(String adeepLinkId) {
 this.deepLinkId = adeepLinkId;
   if (deepLinkId != null && !deepLinkId.isEmpty()) {
       // load the business object....
       this.load(deepLinkId);
       // finally destroy the deepLinkId to avoid a reload on the next
       // post event
       deepLinkId = null; // !
    }
 }

This method is necessary so in case the bookmarkable URL is requested later from a user we load the corresponding Object before we load the page.

That’s it.

h:command Buttons

The bookmarkable URLs can become a little bit tricky if you have more complex action methods. For example I used in a h:commandButton like this:

<h:commandButton action="#{workflowController.process}" value="My button">
 <f:setPropertyActionListener
    target="#{workflowController.workitem.item['$ActivityID']}"
     value="#{activity.item['numactivityid']}" />
</h:commandButton>

This is a case where replacing <h:commandButton> with <h:button> will not work out of the box. This is because the outcome property can not evaluate the action result which can – in my case – also become null. To solve this the navigation rules come back into the game. You can achieve the same result as shown in the first example with a navigation rule placed in the faces-config.xml. See the following example:

<navigation-rule>
   <navigation-case>
   <from-outcome>workitem</from-outcome>
   <to-view-id>/page/my_page.xhtml</to-view-id>
   <redirect>
     <view-param>
       <name>id</name>
       <value>#{workflowController.workitem.item['$uniqueid']}</value>
     </view-param>
   </redirect>
 </navigation-case>
</navigation-rule>

Now I moved the param ‘id’ out to the navigation rule with the identifier ‘workitem’. If my action result form the h:commandButton is ‘workitem’ the navigation rule computes the bookmarkable URL:

<a href="http://localhost:8080/myapp/page/my_page.xhtml?id=1">My action Link</a>

I hope this short examples will help you to build bookmakrable jsf applications.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.