Skip Headers
Oracle® Application Development Framework Developer's Guide
10g (10.1.3.1.0)

Part Number B28967-01
Go to Documentation Home
Home
Go to Book List
Book List
Go to Table of Contents
Contents
Go to Index
Index
Go to Master Index
Master Index
Go to Feedback page
Contact Us

Go to previous page
Previous
Go to next page
Next
View PDF

11.2 Using Dynamic Menus for Navigation

The SRDemo pages use a panelPage component to lay out the page with a hierarchical menu system for page navigation. Figure 11-1 shows the Management page with the available menu choices from the SRDemo application's menu hierarchy. Typically, a menu hierarchy consists of global buttons, menu tabs, and a menu bar beneath the menu tabs.

Figure 11-1 Dynamic Navigation Menus in the SRDemo Application

Global buttons, menu tabs, and menu bar with two items

There are two ways to create a menu hierarchy, namely:

For most of the pages you see in the SRDemo application, the declarative technique is employed—using a menu model and managed beans—to dynamically generate the menu hierarchy.

The panelPage component supports menu1 and menu2 facets for creating the hierarchical, navigation menus that enable a user to go quickly to related pages in the application.

The menu1 facet takes a menuTabs component, which lays out a series of menu items rendered as menu tabs. Similarly, the menu2 facet takes a menuBar component that renders menu items in a bar beneath the menu tabs.

Global buttons are buttons that are always available from any page in the application, such as a Help button. The menuGlobal facet on panelPage takes a menuButtons component that lays out a series of buttons.


Note:

The global buttons in the SRDemo application are not generated dynamically, instead they are hard-coded into each page. In some pages, cacheable fragments are used to contain the menuTabs and menuBar components. For purposes of explaining how to create dynamic menus in this chapter, global buttons are included and caching is excluded in the descriptions and code samples. For information about caching, see Chapter 15, "Optimizing Application Performance with Caching".

11.2.1 How to Create Dynamic Navigation Menus

To display hierarchical menus dynamically, you build a menu model and bind the menu components (such as menuTabs and menuBar) to the menu model. At runtime, the menu model generates the hierarchical menu choices for the pages.

To create dynamic navigation menus:

  1. Create a menu model. (See Section 11.2.1.1, "Creating a Menu Model")

  2. Create a JSF page for each menu choice or item in the menu hierarchy. (See Section 11.2.1.2, "Creating the JSF Page for Each Menu Item")

  3. Create one global navigation rule that has navigation cases for each menu item. (See Section 11.2.1.3, "Creating the JSF Navigation Rules")

11.2.1.1 Creating a Menu Model

Use the oracle.adf.view.faces.model.MenuModel, oracle.adf.view.faces.model.ChildPropertyTreeModel, and oracle.adf.view.faces.model.ViewIdPropertyMenuModel classes to create a menu model that dynamically generates a menu hierarchy.

To create a menu model:

  1. Create a class that can get and set the properties for each item in the menu hierarchy or tree.

    For example, each item in the tree needs to have a label, a viewId, and an outcome property. If items have children (for example, a menu tab item can have children menu bar items), you need to define a property to represent the list of children (for example, children property). To determine whether items are shown or not shown on a page depending on security roles, define a boolean property (for example, shown property). Example 11-1 shows the MenuItem class used in the SRDemo application.

    Example 11-1 MenuItem.java for All Menu Items

    package oracle.srdemo.view.menu;
    import java.util.List;
    import oracle.adf.view.faces.component.core.nav.CoreCommandMenuItem;
    public class MenuItem {
        private String _label          = null;
        private String _outcome        = null;
        private String _viewId         = null;
        private String _destination    = null;
        private String _icon           = null;
        private String _type           = CoreCommandMenuItem.TYPE_DEFAULT;
        private List   _children       = null;
        //extended security attributes
        private boolean _readOnly = false;
        private boolean _shown = true; 
        public void setLabel(String label) {
            this._label = label;
        }
        public String getLabel() {
            return _label;
        }
     // getter and setter methods for remaining attributes omitted
    }
    
    

    Note:

    The type property defines a menu item as global or nonglobal. Global items can be accessed from any page in the application. For example, a Help button on a page is a global item.

  2. Configure a managed bean for each menu item or page in the hierarchy, with values for the properties that require setting at instantiation.

    Each bean should be an instance of the menu item class you create in step 1. Example 11-2 shows the managed bean code for all the menu items in the SRDemo application. If an item has children items, the list entries are the children managed beans listed in the order you desire. For example, the Management menu tab item has two children.

    Typically each bean should have none as its bean scope. The SRDemo application, however, uses session scoped managed beans for the menu items because security attributes are assigned to the menu items when they are created dynamically, and the SRDemo application uses a session scoped UserInfo bean to hold the user role information for the user currently logged in. The user role information is used to determine which menu items a user sees when logged in. For example, only users with the user role of 'manager' see the Management menu tab. JSF doesn't let you reference a session scoped managed bean from a none scoped bean; therefore, the SRDemo application uses all session scoped managed beans for the menu system.

    Example 11-2 Managed Beans for Menu Items in the faces-config.xml File

    <!-- If you were to use dynamically generated global buttons -->
    <!-- Root pages: Two global button menu items -->
    <managed-bean>
      <managed-bean-name>menuItem_GlobalLogout</managed-bean-name>
      <managed-bean-class>oracle.srdemo.view.menu.MenuItem</managed-bean-class>
      <managed-bean-scope>session</managed-bean-scope>
      <managed-property>
        <property-name>label</property-name>
        <value>#{resources['srdemo.menu.logout']}</value>
      </managed-property>
      <managed-property>
        <property-name>icon</property-name>
        <value>/images/logout.gif</value>
      </managed-property>
      <managed-property>
        <property-name>type</property-name>
        <value>global</value>
      </managed-property>    
      <managed-property>
        <property-name>viewId</property-name>
        <value>/app/SRLogout.jsp</value>
      </managed-property>
      <managed-property>
        <property-name>outcome</property-name>
        <value>GlobalLogout</value>
      </managed-property>
    </managed-bean>
    
    <managed-bean>
      <managed-bean-name>menuItem_GlobalHelp</managed-bean-name>
      <managed-bean-class>oracle.srdemo.view.menu.MenuItem</managed-bean-class>
      <managed-bean-scope>session</managed-bean-scope>
      <managed-property>
        <property-name>label</property-name>
        <value>#{resources['srdemo.menu.help']}</value>
      </managed-property>
      <managed-property>
        <property-name>icon</property-name>
        <value>/images/help.gif</value>
      </managed-property>
      <managed-property>
        <property-name>type</property-name>
        <value>global</value>
      </managed-property>
      <managed-property>
        <property-name>viewId</property-name>
        <value>/app/SRHelp.jspx</value>
      </managed-property>
      <managed-property>
        <property-name>outcome</property-name>
        <value>GlobalHelp</value>
      </managed-property>
    </managed-bean>
    
    <!-- Root pages: Four menu tabs -->
    <!-- 1. My Service Requests menu tab item -->
    <managed-bean>
      <managed-bean-name>menuItem_MyServiceRequests</managed-bean-name>
      <managed-bean-class>oracle.srdemo.view.menu.MenuItem</managed-bean-class>
      <managed-bean-scope>session</managed-bean-scope> 
      <managed-property>
        <property-name>label</property-name>
        <value>#{resources['srdemo.menu.my']}</value>
      </managed-property>
      <managed-property>
        <property-name>viewId</property-name>
        <value>/app/SRList.jspx</value>
      </managed-property>
      <managed-property>
        <property-name>outcome</property-name>
        <value>GlobalHome</value>
      </managed-property>
    </managed-bean>
    
    <!-- 2. Advanced Search menu tab item -->
    <managed-bean>
      <managed-bean-name>menuItem_AdvancedSearch</managed-bean-name>  
      <managed-bean-class>oracle.srdemo.view.menu.MenuItem</managed-bean-class>
      <managed-bean-scope>session</managed-bean-scope> 
      <managed-property>
        <property-name>label</property-name>
        <value>#{resources['srdemo.menu.advanced']}</value>
      </managed-property>
      <managed-property>
        <property-name>shown</property-name>
        <value>#{userInfo.staff}</value>
      </managed-property>
      <managed-property>
        <property-name>viewId</property-name>
        <value>/app/staff/SRSearch.jspx</value>
      </managed-property>
      <managed-property>
        <property-name>outcome</property-name>
        <value>GlobalSearch</value>
      </managed-property>
    </managed-bean>
    
    <!-- 3. New Service Request menu tab item -->
    <managed-bean>
      <managed-bean-name>menuItem_New</managed-bean-name>  
      <managed-bean-class>oracle.srdemo.view.menu.MenuItem</managed-bean-class>
      <managed-bean-scope>session</managed-bean-scope> 
      <managed-property>
        <property-name>label</property-name>
        <value>#{resources['srdemo.menu.new']}</value>
      </managed-property>
      <managed-property>
        <property-name>viewId</property-name>
        <value>/app/SRCreate.jspx</value>
      </managed-property>
      <managed-property>
        <property-name>outcome</property-name>
        <value>GlobalCreate</value>
      </managed-property>
    </managed-bean>
    
    <!-- 4. Management menu tab item -->
    <!-- This managed bean uses managed bean chaining for children menu items --> 
    <managed-bean>
      <managed-bean-name>menuItem_Manage</managed-bean-name>  
      <managed-bean-class>oracle.srdemo.view.menu.MenuItem</managed-bean-class>
      <managed-bean-scope>session</managed-bean-scope> 
      <managed-property>
        <property-name>label</property-name>
        <value>#{resources['srdemo.menu.manage']}</value>
      </managed-property>
      <managed-property>
        <property-name>shown</property-name>
        <value>#{userInfo.manager}</value>
      </managed-property>
      <managed-property>
        <property-name>viewId</property-name>
        <value>/app/management/SRManage.jspx</value>
      </managed-property>
      <managed-property>
        <property-name>outcome</property-name>
        <value>GlobalManage</value>
      </managed-property>
      <managed-property>
        <property-name>children</property-name>
        <list-entries>
          <value-class>oracle.srdemo.view.menu.MenuItem</value-class>
          <value>#{subMenuItem_Manage_Reporting}</value>
          <value>#{subMenuItem_Manage_ProdEx}</value>
        </list-entries>
      </managed-property>
    </managed-bean>
    <!-- Children menu bar items for Management tab -->
    <managed-bean>
      <managed-bean-name>subMenuItem_Manage_Reporting</managed-bean-name>  
      <managed-bean-class>oracle.srdemo.view.menu.MenuItem</managed-bean-class>
      <managed-bean-scope>session</managed-bean-scope> 
      <managed-property>
        <property-name>label</property-name>
        <value>#{resources['srdemo.menu.manage.reporting']}</value>
      </managed-property>
      <managed-property>
        <property-name>shown</property-name>
        <value>#{userInfo.manager}</value>
      </managed-property>    
      <managed-property>
        <property-name>viewId</property-name>
        <value>/app/management/SRManage.jspx</value>
      </managed-property>
      <managed-property>
        <property-name>outcome</property-name>
        <value>GlobalManage</value>
      </managed-property>
    </managed-bean> 
    <managed-bean>
      <managed-bean-name>subMenuItem_Manage_ProdEx</managed-bean-name>  
      <managed-bean-class>oracle.srdemo.view.menu.MenuItem</managed-bean-class>
      <managed-bean-scope>session</managed-bean-scope> 
      <managed-property>
        <property-name>label</property-name>
        <value>#{resources['srdemo.menu.manage.prodEx']}</value>
      </managed-property>
      <managed-property>
        <property-name>shown</property-name>
        <value>#{userInfo.manager}</value>
      </managed-property>    
      <managed-property>
        <property-name>viewId</property-name>
        <value>/app/management/SRSkills.jspx</value>
      </managed-property>
      <managed-property>
        <property-name>outcome</property-name>
        <value>Skills</value>
      </managed-property>
    </managed-bean>   
    

    Note:

    As you see in Figure 11-1, the Management menu tab has a menu bar with two items: Overview and Technician Skills. As each menu item has its own page or managed bean, so the two items are represented by these managed beans, respectively: subMenuItem_Manage_Reporting and subMenuItem_Manage_ProdEx. The Management menu tab is represented by the menuItem_Manage managed bean, which uses value binding expressions (such as #{subMenuItem_Manage_ProdEx}) inside the list value elements to reference the children managed beans.

  3. Create a class that constructs a ChildPropertyTreeModel instance. The instance represents the entire tree hierarchy of the menu system, which is later injected into a menu model. Example 11-3 shows the MenuTreeModelAdapter class used in the SRDemo application.

    Example 11-3 MenuTreeModelAdapter.java for Holding the Menu Tree Hierarchy

    package oracle.srdemo.view.menu;
    import java.beans.IntrospectionException;
    import java.util.List;
    import oracle.adf.view.faces.model.ChildPropertyTreeModel;
    import oracle.adf.view.faces.model.TreeModel;
     
    public class MenuTreeModelAdapter {
        private String _propertyName = null;
        private Object _instance = null;
        private transient TreeModel _model = null;
     
        public TreeModel getModel() throws IntrospectionException
        {
          if (_model == null)
          {
            _model = new ChildPropertyTreeModel(getInstance(), getChildProperty());
          }
          return _model;
        }
     
        public String getChildProperty()
        {
          return _propertyName;
        }
        /**
         * Sets the property to use to get at child lists
         * @param propertyName
         */
        public void setChildProperty(String propertyName)
        {
          _propertyName = propertyName;
          _model = null;
        }
     
        public Object getInstance()
        {
          return _instance;
        }
        /**
         * Sets the root list for this tree.
         * @param instance must be something that can be converted into a List
         */
        public void setInstance(Object instance)
        {
          _instance = instance;
          _model = null;
        }
        /**
         * Sets the root list for this tree.
         * This is needed for passing a List when using the managed bean list  
         * creation facility, which requires the parameter type of List.
         * @param instance the list of root nodes
         */
        public void setListInstance(List instance)
        {
          setInstance(instance);
        }  
    }
    
    
  4. Configure a managed bean to reference the menu tree model class in step 3. The bean should be instantiated with a childProperty value that is the same as the property value that represents the list of children as created on the bean in step 1.

    The bean should also be instantiated with a list of root pages (listed in the order you desire) as the value for the listInstance property. The root pages are the global button menu items and the first-level menu tab items, as shown in Example 11-2. Example 11-4 shows the managed bean for creating the menu tree model.

    Example 11-4 Managed Bean for Menu Tree Model in the faces-config.xml File

    <managed-bean>
      <managed-bean-name>menuTreeModel</managed-bean-name>
      <managed-bean-class>
        oracle.srdemo.view.menu.MenuTreeModelAdapter
      </managed-bean-class>
      <managed-bean-scope>session</managed-bean-scope>
      <managed-property>
        <property-name>childProperty</property-name>
        <value>children</value>
      </managed-property>
      <managed-property>
        <property-name>listInstance</property-name>
        <list-entries>
          <value-class>oracle.srdemo.view.menu.MenuItem</value-class>
          <value>#{menuItem_GlobalLogout}</value>
          <value>#{menuItem_GlobalHelp}</value>
          <value>#{menuItem_MyServiceRequests}</value>
          <value>#{menuItem_AdvancedSearch}</value>
          <value>#{menuItem_New}</value>
          <value>#{menuItem_Manage}</value>
        </list-entries>
      </managed-property>
    </managed-bean>
    
    
  5. Create a class that constructs a ViewIdPropertyMenuModel instance. The instance creates a menu model from the menu tree model. Example 11-5 shows the MenuModelAdapter class used in the SRDemo application.

    Example 11-5 MenuModelAdapter.java

    package oracle.srdemo.view.menu;
    import java.beans.IntrospectionException;
    import java.io.Serializable;
    import java.util.List;
    import oracle.adf.view.faces.model.MenuModel;
    import oracle.adf.view.faces.model.ViewIdPropertyMenuModel;
    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)
          {
            ViewIdPropertyMenuModel model = 
                                   new ViewIdPropertyMenuModel(getInstance(),
                                                               getViewIdProperty());
                                                               
            if(_aliasList != null && !_aliasList.isEmpty())    
            {
              int size = _aliasList.size();
              if (size % 2 == 1)
                size = size - 1;
                
              for ( int i = 0; i < size; i=i+2)
              {
                model.addViewId(_aliasList.get(i).toString(),
                               _aliasList.get(i+1).toString());
              }
            }
            
            _model = model;
          }
          return _model;
        }
     
        public String getViewIdProperty()
        {
          return _propertyName;
        }
        /**
         * Sets the property to use to get at view id
         * @param propertyName
         */
        public void setViewIdProperty(String propertyName)
        {
          _propertyName = propertyName;
          _model = null;
        }
     
        public Object getInstance()
        {
          return _instance;
        }
        /**
         * Sets the treeModel
         * @param instance must be something that can be converted into a TreeModel
         */
        public void setInstance(Object instance)
        {
          _instance = instance;
          _model = null;
        }
        
        public List getAliasList()
        {
          return _aliasList;
        }
        public void setAliasList(List aliasList)
        {
          _aliasList = aliasList;
        }  
    }
    
    
  6. Configure a managed bean to reference the menu model class in step 5. This is the bean to which all the menu components on a page are bound.

    The bean should be instantiated with the instance property value set to the model property of the menu tree model bean configured in step 4. The instantiated bean should also have the viewIdProperty value set to the viewId property on the bean created in step 1. Example 11-6 shows the managed bean code for creating the menu model.

    Example 11-6 Managed Bean for Menu Model in the faces-config.xml File

    <!-- create the main menu menuModel -->
    <managed-bean>
      <managed-bean-name>menuModel</managed-bean-name>
      <managed-bean-class>
        oracle.srdemo.view.menu.MenuModelAdapter</managed-bean-class>
      <managed-bean-scope>session</managed-bean-scope>
      <managed-property>
        <property-name>viewIdProperty</property-name>
        <value>viewId</value>
      </managed-property>
      <managed-property>
        <property-name>instance</property-name>
        <value>#{menuTreeModel.model}</value>
      </managed-property>
    </managed-bean>
    
    

11.2.1.1.1 What You May Need to Know About Chaining Managed Beans

By using value binding expressions to chain managed bean definitions, you can create a tree-like menu system instead of a flat structure. The order of the individual managed bean definitions in faces-config.xml does not matter, but the order of the children list-entries in a parent bean should be in the order you want the menu choices to appear.

When you chain managed bean definitions together, the bean scopes must be compatible. Table 11-1 lists the compatible bean scopes.

Table 11-1 Combinations of Managed Bean Scopes Allowed

A bean of this scope... Can chain with beans of these scopes

none

none

application

none, application

session

none, application, session

request

none, application, session, request


11.2.1.1.2 What You May Need to Know About Accessing Resource Bundle Strings

The String resources for all labels in the SRDemo application are contained in a resource bundle. This resource bundle is configured in faces-config.xml. As described earlier, each menu item is defined as a session scoped managed bean, and the various attributes of a menu item (such as its type and label) are defined through managed bean properties. For the menu item managed bean to access the label to use from the resource bundle, you need to configure a managed bean that provides the access to the bundle.

In the SRDemo application, the ResourceAdapter class exposes the resource bundle within EL expressions via the resources managed bean. Example 11-7 shows the ResourceAdapter class, and the JSFUtils.getStringFromBundle() method that retrieves a String from the bundle.

Example 11-7 Part of ResourceAdapter.java and Part of JSFUtils.java

package oracle.srdemo.view.resources;
import oracle.srdemo.view.util.JSFUtils;
/**
 * Utility class that allows us to expose the specified resource bundle within
 * general EL
 */
public class ResourceAdapter implements Map {
    
    public Object get(Object resourceKey) {
        return JSFUtils.getStringFromBundle((String)resourceKey);
    }
 // Rest of file omitted from here
}
...
/** From JSFUtils.java */
package oracle.srdemo.view.util;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
...
public class JSFUtils {
    private static final String NO_RESOURCE_FOUND = "Missing resource: ";
    /**
       * Pulls a String resource from the property bundle that
       * is defined under the application's message-bundle element in
       * faces-config.xml. Respects Locale.
       * @param key
       * @return Resource value or placeholder error String
       */
    public static String getStringFromBundle(String key) {
        ResourceBundle bundle = getBundle();
        return getStringSafely(bundle, key, null);
    }
    /*
     * Internal method to proxy for resource keys that don't exist
     */
    private static String getStringSafely(ResourceBundle bundle, String key, 
                                          String defaultValue) {
      String resource = null;
      try {
        resource = bundle.getString(key);
      } catch (MissingResourceException mrex) {
        if (defaultValue != null) {
          resource = defaultValue;
        } else {
          resource = NO_RESOURCE_FOUND + key;
        }
      }
      return resource;
    }
//Rest of file omitted from here
}

Example 11-8 shows the resources managed bean code that provides the access for other managed beans to the String resources.

Example 11-8 Managed Bean for Accessing the Resource Bundle Strings

<!-- Resource bundle -->
<application>
  <message-bundle>oracle.srdemo.view.resources.UIResources</message-bundle>
  ...
</application>

<!-- Managed bean for ResourceAdapater class -->
<managed-bean>
  <managed-bean-name>resources</managed-bean-name>
  <managed-bean-class>
    oracle.srdemo.view.resources.ResourceAdapter</managed-bean-class>
  <managed-bean-scope>application</managed-bean-scope>
</managed-bean>

The resources managed bean defines a Map interface onto the resource bundle that is defined in faces-config.xml. The menu item labels automatically pick up the correct language strings.


Tip:

The menu model is built when it is first referenced. This means it is not rebuilt if the browser language is changed within a single session.

11.2.1.2 Creating the JSF Page for Each Menu Item

Each menu item (whether it is a menu tab item, menu bar item, or global button) has its own page. To display the available menu choices on a page, bind the menu components (such as menuTabs, menuBar, or menuButtons) to the menu model. Example 11-9 shows the menuTabs component code that binds the component to a menu model.

Example 11-9 MenuTabs Component Bound to a Menu Model

<af:panelPage title="#{res['srmanage.pageTitle']}"
              binding="#{backing_SRManage.panelPage1}"
              id="panelPage1">
  <f:facet name="menu1">
    <af:menuTabs value="#{menuModel.model}"...>
      ...
    </af:menuTabs>
  </f:facet>
  ...
</af:panelPage>

Each menu component has a nodeStamp facet, which takes one commandMenuItem component, as shown in Example 11-10. By using a variable and binding the menu component to the model, you need only one commandMenuItem component to display all items in a menu, which is accomplished by using an EL expression similar to #{var.label} for the text value, and #{var.getOutcome} for the action value on the commandMenuItem component. It is the commandMenuItem component that provides the actual label you see on a menu item, and the navigation outcome when the menu item is activated.

Example 11-10 NodeStamp Facet and CommandMenuItem Component

<af:panelPage title="#{res['srmanage.pageTitle']}"
              binding="#{backing_SRManage.panelPage1}"
              id="panelPage1">
  <f:facet name="menu1">
    <af:menuTabs var="menuTab"
                 value="#{menuModel.model}">
      <f:facet name="nodeStamp">
        <af:commandMenuItem text="#{menuTab.label}"
                            action="#{menuTab.getOutcome}"
                            .../>
      </f:facet>
    </af:menuTabs>
  </f:facet>
  ...
</af:panelPage>

Whether a menu item renders on a page is determined by the security role of the current user logged in. For example, only users with the manager role see the Management menu tab. The rendered and disabled attributes on a commandMenuItem component determine whether a menu item should be rendered or disabled.

Following along with the MenuItem class in Example 11-1: For global items, bind the rendered attribute to the variable's type property and set it to global. For nonglobal items, bind the rendered attribute to the variable's shown property and the type property, and set the type property to default. For nonglobal items, bind also the disabled attribute to the variable's readOnly property. Example 11-11 shows how this is done for menuTabs (a nonglobal component) and menuButtons (a global component).

Example 11-11 Rendered and Disabled Menu Item Components

<af:menuTabs var="menuTab" value="#{menuModel.model}">
  <f:facet name="nodeStamp">
    <af:commandMenuItem text="#{menuTab.label}"
                        action="#{menuTab.getOutcome}"
                        rendered="#{menuTab.shown and
                                    menuTab.type=='default'}"
                        disabled="#{menuTab.readOnly}"/>
  </f:facet>
</af:menuTabs>
...
<af:menuButtons var="menuOption" value="#{menuModel.model}">
  <f:facet name="nodeStamp">
    <af:commandMenuItem text="#{menuOption.label}"
                        action="#{menuOption.getOutcome}"
                        rendered="#{menuOption.type=='global'}"
                        icon="#{menuOption.icon}"/>
  </f:facet>
</af:menuButtons>

You can use any combination of menus you desire in an application. For example, you could use only menu bars, without any menu tabs. To let ADF Faces know the start level of your menu hierarchy, you set the startDepth attribute on the menu component. Based on a zero-based index, the possible values of startDepth are 0, 1, and 2, assuming three levels of menus are used. If startDepth is not specified, it defaults to zero (0).

If an application uses global menu buttons, menu tabs, and menu bars: A global menuButtons component always has a startDepth of zero. Since menu tabs are the first level, the startDepth for menuTabs is zero as well. The menuBar component then has a startDepth value of 1. Example 11-12 shows part of the menu code for a panelPage component.

Example 11-12 PanelPage Component with Menu Facets

<af:panelPage title="#{res['srmanage.pageTitle']}">
  <f:facet name="menu1">
    <af:menuTabs var="menuTab" value="#{menuModel.model}">
      <f:facet name="nodeStamp">
        <af:commandMenuItem text="#{menuTab.label}"
                            action="#{menuTab.getOutcome}"
                            rendered="#{menuTab.shown and
                                       menuTab.type=='default'}"
                            disabled="#{menuTab.readOnly}"/>
          </f:facet>
    </af:menuTabs>
  </f:facet>
  <f:facet name="menu2">
    <af:menuBar var="menuSubTab" startDepth="1"
                value="#{menuModel.model}">
      <f:facet name="nodeStamp">
        <af:commandMenuItem text="#{menuSubTab.label}"
                            action="#{menuSubTab.getOutcome}"
                            rendered="#{menuSubTab.shown and
                                        menuSubTab.type=='default'}"
                            disabled="#{menuSubTab.readOnly}"/>
      </f:facet>
    </af:menuBar>
  </f:facet>
  <f:facet name="menuGlobal">
    <af:menuButtons var="menuOption" value="#{menuModel.model}">
      <f:facet name="nodeStamp">
        <af:commandMenuItem text="#{menuOption.label}"
                            action="#{menuOption.getOutcome}"
                            rendered="#{menuOption.type=='global'}"
                            icon="#{menuOption.icon}"/>
      </f:facet>
    </af:menuButtons>
  </f:facet>
  ...
</af:panelPage>


Tip:

If your menu system uses menu bars as the first level, then the startDepth on menuBar should be set to zero, and so on.

11.2.1.2.1 What You May Need to Know About the PanelPage and Page Components

Instead of using a panelPage component and binding each menu component on the page to a menu model object, you can use the page component with a menu model. By value binding the page component to a menu model, as shown in the following code snippet, you can take advantage of the more flexible rendering capabilities of the page component. For example, you can easily change the look and feel of menu components by creating a new renderer for the page component. If you use the panelPage component, you need to change the renderer for each of the menu components.

<af:page title="Title 1" var="node" value="#{menuModel.model}">
  <f:facet name="nodeStamp">
    <af:commandMenuItem text="#{node.label}" 
                        action="#{node.getOutcome}"
                        type="#{node.type}"/>
  </f:facet>
</af:page>

Because a menu model dynamically determines the hierarchy (that is, the links that appear in each menu component) and also sets the current items in the focus path as "selected," you can use practically the same code on each page.

11.2.1.3 Creating the JSF Navigation Rules

Create one global navigation rule that has navigation cases for each first-level and global menu item. Children menu items are not included in the global navigation rule. For menu items that have children menu items (for example, the Management menu tab has children menu bar items), create a navigation rule with all the navigation cases that are possible from the parent item, as shown in Example 11-13.

Example 11-13 Navigation Rules for a Menu System in the faces-config.xml File

<navigation-rule>
  <from-view-id>*</from-view-id>
  <navigation-case>
    <from-outcome>GlobalHome</from-outcome>
    <to-view-id>/app/SRList.jspx</to-view-id>
    <redirect/>
  </navigation-case>
  <navigation-case>
    <from-outcome>GlobalSearch</from-outcome>
    <to-view-id>/app/staff/SRSearch.jspx</to-view-id>
  </navigation-case>
  <navigation-case>
    <from-outcome>GlobalCreate</from-outcome>
    <to-view-id>/app/SRCreate.jspx</to-view-id>
  </navigation-case>
  <navigation-case>
    <from-outcome>GlobalManage</from-outcome>
    <to-view-id>/app/management/SRManage.jspx</to-view-id>
    <redirect/>
  </navigation-case>
  <navigation-case>
    <from-outcome>GlobalLogout</from-outcome>
    <to-view-id>/app/SRLogout.jspx</to-view-id>
    <redirect/>
  </navigation-case>
  <navigation-case>
    <from-outcome>GlobalAbout</from-outcome>
    <to-view-id>/app/SRAbout.jspx</to-view-id>
  </navigation-case>
</navigation-rule>
<!-- Navigation rule for Management menu tab with children items -->
<navigation-rule>
  <from-view-id>/app/management/SRManage.jspx</from-view-id>
  <navigation-case>
    <from-outcome>Skills</from-outcome>
    <to-view-id>/app/management/SRSkills.jspx</to-view-id>
  </navigation-case>
</navigation-rule>

11.2.2 What Happens at Runtime

MenuModelAdapter constructs the menu model, which is a ViewIdPropertyMenuModel instance, via the menuModel managed bean. When the menuTreeModel bean is requested, this automatically triggers the creation of the chained beans menuItem_GlobalLogout, menuItem_GlobalHelp, menuItem_MyServiceRequests, and so on. The tree of menu items is injected into the menu model. The menu model provides the model that correctly highlights and enables the items on the menus as you navigate through the menu system.

The individual menu item managed beans (for example, menuItem_MyServiceRequests) are instantiated with values for label, viewId, and outcome that are used by the menu model to dynamically generate the menu items. The default JSF actionListener mechanism uses the outcome values to handle the page navigation.

Each menu component has a nodeStamp facet, which is used to stamp the different menu items in the menu model. The commandMenuItem component housed within the nodeStamp facet provides the text and action for each menu item.

Each time nodeStamp is stamped, the data for the current menu item is copied into an EL reachable property. The name of this property is defined by the var attribute on the menu component that houses the nodeStamp facet. Once the menu has completed rendering, this property is removed (or reverted back to its previous value). In Example 11-14, the data for each menu bar item is placed under the EL property menuSubTab. The nodeStamp displays the data for each item by getting further properties from the menuSubTab property.

Example 11-14 MenuBar Component Bound to a Menu Model

<af:menuBar var="menuSubTab" startDepth="1"
            value="#{menuModel.model}">
  <f:facet name="nodeStamp">
    <af:commandMenuItem text="#{menuSubTab.label}"
                        action="#{menuSubTab.getOutcome}"
                        rendered="#{menuSubTab.shown and
                                    menuSubTab.type=='default'}"
                        disabled="#{menuSubTab.readOnly}"/>
  </f:facet>
</af:menuBar>

By binding a menu component to a menu model and using a variable to represent a menu item, you need only one commandMenuItem component to display all menu items at that hierarchy level, allowing for more code reuse between pages, and is much less error prone than manually inserting a commandMenuItem component for each item. For example, if menu is the variable, then EL expressions such as #{menu.label} and #{menu.getOutcome} specify the text and action values for a commandMenuItem component.

The menu model in conjunction with nodeStamp controls whether a menu item is rendered as selected. As described earlier, a menu model is created from a tree model, which contains viewId information for each node. ViewIdPropertyMenuModel, which is an instance of MenuModel, uses the viewId of a node to determine the focus rowKey. Each item in the menu model is stamped based on the current rowKey. As the user navigates and the current viewId changes, the focus path of the model also changes and a new set of items is accessed. MenuModel has a method getFocusRowKey() that determines which page has focus, and automatically renders a node as selected if the node is on the focus path.

11.2.3 What You May Need to Know About Menus

Sometimes you might want to create menus manually instead of using a menu model.

The first-level menu tab My Service Requests has one second-level menu bar with several items, as illustrated in Figure 11-2. From My Service Requests, you can view open, pending, closed, or all service requests, represented by the first, second, third, and fourth menu bar item from the left, respectively. Each view is actually generated from the SRList.jspx page.

Figure 11-2 Menu Bar Items on My Service Requests Page (SRList.jspx)

Menu tab with five menu bar items.

In the SRList.jspx page, instead of binding the menuBar component to a menu model and using a nodeStamp to generate the menu items, you use individual children commandMenuItem components to display the menu items because the command components require a value to determine the type of requests to navigate to (for example, open, pending, closed, or all service requests). Example 11-15 shows part of the code for the menuBar component used in the SRList.jspx page.

Example 11-15 MenuBar Component with Children CommandMenuItem Components

<af:menuBar>
  <af:commandMenuItem text="#{res['srlist.menubar.openLink']}"
                      disabled="#{!bindings.findServiceRequests.enabled}"
                      selected="#{userState.listModeOpen}"
                      actionListener="#{bindings.findServiceRequests.execute}">
    <af:setActionListener from="#{'Open'}"
                          to="#{userState.listMode}"/>
  </af:commandMenuItem>
  <af:commandMenuItem text="#{res['srlist.menubar.pendingLink']}"
                      disabled="#{!bindings.findServiceRequests.enabled}"
                      selected="#{userState.listModePending}"
                      actionListener="#{bindings.findServiceRequests.execute}"
    <af:setActionListener from="#{'Pending'}"
                          to="#{userState.listMode}"/>
  </af:commandMenuItem>
  ...
  <af:commandMenuItem text="#{res['srlist.menubar.allRequests']}"
                      selected="#{userState.listModeAll}"
                      disabled="#{!bindings.findServiceRequests.enabled}"
                      actionListener="#{bindings.findServiceRequests.execute}">
    <af:setActionListener from="#{'%'}"
                          to="#{userState.listMode}"/>
  </af:commandMenuItem>
  ...
</af:menuBar>

The af:setActionListener tag, which declaratively sets a value on an ActionSource component before navigation, passes the correct list mode value to the userState managed bean. The session scoped userState managed bean stores the current list mode of the page.

When the commandMenuItem component is activated, the findServiceRequests method executes with the list mode value, and returns a collection that matches the value. The commandMenuItem components also use convenience functions in the userSystemState bean to evaluate whether the menu item should be marked as selected or not.