Dynamic Menu Tutorial

Table of Contents

Overview

This tutorial walks the reader through the creation of a simple to-do list application, where each to-do item can be in one of several states (new, in progress, stopped, and done). Different menu options are presented to the user, depending on the state of the item, thus making the menu component truly dynamic.

The application itself is composed primarily of one ace:dataTable component and one ace:menu component. The data table is used to display the to-do items and their properties and also to allow sorting and selection of the items. The menu component will present different actions for the item that is currently selected in the data table.

This tutorial assumes that the reader is familiar with JSF, ICEfaces, and how to set up a basic Java web application.

Setup

The application only consists of one bean, one data object (i.e. POJO) and one xhtml page. In this tutorial the bean is named TodoListBean and the data object class is simply named Item and is an inner class of the bean. The xhtml page is named index.xhtml and is the welcome page of the application.

This simple to-do list application can be a stand-alone application or it can be integrated to an existing application. This tutorial, however, creates a stand-alone application. So, to get started, create the following Java class and the following xhtml page:

package org.icefaces.tutorial;

import org.icefaces.ace.component.ajax.AjaxBehavior;
import org.icefaces.ace.component.menuitem.MenuItem;
import org.icefaces.ace.component.submenu.Submenu;
import org.icefaces.ace.event.SelectEvent;
import org.icefaces.ace.event.UnselectEvent;
import org.icefaces.ace.model.DefaultMenuModel;
import org.icefaces.ace.model.MenuModel;

import javax.el.ELContext;
import javax.el.ExpressionFactory;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.SessionScoped;
import javax.faces.context.FacesContext;
import javax.faces.event.ActionEvent;
import javax.faces.event.AjaxBehaviorEvent;
import javax.faces.event.MethodExpressionActionListener;

import java.io.Serializable;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

@ManagedBean(name="todoListBean")
@SessionScoped
public class TodoListBean implements Serializable {

	private List<Item> items;
	
	public TodoListBean() {
		items = new ArrayList<Item>();
		items.add(new Item("Sample task A"));
		items.add(new Item("Sample task B"));
		items.add(new Item("Sample task C"));
		items.add(new Item("Sample task D"));
	}

	public List<Item> getItems() {
		return items;
	}

	public void setItems(List<Item> items) {
		this.items = items;
	}
	
	public static class Item implements Serializable {
		
		private static SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
		
		private String description;
		private String status;
		private Date created;
		private Date finished;

		public Item(String description) {
			this.description = description;
			this.status = "New";
			this.created = new Date();
			this.finished = null;
		}

		public String getDescription() {
			return description;
		}

		public void setDescription(String description) {
			this.description = description;
		}
		
		public String getStatus() {
			return status;
		}

		public void setStatus(String status) {
			this.status = status;
		}
		
		public String getCreated() {
			return dateFormat.format(created);
		}
		
		public String getFinished() {
			if (finished == null) return "";
			return dateFormat.format(finished);
		}
	}
}
<?xml version='1.0' encoding='UTF-8' ?>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
	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:icecore="http://www.icefaces.org/icefaces/core"
	xmlns:ace="http://www.icefaces.org/icefaces/components"
	xmlns:ice="http://www.icesoft.com/icefaces/component">
	<f:view contentType="text/html">
	<h:head>
		<title>Dynamic Menu Tutorial</title>
	</h:head>
	<h:body>
		<h:form>
			<ace:dataTable value="#{todoListBean.items}" var="item" selectionMode="single">
				<ace:column headerText="Description" sortBy="#{item.description}">
					<h:outputText value="#{item.description}"/>
				</ace:column>
				<ace:column headerText="Status" sortBy="#{item.status}">
					<h:outputText value="#{item.status}"/>
				</ace:column>
				<ace:column headerText="Created" sortBy="#{item.created}">
					<h:outputText value="#{item.created}"/>
				</ace:column>
				<ace:column headerText="Finished" sortBy="#{item.finished}">
					<h:outputText value="#{item.finished}"/>
				</ace:column>
			</ace:dataTable>
		</h:form>
	</h:body>
	</f:view>
</html>

This is a good skeleton to start coding our application. This initial version should be able to compile and the page should display a table with some sample tasks we added in the bean's constructor. This initial version of the bean already contains all the classes we need to import, so we won't have to worry about them for the rest of the tutorial. We also have the final version of the data object that we'll be using to represent our to-do items. So, for the remainder of the tutorial we will concentrate on adding functionality to our list.

Development

Now that we have the structure of our application, we will be adding some features and behavior to our application

Making the bean aware of the currently selected item

The ace:dataTable component supports row selection, and in the structure we have at this point we can already select rows on the page. However, we need our bean to be aware of which item is currently selected so we can act on it. We will use the rowSelectListener attribute of our data table to bind a method to it. Add the following attribute to the ace:dataTable tag in the xhtml file:

rowSelectListener="#{todoListBean.rowSelectListener}"

Now, add the following lines to the bean (it's not necessary that they appear together):

	private Item selectedItem = null;

	public void rowSelectListener(SelectEvent event) {
		selectedItem = (Item) event.getObject();
	}

The SelectEvent object has a reference to the data object that was selected. Now, we will be able to know in our bean which data object is currently selected by checking the selectedItem property.

We will also add an ajax event to force the entire form to be evaluated when the user selects a row. Thus, we ensure that all parts of our app reflect the current state of our to-do list.

<ace:ajax event="select" render="@form" execute="@form"/>

At the same time, it is important that the bean is aware if there is no to-do item currently selected or if the item that was selected has just been deselected. For this, we will add an ajax behavior to our data table. Add the following tag as a direct child of the data table:

<ace:ajax event="deselect" render="@form" execute="@form" listener="#{todoListBean.rowDeselectListener}" />

Now, add the following method to the bean:

	public void rowDeselectListener(AjaxBehaviorEvent event) {
		selectedItem = null;
	}

Now that the bean is fully aware if an item is currently selected and which item it is, we will now take advantage of this functionality to display different options for each item.

Creating a menu from a model

In order to have a dynamic menu, we have to programmatically build the menu in the bean, instead of hard-coding the markup in our xhtml page. So, first, we will illustrate how to build a menu in the bean.

In the xhtml page, simply add the following tag somwhere in the body:

<ace:menu type="plain" model="#{todoListBean.menu}"/>

This binds the menu component to a menu model that we will construct in the bean. Now add the getter for the menu model in the bean:

public MenuModel getMenu() {

		MenuModel defaultMenu = new DefaultMenuModel();
		
		Submenu defaultSubmenu = new Submenu();
		defaultSubmenu.setLabel("Menu");
		defaultMenu.addSubmenu(defaultSubmenu);

		return defaultMenu;
}

Basically, an instance of DefaultMenuModel has to be created, and then the submenus and menu items are added to it. To create submenus and menu items we create instances of the actual component classes Submenu and MenuItem. Then, we simply use the setter methods of the component to specify all the attributes.

As we will see later, it is not necessary to actually have a 'menu' instance variable in the bean; we just need to provide a getter method for now. You can try and run the application at this point to see that a very basic menu is displayed on the page.

Making the menu dynamic

This is the core of the tutorial. Now that we have a menu created programmatically, we can have a dynamic menu by adding some logic in our bean. Our strategy will be the following: we will create 5 different menus, one for each state (new, in progress, stopped, done, and default), and we will return the corresponding menu in getMenu(), depending on which item is currently selected (if any) and in which state it is.

First of all, replace the contents of getMenu() for the following:

    public MenuModel getMenu() {
		
		if (selectedItem == null) {
			return defaultMenu;
		} else if ("New".equals(selectedItem.getStatus())) {
			return newMenu;
		} else if ("In Progress".equals(selectedItem.getStatus())) {
			return inProgressMenu;
		} else if ("Stopped".equals(selectedItem.getStatus())) {
			return stoppedMenu;
		} else if ("Done".equals(selectedItem.getStatus())) {
			return doneMenu;
		}
		
        return defaultMenu;
    }

This is the main logic for having a dynamic menu. Now let's create the menu models that we are referencing in this method. First, we will add the instance variables for these models:

	private MenuModel defaultMenu;
	private MenuModel newMenu;
	private MenuModel inProgressMenu;
	private MenuModel stoppedMenu;
	private MenuModel doneMenu;

Now, we will add the following method, which will construct all of our menus:

	private void constructMenus() {
	
		ELContext elContext = FacesContext.getCurrentInstance().getELContext();
		ExpressionFactory expressionFactory = FacesContext.getCurrentInstance().getApplication().getExpressionFactory();
		
		// default menu
		defaultMenu = new DefaultMenuModel();
		
		Submenu defaultSubmenu = new Submenu();
		defaultSubmenu.setLabel("Menu");
		defaultMenu.addSubmenu(defaultSubmenu);
		
		// 'New' menu
		newMenu = new DefaultMenuModel();
		
		Submenu newSubmenu = new Submenu();
		newSubmenu.setLabel("Menu");
		newMenu.addSubmenu(newSubmenu);
		
		MenuItem newStart = new MenuItem();
		newStart.setId("start");			
		newStart.setValue("Start");
		newStart.addActionListener(new MethodExpressionActionListener(expressionFactory.createMethodExpression(elContext, 
			"#{todoListBean.start}", null, new Class[] { ActionEvent.class })));
		addAjaxBehaviorTo(newStart);
		newSubmenu.getChildren().add(newStart);
		
		MenuItem newDone = new MenuItem();
		newDone.setId("new-done");			
		newDone.setValue("Done");
		newDone.addActionListener(new MethodExpressionActionListener(expressionFactory.createMethodExpression(elContext, 
			"#{todoListBean.done}", null, new Class[] { ActionEvent.class })));
		addAjaxBehaviorTo(newDone);
		newSubmenu.getChildren().add(newDone);	
		
		// 'In Progress' menu
		inProgressMenu = new DefaultMenuModel();
		
		Submenu inProgressSubmenu = new Submenu();
		inProgressSubmenu.setLabel("Menu");
		inProgressMenu.addSubmenu(inProgressSubmenu);
		
		MenuItem inProgressStop = new MenuItem();
		inProgressStop.setId("stop");			
		inProgressStop.setValue("Stop");
		inProgressStop.addActionListener(new MethodExpressionActionListener(expressionFactory.createMethodExpression(elContext, 
			"#{todoListBean.stop}", null, new Class[] { ActionEvent.class })));
		addAjaxBehaviorTo(inProgressStop);
		inProgressSubmenu.getChildren().add(inProgressStop);
		
		MenuItem inProgressDone = new MenuItem();
		inProgressDone.setId("inprogress-done");			
		inProgressDone.setValue("Done");
		inProgressDone.addActionListener(new MethodExpressionActionListener(expressionFactory.createMethodExpression(elContext, 
			"#{todoListBean.done}", null, new Class[] { ActionEvent.class })));
		addAjaxBehaviorTo(inProgressDone);
		inProgressSubmenu.getChildren().add(inProgressDone);
		
		// 'Stopped' menu
		stoppedMenu = new DefaultMenuModel();
		
		Submenu stoppedSubmenu = new Submenu();
		stoppedSubmenu.setLabel("Menu");
		stoppedMenu.addSubmenu(stoppedSubmenu);
		
		MenuItem stoppedRestart = new MenuItem();
		stoppedRestart.setId("restart");			
		stoppedRestart.setValue("Restart");
		stoppedRestart.addActionListener(new MethodExpressionActionListener(expressionFactory.createMethodExpression(elContext, 
			"#{todoListBean.start}", null, new Class[] { ActionEvent.class })));
		addAjaxBehaviorTo(stoppedRestart);
		stoppedSubmenu.getChildren().add(stoppedRestart);
		
		MenuItem stoppedDone = new MenuItem();
		stoppedDone.setId("stopped-done");			
		stoppedDone.setValue("Done");
		stoppedDone.addActionListener(new MethodExpressionActionListener(expressionFactory.createMethodExpression(elContext, 
			"#{todoListBean.done}", null, new Class[] { ActionEvent.class })));
		addAjaxBehaviorTo(stoppedDone);
		stoppedSubmenu.getChildren().add(stoppedDone);
		
		// 'Done' menu
		doneMenu = new DefaultMenuModel();
		
		Submenu doneSubmenu = new Submenu();
		doneSubmenu.setLabel("Menu");
		doneMenu.addSubmenu(doneSubmenu);
		
		MenuItem doneRemove = new MenuItem();
		doneRemove.setId("remove");			
		doneRemove.setValue("Remove");
		doneRemove.addActionListener(new MethodExpressionActionListener(expressionFactory.createMethodExpression(elContext, 
			"#{todoListBean.remove}", null, new Class[] { ActionEvent.class })));
		addAjaxBehaviorTo(doneRemove);
		doneSubmenu.getChildren().add(doneRemove);
	}

As we mentioned before, submenus and menu items are actual component instances; we just set their attributes via their setter methods. It is a good practice to set an id to each menu item to make sure they work correctly in any scenario, since menu items have a higher level of complexity than other components.

Our ace:menuItem components extend from UICommand, so they can have actionListener's bound to them. Binding a method from an xhtml page is straight-forward, but doing so from a bean needs some more coding. Pay attention to the examples above on how to add an action listener to a UICommand component, so that you can add your own action listeners to the menu items in this or other applications.

Let's not forget to call the method above from our constructor so that the menus are ready from the start. Now, our constructor would look like this:

	public TodoListBean() {
		items = new ArrayList<Item>();
		items.add(new Item("Sample task A"));
		items.add(new Item("Sample task B"));
		items.add(new Item("Sample task C"));
		items.add(new Item("Sample task D"));
		constructMenus();
	}

Basically, what all the code above means is that items which are New can be started ("in progress") or marked as done; items In Progress can be stopped or marked as done; items Stopped can be restarted or marked as done; and items Done can be removed from the list.

Alright, we have our menus now, but at this point the app is not ready to run. If you noticed, we are referencing some methods that haven't been defined yet. We already have the menus, but we have to actually code the operations they trigger.

Coding the operations

Now, let's actually code the operations described above. Add the following methods to the bean:

	public void start(ActionEvent event) {
		selectedItem.setStatus("In Progress");
	}
	
	public void stop(ActionEvent event) {
		selectedItem.setStatus("Stopped");
	}
	
	public void restart(ActionEvent event) {
		selectedItem.setStatus("In Progress");
	}
	
	public void done(ActionEvent event) {
		selectedItem.setStatus("Done");
		selectedItem.finished = new Date();
	}
	
	public void remove(ActionEvent event) {
		items.remove(selectedItem);
		selectedItem = null;
	}

Finally, we have to add a utility method that we invoke while constructing the menus. This utility method adds an ace:ajax behavior to the menu items, in order to update the table and menu when the list or selected item change. Add the following method to the bean:

	private void addAjaxBehaviorTo(MenuItem menuItem) {
		AjaxBehavior ajaxBehavior = new AjaxBehavior();
		ajaxBehavior.setExecute("@form");
		ajaxBehavior.setRender("@form");
		menuItem.addClientBehavior("activate", ajaxBehavior);
	}

At this point, the app will compile and will be fully functional. However, it can still be improved as we'll see next.

Optional improvements

The main topic of dynamic menus has been illustrated above. The following are some optional improvements to the application itself that can make it more usable.

Layout improvements

In order to have the menu appear to the right of the list, as in the preview image shown at the top, one can simply wrap the data table and menu components inside an HTML table and add some styling to make them look better. The body of the xhtml page could be changed to something like the following markup:

<h:form>
	<table style="font-size: x-small;">
		<tr>
			<td valign="top" style="width:600px;">
				<ace:dataTable value="#{todoListBean.items}" var="item" selectionMode="single" 
						rowSelectListener="#{todoListBean.rowSelectListener}">
					<ace:ajax event="deselect" render="@form" execute="@form" 
						listener="#{todoListBean.rowDeselectListener}" />
					<ace:ajax event="select" render="@form" execute="@form" />
					<ace:column headerText="Description" sortBy="#{item.description}">
						<h:outputText value="#{item.description}"/>
					</ace:column>
					<ace:column headerText="Status" sortBy="#{item.status}">
						<h:outputText value="#{item.status}"/>
					</ace:column>
					<ace:column headerText="Created" sortBy="#{item.created}">
						<h:outputText value="#{item.created}"/>
					</ace:column>
					<ace:column headerText="Finished" sortBy="#{item.finished}">
						<h:outputText value="#{item.finished}"/>
					</ace:column>
				</ace:dataTable>
			</td>
			<td valign="top">
				<ace:menu type="plain" model="#{todoListBean.menu}"/>
			</td>
		</tr>
	</table>
</h:form>
Adding new tasks

For the purposes of a to-do list application, it would be necessary to be able to add new tasks on the page itself, instead of simply hard-coding them in the bean. One could add this second form to the body of the xhtml page:

	<h:form>
		<h:outputText value="Enter description: " />
		<h:inputText style="width:400px;"  value="#{todoListBean.newItem}" />
		<h:commandButton value="Add new task" actionListener="#{todoListBean.addNewItem}"/>
	</h:form>

The code for this form to work should be added to the bean as well:

	private String newItem = "";
	
	public String getNewItem() {
		return newItem;
	}
	
	public void setNewItem(String newItem) {
		this.newItem = newItem;
	}
	
	public void addNewItem(ActionEvent event) {
		items.add(new Item(newItem));
		newItem = "";
	}

Conclusion

Creating dynamic menus is not as hard as it seems. One simply has to set up a menu component that builds from a model in the bean and use some creativity to display the different menu options according to the purposes of the application.

Source

The source files of the final version of this app can be downloaded here.

Enter labels to add to this page:
Please wait 
Looking for a label? Just start typing.

© Copyright 2018 ICEsoft Technologies Canada Corp.