Oracle® Application Development Framework Developer's Guide 10g (10.1.3.1.0) Part Number B28967-01 |
|
|
View PDF |
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.
There are two ways to create a menu hierarchy, namely:
Manually by inserting individual menu item components into each menu component, and marking the current menu items as "selected" on each page
Declaratively by binding each menu component to a menu model object and using the menu model display the appropriate menu items, including setting the current items as "selected"
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 themenuTabs 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". |
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:
Create a menu model. (See Section 11.2.1.1, "Creating a Menu Model")
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")
Create one global navigation rule that has navigation cases for each menu item. (See Section 11.2.1.3, "Creating the JSF Navigation Rules")
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:
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: Thetype 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. |
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. |
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); } }
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>
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; } }
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>
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.
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. |
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 thestartDepth on menuBar should be set to zero, and so on. |
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.
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>
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.
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.
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.