Handling Dynamic Lists and Tables

Using JavaServer Faces

Building JSPs and their associated ManagedBeans that can handle dynamic lists and tables is a bit of a challenge.  The task is simplified by several tags included as part of Tomahawk along with the support classes they require.  Useful tags include <t:dataList> and <t:dataTable>.

Several examples are shown below. 

The first displays a dynamic list of links to support browsing over a hierarch of category names, shown as an expanding and contracting row of links:

Catalog > Tools

The second is a table, with only one column, that shows the possible subcategories for the last category in the list, shown above.  Clicking on one of those links extends the row of links, above.  the same basic logic can be used to build a multi-column table, such as one suitable for a table of products along with pictures of them, by using a faces grid.

Hammers
Saws
Screwdrivers

Two other examples show a table of products that might be found in an online catalog and an expansion of one such product in which additional detail is shown.

For each type of data, the discussion includes core code from the JSP and from the associated ManagedBean.

1. Define the JSP for a dynamic expanding list, to be shown horizontally:

<%@ taglib prefix="t"  uri="http://myfaces.apache.org/tomahawk"%>
.
.
.
<h:form id="categoryPathForm">
<h4>					

<t:dataList id="categoryPathList"
	value="#{managedBeanCatalogBrowse.categoryPathList}" var="beanCategory"
	binding="#{managedBeanCatalogBrowse.categoryDataList}"
	layout="simple" 
	rowCountVar = "count"
	rowIndexVar = "index" >
	<h:column>
	<h:commandLink action="#{managedBeanCatalogBrowse.actionExpandCategory}">
	<h:outputText value="#{beanCategory.name}"/>
	<f:setPropertyActionListener target="#{managedBeanCatalogBrowse.categoryId}" 
		value="#{beanCategory.categoryId}" />
	</h:commandLink>
	<h:outputText value=" > " rendered="#{index+1 < count}" />
	</h:column>	
</t:dataList>

</h4>
</h:form>

		

2. Define the ManagedBean for the JSP  In this example, the list is generated in the methods actionLoadCatalog and actionExpandCatalog.

import org.apache.myfaces.custom.datalist.HtmlDataList;
import org.apache.myfaces.component.html.ext.HtmlDataTable;
.
.
.
//	category path support
	private List categoryPathList = null;
	private HtmlDataList categoryDataList;
	private String categoryId = null;
	
//	product list support
	private List productList = null;
	private HtmlDataTable productTable;
	private String productId = null;
.
.
.
	public String actionLoadCatalog()  {

		//  get admin user's  role
		String roleAdmin = (String) session.getAttribute("role");

		// instantiate models
		ModelCategoryAdmin modelCategoryAdmin = new ModelCategoryAdmin();

		//  reset lists support
		resetCategoryPathList();
		resetCategoryChildrenList();
		resetProductList();
		resetProductExpandList();
		
		//  call model
		BeanCategory dataBeanNew = modelCategoryAdmin.getRoot( roleAdmin );
	
		if ( !(dataBeanNew.isOk()) )  {
					
			//  need to add error message
				
			return "browseCatalogAgain";
								
		}  else  {
				
			//get and pass category path list
			CollectionBean collectionBeanCategoryPathLinks = new CollectionBean();
			collectionBeanCategoryPathLinks.add(dataBeanNew);
			setCategoryPathList( collectionBeanCategoryPathLinks.getDataBeans() );
			session.setAttribute( "collectionBeanCategoryPathLinks", collectionBeanCategoryPathLinks );
				
			return "browseCatalogAgain";
			
		}	

	}

	public String actionExpandCategory()  {

		//  get admin user's  role
		String roleAdmin = (String) session.getAttribute("role");

		// instantiate models
		ModelCategoryAdmin modelCategoryAdmin = new ModelCategoryAdmin();
		ModelProductAdmin modelProductAdmin = new ModelProductAdmin();

		//  reset lists support
		resetProductList();
		resetProductExpandList();

		BeanCategory dataBeanFind = new BeanCategory();
		dataBeanFind.setCategoryId( categoryId );
		
		BeanCategory dataBeanNew = modelCategoryAdmin.find( dataBeanFind, roleAdmin );
			
		//  test failure
		if ( ! dataBeanNew.isOk() )  {
			return "browseCatalogAgain";
		}
			
		//  update the category navigation resources, as necessary
		
		//  test for interior category, prune if appropriate
		CollectionBean collectionBeanCategoryPathLinks = (CollectionBean) session.getAttribute("collectionBeanCategoryPathLinks");
		BeanCategory beanCategoryLast = (BeanCategory) collectionBeanCategoryPathLinks.getBeanLast();
		if ( ! ((dataBeanNew.getCategoryId()).equals(beanCategoryLast.getCategoryId()) ) )  {
			for (int i = 0; i < collectionBeanCategoryPathLinks.size(); i++ )  {
				beanCategoryLast = (BeanCategory) collectionBeanCategoryPathLinks.getBeanLast();
				if ( ! ((dataBeanNew.getCategoryId()).equals(beanCategoryLast.getCategoryId()) ) )
					collectionBeanCategoryPathLinks.remove(beanCategoryLast);
				else
					break;
							
			}
		}
		//  save (possibly) updated category path links collectionBean and 
		session.setAttribute( "collectionBeanCategoryPathLinks", collectionBeanCategoryPathLinks );
		setCategoryPathList( collectionBeanCategoryPathLinks.getDataBeans() );
		
		//  get children, if any
		CollectionBean collectionBeanCategoryChildren = modelCategoryAdmin.getChildren( dataBeanNew, roleAdmin );
			
		//  test not leaf node ==> get categories
		int size = collectionBeanCategoryChildren.size();
		if ( size > 0 )  {
			session.setAttribute("collectionBeanCategoryChildren", collectionBeanCategoryChildren);					
			setCategoryChildrenList( collectionBeanCategoryChildren.getDataBeans() );
			session.removeAttribute("collectionBeanProducts");				
			
		//  else leaf node ==> get products
		}  else  {
			//  get products for leaf category
			BeanProduct dataBeanProduct = new BeanProduct();
			dataBeanProduct.setCategoryId( categoryId );
			CollectionBean collectionBeanProducts = modelProductAdmin.search( dataBeanProduct, roleAdmin );
			
			//  add image url (for small image)
			for ( int i=0; i<collectionBeanProducts.size(); i++ )  {
				BeanProduct bean = (BeanProduct) collectionBeanProducts.getBeanNext();
				int productId = bean.getProductId();
				String imageType = bean.getImageTypeSmall();
				String imageFileSmallUrl = Util.getImageUrl( productId, imageType, Constant.IMAGESMALL );
				bean.setImageFileSmallUrl( imageFileSmallUrl );
			}

			session.setAttribute( "collectionBeanProducts", collectionBeanProducts );				
			setProductList( collectionBeanProducts.getDataBeans() );

		}
			
		return "browseCatalogAgain";

	}
	
	

3. Define the JSP for a dynamic expanding table:

<%@ taglib prefix="t"  uri="http://myfaces.apache.org/tomahawk"%>
.
.
.
<h:form id="categoryChildrenForm">
<blockquote>
<h4>					
 
<t:dataTable id="categoryChildrenTable"
	value="#{managedBeanCatalogBrowse.categoryChildrenList}" var="beanChild"
	binding="#{managedBeanCatalogBrowse.categoryChildrenTable}"
	renderedIfEmpty="false" >
	<h:column>
	<f:facet name="header">
    	<b><h:outputText value="Select Category"  /></b>
	</f:facet>
	<h:commandLink action="#{managedBeanCatalogBrowse.actionAddCategorytoPath}">
	<h:outputText value="#{beanChild.name}"/>
	<f:setPropertyActionListener target="#{managedBeanCatalogBrowse.categoryId}" 
		value="#{beanChild.categoryId}" />
	</h:commandLink>
	</h:column>	
</t:dataTable>

</h4>
</blockquote>
</h:form>

		

4.  Define the ManagedBean for the JSP  In this example, the list is generated in the methods addCategoryToPath.

import org.apache.myfaces.custom.datalist.HtmlDataList;
import org.apache.myfaces.component.html.ext.HtmlDataTable;
.
.
.
//	category children support
	private List categoryChildrenList = null;
	private HtmlDataTable categoryChildrenTable;
	private String categoryChildId = null;
.
.
.
	public String actionAddCategorytoPath()  {

		//  get admin user's  role
		String roleAdmin = (String) session.getAttribute("role");

		// instantiate models
		ModelCategoryAdmin modelCategoryAdmin = new ModelCategoryAdmin();

		//  reset lists support
		resetProductList();
		resetProductExpandList();

		BeanCategory dataBeanFind = new BeanCategory();
		dataBeanFind.setCategoryId( categoryId );
		BeanCategory dataBeanNew = modelCategoryAdmin.find( dataBeanFind, roleAdmin );
			
		//  test failure
		if ( ! dataBeanNew.isOk() )  {
			return "browseCatalogAgain";
		}

		//  rebuild category collectionBean and links string
		CollectionBean collectionBeanCategoryPathLinks = (CollectionBean) session.getAttribute( "collectionBeanCategoryPathLinks" );
		collectionBeanCategoryPathLinks.add( dataBeanNew );
		//String categoryPathLinks = Util.getCategoryPathLinksString(collectionBeanCategoryPathLinks);
		session.setAttribute( "collectionBeanCategoryPathLinks", collectionBeanCategoryPathLinks );
		//session.setAttribute( "categoryPathLinks", categoryPathLinks );
		
		//  clear categoryChildrenList and productList
		categoryChildrenList.clear();
		if ( ! (productList == null) ) 
			productList.clear();
			
		return "browseCatalogAgain";

	}
		
Building more complex tables, such as those with multiple columns, will require additional tags or tag attributes.  See, especially,  <h:panelGrid>, <h:column>, and <f:facet>

5. Define the JSP for a dynamic expanding table with multiple columns and URLs;  in this example, the table is supported by the product support clause in 2, above:

<h:form id="browseProductsForm">
<blockquote>
				
<t:dataTable id="productTable"
	value="#{managedBeanCatalogBrowse.productList}" var="beanProduct"
	binding="#{managedBeanCatalogBrowse.productTable}"
	renderedIfEmpty="false" >
	
	<h:column>	
	<f:facet name="header">
    	<h3><h:outputText value="Browse Products"  /></h3>
	</f:facet>
	
	
	<h:panelGrid columns="2" width="60%"  >
	
	<!--  left column    -->
	<h:panelGrid columns="1" width="150"  >
	<t:graphicImage url="#{beanProduct.imageFileSmallUrl}" />
	</h:panelGrid>
	
	<!--  right column    -->
	<h:panelGrid columns="1" width="300" columnClasses="COLUMNLABEL" >
	<h:outputLabel value="#{beanProduct.name}" />
	<h:outputText value="#{beanProduct.descriptionShort}"/>
	<h:outputText value="Price: #{beanProduct.priceAsString}"/>
	<h:outputText value="Available: #{beanProduct.availableAsString}"/>
	<h:commandLink action="#{managedBeanCatalogBrowse.actionExpandProduct}" >
	<h:outputText value="See Product Details"/>
	<f:setPropertyActionListener target="#{managedBeanCatalogBrowse.productId}"
		value="#{beanProduct.productId}" />
	</h:commandLink>
	</h:panelGrid>
	
	</h:panelGrid>
	
	<!--  separator    -->
	<h:panelGrid columns="1" width="450">
	<hr/>
	</h:panelGrid>
	
	
</h:column>

</t:dataTable>


</blockquote>
</h:form>

	

6. Define the ManagedBean for the JSP to display expanded product information.  In this example, the list is generated in the methods actionExpandProduct

public String actionExpandProduct()  {

	//	product expand support
	private List productExpandList = null;
	private HtmlDataTable productExpandTable;
	private String quantityAsString = "1";
	.
	.
	.
	//  get admin user's  role
	String roleAdmin = (String) session.getAttribute("role");

	// instantiate models
	ModelProductAdmin modelProductAdmin = new ModelProductAdmin();

	//  get the full product info
	BeanProduct dataBeanFind = new BeanProduct();
	dataBeanFind.setProductId( productId );
	BeanProduct dataBeanNew = modelProductAdmin.find( dataBeanFind, roleAdmin );
			
	//  test failure
	if ( ! dataBeanNew.isOk() )  {
			
		return "browseCatalogAgain";
			
	}  else  {
			
		CollectionBean collectionBeanProductExpand = new CollectionBean();
			
		//  update product bean
		int productId = dataBeanNew.getProductId();
		String imageType = dataBeanNew.getImageType();
		dataBeanNew.setImageFileUrl( Util.getImageUrl(productId, imageType, Constant.IMAGELARGE) );
			
		collectionBeanProductExpand.add( dataBeanNew );
			
		session.setAttribute( "collectionBeanProductExpand", collectionBeanProductExpand );				
		setProductExpandList( collectionBeanProductExpand.getDataBeans() );

		return "browseProductExpand";

	}
				
}

7. To display expanded product information, as generated in 6, above, you will most likely need a new JSP.  It may include some of the display components shown above, such as category context; the key dynamic table for expanded product information is shown, below.
<t:dataTable id="productTable"
	value="#{managedBeanCatalogBrowse.productExpandList}" var="beanProduct"
	binding="#{managedBeanCatalogBrowse.productExpandTable}"
	renderedIfEmpty="false" >
	
	<h:column>	
	
	<h:panelGrid columns="2" width="60%"  >
	
	<!--  left column    -->
	<h:panelGrid columns="1" width="150"  >
	<t:graphicImage url="#{beanProduct.imageFileUrl}" />
	</h:panelGrid>
	
	<!--  right column    -->
	<h:panelGrid columns="1" width="300" columnClasses="COLUMNLABEL" >
    	<h:outputText value="#{beanProduct.name}" />
	<h:outputText value="#{beanProduct.description}"/>
	<h:outputText value="Price: #{beanProduct.priceAsString}"/>
	<h:outputText value="Available: #{beanProduct.availableAsString}"/>
	<h:panelGrid columns="2" width="300" columnClasses="COLUMNLABEL" >
    	<h:outputText value="Quantity: "  />
    	<h:inputText id="quantityAsString" value="#{managedBeanCatalogBrowse.quantityAsString}" />
	</h:panelGrid>
	<h:commandButton action="#{managedBeanCatalogBrowse.actionAddToCart}"
		 value="Add To Cart" style="background-color: rgb(163, 200, 250)" >
	<f:setPropertyActionListener target="#{managedBeanCatalogBrowse.productId}"
		value="#{beanProduct.productId}" />
	</h:commandButton>
    	<h:outputText value="#{managedBeanCatalogBrowse.addToCartMessage}"  />
	<h:commandLink action="#{managedBeanCatalogBrowse.actionContinueShopping}">
	<h:outputText value="Continue Shopping"/>
	</h:commandLink>
	
	</h:panelGrid>
	
	</h:panelGrid>
		
	</h:column>

</t:dataTable>