Hierarchical Navigation in JSF
(Note: I typed this up in TextEdit. The code provided is for illustration purposes and was not tested. If there are any errors, please post corrections in the article comments.)
One of the biggest problems I encountered when learning Java Server Faces (JSF) was figuring out the best way to navigate from a list of items to a view or edit screen for a particular item in the list. By "best" I mean one that is in the spirit of the framework and is not difficult to implement or unreadable. The scenario I will discuss happens pretty frequently in the applications I work with, but it is not specifically addressed in Core Java Server Faces or any of the tutorials I have encountered on the web.
The setup is this: one page (items.jsp) contains a table with one row per item. Each row has a button or a link that takes the user to the next page, editItem.jsp, which contains a form allowing the user to view and change the properties for a particular item. When the editItem.jsp form is submitted, the user is taken back to items.jsp and the changes are visible. This model can of course be more complex, such as in the case where there is a whole heirarchy of objects (Category -> SubCategory -> Item -> Feature).
The old way of doing things was to create a servlet (EditItem.java) that:
Each row of the table in items.jsp would have a link explicitly specifying the GET method parameter "itemId" so that the item ID passed to EditItem.java varies depending on which link (row) is clicked. A POST method solution can be achieved by using submit inputs in lieu of links and using a Javascript onclick event to populate a hidden form input named "itemId" before the form is submitted (this is, in fact, how Struts implements some of the controls).
JSF can coexist with servlets, but in using the approach described above I find that most of the application logic ends up taking place in servlets, using JSF only for component binding or backing beans. It is possible to access the request map through the external context but that is kind of dodgy. What we would like to do is call a backing bean method and have that method infer which row was clicked on using only JSF components and events. Other than the request parameter trick, there are a couple good ways to do this.
One approach would be to add an attribute to the <h:commandButton> or <h:commandLink> component and then bind the component to an action listener method on the backing bean. Note that an action listener method differs from a normal action method in that it is passed an ActionEvent parameter that provides a pointer to the UICommand component that invoked the method. The attribute, in this case the item ID, can be read from the UICommand component's attribute map and used to load the appropriate Item. This item is then stored in another backing bean property, currentItem, so it can be referenced by editItem.jsp.
This approach has a drawback: your backing bean must be session-scoped. This means that cleanup may be necessary to prevent a previously selected Item from bleeding over onto another form. For example, suppose an Item was selected and then edited. Afterward, the user chooses to create a new item. If the addItem.jsp page also uses the currentItem property of the backing bean, then it is possible that the addItem.jsp page will show the data for the Item that was just edited. To prevent this, either create a currentItem property for each page; use separate backing beans; or set currentItem to null whenever changes are committed to the persistent store or abandoned.
items.jsp
ItemBean.java
editItem.jsp
This is a very involved approach. I prefer it because it makes use of the Inversion of Control (IOC) and offers a very clean delineation between the model (POJO) and view/controller (JSF). Such a loose coupling of frameworks allows interchangeability of frameworks (JSF, Spring, Hibernate) and offers a clean separation of concerns.
A <h:dataTable> iterates over a list and assigns each element in the list to a temporary variable (i) that can be used to extract property values specific to each item in a list. We used that functionality in the previous approach to extract the ID from each item and assign it to an attribute on an action listener method. That method used the attribute value to load the appropriate Item and initialized the currentItem property of ItemBean for use by editItem.jsp. In the previous example, all of the <h:commandButton> components were bound to the same method of the session-scoped bean ItemBean. Instead of binding every <h:commandButton> to the same method, couldn't they be bound to a method of the temporary variable (#{i.editItem})?
This has very interesting implications. If the <commandButton> is bound directly to a method on the Item, then do we still need to pass the itemId? No, because the method only has access to one Item: the Item that is the object instance the method was invoked on.
But does this really solve any problems? Suppose that the Item object has a method called editThisItem and that every <h:commandButton> is bound to the editThisItem method of a particular Item instance. The method knows which Item to operate on, and what's more, it doesn't need to load it from the persistent store. All that is left is to navigate to editItem.jsp... so why even bother invoking the method in the first place?
The problem is that when editItem.jsp loads, it will have nothing to reference. In the previous approach, a property called currentItem was initialized and referenced by editItem.jsp. Something similar must be done with this approach--the editThisItem method must store a reference to its Item instance in a managed bean so that it can be used by editItem.jsp. Here is where it gets involved.
Three managed beans are needed:
Both ItemsBean and ItemBean contain a property named sessionBean that stores a reference to the SessionBean instance. ItemBean also has a propert named item that points to the Item instance it wraps. Both of these are managed properties that are initialized by the JSF container when the beans are iniitalized.
The <h:dataTable> in items.jsp iterates over the itemBeans collection of the ItemsBean bean. The temporary variable stores an ItemBean reference that can be used to refer to the Item that is being wrapped (item) or the action method (editThisItem). The editThisItem method of ItemBean will initialize the currentItem property of sessionBean. The editItem.jsp page will use an ItemBean backing bean whose item property will be initialized with the value of currentItem from the sessionBean by the JSF container.
The sequence of events is this:
SessionBean.java
ItemsBean.java
ItemBean.java
faces-config.xml
items.jsp
editItem.jsp
One of the biggest problems I encountered when learning Java Server Faces (JSF) was figuring out the best way to navigate from a list of items to a view or edit screen for a particular item in the list. By "best" I mean one that is in the spirit of the framework and is not difficult to implement or unreadable. The scenario I will discuss happens pretty frequently in the applications I work with, but it is not specifically addressed in Core Java Server Faces or any of the tutorials I have encountered on the web.
The setup is this: one page (items.jsp) contains a table with one row per item. Each row has a button or a link that takes the user to the next page, editItem.jsp, which contains a form allowing the user to view and change the properties for a particular item. When the editItem.jsp form is submitted, the user is taken back to items.jsp and the changes are visible. This model can of course be more complex, such as in the case where there is a whole heirarchy of objects (Category -> SubCategory -> Item -> Feature).
The old way of doing things was to create a servlet (EditItem.java) that:
- looks for a request parameter ("itemId")
- loads that item from a persistent store
- stores the item in the request or session context
- forwards to editItem.jsp
Each row of the table in items.jsp would have a link explicitly specifying the GET method parameter "itemId" so that the item ID passed to EditItem.java varies depending on which link (row) is clicked. A POST method solution can be achieved by using submit inputs in lieu of links and using a Javascript onclick event to populate a hidden form input named "itemId" before the form is submitted (this is, in fact, how Struts implements some of the controls).
JSF can coexist with servlets, but in using the approach described above I find that most of the application logic ends up taking place in servlets, using JSF only for component binding or backing beans. It is possible to access the request map through the external context but that is kind of dodgy. What we would like to do is call a backing bean method and have that method infer which row was clicked on using only JSF components and events. Other than the request parameter trick, there are a couple good ways to do this.
Component Attributes
One approach would be to add an attribute to the <h:commandButton> or <h:commandLink> component and then bind the component to an action listener method on the backing bean. Note that an action listener method differs from a normal action method in that it is passed an ActionEvent parameter that provides a pointer to the UICommand component that invoked the method. The attribute, in this case the item ID, can be read from the UICommand component's attribute map and used to load the appropriate Item. This item is then stored in another backing bean property, currentItem, so it can be referenced by editItem.jsp.
This approach has a drawback: your backing bean must be session-scoped. This means that cleanup may be necessary to prevent a previously selected Item from bleeding over onto another form. For example, suppose an Item was selected and then edited. Afterward, the user chooses to create a new item. If the addItem.jsp page also uses the currentItem property of the backing bean, then it is possible that the addItem.jsp page will show the data for the Item that was just edited. To prevent this, either create a currentItem property for each page; use separate backing beans; or set currentItem to null whenever changes are committed to the persistent store or abandoned.
Code
items.jsp
...
<f:form>
<h:dataTable value="#{itemBean.items}" var="i">
<h:column>
<f:facet name="header"><f:verbatim>Name</f:verbatim></f:facet>
<h:outputText value="#{i.name}"/>
</h:column>
<h:column>
<f:facet name="header"><f:verbatim>Action</f:verbatim></f:facet>
<h:commandButton value="Edit" actionListener="#{itemsBean.editItem}">
<f:attribute name="itemId" value="#{i.id}"/>
</h:commandButton>
</h:column>
</h:dataTable>
</f:form>
...
ItemBean.java
public class ItemBean
{
private Item _currentItem = null;
public void setCurrentItem( Item currentItem )
{
_currentItem = currentItem;
}
public Item getCurrentItem( )
{
return _currentItem;
}
public List getItems( )
{
return new ItemDao( ).loadAll( );
}
public String editItem( ActionEvent event )
{
UICommand command = (UICommand)event.getComponent( );
String itemId = (String)command.getAttributes( ).get( "itemId" );
Item itemToEdit = new ItemDao( ).loadById( itemId );
setCurrentItem( itemToEdit );
return "success";
}
public String saveCurrentItem( )
{
new ItemDao( ).save( getCurrentItem( ) );
setCurrentItem( null );
return "success";
}
}
...
editItem.jsp
...
<h:inputLabel for="name"><f:verbatim>Name</f:verbatim></h:inputLabel>
<h:inputText id="name" value="#{itemBean.currentItem.name}"/>
<h:commandButton value="Save" action="#{itemBean.saveCurrentItem"/>
...
Decorators
This is a very involved approach. I prefer it because it makes use of the Inversion of Control (IOC) and offers a very clean delineation between the model (POJO) and view/controller (JSF). Such a loose coupling of frameworks allows interchangeability of frameworks (JSF, Spring, Hibernate) and offers a clean separation of concerns.
A <h:dataTable> iterates over a list and assigns each element in the list to a temporary variable (i) that can be used to extract property values specific to each item in a list. We used that functionality in the previous approach to extract the ID from each item and assign it to an attribute on an action listener method. That method used the attribute value to load the appropriate Item and initialized the currentItem property of ItemBean for use by editItem.jsp. In the previous example, all of the <h:commandButton> components were bound to the same method of the session-scoped bean ItemBean. Instead of binding every <h:commandButton> to the same method, couldn't they be bound to a method of the temporary variable (#{i.editItem})?
This has very interesting implications. If the <commandButton> is bound directly to a method on the Item, then do we still need to pass the itemId? No, because the method only has access to one Item: the Item that is the object instance the method was invoked on.
But does this really solve any problems? Suppose that the Item object has a method called editThisItem and that every <h:commandButton> is bound to the editThisItem method of a particular Item instance. The method knows which Item to operate on, and what's more, it doesn't need to load it from the persistent store. All that is left is to navigate to editItem.jsp... so why even bother invoking the method in the first place?
The problem is that when editItem.jsp loads, it will have nothing to reference. In the previous approach, a property called currentItem was initialized and referenced by editItem.jsp. Something similar must be done with this approach--the editThisItem method must store a reference to its Item instance in a managed bean so that it can be used by editItem.jsp. Here is where it gets involved.
Three managed beans are needed:
- SessionBean - a session-scoped bean that holds a pointer to the current item
- ItemsBean - a request-scoped bean that contains a list of ItemBeans
- ItemBean - a decorator for a Item instance that exposes action methods. Also a request-scoped bean used by editItem.jsp.
Both ItemsBean and ItemBean contain a property named sessionBean that stores a reference to the SessionBean instance. ItemBean also has a propert named item that points to the Item instance it wraps. Both of these are managed properties that are initialized by the JSF container when the beans are iniitalized.
The <h:dataTable> in items.jsp iterates over the itemBeans collection of the ItemsBean bean. The temporary variable stores an ItemBean reference that can be used to refer to the Item that is being wrapped (item) or the action method (editThisItem). The editThisItem method of ItemBean will initialize the currentItem property of sessionBean. The editItem.jsp page will use an ItemBean backing bean whose item property will be initialized with the value of currentItem from the sessionBean by the JSF container.
The sequence of events is this:
- SessionBean instance is created as sessionBean
- ItemsBean instance is created as itemsBean. itemsBean.sessionBean is initalized with sessionBean.
- itemBeans property is initialized as a List of unmanaged ItemBean elements
- items.java initializes <h:dataTable>
- <h:commandButton> is clicked
- editThisItem method is invoked. sessionBean.item is initalized with the Item instance wrapped by the ItemBean decorator.
- ItemBean instance is created as itemBean. itemBean.sessionBean is initialized with sessionBean. itemBean.item is initialized with sessionBean.item
- edit.jsp extracts values from and applies values to itemBean.item
Code
SessionBean.java
public class SessionBean
{
private Item _currentItem = null;
public void setCurrentItem( Item currentItem )
{
_currentItem = currentItem;
}
public Item getCurrentItem( )
{
return _currentItem;
}
}
ItemsBean.java
public class ItemsBean
{
private SessionBean _sessionBean = null;
public void setSessionBean( SessionBean sessionBean )
{
_sessionBean = sessionBean;
}
public List getItemBeans( )
{
List items = new ItemDao( ).loadAll( );
List itemBeans = new ArrayList( );
for( Iterator itemIter = items.iterator( ); itemIter.hasNext( ); )
{
itemBeans.add( new ItemBean( _sessionBean, (Item)itemIter.next( ) ) );
}
return itemBeans;
}
}
ItemBean.java
public class ItemBean
{
private SessionBean _sessionBean = null;
public void setSessionBean( SessionBean sessionBean )
{
_sessionBean = sessionBean;
}
private Item _item = null;
public void setItem( Item item )
{
_item = item;
}
public Item getItem( )
{
return _item;
}
public ItemBean( ) { }
public ItemBean( SessionBean sessionBean, Item item )
{
_sessionBean = sessionBean;
_item = item;
}
public String editThisItem( )
{
_sessionBean.setCurrentItem( _item );
return "success";
}
public String removeThisItem( )
{
new ItemDao( ).delete( _item );
return "success";
}
public String saveThisItem( )
{
new ItemDao( ).save( _item );
return "success";
}
}
faces-config.xml
...
<managed-bean>
<managed-bean-name>sessionBean</managed-bean-name>
<managed-bean-class>SessionBean<managed-bean-class>
<managed-bean-scope>session<managed-bean-scope>
<managed-bean>
<managed-bean>
<managed-bean-name>itemsBean</managed-bean-name>
<managed-bean-class>ItemsBean<managed-bean-class>
<managed-bean-scope>request<managed-bean-scope>
<managed-property>
<property-name>sessionBean<property-name>
<property-class>SessionBean<property-class>
<property-value>#{sessionBean}<property-value>
<managed-property>
<managed-bean>
<managed-bean>
<managed-bean-name>itemBean</managed-bean-name>
<managed-bean-class>ItemBean<managed-bean-class>
<managed-bean-scope>request<managed-bean-scope>
<managed-property>
<property-name>sessionBean<property-name>
<property-class>SessionBean<property-class>
<property-value>#{sessionBean}<property-value>
<managed-property>
<managed-property>
<property-name>item<property-name>
<property-class>Item<property-class>
<property-value>#{sessionBean.item}<property-value>
<managed-property>
<managed-bean>
...
items.jsp
...
<f:form>
<h:dataTable value="#{itemsBean.itemBeans}" var="ib">
<h:column>
<f:facet name="header"><f:verbatim>Name</f:verbatim></f:facet>
<h:outputText value="#{ib.item.name}"/>
</h:column>
<h:column>
<f:facet name="header"><f:verbatim>Action</f:verbatim></f:facet>
<h:commandButton value="Edit" actionListener="#{ib.editThisItem}"/>
</h:column>
</h:dataTable>
</f:form>
...
editItem.jsp
...
<h:inputLabel for="name"><f:verbatim>Name</f:verbatim></h:inputLabel>
<h:inputText id="name" value="#{itemBean.item.name}"/>
<h:commandButton value="Save" action="#{itemBean.item.saveThisItem"/>
...

Comments