Building Dynamic RichFaces Components with Seam

There are a couple of different ways to dynamically build RichFaces components. We can implement it inside the view or we can implement it on the server side.

Let’s say we want to build a panelMenu where the user can select a customer and display that customer’s information in a side panel. We can do that directly in the view. See the customers.xhtml below. Notice we have to use some JSTL to generate it because we need the menu item components to be created prior to the JSF component tree getting built.

<!DOCTYPE composition PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
    xmlns:s="http://jboss.com/products/seam/taglib"
    xmlns:a="http://richfaces.org/a4j"
    xmlns:ui="http://java.sun.com/jsf/facelets"
    xmlns:f="http://java.sun.com/jsf/core"
    xmlns:h="http://java.sun.com/jsf/html"
    xmlns:rich="http://richfaces.org/rich"
    xmlns:c="http://java.sun.com/jstl/core">
<style>
    .cols
    {
        vertical-align:top;
    }
</style>
<h:panelGrid columns="2" columnClasses="cols,cols">  
   <rich:panelMenu id="customersPanelMenuId" expandSingle="false"
                          width="200px" iconExpandedGroup="disc" iconCollapsedGroup="disc"
                          iconExpandedTopGroup="chevronUp" iconGroupTopPosition="right"
                          iconCollapsedTopGroup="chevronDown" mode="ajax">
           <rich:panelMenuGroup label="Customers" reRender="customerPanel">
               <c:forEach var="customer" items="#{customersQuery.resultList}">
                    <rich:panelMenuItem label="#{customer.name}" reRender="customerPanel"
                           action="#{customerHome.customerSelected(customer.id)}">
               </c:forEach>
           </rich:panelMenuGroup>
    </rich:panelMenu>
    <a:outputPanel id="customerPanel">
        <s:fragment rendered="#{customerHome.idDefined}" >
            <ui:include src="/customer.xhtml" />
        </s:fragment>
    </a:outputPanel>
</h:panelGrid>
</ui:composition>

Another option is to build the component on the server side. This may be necessary if building the component is complex. For example, if you need to recursively build a tree. Lets say we want to build a panelMenu with a customer’s accounts and sub accounts. So based on the user selecting a customer in the first panelMenu, we want to display that customers accounts and subaccounts in a seperate panelMenu.

Because the conversation is not available in the JSF Restore View phase, we need a work around here. What we need to do is create an event scoped seam component that is responsible for building the menu. So when a customer is selected from the previously built customerPanelMenu, we will ask the accountPanelMenu to be rebuilt. So we set the binding on the accountPanelMenu to our event scoped accountPanelMenu component’s getMenu() method. See the code below.

<rich:panelMenu id="accountPanelMenu"
binding="#{accountPanelMenu.menu}"
width="200px"
expandSingle="false"
        iconExpandedGroup="disc" iconCollapsedGroup="disc"
        iconExpandedTopGroup="chevronUp" iconGroupTopPosition="right"
        iconCollapsedTopGroup="chevronDown"
mode="ajax" />

In our accountPanelMenu component, we need to set the action expression for both the groups and the items. So we dynamically build the EL expression we would like to have invoked.


@Name("accountPanelMenu")
@Scope(ScopeType.EVENT)
public class AccountPanelMenu implements Serializable
{
    private HtmlPanelMenu menu;

    public HtmlPanelMenu getMenu()
    {
        if (menu == null)
        {
            createMenu();
        }
        return menu;
    }

    private void createMenu()
    {
        // Create menu
     menu = new HtmlPanelMenu();
        menu.setId("accountPanelMenu");
    }

    public void buildMenu(List rootAccounts)
    {
     menu.getChildren().clear();
        // For simplicity sake, we are just going to grab one account and create a group and
        // the add a menu item for a subaccount underneath it
        HtmlPanelMenuGroup group = createAccountMenuGroup(rootAccounts.get(0));
        HtmlPanelMenuItem item = createAccountMenuItem(rootAccounts.getChildren().get(0));
        
        // Add item to group
        group.getChildren().add(item);
        
        // Add group to menu
        menu.getChildren().add(group);
    }

    private HtmlPanelMenuGroup createAccountMenuGroup(Account account)
    {
     // Create menu group
        HtmlPanelMenuGroup menuGroup = new HtmlPanelMenuGroup();
        menuGroup.setId(account.getId());
        menuGroup.setLabel(account.getName());
        
        // Add Ajax support so that when the user clicks on a group it loads that account
        // in addition to expanding the group
        HtmlAjaxSupport support = new HtmlAjaxSupport();
        support.setEvent("onclick");
        support.setActionExpression(
         getActionExpression("customerHome.accountSelected(" + account.getId() +")"));
        
        // Add support to menu group
        menuGroup.getChildren().add(support);
        
        return menuGroup;
    }

    private HtmlPanelMenuItem createAccountMenuItem(Account account, HtmlPanelMenuGroup menuGroup)
    {
        // Create menu item
        HtmlPanelMenuItem menuItem = new HtmlPanelMenuItem();
        menuItem.setId(account.getId());
        menuItem.setLabel(account.getName());
        menuItem.setValue(account.getId());
        menuItem.setActionExpression(
         getActionExpression("customerHome.accountSelected(" + account.getId() +")"));
          
        return menuItem;
    }

    public static MethodExpression getActionExpression(String expression)
    {
        ExpressionFactory expressionFactory =  
              FacesContext.getCurrentInstance().getApplication().getExpressionFactory();
    
        ELContext elContext = FacesContext.getCurrentInstance().getELContext();
        
        return getActionExpression(expression, expressionFactory, elContext);
    }
    
    public static MethodExpression getActionExpression(String expression,
            ExpressionFactory   expressionFactory, ELContext elContext)
    {      
        MethodExpression methodExpression =
            expressionFactory.createMethodExpression(
                elContext, "#{" + expression + "}", Object.class, new Class[0]);
        
        return methodExpression;
    }
}

In our CustomerHome class we need to define the customerSelected and accountSelected methods.


    @In(create=true)
    private AccountHome accountHome;

    @In(required=false)
    private AccountTaskPanelMenu accountPanelMenu;

    public void customerSelected(Long customerId)
    {
         this.setCustomerId(customerId);
         accountPanelMenu.buildMenu(getRootAccounts());
         accountHome.clearInstance();
    }

    public void accountSelected(Long accountId)
    {
         accountHome.setAccountId(accountId);
    }

2 thoughts on “Building Dynamic RichFaces Components with Seam