Thursday Apr 16, 2009

Core ADF 11: Mapping Multiple FocusViewId's with XMLMenuModel

In the first post of this Core ADF 11 series, I discussed various options for designing your page structure. This post applies when you use stand-alone .jspx pages. If you make these pages available through a menu (af:navigationPane) using XMLMenuModel where each menu item navigates to a page (either in the unbounded task flow or by calling a bounded task flow), the menu tab will be automatically highlighted when you set the focusViewId property of the itemNode in the XML Menu Model file to the name of the .jspx page that appears when you click on the menu item. Unfortunately, you can specify only one focusViewId for an itemNode. If your .jspx page has a button or link to navigate to a secondary page, you will loose the menu item highlighting because the focusViewId of the menu itemNode no longer matches the view id of the current (secondary) page. Even worse, if you have nested itemNodes defined in the XML Menu Model, the submenu that was displayed with the primary page will disappear when navigating to the secondary page.

The "poor-man's solution" to this problem is adding nested item nodes to the XML Menu Model for the detail pages. This technique is easy but can only be applied when there is no page-specific submenu that should be displayed with the pages. You simply create child itemNodes in the XML Menu model for the detail pages in the task flow, which will never be displayed as menu items because you do not include an af:navigationPane that displays this menu level.

A more sophisticated solution, that allows for page-specific submenus, is to subclass XMLMenuModel. The subclass looks up a MenuViewIdMapping managed bean that maps all viewIds of the secondary pages in the task flow to the viewId of the primary page that is defined as focusViewId in the XML Menu Model file.

Here are the steps to implement this technique:

1. Create a subclass of org.apache.myfaces.trinidad.model.XMLMenuModel and override method getFocusRowKey as follows:

  public Object getFocusRowKey()
  {
    // first check whether default behavior results in focus of some menu item 
    Object focusRowKey = super.getFocusRowKey();          
    if (focusRowKey!=null)
    {
      return focusRowKey;
    }
    Map> focusPathMap = getViewIdFocusPathMap();
    // check whether there is a menu mapping for the current viewId
    Map viewIdMapping = getViewIdMapping();
    String viewId = FacesContext.getCurrentInstance().getViewRoot().getViewId();
    if (viewIdMapping!=null && viewIdMapping.get(viewId)!=null)
    {
      String mappedViewId = (String) viewIdMapping.get(viewId);
      List focusPath = focusPathMap.get(mappedViewId);
      return focusPath != null? focusPath.get(0): null;                  
    }
    return null;
  }

Add the following helper method:

  public Map getViewIdMapping()
  {
    ExpressionFactory ef = 
      FacesContext.getCurrentInstance().getApplication().getExpressionFactory();
    ELContext context = FacesContext.getCurrentInstance().getELContext();
    return (Map) ef.createValueExpression(context
                                          ,"#{pageFlowScope.MenuViewIdMapping}"
                                          , Map.class).getValue(context);
  }

2. Change the menu bean property managed-bean-class to reference your subclass:

  <managed-bean>
    <description>Menu Model Managed Bean</description>
    <managed-bean-name>root_menu</managed-bean-name>
    <managed-bean-class>view.MyXMLMenuModel</managed-bean-class>
    <managed-bean-scope>request</managed-bean-scope>
    <managed-property>
      <property-name>createHiddenNodes</property-name>
      <value>false</value>
    </managed-property>
    <managed-property>
      <property-name>source</property-name>
      <property-class>java.lang.String</property-class>
      <value>/WEB-INF/root_menu.xml</value>
    </managed-property>
  </managed-bean>

3. In each taskflow that can be launched from a menu item, add a managed bean named "MenuViewIdMapping" that defines the mapping between secondary pages and the page defined as focusViewId in the XML Menu Model. Here is an example:

  <managed-bean>
    <managed-bean-name>MenuViewIdMapping</managed-bean-name>
    <managed-bean-class>java.util.HashMap</managed-bean-class>
    <managed-bean-scope>pageFlow</managed-bean-scope>
       <map-entries>
         <map-entry>
           <key>/task-flow-definition1/BoundedTaskFlow1DetailPage</key>
           <value>/task-flow-definition1/BoundedTaskFlow1Page</value>
         </map-entry>  
         <map-entry>
           <key>/task-flow-definition1/BoundedTaskFlow1AnotherDetailPage</key>
           <value>/task-flow-definition1/BoundedTaskFlow1Page</value>
         </map-entry>  
       </map-entries>
  </managed-bean>

That's it. You can download a sample workspace that illustrates the solution here. In a later post, we will discuss the solution for the "one-page application" design with dynamic regions. In that scenario the correct menu item should be highlighted based on the currently selected dynamic region.

About

Java EE Consultants - JHeadstart, ADF, JSF

Search

Archives
« April 2014
SunMonTueWedThuFriSat
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today