How to Create Dynamic Menus in JSF

written

This post presents an effective way to create a dynamic menu in JSF. Code for the menu can be accessed from its Google Code Project.

The menu, with some basic CSS styling is shown below. The menu uses plain JSF (without needing Primefaces). Some extra CSS can be used to make it look pretty.

Dynamic Menu using JSF

I used random numbers instead of a real example, so try and imaging names such as “Computer components”, “Laptops” and “Headphones” instead of the long numbers.

This menu is dynamic: clicking a menu item will cause the menu to be regenerated and the new menu items will be chosen based on the item that was clicked. The most common example of this behaviour is a hierarchical navigation menu, such as a category menu on eBay or Amazon.

How to make the menu

The code required to create this menu is as follows:

The XHTML file
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:mt="http://nz.co.kevindoran/jsf-menu">
    <h:head>
        <title>Menu Table Test</title>
    </h:head>
    <h:body>
        <h:form>
            <h2>JSF Menu Table Test</h2>
            Current category: #{randomMenu.currentCategory}
            <mt:MenuTable model="#{randomMenu.menuTable}" updateID="@form"/>
        </h:form>
    </h:body>
</html>
MenuBean.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import nz.co.kevindoran.jsfmenutable.MenuTable;
import nz.co.kevindoran.jsfmenutable.Subscriber;

@ManagedBean
@ViewScoped
public class RandomMenu {
    private MenuTable<Category> menuTable;
    private static final int columnCount = 4;
    private Category currentCategory = new Category();

    public RandomMenu()  {
        menuTable = new MenuTable<>(columnCount);
        menuTable.setContents(currentCategory.getChildCategories());
        MenuClickListener menuListener = new MenuClickListener();
        menuTable.addSubscriber(menuListener);
    }

    private class MenuClickListener implements Subscriber<Category> {

        @Override
        public void update(Category change) {
            currentCategory = change;
            menuTable.setContents(currentCategory.getChildCategories());
        }
    }

    public MenuTable<Category> getMenuTable() {
        return menuTable;
    }

    public Category getCurrentCategory() {
        return currentCategory;
    }
}

The main point to note here is how the RandomMenu listens for menu click events. Its inner class MenuClickListener implements Subscriber (you don’t need to use an inner class, RandomMenu could implement Subscriber<Category>). In other words, it is subscribing to the event of a Category being updated. When the menu is clicked, the MenuTable with call the update(Category change) method, passing it the Category that was clicked.

The Category class I use simply generates random numbers for each of the menu items. In a more realistic situation, the following class would populate the menu with child categories of the clicked category, or something similar. The simply Category class is as follows:

Category.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Category {
    private String name;

    public Category() {
        name = "" + Math.random();
    }

    public List<Category> getChildCategories() {
        List<Category> childCategories = new ArrayList<>();
        for(int i=0; i<20; i++) {
            childCategories.add(new Category());
        }
        return childCategories;
    }

    @Override
    public String toString() {
        return name;
    }
}

Back end

The above code will work as is if you download the accompanying JAR from the Google Code project. If you are interested it how to make your own menu from scratch, below is the two extra pieces of code you will have to write:

A Java Back Bean (MenuTable.java)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
public class MenuTable<T> {
    public List<Row<T>> rows;
    private List<Subscriber<T>> subscribers = new ArrayList<>();
    private int noOfColumns;

    public MenuTable(int noOfColumns) {
        this.noOfColumns = noOfColumns;
    }

    /**
     * Organised the input objects into rows which are as even as possible 
     * (so that there isn't one huge row and other tiny rows).
     *
     */
    public void setContents(Collection<T> objects) {
        rows = new ArrayList<>();
        int count = objects.size();
        int maxColumnLength = (int) Math.ceil(((double)count) / noOfColumns);
        for(int i=0; i<maxColumnLength; i++) {
            rows.add(new Row<T>());
        }

        Iterator<T> it = objects.iterator();
        for(int col = 0; col < noOfColumns; col++) {
            double left = count - col;
            double temp = left / noOfColumns;
            int columnLength = (int) Math.ceil(temp);
            for(int i=0; i<columnLength; i++){
                rows.get(i).add(it.next());
            }
        }
    }

    public void setSelected(T selected) {
        updateSubscribers(selected);
    }

    private void updateSubscribers(T latest) {
        for(Subscriber<T> s : subscribers) {
            s.update(latest);
        }
    }

    public List<Row<T>> getRows() {
        return rows;
    }

    public int getNoOfColumns() {
        return noOfColumns;
    }

    public void addSubscriber(Subscriber<T> subscriber) {
        subscribers.add(subscriber);
    }

    public class Row<T> {
        private List<T> contents = new ArrayList<>();

        public List<T> getContents() {
            return contents;
        }

        public void add(T object) {
            contents.add(object);
        }
    }
}
JSF Composite Component (MenuTable.xhtml)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:cc="http://java.sun.com/jsf/composite"
      xmlns:ui="http://java.sun.com/jsf/facelets"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:h="http://java.sun.com/jsf/html">

    <cc:interface>
        <cc:attribute name="model" type="nz.co.kevindoran.jsfmenutable.MenuTable"/>
        <cc:attribute name="updateID" />
    </cc:interface>

    <cc:implementation>
        <table>
            <ui:repeat id="repeat1" value="#{cc.attrs.model.rows}" var="row">
                <tr>
                <ui:repeat id="repeat2" value="#{row.contents}" var="entry">
                    <td>
                        <h:commandLink actionListener="#{cc.attrs.model.setSelected(entry)}" value="#{entry.toString()}">
                            <f:ajax render="#{cc.attrs.updateID}"/>
                        </h:commandLink>
                    </td>
                </ui:repeat>
                </tr>
            </ui:repeat>
        </table>
    </cc:implementation>
</html>

Accessing the code

The code to create the menu is hosted on a Google Code project. There is also a convenient JAR that can be downloaded. There are three ways to use the project:

  1. Download the JAR from the downloads list, and include it on your classpath.
  2. Obtain the source code by cloning the Mercurial repository.
  3. The project is a Maven project, so you can install it in your local Maven repository (run: mvn install) and then add it as a dependency like so:
Maven Pom Segment
1
2
3
4
5
6
7
8
<dependencies>
    <dependency>
        <groupId>nz.co.kevindoran</groupId>
        <artifactId>google-charts-jsf</artifactId>
        <version>1.0-SNAPSHOT</version>
        <type>jar</type>
    </dependency>
</dependencies>

If you have obtained the Maven project, you can change the packaging element: <packaging>jar</packaging> to <packaging>war</packaging>. Deploying the resulting WAR file will run he examples show above.

Some useful resources

There are a number of books on JSF and Primefaces that I have found useful when working on this topic:


Comments