{scrollbar}

Handling Command components within table columns

In a typical master/detail scenario, a table displays a Collection of objects. There is a command component (a link or button) in each table row that performs some operation on the object for that row (for example, navigating to a separate page that shows more details about that object, or allows it to be edited).

The problem is that the command's "action" attribute points to the same backing bean method to run for each row. When the method is executed, it somehow needs to know which of the rows was actually selected.

Using f:setActionPropertyListener or t:updateActionListener

Note: The f:setActionPropertyListener tag was added in JSF1.2. For people using JSF1.1, the Tomahawk library provides t:updateActionListener which does the same thing.

This approach causes two things to happen when a command component is clicked:
1. store the command component in a property on the backing bean, then
2. run the "action method", which can look at that property to see exactly which item was selected

This solution looks something like the following:

xml <h:dataTable var="emp" .... > <h:commandLink action="#{employeeAction.prepareForEdit}"> <h:outputText value="#{msgs.edit}" /> <t:updateActionListener property="#{employeeAction.currentEmployee}" value="#{emp}" /> </h:commandLink>

When the above link is clicked, JSF will first call the setCurrentEmployee(Employee) method on the backing bean (which should store its parameter as a member on the backing bean). The prepareForEdit method can then just use that member to know which specific employee was chosen.

Using the table's DataModel

Each table component (h:dataTable) has a DataModel object, and this DataModel knows which is the "currently selected" row.

  1. An h:dataTable can be configured to use a !DataModel explicitly provided by your code, ie the "value" attribute of the h:dataTable can point to a backing-bean method that returns a !DataModel. If you are doing this, and the !DataModel object is cached in a member of your backing bean, then the "action method" that is triggered can simply access that member and call its getRowData() method to get the object for the "selected row".
  2. If the "value" attribute of the h:dataTable instead points to a backing-bean method that returns a List or similar collection, then the dataTable will internally create a !DataModel to wrap that list. You can access this DataModel instance by using the "actionListener" attribute on the command component, rather than the "action" one. The backing-bean method is then passed an ActionEvent object which contain a reference to the command-component that was clicked on; by walking up the component tree the enclosing UIData component can be found, and its DataModel then retrieved. Actually, it's even simpler, as the "getRowData" method on the UIData component can be used directly (it just delegates to the DataModel method).
    The JSF page looks like this: xml <h:commandLink actionListener="#{employeeAction.prepareForEdit}"> <h:outputText value="#{msgs.edit}" /> </h:commandLink> And the backing bean looks like this: java public void prepareForEdit(ActionEvent anEvent) { YourBeanClass tmpBean = null; UIComponent tmpComponent = anEvent.getComponent(); while (null != tmpComponent && !(tmpComponent instanceof UIData)) { tmpComponent = tmpComponent.getParent(); } if (tmpComponent != null && (tmpComponent instanceof UIData)) { Object tmpRowData = ((UIData) tmpComponent).getRowData(); if (tmpRowData instanceof YourBeanClass) { tmpBean = (YourBeanClass) tmpRowData; //TODO Implementation of your method } } //TODO Exception Handling if UIData not found or tmpRowBean of wrong type }
  3. You could also use the "binding" attribute on the h:dataTable to make it accessable from the backing bean. However component bindings have their own problems and should be avoided where possible.

Passing params with f:param

If you are coming from Struts or some other servlet MVC framework you may have previously solved this problem by passing some kind of primary key as request parameters as part of a link, perhaps via something like this:

xml<a href="/appContext/someAction.do?id=1234&userAction=prepareEdit">Edit</a>

Using JSTL to create the above:

xml <c:url value="someAction.do" var="url"> <c:param name="id" value="1234" /> <c:param name="userAction" value="prepareEdit" /> </c:url> <a href="${url}">Edit</a>

It is possible to use this approach with JSF, but it is not in the spirit of JSF; the solutions documented above are generally considered better. Nevertheless, this can be implemented as follows:

Use an f:param tag inside of the commandButton or commandLink:

xml <t:dataTable var="emp" .... > <h:commandLink id="editLink" action="#{employeeAction.prepareEdit}"> <h:outputText value="#{msg.edit}"/> <f:param name="id" value="#{emp.id}"/> </h:commandLink>

To then get a handle to this request parameter in your "prepareEdit" method, you could do:

java FacesContext context = FacesContext.getCurrentInstance(); Map map = context.getExternalContext().getRequestParameterMap(); String employeeID = (String) map.get("id");

Note that in this case the parameter is a String, which you'll have to map to the appropriate object id yourself. Plus, you have extra lines of code just to get a handle to the map holding the parameters. Sure you can push that off to a utility class, but the above solutions are cleaner ways to handle this.

{scrollbar}
  • No labels