Skip navigation.

JOracle

Oracle's Java technologies: JDeveloper, OC4J, ADF

Curbing the shown level of an ADF Faces menu tree

The ADF Developer's Guide (link) contains a lot of useful sections on implementing different features. One of them is how to model a dynamic menu structure (19.2 Using Dynamic Menus for Navigation).

This chapter shows how to take advantage of the af: page nodeStamp facet to achieve a multi-level menu system based on managed beans. This has three levels of menu - the top level is represented by items in a menuTabs component, the second level by items in a menuBar component and the third level by items in panelSideBar. After that, deeper levels are represented as nodes on a menuTree component inside the panelSideBar. And the great thing about this is that for the current page, all the right items in the tree are highlighted, showing the menu path.

There are two problems I see with this. The first is that at three levels, the elements in the panelSideBar are bulleted, whereas at four or more levels, the bullets disappear as the list is replaced with a menuTree. This can make the items harder to differentiate (is that one item that has gone onto two lines, or is it two separate items?).

The second problem is that you may have pages that you don't want to show up on the menu system, but you do want to see the menu path to the page. For example, I have a series of pages that allow you to create/edit an item in a list. I don't want these pages to show up in the menu system because I want the user to only have one access point to them (the create/edit buttons on the list page). I do, however, want to show the user what area of the application they are in.

For me both of these problems happened at the same level in the menu system (my create/edit pages are on the fourth level). I needed a way of curbing the shown level of the menu tree, but at the same time not sacrificing the highlighted menu path functionality.

To do this, I have added a maxLevel attribute to the MenuTreeModelAdapter and subclassed both ChildPropertyTreeModel and ViewIdPropertyMenuModel. First, the ChildPropertyTreeModel subclass.

The code for all of these classes is based on the code in the Trinidad repository.

MaxLevelChildPropertyTreeModel
(imports)
public class MaxLevelChildPropertyTreeModel extends ChildPropertyTreeModel
{
  private int maxLevel;
  
  public MaxLevelChildPropertyTreeModel(Object instance, String childProperty, int maxLevel)
  {
    super(instance, childProperty);
    this.maxLevel = maxLevel;
  }
  
  public boolean isContainer()
  {
    boolean container = super.isContainer() && (getDepth() < maxLevel);
    return container;
  }
  
  public boolean isReallyContainer()
  {
    boolean container = super.isContainer();
    return container;
  }
  
  public boolean isContainerReallyEmpty()
  {
    if (!isReallyContainer()) return true;
    
    enterContainer();
    try
    {
      int kids = getRowCount();
      if (kids < 0)
      {
        setRowIndex(0);
        return !isRowAvailable();
      }
      return (kids == 0);
    }
    finally
    {
      exitContainer();
    }
  }
}


As you can see, there is a maxLevel attribute passed in the constructor. This comes from the value in the MenuTreeModelAdapter. I have overridden the isContainer method to return false if the current depth is greater or equal to the maxLevel. This ensures that all nodes of the menu tree at that level are considered empty. This results in the menu being rendered to only maxLevel levels, which for me is 2 (where 0 is the top level).

The other two methods are for the benefit of our MaxLevelViewIdPropertyMenuModel, so that it can traverse the whole tree to find all the pages. This is so when the application requests the row key of the current page, there will be an entry in the list. It doesn't matter how long the row key is, the correct menu path will be highlighted (i.e. using a maxLevel of 2, the row key [1, 0, 3, 0, 1] will highlight [1], [1, 0] and [1, 0, 3] in the menu system.

MaxLevelViewIdPropertyMenuModel
(imports)
public class MaxLevelViewIdPropertyMenuModel extends ViewIdPropertyMenuModel
{
  private Map<Object, Object> _focusPathMap;
  
  public MaxLevelViewIdPropertyMenuModel(Object instance, String viewIdProperty) throws IntrospectionException
  {
    super(instance, viewIdProperty);
  }
  
  public void setWrappedData(Object data)
  {
    Object oldPath = getRowKey();
    
    //set the focus path map
    _focusPathMap = new HashMap<Object, Object>();
    _focusPathMap.clear();
    setRowKey(null);
    FacesContext context = FacesContext.getCurrentInstance();
    _addToMap(context, (MaxLevelChildPropertyTreeModel)data, _focusPathMap, getViewIdProperty());
    setRowKey(oldPath);
  }

  private static void _addToMap(
    FacesContext context,
    MaxLevelChildPropertyTreeModel tree,
    Map<Object, Object> focusPathMap,
    String viewIdProperty
    )
  {
    for ( int i = 0; i < tree.getRowCount(); i++)
    {
      tree.setRowIndex(i);
      if (viewIdProperty != null)
      {
        Object focusPath = tree.getRowKey();
        
        Object data = tree.getRowData();
        PropertyResolver resolver =
          context.getApplication().getPropertyResolver();
        Object viewIdObject = resolver.getValue(data, viewIdProperty);
        focusPathMap.put(viewIdObject, focusPath);
      }

      if (tree.isReallyContainer() && !tree.isContainerReallyEmpty())
      {
        tree.enterContainer();
        _addToMap(context, tree, focusPathMap, viewIdProperty);
        tree.exitContainer();
      }

    }
  }
  
  public Object getFocusRowKey()
  {
    String currentViewId = getCurrentViewId();
    Object focusRowKey = _focusPathMap.get(currentViewId);
    return focusRowKey;
  }
}


This is slightly more complex. There is nothing different with the constructor - the values are simply passed on to the superclass constructor. In the superclass constructor, the setWrappedData method is called, so we override that with our own version. This calls the _addToMap method in the class rather than the _addToMap method in the superclass. This method takes MaxLevelChildPropertyTreeModel rather than a TreeModel. This is so that we can call the isReallyContainer and isContainerReallyEmpty methods to get the real return values of each. This means that the real menu tree is mapped to the _focusPathMap attribute. We then override the getFocusRowKey to return the value from the map.

This all means that the correct row key will be return for all pages in the menu tree, even if they are not rendered.

The last thing to do is add the maxLevel attribute to MenuTreeModelAdapter and change the model classes in MenuTreeModelAdapter and MenuModelAdapter to our new classes.

MenuModelAdapter
public class MenuModelAdapter implements Serializable {
    private           String    _propertyName = null;
    private           Object    _instance = null;
    private transient MenuModel _model = null;
    private           List      _aliasList = null;

    public MenuModel getModel() throws IntrospectionException
    {
      if (_model == null)
      {
        MaxLevelViewIdPropertyMenuModel model = 
                               new MaxLevelViewIdPropertyMenuModel(getInstance(),
                                                           getViewIdProperty());
...


The important part is where the model attribute is populated with an instance of MaxLevelViewIdPropertyMenuModel.

MenuTreeModelAdapter
public class MenuTreeModelAdapter {
    private String _propertyName = null;
    private Object _instance = null;
    private transient TreeModel _model = null;
    private int _maxLevel = Integer.MAX_VALUE;

    public TreeModel getModel() throws IntrospectionException
    {
      if (_model == null)
      {
        _model = new MaxLevelChildPropertyTreeModel(getInstance(), getChildProperty(), getMaxLevel());
      }
      return _model;
    }
  
    public void setMaxLevel(int maxLevel)
    {
      this._maxLevel = maxLevel;
    }
  
    public int getMaxLevel()
    {
      return _maxLevel;
    }
...


This shows the maxLevel attribute and how _model is populated with an instance of MaxLevelChildPropertyTreeModel.

And that's pretty much it. This is set up so that there's one global maximum level, but it could be improved upon to have custom levels for each part of the menu tree, say 2 levels here and 5 levels there.

I have included a JDeveloper application which has all of this code and a few pages that demonstrated the solution. Unzip it into your JDeveloper mywork folder.
JOracle.zip

Installing Oracle MetaLink Patches Without PerlShowing a list of items based on the contents of another list in ADF BC

Comments

Anonymous 12. June 2008, 14:38

Andy writes:

I've implemented successfully a 4 level menu application based on the above info and everything works just fine except for the fact that the nodes in the menu tree that have children, won't expand nor collapse when clicking their + or - icon. Any ideas?

dominionspy 14. June 2008, 08:25

Hi Andy,

I'm not really sure. The original reason I did this was so that the menu tree would never appear, since it was, in my opinion, a little ugly.

It may be that this doesn't work properly for levels greater than 3, which I did not test for. However, I can't think of a good reason why this would happen.

Is it possible for you to send me your workspace?

Anonymous 15. October 2008, 17:32

Dave writes:

Great stuff!

Technique-wise, it would be better to rewrite MenuTreeModelAdapter's "getModel()" method as follows (which should have been done by the original author):

public TreeModel getModel() throws IntrospectionException {
if( this.model == null ) {
setModel( createChildPropertyTreeModel( getInstance(),
getChildProperty() ) );
}

return this.model;
}

Then add this method:

protected ChildPropertyTreeModel createChildPropertyTreeModel( Object instance,
String childProperty ) {
return new ChildPropertyTreeModel( instance, childProperty );
}

Now subclasses can override createChildPropertyTreeModel to change the behaviour. This allows for greater reuse.

How to use Quote function:

  1. Select some text
  2. Click on the Quote link

Write a comment

Comment
(BBcode and HTML is turned off for anonymous user comments.)

Type the two words displayed in the image below:


Smilies

July 2009
S M T W T F S
June 2009August 2009
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