- be created with dynamic structure from backend
- be responsive, i.e. desktop- and mobile-friendly
- have submenu items with navigation links
- support touch events
- support keyboard accessibility
- they are not really responsive
- submenu items only collapse / expand the submenus and can not contain navigation links
- ...
But how to output the dynamic menu structure? ui:repeat is not a choice here because the structure (nested sub menus, etc.) is not known a priori. Fortunately, there is OmniFaces with o:tree, which allows to have full control over the markup of a tree hierarchy by declaring the JSF components or HTML elements in the markup. o:tree does not render any HTML markup by itself. Exactly what I need!
I ended up with this XHTML fragment mixing o:treeNode, o:treeNodeItem, o:treeInsertChildren and HTML elements defined by the mentioned FlexNav menu:
<h:outputScript library="js" name="jquery.flexnav.js"/> <h:outputStylesheet library="css" name="flexnav.css"/> <ul id="mainnavi" class="flexnav" data-breakpoint="640" role="navigation"> <o:tree value="#{mainNavigationBean.treeModel}" var="item"> <o:treeNode level="0"> <o:treeNodeItem> <li class="item"> <a href="#{item.href}" title="#{item.title}">#{item.text}</a> <o:treeInsertChildren/> </li> </o:treeNodeItem> </o:treeNode> <o:treeNode> <ul> <o:treeNodeItem> <li> <a href="#{item.href}" title="#{item.title}">#{item.text}</a> <o:treeInsertChildren/> </li> </o:treeNodeItem> </ul> </o:treeNode> </o:tree> </ul> <h:outputScript id="mainnaviScript" target="body"> $(document).ready(function () { $("#mainnavi").flexNav({'calcItemWidths': true}); }); </h:outputScript>The OmniFaces' TreeModel with menu items is created programmatically. The Java code looks like
public TreeModel<NavigationItemDTO> getTreeModel() { // get menu model from a remote service NavigationContainerDTO rootContainer = remoteService.fetchMainNavigation(...); TreeModel<NavigationItemDTO> treeModel = new ListTreeModel<>(); buildTreeModel(treeModel, rootContainer.getNavItem()); return treeModel; } private void buildTreeModel(TreeModel<NavigationItemDTO> treeModel, List<NavigationItemDTO> items) { for (NavigationItemDTO item : items) { buildTreeModel(treeModel.addChild(item), item.getNavItem()); } }And the end result (desktop variant):
Note that submenus are clickable and can be expanded on mouseover.
You see, JSF is flexible and sometimes you don't need full-blown components. Have fun!