Skip navigation.

JOracle

Oracle's Java technologies: JDeveloper, OC4J, ADF

January 2007

( Monthly archive )

Showing a list of items based on the contents of another list in ADF BC

This is a query rather than a solution, although if someone works out how to do it, I'll update this post with the solution.

I have constructed a very simple example schema to illustrate my query. Here is a picture of the schema.


It has three entity tables: Employee, Department and Location, and two intersection tables: Employee_Location and Department_Location.

What I want to do is have a creation process for an employee where you first fill in the details (forname and surname), then assign them to department, then depending on that department assign them to one or more possible locations.

So each department is based in one or more locations and an employee can also be based in one or more locations, but only those where the department is based (imagine that the employee is a door-to-door salesman and the department has several areas of a town that it covers).

The first two steps are easy - the details step can be modelled using a form on the employee view object and the department assignment can be done with a list of values drop down on the department view object.

The third step is a little more complex because you can't just show a list of all locations, since some of these will not apply to the department. So my question is how do I model this using ADF Business Components?

Update: 21st March 2007
I have now been able to solve this problem thanks to a blog entry by Frank Nimphius: Working with the afselectManyListsbox (Part I). This describes how to use the ViewCriteria object to perform a search on a view using a list of values, which is exactly what I needed - thanks Frank.

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
January 2007
S M T W T F S
December 2006February 2007
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