A script-enabled browser is required for this page to function properly.
Oracle Help for the WebContentsIndexSearchGlossaryView Topic


Using Complex UI Components

This chapter describes how to use ADF Faces components to create some of the functionality in the SRDemo application.

This chapter includes the following sections:

Introduction to Complex UI Components

ADF Faces components simplify user interaction. For example, inputFile enables file uploading, and selectInputText has built-in dialog support for navigating to a popup window and returning to the initial page with the selected value. While most of the ADF Faces components can be used out-of-the-box with minimal Java coding, some of them require extra coding in backing beans and configuring in faces-config.xml.

While the SRDemo pages use a custom skin, the descriptions of the rendered UI components and the illustrations in this chapter follow the default Oracle skin.

Read this chapter to understand:

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: Dynamic Navigation Menus in the SRDemo Application 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.

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 Optimizing Application Performance with Caching.

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 Creating a Menu Model)

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

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

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: MenuItem.java for All Menu Items shows the MenuItem class used in the SRDemo application.

    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: Managed Beans for Menu Items in the faces-config.xml File 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.

    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: Dynamic Navigation Menus in the SRDemo Application, 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: MenuTreeModelAdapter.java for Holding the Menu Tree Hierarchy shows the MenuTreeModelAdapter class used in the SRDemo application.

    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: Managed Beans for Menu Items in the faces-config.xml File. Example: Managed Bean for Menu Tree Model in the faces-config.xml File shows the managed bean for creating the menu tree model.

    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: MenuModelAdapter.java shows the MenuModelAdapter class used in the SRDemo application.

    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: Managed Bean for Menu Model in the faces-config.xml File shows the managed bean code for creating the menu model.

    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>
    
    
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: Combinations of Managed Bean Scopes Allowed lists the compatible bean scopes.

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


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: Part of ResourceAdapter.java and Part of JSFUtils.java shows the ResourceAdapter class, and the JSFUtils.getStringFromBundle() method that retrieves a String from the bundle.

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: Managed Bean for Accessing the Resource Bundle Strings shows the resources managed bean code that provides the access for other managed beans to the String resources.

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.

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: MenuTabs Component Bound to a Menu Model shows the menuTabs component code that binds the component to a menu model.

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: NodeStamp Facet and CommandMenuItem Component. 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.

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: MenuItem.java for All Menu Items: 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: Rendered and Disabled Menu Item Components shows how this is done for menuTabs (a nonglobal component) and menuButtons (a global component).

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: PanelPage Component with Menu Facets shows part of the menu code for a panelPage component.

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.

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.

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: Navigation Rules for a Menu System in the faces-config.xml File.

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>

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: MenuBar Component Bound to a Menu Model, 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.

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.

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: Menu Bar Items on My Service Requests Page (SRList.jspx). 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.

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: MenuBar Component with Children CommandMenuItem Components shows part of the code for the menuBar component used in the SRList.jspx page.

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.

Using Popup Dialogs

Sometimes you might want to display a new page in a separate popup dialog instead of displaying it in the same window containing the current page. In the popup dialog, you might let the user enter or select information, and then return to the original page to use that information. Ordinarily, you would need to use JavaScript to launch the popup dialog and manage the process, and create code for managing cases where popup dialogs are not supported on certain client devices such as a PDA. With the dialog framework, ADF Faces has made it easy to launch and manage popup dialogs and processes without using JavaScript.

Consider a simple application that requires users to log in to see their orders. Figure: Page Flow of a Dialog Sample Application shows the page flow for the application, which consists of five pages—login.jspx, orders.jspx, new_account.jspx, account_details.jspx, and error.jspx.

Page Flow of a Dialog Sample Application

Navigation diagram showing five pages.

When an existing user logs in successfully, the application displays the Orders page, which shows the user's orders, if there are any. When a user does not log in successfully, the Error page displays in a popup dialog, as shown in Figure: Error Page in a Popup Dialog.

Error Page in a Popup Dialog

Error message in a popup dialog

On the Error page there is a Cancel button. When the user clicks Cancel, the popup dialog closes and the application returns to the Login page, as shown in Figure: Login Page.

Login Page

Login screen for username and password

When a new user clicks the New User link on the Login page, the New Account page displays in a popup dialog, as shown in Figure: New Account Page in a Popup Dialog.

New Account Page in a Popup Dialog

New Account screen asking for name and address

After entering information such as first name and last name, the user then clicks the Details button to display the Account Details page in the same popup dialog, as shown in Figure: Account Details Page in a Popup Dialog. In the Account Details page, the user enters other information and confirms a password for the new login account. There are two buttons on the Account Details page—Cancel and Done.

Account Details Page in a Popup Dialog

Account Details screen to confirm country and password

If the new user decides not to proceed with creating a new login account and clicks Cancel, the popup dialog closes and the application returns to the Login page. If the new user clicks Done, the popup dialog closes and the application returns to the Login page where the Username field is now populated with the user's first name, as shown in Figure: Login Page With the Username Field Populated. The new user can then proceed to enter the new password and log in successfully.

Login Page With the Username Field Populated

Login screen with username field populated

How to Create Popup Dialogs

To make it easy to support popup dialogs in your application, ADF Faces has built in the dialog functionality to components that implement ActionSource (such as commandButton and commandLink). For ADF Faces to know whether to launch a page in a popup dialog from an ActionSource component, four conditions must exist:


Note:

If useWindow is false or if the client device doesn't support popup dialogs, ADF Faces automatically shows the page in the current window instead of using a popup—code changes are not needed to facilitate this.

The page that displays in a popup dialog is an ordinary JSF page. But for purposes of explaining how to implement popup dialogs in this chapter, a page that displays in a popup dialog is called the dialog page, and a page from which the popup dialog is launched is called the originating page. A dialog process starts when the originating page launches a dialog (which can contain one dialog page or a series of dialog pages), and ends when the user dismisses the dialog and is returned to the originating page.

The tasks for supporting popup dialogs in an application are:

  1. Define a JSF navigation rule for launching a dialog.

  2. Create the JSF page from which a dialog is launched.

  3. Create the dialog page and return a dialog value.

  4. Handle the return value.

  5. Pass a value into a dialog.

The tasks can be performed in any order.

Defining a JSF Navigation Rule for Launching a Dialog

You manage the navigation into a popup dialog by defining a standard JSF navigation rule with a special dialog: outcome. Using the dialog sample application shown in Figure: Page Flow of a Dialog Sample Application, three navigation outcomes are possible from the Login page:

  • Show the Orders page in the same window (successful login)

  • Show the Error dialog page in a popup dialog (login failure)

  • Show the New Account dialog page in a popup dialog (new user)

Example: Dialog Navigation Rules in the faces-config.xml File shows the navigation rule for the three navigation cases from the Login page (login.jspx).

Dialog Navigation Rules in the faces-config.xml File

<navigation-rule>

  <!-- Originating JSF page -->
  <from-view-id>/login.jspx</from-view-id>

  <!-- Navigation case for the New Account dialog page (new user)-->
  <navigation-case>
    <from-outcome>dialog:newAccount</from-outcome>
    <to-view-id>/new_account.jspx</to-view-id>
  </navigation-case>

  <!-- Navigation case for the Error dialog page (upon login failure) -->
  </navigation-case>
    <from-outcome>dialog:error</from-outcome>
    <to-view-id>/error.jspx</to-view-id>
  </navigation-case>

  <!-- Navigation case for the Orders page (upon login success) -->
  </navigation-case>
    <from-outcome>orders</from-outcome>
    <to-view-id>/orders.jspx</to-view-id>
  </navigation-case>

</navigation-rule>
What Happens at Runtime

The dialog navigation rules on their own simply show the specified pages in the main window. But when used with command components with dialog: action outcomes and with useWindow attributes set to true, ADF Faces knows to launch the pages in popup dialogs. This is described in the next step.

Creating the JSF Page That Launches a Dialog

In the originating page from which a popup dialog is launched, you can use either an action method or a static action outcome on the ActionSource component. Whether you specify a static action outcome or use an action method that returns an action outcome, this action outcome must begin with dialog:.

The sample application uses an action method binding on the commandButton component to determine programmatically whether to navigate to the Orders page or the Error dialog page, and a static action outcome on the commandLink component to navigate directly to the New Account dialog page. Both command components are on the Login page. Example: Login Button on the Login Page shows the code for the Login commandButton component.

Login Button on the Login Page

af:commandButton id="cmdBtn"
                 text="Login"
                 action="#{backing_login.commandButton_action}"
                 useWindow="true"
                 windowHeight="200"
                 windowWidth="500"
                 partialSubmit="true"/>

The attributes useWindow, windowHeight, and windowWidth are used in launching pages in popup dialogs. These attributes are ignored if the client device doesn't support popup dialogs.

When useWindow="true" ADF Faces knows to launch the dialog page in a new popup dialog. The windowHeight and windowWidth attributes specify the size of the popup dialog.


Tip:

Set the partialSubmit attribute on the commandButton component to true. This prevents the originating page from refreshing (and hence flashing momentarily) when the popup dialog displays.

The action attribute on commandButton specifies a reference to an action method in the page's backing bean, Login.java. The action method must return an outcome string, which JSF uses to determine the next page to display by comparing the outcome string to the outcomes in the navigation cases defined in faces-config.xml. The code for this action method is shown in Example: Action Method Code for the Login Button.

Action Method Code for the Login Button

public String commandButton_action()
{
  String retValue;
  retValue = "orders";
  _cust = getListCustomer();
  if (_cust == null || !password.equals(_cust.getPassword()))
  {
    retValue = "dialog:error";
  }
  
  return retValue;
}

Example: New User Command Link on the Login Page shows the code for the New User commandLink component that uses a static action outcome.

New User Command Link on the Login Page

<af:commandLink id="cmdLink" 
                text="New User?"
                action="dialog:newAccount"
                useWindow="true"
                partialSubmit="true"
                windowHeight="200"
                windowWidth="500" />

Instead of referencing an action method, the action attribute value is simply a static outcome string that begins with dialog:.

What Happens at Runtime

ADF Faces uses the attribute useWindow="true" in conjunction with an action outcome that begins with dialog: to determine whether to start a dialog process and launch a page in a popup dialog (assuming dialog: navigation rules have been defined in faces-config.xml).

If the action outcome does not begin with dialog:, ADF Faces does not start a process or launch a popup dialog even when useWindow="true". Conversely, if the action outcome begins with dialog:, ADF Faces does not launch a popup dialog if useWindow="false" or if useWindow is not set, but ADF Faces does start a new process.

If the client device does not support popup dialogs, ADF Faces shows the dialog page in the current window after preserving all the state of the current page—you don't have to write any code to facilitate this.

When a command component is about to launch a dialog, it delivers a launch event (LaunchEvent). The launch event stores information about the component that is responsible for launching a popup dialog, and the root of the component tree to display when the dialog process starts. A launch event can also pass a map of parameters into the dialog. For more information, see Passing a Value into a Dialog.

Creating the Dialog Page and Returning a Dialog Value

The dialog pages in our sample application are the Error page, the New Account page, and the Account Details page. The dialog process for a new user actually contains two pages: the New Account page and the Account Details page. The dialog process for a user login failure contains just the Error page.

A dialog page is just like any other JSF page, with one exception. In a dialog page you must provide a way to tell ADF Faces when the dialog process finishes, that is, when the user dismisses the dialog. Generally, you do this programmatically or declaratively via a command component. Example: Cancel Button on the Error Page shows how to accomplish this programmatically via a Cancel button on the Error page.

Cancel Button on the Error Page

<af:commandButton text="Cancel"
                  actionListener="#{backing_error.cancel}" />

The actionListener attribute on commandButton specifies a reference to an action listener method in the page's backing bean, Error.java. The action listener method processes the action event that is generated when the Cancel button is clicked. You call the AdfFacesContext.returnFromDialog() method in this action listener method, as shown in Example: Action Listener Method for the Cancel Button in a Backing Bean.

Action Listener Method for the Cancel Button in a Backing Bean

public void cancel(ActionEvent actionEvent)
{
  AdfFacesContext.getCurrentInstance().returnFromDialog(null, null);
}


Note:

The AdfFacesContext.returnFromDialog() method returns null. This is all that is needed in the backing bean to handle the Cancel action event.

To accomplish the same declaratively on the Account Details dialog page, attach a af:returnActionListener tag to the Cancel button component, as shown in Example: Cancel Button on the Account Details Page. The af:returnActionListener tag calls the returnFromDialog method on the AdfFacesContext—no backing bean code is needed.

Cancel Button on the Account Details Page

<af_commandButton text="Cancel" immediate="true">
  <af:returnActionListener/>
</af:commandButton>

No attributes are used with the af:returnActionListener tag. The immediate attribute on commandButton is set to true: if the user clicks Cancel without entering values in the required Password and Confirm Password fields, the default JSF ActionListener can execute during the Apply Request Values phase instead of the Invoke Application phase, thus bypassing input validation.

The New Account page and Account Details page belong in the same dialog process. A dialog process can have as many pages as you desire, but you only need to call AdfFacesContext.returnFromDialog() once.

The same af:returnActionListener tag or AdfFacesContext.returnFromDialog() method can also be used to end a process and return a value from the dialog. For example, when the user clicks Done on the Account Details page, the process ends and returns the user input values. Example: Done Button on the Account Details Page shows the code for the Done button.

Done Button on the Account Details Page

<af:commandButton text="Done"
                  actionListener="#{backing_new_account.done}" />

The actionListener attribute on commandButton specifies a reference to an action listener method in the page's backing bean, New_account.java. The action listener method processes the action event that is generated when the Done button is clicked. Example: Action Listener Method for the Done Button in a Backing Bean shows the code for the action listener method, where the return value is retrieved, and then returned via the AdfFacesContext.returnFromDialog() method.

Action Listener Method for the Done Button in a Backing Bean

public void done(ActionEvent e)
{
  AdfFacesContext afContext = AdfFacesContext.getCurrentInstance();
  String firstname = afContext.getProcessScope().get("firstname").toString();
  String lastname = afContext.getProcessScope().get("lastname").toString();
  String street = afContext.getProcessScope().get("street").toString();
  String zipCode = afContext.getProcessScope().get("zipCode").toString();
  String country = afContext.getProcessScope().get("country").toString();
  String password = afContext.getProcessScope().get("password").toString();
  String confirmPassword =
   afContext.getProcessScope().get("confirmPassword").toString();
  if (!password.equals(confirmPassword))
  {
    FacesMessage fm = new FacesMessage();
    fm.setSummary("Confirm Password");
    fm.setDetail("You've entered an incorrect password. Please verify that you've
     entered a correct password!");
    FacesContext.getCurrentInstance().addMessage(null, fm);
  }
  else
  {
    //Get the return value 
    Customer cst = new Customer();
    cst.setFirstName(firstname);
    cst.setLastName(lastname);
    cst.setStreet(street);
    cst.setPostalCode(zipCode);
    cst.setCountry(country);
    cst.setPassword(password);
    // And return it
    afContext.getCurrentInstance().returnFromDialog(cst, null);
    afContext.getProcessScope().clear();
  }
}

The AdfFacesContext.returnFromDialog() method lets you send back a return value in the form of a java.lang.Object or a java.util.Map of parameters. You don't have to know where you're returning the value to—ADF Faces automatically takes care of it.

What Happens at Runtime

The AdfFacesContext.returnFromDialog() method tells ADF Faces when the user dismisses the dialog. This method can be called whether the dialog page is shown in a popup dialog or in the main window. If a popup dialog is used, ADF Faces automatically closes it.

In the sample application, when the user clicks the Cancel button on the Error page or Account Details page, ADF Faces calls AdfFacesContext.returnFromDialog(), (which returns null), closes the popup dialog, and returns to the originating page.

The first page in the new user dialog process is the New Account page. When the Details button on the New Account page is clicked, the application shows the Account Details dialog page in the same popup dialog (because useWindow="false"), after preserving the state of the New Account page.

When the Done button on the Account Details page is clicked, ADF Faces closes the popup dialog and AdfFacesContext.returnFromDialog() returns cst to the originating page.

When the dialog is dismissed, ADF Faces generates a return event (ReturnEvent). The AdfFacesContext.returnFromDialog() method sends a return value as a property of the return event. The return event is delivered to the return listener (ReturnListener) that is registered on the command component that launched the dialog (which would be the New User commandLink on the Login page). How you would handle the return value is described in Handling the Return Value.

Handling the Return Value

To handle a return value, you register a return listener on the command component that launched the dialog, which would be the New User link component on the Login page in the sample application. Example: New User Command Link on the Login Page shows the code for the New User link component.

New User Command Link on the Login Page

<af:commandLink id="cmdLink" text="New User?"
                action="dialog:newAccount"
                useWindow="true" partialSubmit="true"
                returnListener="#{backing_login.handleReturn}"
                windowHeight="200" windowWidth="500" />

The returnListener attribute on commandLink specifies a reference to a return listener method in the page's backing bean, Login.java. The return listener method processes the return event that is generated when the dialog is dismissed. Example: Return Listener Method for the New User Link in a Backing Bean shows the code for the return listener method that handles the return value.

Return Listener Method for the New User Link in a Backing Bean

public void handleReturn(ReturnEvent event)
{
  if (event.getReturnValue() != null)
  {
    Customer cst;
    String name;
    String psw;
    cst = (Customer)event.getReturnValue();
    name = cst.getFirstName();
    psw = cst.getPassword();
    CustomerList.getCustomers().add(cst);
    inputText1.setSubmittedValue(null);
    inputText1.setValue(name);
    inputText2.setSubmittedValue(null);
    inputText2.setValue(psw);
  }
}

You use the getReturnValue() method to retrieve the return value, because the return value is automatically added as a property of the ReturnEvent.

What Happens at Runtime

In the sample application, when ADF Faces delivers a return event to the return listener registered on the commandLink component, the handleReturn() method is called and the return value is processed accordingly. The new user is added to a customer list, and as a convenience to the user any previously submitted values in the Login page are cleared and the input fields are populated with the new information.

Passing a Value into a Dialog

The AdfFacesContext.returnFromDialog() method lets you send a return value back from a dialog. Sometimes you might want to pass a value into a dialog. To pass a value into a dialog, you use a launch listener (LaunchListener).

In the sample application, a new user can enter a name in the Username field on the Login page, and then click the New User link. When the New Account dialog page displays in a popup dialog, the First Name input field is automatically populated with the name that was entered in the Login page. To accomplish this, you register a launch listener on the command component that launched the dialog (which would be commandLink). Example: Input Field and New User Command Link on the Login Page shows the code for the commandLink component.

Input Field and New User Command Link on the Login Page

<af:inputText label="Username" value="#{backing_login.username}"/>
<af:commandLink id="cmdLink" text="New User?"
                action="dialog:newAccount"
                useWindow="true" partialSubmit="true"
                launchListener="#{backing_login.handleLaunch}"
                returnListener="#{backing_login.handleReturn}"
                windowHeight="200" windowWidth="500" />

The LaunchListener attribute on commandLink specifies a reference to a launch listener method in the page's backing bean, Login.java. In the launch listener method you use the getDialogParameters() method to add a parameter to a Map using a key-value pair. Example: Launch Listener Method for the New User Command Link in a Backing Bean shows the code for the launch listener method.

Launch Listener Method for the New User Command Link in a Backing Bean

public void handleLaunch(LaunchEvent event)
{
  //Pass the current value of the field into the dialog
  Object usr = username;
  event.getDialogParameters().put("firstname", usr);
}
// Use by inputText value binding 
public String username;
public String getUsername()
{
  return username;
}
public void setUsername(String username)
{
  this.username = username;
}

To show the parameter value in the New Account dialog page, use the ADF Faces processScope to retrieve the key and value via a special EL expression in the format #{processScope.someKey}, as shown in Example: Input Field on the New Account Page.

Input Field on the New Account Page

<af:inputText label="First name" value="#{processScope.firstname}"/>


Note:

You can use processScope with all JSF components, not only with ADF Faces components.

What Happens at Runtime

When a command component is about to launch a dialog (assuming all conditions have been met), ADF Faces queues a launch event. This event stores information about the component that is responsible for launching a dialog, and the root of the component tree to display when the dialog process starts. Associated with a launch event is a launch listener, which takes the launch event as a single argument and processes the event as needed.

In the sample application, when ADF Faces delivers the launch event to the launch listener registered on the commandLink component, the handleLaunch() method is called and the event processed accordingly.

In ADF Faces, a process always gets a copy of all the values that are in the processScope of the page from which a dialog is launched. When the getDialogParameters() method has added parameters to a Map, those parameters also become available in processScope, and any page in the dialog process can get the values out of processScope by referring to the processScope objects via EL expressions.

Unlike sessionScope, processScope values are visible only in the current "page flow" or process. If the user opens a new window and starts navigating, that series of windows has its own process; values stored in each window remain independent. Clicking on the browser's Back button automatically resets processScope to its original state. When you return from a process the processScope is back to the way it was before the process started. To pass values out of a process you would use AdfFacesContext.returnFromDialog(), sessionScope or applicationScope.

How the SRDemo Popup Dialogs Are Created

The SRDemo application uses a popup dialog to:

In the Create New Service Request page (see Figure: First Page of the Create New Service Request Process (SRCreate.jspx)), when the user clicks the Frequently Asked Questions link, the application displays a popup dialog showing the FAQ list.

In the Edit Service Request page, when the user clicks the flashlight icon next to the Assigned to label (see Figure: Edit Service Request Page (SREdit.jspx) with an Unassigned Request), the application displays the Search for Staff popup dialog. In the dialog (as shown in Figure: Search for Staff Popup Dialog (SRStaffSearch.jspx)), the user first makes a search based on user role. Then in the results section, the user clicks the radio button next to a name and clicks Select.

Search for Staff Popup Dialog (SRStaffSearch.jspx)

Search form to search staff by role

After making a selection, the popup dialog closes and the application returns to the Edit Service Request page where the Assigned to display-only fields are now updated with the selected technician's first name and last name, as shown in Figure: Edit Service Request Page (SREdit.jspx) With an Assigned Request.

Edit Service Request Page (SREdit.jspx) With an Assigned Request

Edit Service Request page showing assigned request

To reiterate, the tasks for supporting a popup dialog are (not listed in any particular order):

  1. Create the JSF navigation rules with dialog: outcomes.

  2. Create the page that launches the dialog via a dialog: action outcome.

  3. Create the dialog page and return a value.

  4. Handle the return value.

Firstly, the JSF navigation rules for launching dialogs are shown in Example: Dialog Navigation Rules in the faces-config.xml File. The navigation case for showing the dialog page SRStaffSearch.jspx is defined by the dialog:StaffSearch outcome; the navigation case for showing the SRFaq.jspx dialog page is defined by the dialog:FAQ outcome.

Dialog Navigation Rules in the faces-config.xml File

<navigation-rule>
  <from-view-id>/app/staff/SREdit.jspx</from-view-id>
  ...
  <navigation-case>
    <from-outcome>dialog:StaffSearch</from-outcome>
    <to-view-id>/app/staff/SRStaffSearch.jspx</to-view-id>
  </navigation-case>
</navigation-rule>
<navigation-rule>
  <from-view-id>/app/SRCreate.jspx</from-view-id>
  <navigation-case>
    <from-outcome>dialog:FAQ</from-outcome>
    <to-view-id>/app/SRFaq.jspx</to-view-id>
  </navigation-case>
  ...
</navigation-rule>

Secondly, the pages that launch popup dialogs are SREdit.jspx and SRCreate.jspx. In both pages the useWindow attribute on the commandLink component is set to true, which is a precondition for ADF Faces to know that it has to launch a popup dialog.

Example: CommandLink Component for Launching the SRStaffSearch Dialog Page shows the commandLink component on the page that launches the SRStaffSearch.jspx dialog page. The commandLink component has the static action outcome dialog:StaffSearch.

CommandLink Component for Launching the SRStaffSearch Dialog Page

<af:commandLink id="staffLOVLink" action="dialog:StaffSearch"
                useWindow="true" immediate="true"
                partialSubmit="true"
                returnListener="#{backing_SREdit.handleStaffLOVReturn}"..>
  <af:objectImage height="24" width="24"
                  source="/images/searchicon_enabled.gif"/>
</af:commandLink>

Example: CommandLink Component for Launching the SRFaq Dialog Page shows the commandLink component on the page that launches the SRFaq.jspx dialog page. The commandLink component has the static action outcome dialog:SRFaq.

CommandLink Component for Launching the SRFaq Dialog Page

<af:commandLink action="dialog:FAQ" 
                text="#{res['srcreate.faqLink']}"
                useWindow="true"
                immediate="true"
                partialSubmit="true"/>

Thirdly, the dialog pages SRStaffSearch.jspx and SRFaq.jspx have to call the AdfFacesContext.returnFromDialog() method to let ADF Faces know when the user dismisses the dialogs. In SRStaffSearch.jspx, which uses a table component with a tableSelectOne component to display the names for selection, the AdfFacesContext.returnFromDialog() method is called when the user clicks the Select commandButton component after selecting the radio button for a technician in the table. The action attribute on commandButton is bound to the selectButton_action action method in the page's backing bean (SRStaffSearch.java); the action method retrieves the selected row data from the table, extracts the User object, and then returns the object via the AdfFacesContext.returnFromDialog() method. Example: Action Method for the Select Command Button shows the code snippets for the Select button component and its action method.

Action Method for the Select Command Button

<af:tableSelectOne>
  <af:commandButton text="#{res['srstaffsearch.button.select']}"
                    action="#{backing_SRStaffSearch.selectButton_action}"/>
</af:tableSelectOne>

...
...
public String selectButton_action() {
        
  //get row data from table
  JUCtrlValueBindingRef selectedRowData = 
   (JUCtrlValueBindingRef)this.getResultsTable().getSelectedRowData();
  RowImpl row = (RowImpl)selectedRowData.getRow();
  User staffMember = (User)row.getDataProvider();
                
  // And return it
  AdfFacesContext.getCurrentInstance().returnFromDialog(staffMember, null);
  // no navigation to another page and thus null is returned
  return null;
}

Similarly in SRFaq.jspx, a commandLink component is used to close the dialog and call the AdfFacesContext.returnFromDialog() method. The af:returnActionListener tag calls the returnFromDialog method on the AdfFacesContext—backing bean code is not needed. Example: CommandLink Component for Closing the SRFaq Popup Dialog shows the code snippet for the commandLink. When the user dismisses the SRFaq.jspx popup dialog, ADF Faces simply closes the dialog. No dialog return value is sent, so there's no need to handle a return value.

CommandLink Component for Closing the SRFaq Popup Dialog

<af:commandLink text="#{res['srdemo.close']}">
  <af:returnActionListener/>
</af:commandLink>

When the SRStaffSearch.jspx popup dialog is dismissed, a dialog return value (that is, the selected row data) is sent as a property of the return event (ReturnEvent). The return event is delivered to the return listener registered on the commandLink component of the originating page SREdit.jspx, as shown in Example: Return Listener Method for Handling the Return Value. The returnListener attribute on commandLink is bound to the handleStaffLOVReturn listener method in the page's backing bean (SREdit.java). The return listener method handles the return value from the dismissed dialog. Example: Return Listener Method for Handling the Return Value also shows the code snippet for the handleStaffLOVReturn listener method.

Return Listener Method for Handling the Return Value

<af:commandLink id="staffLOVLink" action="dialog:StaffSearch"
                useWindow="true" immediate="true"
                partialSubmit="true"
                returnListener="#{backing_SREdit.handleStaffLOVReturn}"..>
  <af:objectImage height="24" width="24"
                  source="/images/searchicon_enabled.gif"/>
</af:commandLink>
...
...
  public void handleStaffLOVReturn(ReturnEvent event) {
    //Get the return value from the pop up
    User returnedStaffMember = (User)event.getReturnValue();
 
    if (returnedStaffMember != null) {
      DCBindingContainer bc = (DCBindingContainer)getBindings();

      // Get the handle to the Service Request we are editing
      DCControlBinding thisSRId = 
        (DCControlBinding)bc.getControlBinding("svrId");
      RowImpl srRowImpl = (RowImpl)thisSRId.getCurrentRow();
      ServiceRequest thisSR = (ServiceRequest)srRowImpl.getDataProvider();
 
      //See if a different user has been selected? 
      User oldUser = thisSR.getAssignedTo();
      if ((oldUser == null) || (!oldUser.equals(returnedStaffMember))) {

        //Set the returned Staff member from the LOV
        thisSR.setAssignedTo(returnedStaffMember);
 
        //now re-execute the iterator to refresh the screen
        DCControlBinding accessorData = 
          (DCControlBinding)bc.getControlBinding("assignedToFirstName");
        accessorData.getDCIteratorBinding().executeQuery();
 
        //Now reset the Assigned date
        ADFUtils.setPageBoundAttributeValue(getBindings(), "assignedDate", 
                                     new Timestamp(System.currentTimeMillis()));
 
        //And get the data field to update with the new bound value
               this.getAssignedDate().resetValue();                                                                                     
 
      }
    }
  }

What You May Need to Know About ADF Faces Dialogs

The ADF Faces dialog framework has these known limitations:

Other Information

The ADF Faces select input components (such as selectInputText and selectInputDate) also have built-in dialog support. These components automatically handle launching a page in a popup dialog, and receiving the return event. For example, when you use selectInputText to launch a dialog, all you have to do is to set the action attribute to a dialog: outcome, and specify the width and height of the dialog. When the user dismisses the dialog, the return value from the dialog is automatically used as the new value of the input component. You would still need to define a JSF navigation rule with the dialog: outcome, create the dialog page, and create the dialog page's backing bean to handle the action events.

Besides being able to launch popup dialogs from action events, you can also launch popup dialogs from value change events and poll events. For example, you can programmatically launch a dialog (without a JSF navigation rule) by using the AdfFacesContext.launchDialog() method in a value change listener method or poll listener method.

If you're a framework or component developer you can enable a custom renderer to launch a dialog and handle a return value, or add LaunchEvent and ReturnEvent events support to your custom ActionSource components. For details about the DialogService API that you can use to implement dialogs, see the ADF Faces Javadoc for oracle.adf.view.faces.context.DialogService. See also the ADF Faces Developer's Guide for further information about supporting dialogs in custom components and renderers.

Enabling Partial Page Rendering

ADF Faces components use partial page rendering (PPR), which allows small areas of a page to be refreshed without the need to redraw the entire page. PPR is the same as AJAX-style browser user interfaces that update just parts of the page for a more interactive experience. PPR is currently supported on the following browsers:

On all other platforms, ADF Faces automatically uses full page rendering You don't need to disable PPR or write code to support both cases.

Most of the time you don't have to do anything to enable PPR because ADF Faces components have built-in support for PPR. For example, in the SRSearch.jspx page, the Results section of the page uses a showOneTab component with two showDetailItem components to let the user display either a summary view or detail view of the search results. Figure: Search Page (SRSearch.jspx) with the Summary Result View Selected shows the Results section with the Summary View selected. When the user clicks Detail View, only the portion of the page that is below the Results title will refresh.

Search Page (SRSearch.jspx) with the Summary Result View Selected

Search page showing closed service requests summary table

At times you want to explicitly refresh parts of a page yourself. For example, you may want an output component to display what a user has chosen or entered in an input component, or you may want a command link or button to update another component. Three main component attributes can be used to enable partial page rendering:

How to Enable PPR

The SREdit.jspx page of the SRDemo application uses partial page submits and partial triggers to support PPR.

Figure: Edit Service Request Page (SREdit.jspx) with an Unassigned Request shows the SREdit.jspx page with an unassigned service request. When the user clicks the flashlight icon (which is a commandLink component with an objectImage component), a popup dialog displays to allow the user to search and select a name. After selecting a name, the popup dialog closes and the Assigned to display-only fields (outputText components) and the date field below Status (selectInputDate component) are refreshed with the appropriate values; other parts of the edit page are not refreshed.

Edit Service Request Page (SREdit.jspx) with an Unassigned Request

Edit form for service requests

To enable a command component to partially refresh another component:

  1. On the trigger command component, set the id attribute to a unique value, and set the partialSubmit attribute to true.

  2. On the target component that you want to partially refresh when the trigger command component is activated, set the partialTriggers attribute to the id of the command component.


Tip:

A component's unique ID must be a valid XML name, that is, you cannot use leading numeric values or spaces in the ID. JSF also does not permit colons ( : ) in the ID.

Example: Code for Enabling Partial Page Rendering Through a Partial Submit shows the code snippets for the command and read-only output components used in the SREdit.jspx page to illustrate PPR.

Code for Enabling Partial Page Rendering Through a Partial Submit

<af:panelLabelAndMessage label="#{res['sredit.assignedTo.label']}">
  <af:panelHorizontal>
    <af:outputText value="#{bindings.assignedToFirstName.inputValue}"
                   partialTriggers="staffLOVLink"/>
    <af:outputText value="#{bindings.assignedToLastName.inputValue}"
                  partialTriggers="staffLOVLink"/>
    <af:commandLink id="staffLOVLink" action="dialog:StaffSearch"
                    useWindow="true" immediate="true"
                    partialSubmit="true"
                    returnListener="#{backing_SREdit.handleStaffLOVReturn}"
                    partialTriggers="status"
                    disabled="#{bindings.ServiceRequeststatus.inputValue==2}">
      <af:objectImage height="24" width="24"
                      source="/images/searchicon_enabled.gif"/>
    </af:commandLink>
    <f:facet name="separator">
      <af:objectSpacer width="4" height="10"/>
    </f:facet>
  </af:panelHorizontal>
</af:panelLabelAndMessage>

Tip:

The partialTriggers attribute on a target component can contain the id of one or more trigger components. Use spaces to separate multiple ids.

What Happens at Runtime

ADF Faces command buttons and links can generate partial events. The partialSubmit attribute on commandButton or commandLink determines whether a partial page submit is used to perform an action or not. When partialSubmit is true, ADF Faces performs the action through a partial page submit. Thus you can use a command button or link to update a portion of a page, without having to redraw the entire page upon a submit. By default the value of partialSubmit is false, which means full page rendering is used in response to a partial event. Full page rendering is also automatically used when partial page rendering is not supported in the client browser or platform or when navigating to another page.

In the example, the partialTriggers attributes on the Assigned to display-only outputText components are set to the id of the commandLink component. When the commandLink component fires a partial event, the output components (which are listening for partial events from commandLink) know to refresh their values via partial page rendering.

What You May Need to Know About PPR and Screen Readers

Screen readers do not reread the full page in a partial page request. PPR causes the screen reader to read the page starting from the component that fired the partial action. Hence, you should place the target components after the component that fires the partial request; otherwise the screen reader would not read the updated targets.

Creating a Multipage Process

If you have a set of pages that should be visited in a particular order, consider using the processTrain and processChoiceBar components to show the multipage process. In the SRDemo application, the SRCreate.jspx and SRCreateConfirm.jspx pages use a processTrain and processChoiceBar component to let a user create a new service request.

When rendered, the processTrain component shows the total number of pages in the process as well as the page where the user is currently at, and allows the user to navigate between those pages. For example, Figure: First Page of the Create New Service Request Process (SRCreate.jspx) shows the first page in the create service request process, where the user selects one appliance from a listbox and enters a description of the problem in a textbox.