Introduction
Pages are not written in isolation, an application consists of many pages. Often these pages are organized in a tree, and the user drills down into the tree to access more specific information. The figure below represents a tree of pages.
a1 a2 a3 a4
/ \ / \
a1b1 a1b2 a2b1 a2b2
/ \ / \
a1b1c1 a1b1c2 a1b2c1 a1b2c2
| |
a1b1c1d1 a1b1c2d1
Figure 1
Here's an example at the kind of navigation rules you might find in faces-config for the group of pages above.
<navigation-rule>
<navigation-case>
<from-outcome>guide.page_a1</from-outcome>
<to-view-id>/components/page_a1.jspx</to-view-id>
<redirect/>
</navigation-case>
<navigation-case>
<from-outcome>guide.page_a2</from-outcome>
<to-view-id>/components/page_a2.jspx</to-view-id>
<redirect/>
</navigation-case>
<navigation-case>
<from-outcome>guide.page_a3</from-outcome>
<to-view-id>/components/page_a3.jspx</to-view-id>
<redirect/>
</navigation-case>
<navigation-case>
<from-outcome>guide.page_a4</from-outcome>
<to-view-id>/components/page_a4.jspx</to-view-id>
<redirect/>
</navigation-case>
<navigation-case>
<from-outcome>guide.page_a1b1</from-outcome>
<to-view-id>/components/page_a1b1.jspx</to-view-id>
<redirect/>
</navigation-case>
<navigation-case>
<from-outcome>guide.page_a1b2</from-outcome>
<to-view-id>/components/page_a1b2.jspx</to-view-id>
<redirect/>
</navigation-case>
<navigation-case>
<from-outcome>guide.page_a2b1</from-outcome>
<to-view-id>/components/page_a2b1.jspx</to-view-id>
<redirect/>
</navigation-case>
<navigation-case>
<from-outcome>guide.page_a2b2</from-outcome>
<to-view-id>/components/page_a2b2.jspx</to-view-id>
<redirect/>
</navigation-case>
<navigation-case>
<from-outcome>guide.page_a1b1c1</from-outcome>
<to-view-id>/components/page_a1b1c1.jspx</to-view-id>
<redirect/>
</navigation-case>
<navigation-case>
<from-outcome>guide.page_a1b1c2</from-outcome>
<to-view-id>/components/page_a1b1c2.jspx</to-view-id>
<redirect/>
</navigation-case>
<navigation-case>
<from-outcome>guide.page_a1b2c1</from-outcome>
<to-view-id>/components/page_a1b2c1.jspx</to-view-id>
<redirect/>
</navigation-case>
<navigation-case>
<from-outcome>guide.page_a1b2c2</from-outcome>
<to-view-id>/components/page_a1b2c2.jspx</to-view-id>
<redirect/>
</navigation-case>
<navigation-case>
<from-outcome>guide.page_a1b1c1d1</from-outcome>
<to-view-id>/components/page_a1b1c1d1.jspx</to-view-id>
<redirect/>
</navigation-case>
<navigation-case>
<from-outcome>guide.page_a1b1c2d1</from-outcome>
<to-view-id>/components/page_a1b1c2d1.jspx</to-view-id>
<redirect/>
</navigation-case>
</navigation-rule>
Let's say that we want a1 and a2 to be tabs, and a3 and a4 to be global buttons. Let's also say we are looking at page a1b2c1. The nodes in bold (a1, a1b2, and a1b2c1) represent the focus path in the tree. In BLAF these nodes should look "selected". Here's what the menu navigation on page a1b2c1 should look like in BLAF.
Figure 2
So what components should be used in ADF Faces to achieve the layout above? There are multiple options: use panelPage and menu components that take children, use panelPage and menu components that use a menu model, and use the page component with a menu model. These three techniques are discussed in this chapter.
Menu Components and Children
The panelPage component organizes the content of an entire page. The panelPage has multiple facets which are used to render the menu areas of the page. The menu components then go inside these facets. The "intuitive" way to build up the menu components is to just add children to the menu components. The example that follows should produce the same output as seen in
figure 2
.
<?xml version="1.0" encoding="iso-8859-1" standalone="yes" ?>
<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page" version="1.2"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:af="http://xmlns.oracle.com/adf/faces" >
<jsp:directive.page contentType="text/html;charset=utf-8"/>
<f:view>
<af:document title="Menu Demo">
<af:form>
<af:panelPage title="Title">
<f:facet name="menu1">
<af:menuTabs>
<af:commandMenuItem text="a1" action="guide.page_a1"
selected="true" />
<af:commandMenuItem text="a2" action="guide.page_a2"/>
</af:menuTabs>
</f:facet>
<f:facet name="menu2">
<af:menuBar>
<af:commandMenuItem text="a1b1" action="guide.page_a1b1"/>
<af:commandMenuItem text="a1b2" action="guide.page_a1b2"
selected="true"/>
</af:menuBar>
</f:facet>
<f:facet name="menu3">
<af:menuList>
<af:commandMenuItem text="a1b2c1" action="guide.page_a1b2c1"
selected="true" />
<af:commandMenuItem text="a1b2c2" action="guide.page_a1b2c2"/>
</af:menuList>
</f:facet>
<f:facet name="menuGlobal">
<af:menuButtons>
<af:commandMenuItem text="a3" action="guide.page_a3"/>
<af:commandMenuItem text="a4" action="guide.page_a4"/>
</af:menuButtons>
</f:facet>
<f:facet name="location">
<af:menuPath>
<af:commandMenuItem text="a1" action="guide.page_a1"/>
<af:commandMenuItem text="a1b2" action="guide.page_a1b2"/>
<af:commandMenuItem text="a1b2c1" action="guide.page_a1b2c1"/>
</af:menuPath>
</f:facet>
</af:panelPage>
</af:form>
</af:document>
</f:view>
</jsp:root>
Menu Model
NOTE
: Work on modeling menus continues and it is very possible that there will be changes in a future release.
So what is wrong with the panelPage approach described above? It's simple and easy to understand and it works. The problem is that it's not easy to get things right. For example the page author must take care to show the correct nodes as "selected". It's also not easily maintained. What if a page is moved from one place to another in the tree (see
figure 1
)?
What is needed is a model object that knows the application hierarchy and also knows how to say what is the current "focus path" in that hierarchy. The MenuModel class was created for this purpose. MenuModel extends TreeModel, so if you are not familiar with the model for the tree it is important that you first read the
tree
chapter.
The menu model knows how to go from the current viewId to the focus path. For example if I am on page a1b2c1, a call to getFocusPath() will return the focus path of a1, a1b2, and a1b2c1.
Notice that the menuModel has no special knowledge of page navigation and places no requirements on the nodes that go into the tree. The components that take a menuModel use a command component to stamp out the data in the tree. The default actionListener mechanism is used for page navigation.
The following example shows how a menu model can be created in faces-config. The DemoMenuItem class is a bean. The TreeModelAdapter class creates a ChildPropertyTreeModel instance, and the MenuModelAdapter creates a ViewIdPropertyMenuModel instance which can be retrieved by calling MenuModelAdapter.getModel(). Please see the javadoc for more information on ChildPropertyTreeMod
el and ViewIdPropertyMenuModel.
Skip to end of faces-config fragment.
<!-- create the menuModel nodes -->
<managed-bean>
<managed-bean-name>menuItem_a1b1c1d1</managed-bean-name>
<managed-bean-class>oracle.adfdemo.view.faces.menu.DemoMenuItem</managed-bean-class>
<managed-bean-scope>none</managed-bean-scope>
<managed-property>
<property-name>label</property-name>
<value>a1b1c1d1</value>
</managed-property>
<managed-property>
<property-name>viewId</property-name>
<value>/components/page_a1b1c1d1.jspx</value>
</managed-property>
<managed-property>
<property-name>outcome</property-name>
<value>guide.page_a1b1c1d1</value>
</managed-property>
</managed-bean>
<managed-bean>
<managed-bean-name>menuItem_a1b1c2d1</managed-bean-name>
<managed-bean-class>oracle.adfdemo.view.faces.menu.DemoMenuItem</managed-bean-class>
<managed-bean-scope>none</managed-bean-scope>
<managed-property>
<property-name>label</property-name>
<value>a1b1c2d1</value>
</managed-property>
<managed-property>
<property-name>viewId</property-name>
<value>/components/page_a1b1c2d1.jspx</value>
</managed-property>
<managed-property>
<property-name>outcome</property-name>
<value>guide.page_a1b1c2d1</value>
</managed-property>
</managed-bean>
<managed-bean>
<managed-bean-name>menuItem_a1b1c1</managed-bean-name>
<managed-bean-class>oracle.adfdemo.view.faces.menu.DemoMenuItem</managed-bean-class>
<managed-bean-scope>none</managed-bean-scope>
<managed-property>
<property-name>children</property-name>
<list-entries>
<value-class>oracle.adfdemo.view.faces.menu.DemoMenuItem</value-class>
<value>#{menuItem_a1b1c1d1}</value>
</list-entries>
</managed-property>
<managed-property>
<property-name>label</property-name>
<value>a1b1c1</value>
</managed-property>
<managed-property>
<property-name>viewId</property-name>
<value>/components/page_a1b1c1.jspx</value>
</managed-property>
<managed-property>
<property-name>outcome</property-name>
<value>guide.page_a1b1c1</value>
</managed-property>
</managed-bean>
<managed-bean>
<managed-bean-name>menuItem_a1b1c2</managed-bean-name>
<managed-bean-class>oracle.adfdemo.view.faces.menu.DemoMenuItem</managed-bean-class>
<managed-bean-scope>none</managed-bean-scope>
<managed-property>
<property-name>children</property-name>
<list-entries>
<value-class>oracle.adfdemo.view.faces.menu.DemoMenuItem</value-class>
<value>#{menuItem_a1b1c2d1}</value>
</list-entries>
</managed-property>
<managed-property>
<property-name>label</property-name>
<value>a1b1c2</value>
</managed-property>
<managed-property>
<property-name>viewId</property-name>
<value>/components/page_a1b1c2.jspx</value>
</managed-property>
<managed-property>
<property-name>outcome</property-name>
<value>guide.page_a1b1c2</value>
</managed-property>
</managed-bean>
<managed-bean>
<managed-bean-name>menuItem_a1b2c1</managed-bean-name>
<managed-bean-class>oracle.adfdemo.view.faces.menu.DemoMenuItem</managed-bean-class>
<managed-bean-scope>none</managed-bean-scope>
<managed-property>
<property-name>label</property-name>
<value>a1b2c1</value>
</managed-property>
<managed-property>
<property-name>viewId</property-name>
<value>/components/page_a1b2c1.jspx</value>
</managed-property>
<managed-property>
<property-name>outcome</property-name>
<value>guide.page_a1b2c1</value>
</managed-property>
</managed-bean>
<managed-bean>
<managed-bean-name>menuItem_a1b2c2</managed-bean-name>
<managed-bean-class>oracle.adfdemo.view.faces.menu.DemoMenuItem</managed-bean-class>
<managed-bean-scope>none</managed-bean-scope>
<managed-property>
<property-name>label</property-name>
<value>a1b2c2</value>
</managed-property>
<managed-property>
<property-name>viewId</property-name>
<value>/components/page_a1b2c2.jspx</value>
</managed-property>
<managed-property>
<property-name>outcome</property-name>
<value>guide.page_a1b2c2</value>
</managed-property>
</managed-bean>
<managed-bean>
<managed-bean-name>menuItem_a1b1</managed-bean-name>
<managed-bean-class>oracle.adfdemo.view.faces.menu.DemoMenuItem</managed-bean-class>
<managed-bean-scope>none</managed-bean-scope>
<managed-property>
<property-name>children</property-name>
<list-entries>
<value-class>oracle.adfdemo.view.faces.menu.DemoMenuItem</value-class>
<value>#{menuItem_a1b1c1}</value>
<value>#{menuItem_a1b1c2}</value>
</list-entries>
</managed-property>
<managed-property>
<property-name>label</property-name>
<value>a1b1</value>
</managed-property>
<managed-property>
<property-name>viewId</property-name>
<value>/components/page_a1b1.jspx</value>
</managed-property>
<managed-property>
<property-name>outcome</property-name>
<value>guide.page_a1b1</value>
</managed-property>
</managed-bean>
<managed-bean>
<managed-bean-name>menuItem_a1b2</managed-bean-name>
<managed-bean-class>oracle.adfdemo.view.faces.menu.DemoMenuItem</managed-bean-class>
<managed-bean-scope>none</managed-bean-scope>
<managed-property>
<property-name>children</property-name>
<list-entries>
<value-class>oracle.adfdemo.view.faces.menu.DemoMenuItem</value-class>
<value>#{menuItem_a1b2c1}</value>
<value>#{menuItem_a1b2c2}</value>
</list-entries>
</managed-property>
<managed-property>
<property-name>label</property-name>
<value>a1b2</value>
</managed-property>
<managed-property>
<property-name>viewId</property-name>
<value>/components/page_a1b2.jspx</value>
</managed-property>
<managed-property>
<property-name>outcome</property-name>
<value>guide.page_a1b2</value>
</managed-property>
</managed-bean>
<managed-bean>
<managed-bean-name>menuItem_a2b1</managed-bean-name>
<managed-bean-class>oracle.adfdemo.view.faces.menu.DemoMenuItem</managed-bean-class>
<managed-bean-scope>none</managed-bean-scope>
<managed-property>
<property-name>label</property-name>
<value>a2b1</value>
</managed-property>
<managed-property>
<property-name>viewId</property-name>
<value>/components/page_a2b1.jspx</value>
</managed-property>
<managed-property>
<property-name>outcome</property-name>
<value>guide.page_a2b1</value>
</managed-property>
</managed-bean>
<managed-bean>
<managed-bean-name>menuItem_a2b2</managed-bean-name>
<managed-bean-class>oracle.adfdemo.view.faces.menu.DemoMenuItem</managed-bean-class>
<managed-bean-scope>none</managed-bean-scope>
<managed-property>
<property-name>label</property-name>
<value>a2b2</value>
</managed-property>
<managed-property>
<property-name>viewId</property-name>
<value>/components/page_a2b2.jspx</value>
</managed-property>
<managed-property>
<property-name>outcome</property-name>
<value>guide.page_a2b2</value>
</managed-property>
</managed-bean>
<managed-bean>
<managed-bean-name>menuItem_a1</managed-bean-name>
<managed-bean-class>oracle.adfdemo.view.faces.menu.DemoMenuItem</managed-bean-class>
<managed-bean-scope>none</managed-bean-scope>
<managed-property>
<property-name>children</property-name>
<list-entries>
<value-class>oracle.adfdemo.view.faces.menu.DemoMenuItem</value-class>
<value>#{menuItem_a1b1}</value>
<value>#{menuItem_a1b2}</value>
</list-entries>
</managed-property>
<managed-property>
<property-name>label</property-name>
<value>a1</value>
</managed-property>
<managed-property>
<property-name>viewId</property-name>
<value>/components/page_a1.jspx</value>
</managed-property>
<managed-property>
<property-name>outcome</property-name>
<value>guide.page_a1</value>
</managed-property>
</managed-bean>
<managed-bean>
<managed-bean-name>menuItem_a2</managed-bean-name>
<managed-bean-class>oracle.adfdemo.view.faces.menu.DemoMenuItem</managed-bean-class>
<managed-bean-scope>none</managed-bean-scope>
<managed-property>
<property-name>children</property-name>
<list-entries>
<value-class>oracle.adfdemo.view.faces.menu.DemoMenuItem</value-class>
<value>#{menuItem_a2b1}</value>
<value>#{menuItem_a2b2}</value>
</list-entries>
</managed-property>
<managed-property>
<property-name>label</property-name>
<value>a2</value>
</managed-property>
<managed-property>
<property-name>viewId</property-name>
<value>/components/page_a2.jspx</value>
</managed-property>
<managed-property>
<property-name>outcome</property-name>
<value>guide.page_a2</value>
</managed-property>
</managed-bean>
<managed-bean>
<managed-bean-name>menuItem_a3</managed-bean-name>
<managed-bean-class>oracle.adfdemo.view.faces.menu.DemoMenuItem</managed-bean-class>
<managed-bean-scope>none</managed-bean-scope>
<managed-property>
<property-name>label</property-name>
<value>a3</value>
</managed-property>
<managed-property>
<property-name>viewId</property-name>
<value>/components/page_a3.jspx</value>
</managed-property>
<managed-property>
<property-name>outcome</property-name>
<value>guide.page_a3</value>
</managed-property>
<managed-property>
<property-name>ico</property-name>
<value>/components/images/globalhelp.gif</value>
</managed-property>
<managed-property>
<property-name>type</property-name>
<value>global</value>
</managed-property>
</managed-bean>
<managed-bean>
<managed-bean-name>menuItem_a4</managed-bean-name>
<managed-bean-class>oracle.adfdemo.view.faces.menu.DemoMenuItem</managed-bean-class>
<managed-bean-scope>none</managed-bean-scope>
<managed-property>
<property-name>label</property-name>
<value>a4</value>
</managed-property>
<managed-property>
<property-name>viewId</property-name>
<value>/components/page_a4.jspx</value>
</managed-property>
<managed-property>
<property-name>outcome</property-name>
<value>guide.page_a4</value>
</managed-property>
<managed-property>
<property-name>ico</property-name>
<value>/components/images/globalhelp.gif</value>
</managed-property>
<managed-property>
<property-name>type</property-name>
<value>global</value>
</managed-property>
</managed-bean>
<!-- create the treemodel -->
<managed-bean>
<managed-bean-name>menuTreeModel</managed-bean-name>
<managed-bean-class>oracle.adfdemo.view.faces.TreeModelAdapter</managed-bean-class>
<managed-bean-scope>none</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.adfdemo.view.faces.menu.DemoMenuItem</value-class>
<value>#{menuItem_a1}</value>
<value>#{menuItem_a2}</value>
<value>#{menuItem_a3}</value>
<value>#{menuItem_a4}</value>
</list-entries>
</managed-property>
</managed-bean>
<!-- create the menuModel -->
<managed-bean>
<managed-bean-name>menuModel</managed-bean-name>
<managed-bean-class>oracle.adfdemo.view.faces.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>
Menu Components and the MenuModel
Note
: In a subsequent release the use of the startDepth attribute will almost certainly be replaced with an el expression in the 'value' attribute.
As in the first example, we are still using the panelPage and menu components, but in the example that follows we are binding the menu components to a menuModel. Notice it is the command component stamp which renders the data in the node.The example that follows should produce the same output as seen in
figure 2
.
<?xml version="1.0" encoding="iso-8859-1" standalone="yes" ?>
<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page" version="1.2"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:af="http://xmlns.oracle.com/adf/faces" >
<jsp:directive.page contentType="text/html;charset=utf-8"/>
<f:view>
<af:document title="Menu Demo">
<af:form>
<af:panelPage title="Title">
<f:facet name="menu1">
<af:menuTabs var="foo" value="#{menuModel.model}">
<f:facet name="nodeStamp">
<af:commandMenuItem text="#{foo.label}"
action="#{foo.getOutcome}"
rendered="#{foo.type=='default'}"/>
</f:facet>
</af:menuTabs>
</f:facet>
<f:facet name="menu2">
<af:menuBar startDepth="1" var="foo" value="#{menuModel.model}">
<f:facet name="nodeStamp">
<af:commandMenuItem text="#{foo.label}"
action="#{foo.getOutcome}"
rendered="#{foo.type=='default'}"/>
</f:facet>
</af:menuBar>
</f:facet>
<f:facet name="menu3">
<af:menuList startDepth="2" var="foo"
value="#{menuModel.model}">
<f:facet name="nodeStamp">
<af:commandMenuItem text="#{foo.label}"
action="#{foo.getOutcome}"
rendered="#{foo.type=='default'}"/>
</f:facet>
</af:menuList>
</f:facet>
<f:facet name="menuGlobal">
<af:menuButtons var="foo" value="#{menuModel.model}">
<f:facet name="nodeStamp">
<af:commandMenuItem text="#{foo.label}"
action="#{foo.getOutcome}"
rendered="#{foo.type=='global'}"/>
</f:facet>
</af:menuButtons>
</f:facet>
<f:facet name="location">
<af:menuPath var="foo" value="#{menuModel.model}">
<f:facet name="nodeStamp">
<af:commandMenuItem text="#{foo.label}"
action="#{foo.getOutcome}"/>
</f:facet>
</af:menuPath>
</f:facet>
</af:panelPage>
</af:form>
</af:document>
</f:view>
</jsp:root>
Page
Using the menuModel with menu components has made some things easier, but it hasn't solved all our problems. For example, if we were looking at page a1b1c1 instead of a1b2c1 (see
figure 1
), we should use a menuTree in the menu3 facet rather than a menuList. If we were looking at page a1, we don't need anything at all in the menu3 facet.
More importantly, what if we want
to make a dramatic change the way menuing looks. Maybe we want the page component to render cascading menus, meaning menu's that drop down off of an item when you click it. Using the panelPage and menu components ties you to specific renderers for the menu components.
The page component is more flexible still, binding to a menuModel and stamping out the menu items. The advantages of using a page componenent are that the renderer could render the menu items in many different ways, for example it could render the cascading menus described above. Also, the page author doesn't need to know anything about where this page is in the hierarchy of pages. To render a1b1c1 or a1 we would use the exact same syntax as below.
The example that follows should produce the same output as seen in
figure 2
.
<?xml version="1.0" encoding="iso-8859-1" standalone="yes" ?>
<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page" version="1.2"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:af="http://xmlns.oracle.com/adf/faces" >
<jsp:directive.page contentType="text/html;charset=utf-8"/>
<f:view>
<af:document title="Menu Demo">
<af:form>
<af:page var="foo" value="#{menuModel.model}" title="Title">
<f:facet name="nodeStamp">
<af:commandMenuItem text="#{foo.label}"
action="#{foo.getOutcome}"
type="#{foo.type}"/>
</f:facet>
</af:page>
</af:form>
</af:document>
</f:view>
</jsp:root>