Category Navigation:

Concepts, Techniques, & suggestions


Category navigation is a special instance of a more general design issue:  navigation between view, implemented as a JSP, and a controller, implemented as a Struts Action.  Consequently, the discussion will focus on the specific problem, but point out general issues as they arise.

The problem arises in view-controller interactions in which the view does not include am html form.  Consequently, the implementation does not lend itself to a conventional Struts interaction in which the particular action class is identified by the struts-config defined path, and there is not an associated formbean (Struts ActionForm) available for carrying data back and forth between view and controller/action.

One solution is rather straight-forward.  The linkage between view and controller/action can be accomplished with a simple html link, defined using a conventional link or anchor tag.  The href parameter can identify a particular controller/action by appending a .do suffix to the struts-config designated path.  Additional parameters can also be sent by attaching them as attribute value pairs following the href url and an appended ?.  Similarly, data can be returned from the controller/action object to the JSP view by setting them as attributes of either the request or session objects.  These data can then be read as Objects, cast, and used in the JSPs.

In the sections, below, the steps required to provide simple category navigation and expansion (into either lower-level category child nodes or into associated products for category leaf nodes) will be discussed, and key coding concepts will be illustrated.  The example that will be used is a catalog of products in which each item has been assigned a single category designation. In the first part of the discussion, the steps will be aimed at supporting a view such as the following:


 


Catalog View/Action cycle

In designing and building the catalog, it is important to understand and keep in mind the cycle among view and action objects.  The cycle can be begun at any point, but from a user's point of view starting with the HTML view may be a good choice.

The user selects a link from the navigation panel of the store, such as browse catalog.  That link sends a request, first, to the Struts ActionServlet which, in turn, forwards it in this case to the actionCatalogBrowse object.  There, it is further directed to the segment of code for loadCatalog.  This segment first obtains the root category for the catalog, places that category databean in a collection bean, and generates a category link string that will be used subsequently to browse the catalog.  Both the category link string and the category collection bean are stored as attributes in the Session.  After control is passed to the JSP view, it retrieves these two objects and inserts values derived from them into the view's HTML.  The container then returns this dynamically generated and modified html to the user's browser.  And, the cycle begins again.

 


The discussion will proceed by going into additional detail for each of the major steps in this cycle.
 


Browse the Catalog

The Browsing function (use case) can be initiated using a simple link, such as the following:

<a href='/contextName/actionCatalogBrowse.do?userAction=loadCatalog'>Browse Catalog</a> 

(Note:  the link has been simplified to emphasize view/controller interaction)  Instead of addressing a view JSP, the link addresses the controller object, using the struts-config defined path and appending the .do suffix to it, signaling the Struts control servlet that it should forward the request to the appropriate action object.  A parameter, userAction, is appended to the url along with its value, loadCatalog

The loadCatalog function within the actionCatalogBrowse controller/action will be activated and any user parameters made available to it.
 


Model & DataBase

The discussion now shifts to the model and database that provide the category and product data to the action/controller.

The database includes tables that provide persistence for two objects:  products and categories.  The product table includes an autogenerated Integer key, name, description, price, availability, etc.;  it also includes a foreign key identifying its assigned category.  The category table includes a key, name, and a foreign key to itself identifying the parent of a given category entry.  the root of the category hierarchy is marked by a special key value.

Access to category and product information is provided by model objects.  The models support the five cruds methods (create/add, retrieve, update, delete, and search), as is customary for dbms applications.  In addition, the category model supports the following extra-cruds methods: 

All can easily be implemented using cruds methods, the special root key marker, and one non-cruds method calling another (e.g., getPathToRoot can use getParent).
 


Build and Store the Navigation Links String

The loadCatalog function retrieves the root of the category tree from the database using the model's getRoot method, described above.  It then uses those data to build the category links string that is used for navigation.  In doing so, it builds two objects that are (potentially) retained over multiple user interactions.  The first is the links string, itself;  in this example it is named categoryPathLinks.  The second is a collection bean, named collectionBeanCategoryPathLinks; it includes the databean for the category root, and to it will be added additional category databeans as the path is expanded.  (In the image shown above, it would include two databeans:  catalog and tools).  The categoryPathLinks is, of course, constructed from the collectionBean.

These two objects -- categoryPathLinks and collectionBeanCategoryPathLinks -- are stored in the session object.  Thus, they may be used for processing subsequent requests (such as expanding the category links string), and they can be used within the JSP views to generate the actual display.

Building the actual display string -- a composite of perhaps multiple links -- is made much easier by using several utility methods.  To illustrate the point, two such methods are included here.  The first generates a single link; the second generates a complete links string that includes (perhaps) multiple such links.

public static String getCategoryLink(String actionPath, String userAction, BeanCategory dataBean) {
		
	String categoryId = dataBean.getCategoryId();
	String categoryName = dataBean.getName();
		
	//  test and prune leading /
	String actionPathNew;
	if ( 	(actionPath.substring(0,1)).equals("/") || 
		(actionPath.substring(0,1)).equals("\\") )
		actionPathNew = actionPath.substring(1);
	else 
		actionPathNew = actionPath;
		
	String link	 = "";
		
	link += "<a href='";
	link += "/" + actionPathNew;
	link += "?userAction=" + userAction + "&categoryId=" + categoryId + "'>";
	link += "<b>" + categoryName + "</b>";
	link += "</a>";
		
	return link;

	}
	

The code is straight-forward except, perhaps, for the step of stripping any leading slash and replacing it with a designated form of the symbol.

Whereas this method generates the string for a single link, the navigation string can contain multiple such links, interspersed by some separator symbol.  The second method generates a string that includes multiple such links.  Of course, it relies on the first method for support.

public static String getCategoryPathLinksString(CollectionBean collectionBean) {
		
	BeanCategory dataBean;
	String categoryString = "";
	int size = collectionBean.size();
		
	//  empty category list
	if (size == 0 )
		return categoryString;
			
	collectionBean.resetIndex();

	//  start the hierarchy string -- to handle > symbols in the string
	dataBean = (BeanCategory) collectionBean.getBeanFirst();
	categoryString = getCategoryLink("/" + Context.getProjectName() + 
		"/actionCatalogBrowse.do", "expandCategory", dataBean);
		
	for( int i = 1; i < size; i++ )  {

		dataBean = (BeanCategory) collectionBean.getBeanNext();
		String categoryLink = getCategoryLink("/" + Context.getProjectName() + 
			"/actionCatalogBrowse.do", "expandCategory", dataBean);
			
		categoryString += Constant.CATEGORYPATHSEPARATOR + categoryLink; 
	}
			
	return categoryString;

	}
	

Once the category navigation links string has been built and stored in the session object, it can be retrieved and included in the JSP view.
 


Display the Navigation Links string

The category navigations links string is made available to the JSP view through the session object.  It's a simple matter to obtain the string and display it, as shown in the following code fragments:

<%

//  handle categoryPathLinks
String categoryPathLinks = "";
Object categoryPathLinksObject = session.getAttribute("categoryPathLinks");
if (categoryPathLinksObject != null) 
	categoryPathLinks = (String) categoryPathLinksObject; 
 
%>	
This JSP code assumes that a controller has created and stored in the session object the navigation links string to be displayed.  Consequently, the JSP simply obtains the string, as shown above, and displays it within the body of the html, as shown below.
<h4>
<%= categoryPathLinks %>
</h4>	


Expand the Navigation Links String

The discussion so far has described the process of building a Navigation Links string that includes a single link -- the catalog.   The whole point of such a string is to allow a user to click on one of the elements of the string and have it display the subcategories under that link, if the category is an interior node, or the products so classified, if the category is a leaf node.  The parameters included in these links is this example are expandCategory and the categroyId for the selected category. 

When the controller receives such a request, it is processed by the expandCategory function, as would be expected.  There, several operations may occur.

First, it retrieves the databean for the selected category form the database and the collection bean used previously to generate the navigation links string from the session.  It then tests that collection of category bean to see if the selected category is an interior node of the path;  if it is, it prunes the path in the collection bean, rebuilds the navigation string, and stores both in the session, the latter to be used for subsequent display.

It then retrieves either the category children of the selected category, if it is an interior node, or the products so categorized, if it is a leaf node.  Those objects are added to an appropriate collection bean and stored in the session for subsequent display in the JSP view.  The core code for expanding either the subcategories or products for a selected category is shown, below.

//  update  
//	category list for interior category node) 
//	product list for leave category node
			
//  get children, if any
CollectionBean collectionBeanCategoryChildren = modelCategoryAdmin.getChildren( dataBeanNew, role );
				
//  test not leaf node
int size = collectionBeanCategoryChildren.size();
if ( size > 0 )  {
	request.setAttribute("collectionBeanCategoryChildren", collectionBeanCategoryChildren);					
	session.removeAttribute("collectionBeanProducts");				
				
//  test leaf node
}  else  {
	//  get products for leaf category
	BeanProduct dataBeanProduct = new BeanProduct();
	dataBeanProduct.setCategoryId( categoryId );
	CollectionBean collectionBeanProducts = modelProductAdmin.search( dataBeanProduct, role );
	session.setAttribute( "collectionBeanProducts", collectionBeanProducts );				

}
	

The JSP view will then obtain one or the other of these collections and display the individual objects as items in a list or table.
 


Display Expanded set of Categories or products

The (same) JSP view can also display multiple category or product items, obtained from the session or request objects as one of two types of collection beans.  In this example, the expansion shown is for categories; similar logic, although perhaps including more extensive html, can be used to show multiple products, often in the form of a table or multiple tables. 

In the first code example, the category data are obtained from the request.  Note the way Java import statements are specified in JSP directives.

<%@ page import="jbs.eb2c.databean.*, jbs.eb2c.util.*" %>
<%! String projectName = jbs.eb2c.util.Context.getProjectName(); %>
<%

String categoryName = "";
String categoryLink = "";
String actionPath = "/" + projectName + "/actionCatalogBrowse.do";

//  handle category set
CollectionBean collectionBeanCategoryChildren = null;
int sizeCollectionBeanCategoryChildren = 0;
Object collectionBeanCategoryChildrenObject = request.getAttribute("collectionBeanCategoryChildren");
if (collectionBeanCategoryChildrenObject != null) {
	collectionBeanCategoryChildren = (CollectionBean) collectionBeanCategoryChildrenObject;
	sizeCollectionBeanCategoryChildren = collectionBeanCategoryChildren.size();
	}
 
%>	
In the second code example, the data is used to generate a list of (selectable) categories that may be added to the navigation string. In the image shown above, this would be the list of hammers, saws, screwdrivers.  Note in particular the interspersed Java and html code; for emphasis, the Java code is shown in bold:
<%
if ( sizeCollectionBeanCategoryChildren > 0 ) {%>
	<blockquote>
	<h4>Select Category</h4>
	<%for ( int i=0; i<sizeCollectionBeanCategoryChildren; i++ )  {
		BeanCategory dataBean = (BeanCategory)(collectionBeanCategoryChildren.getBeanNext());
		categoryLink = Util.getCategoryLink( actionPath, "addCategoryToPath", dataBean);%>
		<b><%=categoryLink%></b>
		<br/>
	<%}%>
	</blockquote>
<%}%>	

A list of products can be displayed in exactly the same manner, although, as observed above, the html may be more elaborate, especially if tables are used to format individual product listings.
 


Display Product Details

Once a list of products for a given category is displayed, an individual product may be selected and a different view shown that includes additional details for the product.  This view may include a basic Struts Form for the product data, per se, but it may also include context information and additional navigation links, such as continue shopping.   If the view is implemented using a form, then one would probably pass data to it using a conventional Struts formbean (ActionForm).  For context and/or navigation links, one might use the session- or request-passing approach, described above.