Skip Headers
Oracle® Application Development Framework Developer's Guide
10g Release 3 (10.1.3.0)

Part Number B28967-02
Go to Documentation Home
Home
Go to Table of Contents
Contents
Go to Index
Index
Go to Feedback page
Contact Us

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

11 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:

11.1 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:

11.2 Using Dynamic Menus for Navigation

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

Figure 11-1 Dynamic Navigation Menus in the SRDemo Application

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

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

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

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

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

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

Note:

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

11.2.1 How to Create Dynamic Navigation Menus

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

To create dynamic navigation menus:

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

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

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

11.2.1.1 Creating a Menu Model

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

To create a menu model:

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

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

    Example 11-1 MenuItem.java for All Menu Items

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

    Note:

    The type property defines a menu item as global or nonglobal. Global items can be accessed from any page in the application. For example, a Help button on a page is a global item.
  2. Configure a managed bean for each menu item or page in the hierarchy, with values for the properties that require setting at instantiation.

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

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

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

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

    Note:

    As you see in Figure 11-1, the Management menu tab has a menu bar with two items: Overview and Technician Skills. As each menu item has its own page or managed bean, so the two items are represented by these managed beans, respectively: subMenuItem_Manage_Reporting and subMenuItem_Manage_ProdEx. The Management menu tab is represented by the menuItem_Manage managed bean, which uses value binding expressions (such as #{subMenuItem_Manage_ProdEx}) inside the list value elements to reference the children managed beans.
  3. Create a class that constructs a ChildPropertyTreeModel instance. The instance represents the entire tree hierarchy of the menu system, which is later injected into a menu model. Example 11-3 shows the MenuTreeModelAdapter class used in the SRDemo application.

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

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

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

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

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

    Example 11-5 MenuModelAdapter.java

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

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

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

    <!-- create the main menu menuModel -->
    <managed-bean>
      <managed-bean-name>menuModel</managed-bean-name>
      <managed-bean-class>
        oracle.srdemo.view.menu.MenuModelAdapter</managed-bean-class>
      <managed-bean-scope>session</managed-bean-scope>
      <managed-property>
        <property-name>viewIdProperty</property-name>
        <value>viewId</value>
      </managed-property>
      <managed-property>
        <property-name>instance</property-name>
        <value>#{menuTreeModel.model}</value>
      </managed-property>
    </managed-bean>
    
11.2.1.1.1 What You May Need to Know About Chaining Managed Beans

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

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

Table 11-1 Combinations of Managed Bean Scopes Allowed

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

none

none

application

none, application

session

none, application, session

request

none, application, session, request


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

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

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

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

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

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

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

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

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

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

Tip:

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

11.2.1.2 Creating the JSF Page for Each Menu Item

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

Example 11-9 MenuTabs Component Bound to a Menu Model

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

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

Example 11-10 NodeStamp Facet and CommandMenuItem Component

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

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

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

Example 11-11 Rendered and Disabled Menu Item Components

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

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

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

Example 11-12 PanelPage Component with Menu Facets

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

Tip:

If your menu system uses menu bars as the first level, then the startDepth on menuBar should be set to zero, and so on.
11.2.1.2.1 What You May Need to Know About the PanelPage and Page Components

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

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

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

11.2.1.3 Creating the JSF Navigation Rules

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

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

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

11.2.2 What Happens at Runtime

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

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

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

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

Example 11-14 MenuBar Component Bound to a Menu Model

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

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

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

11.2.3 What You May Need to Know About Menus

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

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

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

Menu tab with five menu bar items.

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

Example 11-15 MenuBar Component with Children CommandMenuItem Components

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

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

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

11.3 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 11-3 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.

Figure 11-3 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 11-4.

Figure 11-4 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 11-5.

Figure 11-5 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 11-6.

Figure 11-6 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 11-7. 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.

Figure 11-7 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 11-8. The new user can then proceed to enter the new password and log in successfully.

Figure 11-8 Login Page With the Username Field Populated

Login screen with username field populated

11.3.1 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:

  • There must be a JSF navigation rule with an outcome that begins with "dialog:".

  • The command component's action outcome must begin with "dialog:".

  • The useWindow attribute on the command component must be "true".

  • The client device must support popup dialogs.

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.

11.3.1.1 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 11-3, 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 11-16 shows the navigation rule for the three navigation cases from the Login page (login.jspx).

Example 11-16 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>
11.3.1.1.1 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.

11.3.1.2 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 11-17 shows the code for the Login commandButton component.

Example 11-17 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 11-18.

Example 11-18 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 11-19 shows the code for the New User commandLink component that uses a static action outcome.

Example 11-19 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:.

11.3.1.2.1 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 Section 11.3.1.5, "Passing a Value into a Dialog".

11.3.1.3 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 11-20 shows how to accomplish this programmatically via a Cancel button on the Error page.

Example 11-20 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 11-21.

Example 11-21 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 11-22. The af:returnActionListener tag calls the returnFromDialog method on the AdfFacesContext—no backing bean code is needed.

Example 11-22 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 11-23 shows the code for the Done button.

Example 11-23 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 11-24 shows the code for the action listener method, where the return value is retrieved, and then returned via the AdfFacesContext.returnFromDialog() method.

Example 11-24 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.

11.3.1.3.1 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 Section 11.3.1.4, "Handling the Return Value".

11.3.1.4 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 11-25 shows the code for the New User link component.

Example 11-25 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 11-26 shows the code for the return listener method that handles the return value.

Example 11-26 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.

11.3.1.4.1 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.

11.3.1.5 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 11-27 shows the code for the commandLink component.

Example 11-27 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 11-28 shows the code for the launch listener method.

Example 11-28 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 11-29.

Example 11-29 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.
11.3.1.5.1 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.

11.3.2 How the SRDemo Popup Dialogs Are Created

The SRDemo application uses a popup dialog to:

  • Display a list of frequently asked questions (FAQ).

  • Select and assign a technician to an open service request.

In the Create New Service Request page (see Figure 11-13), 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 11-12), the application displays the Search for Staff popup dialog. In the dialog (as shown in Figure 11-9), 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.

Figure 11-9 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 11-10.

Figure 11-10 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 11-30. 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.

Example 11-30 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 11-31 shows the commandLink component on the page that launches the SRStaffSearch.jspx dialog page. The commandLink component has the static action outcome dialog:StaffSearch.

Example 11-31 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 11-32 shows the commandLink component on the page that launches the SRFaq.jspx dialog page. The commandLink component has the static action outcome dialog:SRFaq.

Example 11-32 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 11-33 shows the code snippets for the Select button component and its action method.

Example 11-33 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 11-34 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.

Example 11-34 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 11-35. 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 11-35 also shows the code snippet for the handleStaffLOVReturn listener method.

Example 11-35 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();                                                                                     
 
      }
    }
  }

11.3.3 What You May Need to Know About ADF Faces Dialogs

The ADF Faces dialog framework has these known limitations:

  • Does not support the use of </redirect> in navigation rules that may launch dialog pages in new popup dialogs. You can, however, use </redirect> in navigation rules that launch dialog pages within the same window.

  • Cannot detect popup blockers. If you use popup dialogs in your web application, tell your users to disable popup blocking for your site.

11.3.4 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.

11.4 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 11-11 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.

Figure 11-11 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:

11.4.1 How to Enable PPR

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

Figure 11-12 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.

Figure 11-12 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 11-36 shows the code snippets for the command and read-only output components used in the SREdit.jspx page to illustrate PPR.

Example 11-36 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.

11.4.2 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.

11.4.3 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.

11.5 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 11-13 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. The number of nodes (circles) in the train indicates the total number of predefined pages in the process; the solid node indicates that the user is currently working on that page in the process. To go to the next page in the process, the user clicks the active text link below the node.

Figure 11-13 First Page of the Create New Service Request Process (SRCreate.jspx)

Create service request page to enter product and description

Note that the illustrations in this chapter use the Oracle skin and not the SRDemo skin.

The processChoiceBar component renders a dropdown menu for selecting a page in the process, and where applicable, one or more buttons for navigating forward and backward in the process.

On the first page in the create service request process, when the user clicks the Confirm text link or the Continue button, or selects Confirm from the dropdown menu, the application displays the second page of the process, as shown in Figure 11-14.

Figure 11-14 Second Page of the Create New Service Request Process (SRCreateConfirm.jspx)

Confirmation page for creating service request

From the second page, the user can return to the problem description page by clicking Basic Problem Details in the train or clicking the Back button, or by selecting Basic Problem Details from the dropdown menu.

If done the user clicks Submit Request, and the application displays the Request Submitted page, as shown in Figure 11-15.

Figure 11-15 Request Submitted Page (SRCreateDone.jspx)

Request Submitted page for creating service request

11.5.1 How to Create a Process Train

To display a process train on each page, you bind the processTrain component to a process train model. At runtime the train model dynamically creates the train for each page in the process.

To create and use a process train:

  1. Create a process train model. (See Section 11.5.1.1, "Creating a Process Train Model")

  2. Create the JSF page for each node in the train. (See Section 11.5.1.2, "Creating the JSF Page for Each Train Node")

  3. Create a navigation rule that has navigation cases for each node. (See Section 11.5.1.3, "Creating the JSF Navigation Rules")

11.5.1.1 Creating a Process Train Model

Use the oracle.adf.view.faces.model.MenuModel class and the oracle.adf.view.faces.model.ProcessMenuModel class to create a process train model that dynamically generates a process train. The MenuModel class is the same menu model mechanism that is used for creating menu tabs and menu bars, as described in Section 11.2.1, "How to Create Dynamic Navigation Menus".

To create a process train model:

  1. Create a class that can get and set the properties for each node in the process train.

    Each node in the train needs to have a label, a viewId and an outcome property. Example 11-37 shows part of the MenuItem class used in the SRDemo application.

    Example 11-37 MenuItem.java for Process Train Nodes

    package oracle.srdemo.view.menu;
    public class MenuItem {
        private String _label          = null;
        private String _outcome        = null;
        private String _viewId         = 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
    }
    
  2. Configure a managed bean for each node in the train, with values for the properties that require setting at instantiation.

    Each bean should be an instance of the class you create in step 1. Example 11-38 shows the managed bean code for the process train nodes in faces-config.xml.

    Example 11-38 Managed Beans for Process Train Nodes in the faces-config.xml File

    <!--First train node -->
    <managed-bean>
      <managed-bean-name>createTrain_Step1</managed-bean-name>  
      <managed-bean-class>oracle.srdemo.view.menu.MenuItem</managed-bean-class>
      <managed-bean-scope>application</managed-bean-scope> 
      <managed-property>
        <property-name>label</property-name>
        <value>#{resources['srcreate.train.step1']}</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>
    
    <!-- Second train node-->
    <managed-bean>
      <managed-bean-name>createTrain_Step2</managed-bean-name>  
      <managed-bean-class>oracle.srdemo.view.menu.MenuItem</managed-bean-class>
      <managed-bean-scope>application</managed-bean-scope> 
      <managed-property>
        <property-name>label</property-name>
        <value>#{resources['srcreate.train.step2']}</value>
      </managed-property>
      <managed-property>
        <property-name>viewId</property-name>
        <value>/app/SRCreateConfirm.jspx</value>
      </managed-property>
      <managed-property>
        <property-name>outcome</property-name>
        <value>Continue</value>
      </managed-property>
    </managed-bean>
    
  3. Configure a managed bean that is an instance of a list with application as its scope.

    The list entries are the train node managed beans you create in step 2, listed in the order that they should appear on the train. Example 11-39 shows the managed bean code for creating the process train list.

    Example 11-39 Managed Bean for Process Train List in the faces-config.xml File

    <!-- create the list to pass to the train model -->
    <managed-bean>
      <managed-bean-name>createTrainNodes</managed-bean-name>
      <managed-bean-class>java.util.ArrayList</managed-bean-class>
      <managed-bean-scope>application</managed-bean-scope>
      <list-entries>
        <value-class>oracle.srdemo.view.menu.MenuItem</value-class>
        <value>#{createTrain_Step1}</value>
        <value>#{createTrain_Step2}</value>
      </list-entries>
    </managed-bean>
    
  4. Create a class to facilitate the construction of a ProcessMenuModel instance. This class must have at least two properties, viewIdProperty and instance.

    Example 11-40 shows the TrainModelAdapter class used in the SRDemo application.

    Example 11-40 TrainModelAdapter.java for Holding the Process Train Nodes

    package oracle.srdemo.view.menu;
    import oracle.adf.view.faces.model.MenuModel;
    import oracle.adf.view.faces.model.ProcessMenuModel;
    ...
    public class TrainModelAdapter implements Serializable {
        private String _propertyName = null;
        private Object _instance = null;
        private transient MenuModel _model = null;
        private Object _maxPathKey = null;
        public MenuModel getModel() throws IntrospectionException {
            if (_model == null)
            {
              _model = new ProcessMenuModel(getInstance(), 
                                            getViewIdProperty(),
                                            getMaxPathKey());
            }
            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 Object getMaxPathKey()
        {
          return _maxPathKey;
        }
        public void setMaxPathKey(Object maxPathKey)
        {
          _maxPathKey = maxPathKey;
        } 
    }
    

    If you wish to write your own menu model instead of using ProcessMenuModel, you can use ProcessUtils to implement the PlusOne or MaxVisited behavior for controlling page access. For information about how to control page access using those process behaviors, see Section 11.5.1.1.1, "What You May Need to Know About Controlling Page Access".

  5. Configure a managed bean to reference the class you create in step 4. This is the bean to which the processTrain component is bound.

    The bean should be instantiated to have the instance property value set to the managed bean that creates the train list (as configured in step 3). The instantiated bean should also have the viewIdProperty value set to the viewId property on the bean created in step 1. Example 11-41 shows the managed bean code for creating the process train model.

    Example 11-41 Managed Bean for Process Train Model in the faces-config.xml File

    <!-- create the train menu model -->
    <managed-bean>
      <managed-bean-name>createTrainMenuModel</managed-bean-name>
      <managed-bean-class>
        oracle.srdemo.view.menu.TrainModelAdapter</managed-bean-class>
      <managed-bean-scope>application</managed-bean-scope>
      <managed-property>
        <property-name>viewIdProperty</property-name>
        <value>viewId</value>
      </managed-property>
      <managed-property>
        <property-name>instance</property-name>
        <value>#{createTrainNodes}</value>
      </managed-property>
    </managed-bean>
    
11.5.1.1.1 What You May Need to Know About Controlling Page Access

When you want to control the pages users can access based on the page they are currently on, you can use one of two process scenarios provided by ADF Faces, namely Max Visited or Plus One.

Suppose there are five pages or nodes in a process train, and the user has navigated from page 1 to page 4 sequentially. At page 4 the user jumps back to page 2. Where the user can go next depends on which process scenario is used.

In the Max Visited process, from the current page 2 the user can go back to page 1, go ahead to page 3, or jump ahead to page 4. That is, the Max Visited process allows the user to return to a previous page or advance to any page up to the furthest page already visited. The user cannot jump ahead to page 5 from page 2 because page 5 has not yet been visited.

Given the same situation, in the Plus One process the user can only go ahead to page 3 or go back to page 1. That is, the Plus One process allows the user to return to a previous page or to advance one node in the train further than they are on currently. The user cannot jump ahead to page 4 even though page 4 has already been visited.

If you were to use the Max Visited process, you would add code similar to the next code snippet, for the createTrainMenuModel managed bean (see Example 11-41) in faces-config.xml:

<managed-property>
  <property-name>maxPathKey</property-name>
  <value>TRAIN_DEMO_MAX_PATH_KEY</value>
</managed-property>

ADF Faces knows to use the Max Visited process because a maxPathKey value is passed into the ProcessMenuModel (see Example 11-40).

The Create New Service Request process uses the Plus One process because faces-config.xml doesn't have the maxPathKey managed-property setting, thus null is passed for maxPathKey. When null is passed, ADF Faces knows to use the PlusOne process.

The process scenarios also affect the immediate and readOnly attributes of the command component used within a processTrain component. For information, see Section 11.5.1.2.1, "What You May Need to Know About the Immediate and ReadOnly Attributes".

11.5.1.2 Creating the JSF Page for Each Train Node

Each train node has its own page. To display the process train, on each page bind the processTrain component to the process train model, as shown in Example 11-42.

A processTrain component is usually inserted in the location facet of a panelPage or page component. Like a menu component, a processTrain component has a nodeStamp facet that accepts one commandMenuItem component. It is the commandMenuItem component that provides the actual label you see below a train node, and the navigation outcome when the label is activated.

Example 11-42 ProcessTrain Component in the SRCreate.jspx File

<af:panelPage..>
  ...
  <f:facet name="location">
    <af:processTrain var="train"
                     value="#{createTrainMenuModel.model}">
      <f:facet name="nodeStamp">
        <af:commandMenuItem text="#{train.label}"
                            action="#{train.getOutcome}"
                            readOnly="#{createTrainMenuModel.model.readOnly}"
                            immediate="false"/>
      </f:facet>
    </af:processTrain>
  </f:facet>
  ...
</af:panelPage>

Note:

You can use the same code for the process train on each page because the process train model dynamically determines the train node links, the order of the nodes, and whether the nodes are enabled, disabled, or selected.

Typically, you use a processTrain component with a processChoiceBar component. The processChoiceBar component, which is also bound to the same process train model, gives the user additional navigation choices for stepping through the multipage process. Example 11-43 shows the code for the processChoiceBar component in the SRCreate.jspx page. A processChoiceBar component is usually inserted in the actions facet of a panelPage or page component.

Example 11-43 ProcessChoiceBar Component in the SRCreate.jspx File

<af:panelPage ..>
  <f:facet name="actions">
    <af:panelButtonBar>
      <af:commandButton text="#{res['srdemo.cancel']}"
                        action="#{backing_SRCreate.cancelButton_action}"
                        immediate="true"/>
      <af:processChoiceBar var="choice"
                           value="#{createTrainMenuModel.model}">
        <f:facet name="nodeStamp">
          <af:commandMenuItem text="#{choice.label}"
                              action="#{choice.getOutcome}"
                              readOnly="#{createTrainMenuModel.model.readOnly}"
                              immediate="false"/>
        </f:facet>
      </af:processChoiceBar>
    </af:panelButtonBar>
  </f:facet>
  ...
</af:panelPage>

As illustrated in Figure 11-13 and Figure 11-14, the processChoiceBar component automatically provides a Continue button and a Back button for navigating forward and backward in the process. You don't have to write any code for these buttons. If you want to provide additional buttons (such as the Cancel and Submit Request buttons in Figure 11-14), use a panelButtonBar to lay out the button components and the processChoiceBar component.

Note:

If your multipage process has only two pages, ADF Faces uses Continue as the label for the button that navigates forward. If there is more than two pages in the process, the forward button label is Next.
11.5.1.2.1 What You May Need to Know About the Immediate and ReadOnly Attributes

The two process scenarios provided by ADF Faces and described in Section 11.5.1.1.1, "What You May Need to Know About Controlling Page Access" have an effect on both the immediate and readOnly attributes of the commandMenuItem component used within processTrain. When binding processTrain to a process train model, you can bind the node's immediate or readOnly attribute to the model's immediate or readOnly attribute. The ProcessMenuModel class then uses logic to determine the value of the immediate or readOnly attribute.

When the data on the current page does not need to be validated, the immediate attribute should be set to true. For example, in the Plus One scenario described in Section 11.5.1.1.1, if the user is on page 4 and goes back to page 2, the user has to come back to page 4 again later, so that data does not need to be validated when going to page 1 or 3, but should be validated when going ahead to page 5.

The ProcessMenuModel class uses the following logic to determine the value of the immediate attribute:

  • Plus One: immediate is set to true for any previous step, and false otherwise.

  • Max Visited: When the current page and the maximum page visited are the same, the behavior is the same as the Plus One scenario. If the current page is before the maximum page visited, then immediate is set to false.

The readOnly attribute should be set to true only if that page of the process cannot be reached from the current page. The ProcessMenuModel class uses the following logic to determine the value of the readOnly attribute:

  • Plus One: readOnly will be true for any page past the next available page.

  • Max Visited: When the current step and the maximum page visited are the same, the behavior is the same as the Plus One scenario. If the current page is before the maximum page visited, then readOnly is set to true for any page past the maximum page visited.

11.5.1.3 Creating the JSF Navigation Rules

The <from-outcome> and <to-view-id> values in the navigation cases must match the properties set in the process train model.

In the SRDemo application, a global navigation rule is used for the first page of the Create New Service Request process because the SRCreate.jspx page is accessible from any page in the application. The second page of the process, SRCreateConfirm.jspx, is not included in the global navigation rule because it is only accessible from the SRCreate.jspx page. Example 11-44 shows the navigation rules and cases for the process.

Example 11-44 Navigation Rules for Process Train Nodes in the faces.config.xml File

<navigation-rule>
  <from-view-id>*</from-view-id>
  <navigation-case>
    <from-outcome>GlobalCreate</from-outcome>
    <to-view-id>/app/SRCreate.jspx</to-view-id>
  </navigation-case>
  ...
</navigation-rule>
<navigation-rule>
  <from-view-id>/app/SRCreate.jspx</from-view-id>
  <navigation-case>
    <from-outcome>Continue</from-outcome>
    <to-view-id>/app/SRCreateConfirm.jspx</to-view-id>
  </navigation-case>
  ...
</navigation-rule>
<navigation-rule>
  <from-view-id>/app/SRCreateConfirm.jspx</from-view-id>
  <navigation-case>
    <from-outcome>Back</from-outcome>
    <to-view-id>/app/SRCreate.jspx</to-view-id>
  </navigation-case>
  <navigation-case>
    <from-outcome>Complete</from-outcome>
    <to-view-id>/app/SRCreateDone.jspx</to-view-id>
  </navigation-case>
</navigation-rule>

11.5.2 What Happens at Runtime

Java automatically adds a no-arg constructor to TrainModelAdapter because the TrainModelAdapter class is used as a managed bean. TrainModelAdapter constructs the process train model, which is a ProcessMenuModel instance, via the createTrainMenuModel managed bean. The createTrainNodes managed bean creates and injects the train node list into the train model. The train model provides the model that correctly highlights and enables the nodes on the train as you step through the process.

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

In the SRDemo application, the individual train node managed beans access String resources in the resource bundle via the resources managed bean, so that the correct node label is dynamically retrieved and display at runtime.

At runtime if maxPathKey has a value (set in faces-config.xml), ADF Faces knows to use the Max Visited process scenario. If maxPathKey is null (as in the SRDemo application), ADF Faces uses the Plus One process to control page access from the current page.

Like the menuTab component, the processTrain and processChoiceBar components have a nodeStamp facet, which takes one commandMenuItem component. By using train as the variable and binding the processTrain component to the process train model, you need only one commandMenuItem component to display all train node items using #{train.label} as the text value and #{train.getOutcome} as the action value on the command component. Similarly, by using choice as the variable and binding the processChoiceBar component to the process train model, you need only one commandMenuItem component to display all items as menu options using #{choice.label} as the text value and #{choice.getOutcome} as the action value.

The enabling and disabling of a node is not controlled by the MenuItem class, but by the process train model based on the current view using the EL expression #{createTrainMenuModel.model.readOnly} on the readOnly attribute of the processTrain or processChoiceBar component.

Tip:

Disabled menu choices are not rendered on browsers that don't support disabled items in a dropdown menu. On browsers that support disabled items in a dropdown menu, the unreachable items will look disabled.

11.5.3 What You May Need to Know About Process Trains and Menus

The ProcessMenuModel class extends the ViewIdPropertyMenuModel class, which is used to create dynamic menus, as described in Section 11.2, "Using Dynamic Menus for Navigation". Like menus and menu items, each node on a train is defined as a menu item. But unlike menus where the menu items are gathered into the intermediate menu tree object (MenuTreeModelAdapter), the complete list of train nodes is gathered into an ArrayList that is then injected into the TrainModelAdapter class. Note, however, that both ViewIdPropertyMenuModel and ProcessMenuModel can always take a List and turn it into a tree internally.

In the SRDemo application, the nodes on the train are not secured by user role as any user can create a new service request, which means that the train model can be stored as an application scoped managed bean and shared by all users.

The menu model is stored as a session scoped managed bean because the menu tab items are secured by user role, as some tabs are not available to some user roles.

To add a new page to a process train, configure a new managed bean for the page (Example 11-38), add the new managed bean to the train list (Example 11-39), and add the navigation case for the new page (Example 11-44).

11.6 Providing File Upload Capability

File uploading is a capability that is required in many web applications. Standard J2EE technologies such as Servlets and JSP, and JSF 1.1.x, do not directly support file uploading. The ADF Faces framework, however, has integrated file uploading support at the component level via the inputFile component.

During file uploading, ADF Faces temporarily stores incoming files either in memory or on disk. You can set a default directory storage location, and default values for the amount of disk space and memory that can be used in any one file upload request.

Figure 11-16 shows the SRMain.jspx page of the SRDemo application, where users can upload files for a particular service request.

Figure 11-16 File Upload Button on the SRMain Page

Upload function on service request information page

When the user clicks Upload document, the upload form displays in a popup dialog, as shown in Figure 11-17.

Figure 11-17 File Upload Form in the SRDemo Application

File upload form for entering file

The user can enter the full pathname of the file for uploading or click Browse to locate and select the file. When Begin Upload is clicked, ADF Faces automatically uploads the selected file. Upon a successful upload, ADF Faces displays some information about the uploaded file, as shown in Figure 11-18. If uploading is unsuccessful for some reason, the application displays the error stack trace in the same popup dialog.

Figure 11-18 File Upload Success Information

File upload form with success message box

11.6.1 How to Support File Uploading on a Page

Use the following tasks to provide file uploading support in a JSF application.

To provide file uploading support:

  1. Make sure the ADF Faces filter has been installed.

    The ADF Faces filter is a servlet filter that ensures ADF Faces is properly initialized by establishing an AdfFacesContext object. JDeveloper automatically installs the filter for you in web.xml when you insert an ADF Faces component into a JSF page for the first time. Example 11-45 shows the ADF Faces filter and mapping configuration setting in web.xml.

    Example 11-45 ADF Faces Filter in the web.xml File

    <!-- Installs the ADF Faces Filter -- >
    <filter>
      <filter-name>adfFaces</filter-name>
      <filter-class>oracle.adf.view.faces.webapp.AdfFacesFilter</filter-class>
    </filter>
    
    <!-- Adds the mapping to ADF Faces Filter -- >
    <filter-mapping>
      <filter-name>adfFaces</filter-name>
      <servlet-name>Faces Servlet</servlet-name>
    </filter-mapping>
    
  2. In web.xml set a context initialization parameter for the storage location of uploaded files. It's up to you where you want to save the uploaded files. Example 11-46 shows the context parameter used in the SRDemo application for uploaded files.

    Example 11-46 Uploaded File Storage Location in the web.xml File

    <context-param>
      <description>Parent directory location of SRDemo fileuploads</description>
      <param-name>SRDemo.FILE_UPLOADS_DIR</param-name>
      <param-value>/tmp/srdemo_fileuploads</param-value>
    </context-param>
    
  3. Create a backing bean for handling uploaded files. Example 11-47 shows the managed bean code in faces-config.xml for the SRDemo file upload page.

    Example 11-47 Managed Bean for the SRFileUpload Page in the faces.config.xml File

    <managed-bean>
      <managed-bean-name>backing_SRFileUpload</managed-bean-name>
      <managed-bean-class>
        oracle.srdemo.view.backing.SRFileUpload</managed-bean-class>
      <managed-bean-scope>request</managed-bean-scope>
      ...
    </managed-bean>
    
  4. In the JSF page you can use either af:form or h:form for file uploading. Make sure you set the enclosing form to support file uploads, as shown in the next code snippet:

    <af:form usesUpload="true"/>
    ..
    <h:form enctype="multipart/form-data"/>
    
  5. Use the inputFile component to provide a standard input field with a label, and a Browse button, as shown in Figure 11-17.

    The inputFile component delivers standard value change events as files are uploaded, and manages the processing of the uploaded contents for you. It is up to you how you want to handle the contents.

    To process file uploading, you could either implement a value change listener method in the backing bean to handle the event, or bind the value attribute of inputFile directly to a managed bean property of type oracle.adf.view.faces.model.UploadedFile. Either way you have to write your own Java code in the backing bean for handling the uploaded files.

    The following code snippet shows the code for an inputFile component if you were to bind the component to a managed bean property of type oracle.adf.view.faces.model.UploadedFile.

    <af:inputFile value="#{myuploadBean.myuploadedFile}".../>
    

    The SRDemo file upload form uses a value change listener method. Example 11-48 shows the code for the method binding expression in the valueChangeListener attribute of the inputFile component.

    Example 11-48 InputFile Component in the SRFileUpload.jspx File

    <af:inputFile label="#{res['srfileupload.uploadlabel']}"
                  valueChangeListener="#{backing_SRFileUpload.fileUploaded}"
                  binding="#{backing_SRFileUpload.srInputFile}"
                  columns="40"/>
    
  6. In the page's backing bean, write the code for handling the uploaded contents. For example, you could write the contents to a local directory in the file system. Example 11-49 shows the value change listener method that handles the value change event for file uploading in the SRDemo application.

    Example 11-49 Value Change Listener Method for Handling a File Upload Event

    public void fileUploaded(ValueChangeEvent event) {
    
        InputStream in;
        FileOutputStream out;
        
        // Set fileUPloadLoc to "SRDemo.FILE_UPLOADS_DIR" context init parameter
        String fileUploadLoc = FacesContext.getCurrentInstance().getExternalContext().getInitParameter("SRDemo.FILE_UPLOADS_DIR");
        
        if (fileUploadLoc == null) {
           // Backup value if context init parameter not set.
          fileUploadLoc = "/tmp/srdemo_fileuploads";
        }
                
        
       //get svrId and append to file upload location
       Integer svrId = (Integer)JSFUtils.getManagedBeanValue("userState.currentSvrId");
       fileUploadLoc += "/sr_" + svrId + "_uploadedfiles";
       
     
        // Create upload directory if it does not exists.
        boolean exists = (new File(fileUploadLoc)).exists();
        if (!exists) {
            (new File(fileUploadLoc)).mkdirs();
        }
     
     
        UploadedFile file = (UploadedFile)event.getNewValue();
    
            
        if (file != null && file.getLength()>0) {
            FacesContext context = FacesContext.getCurrentInstance();
            FacesMessage message =
                        new FacesMessage(JSFUtils.getStringFromBundle("srmain.srfileupload.success")+" "+
                                       file.getFilename() + " (" +
                                       file.getLength() +
                                       " bytes)");
            context.addMessage(event.getComponent().getClientId(context),
                               message);
     
            try {
                out =
                    new FileOutputStream(fileUploadLoc + "/" + file.getFilename());
                in = file.getInputStream();
                
                for (int bytes = 0; bytes < file.getLength(); bytes++) {
                  out.write(in.read());
                }  
                  
                in.close();
                out.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        else {
            // need to check for null value here as otherwise closing
            // the dialog after a failed upload attempt will lead to
            // a nullpointer exception
            String filename = file != null ? file.getFilename() : null;
            String byteLength = file !=null ? "" + file.getLength() : "0";
            
            FacesContext context = FacesContext.getCurrentInstance();
            FacesMessage message =
            new FacesMessage(FacesMessage.SEVERITY_WARN, JSFUtils.getStringFromBundle("srmain.srfileupload.error") + " " + 
                            filename + " (" + byteLength + " bytes)", null);
            context.addMessage(event.getComponent().getClientId(context),message);
        }
    }
    
  7. Use a commandButton component to submit the form. Example 11-50 shows the commandButton code in the SRDemo file upload form, and also the action method code in the page's backing bean.

    Example 11-50 Code for the Command Button and Action Method

    <af:commandButton text="#{res['srfileupload.uploadbutton']}"
                      action="#{backing_SRFileUpload.UploadButton_action}"/>
    
    ...
    ...
    public String UploadButton_action() {
        if (this.getSrInputFile().getValue() == null){                        
            FacesContext context = FacesContext.getCurrentInstance();
            FacesMessage message =
            new FacesMessage(FacesMessage.SEVERITY_WARN, JSFUtils.getStringFromBundle("srmain.srfileupload.emptyfielderror"), null);
            context.addMessage(this.getSrInputFile().getId(), message);
                
        }
        
        return null;
    }
    
  8. If using a popup dialog, add a commandLink component to let the user close the dialog. For more information about closing a popup dialog, see Section 11.3.1.3, "Creating the Dialog Page and Returning a Dialog Value". Example 11-51 shows the code for the commandLink component and the action method in the page's backing bean.

    Example 11-51 Code for the Command Link and Action Method

    <af:commandLink action="#{backing_SRFileUpload.closeFileUpload_action}"../>
    ..
    public String closeFileUpload_action() {
      AdfFacesContext.getCurrentInstance().returnFromDialog(null, null);
      return null;
    }
    

11.6.2 What Happens at Runtime

The SRDemo application creates a directory such as C:\tmp\srdemo_fileuploads to store uploaded files. Uploaded files for a service request are placed in a subdirectory prefixed with the service request id, for example C:\tmp\srdemo_fileuploads\sr_103_uploadedfiles.

The oracle.adf.view.faces.webapp.UploadedFileProcessor API is responsible for processing file uploads. Each application has a single UploadedFileProcessor instance, which is accessible from AdfFacesContext.

The UploadedFileProcessor processes each uploaded file as it comes from the incoming request, converting the incoming stream into an oracle.adf.view.faces.model.UploadedFile instance, and making the contents available for the duration of the current request. In other words, the value attribute of the inputFile component is automatically set to an instance of UploadedFile. If the inputFile component's value is bound to a managed bean property of type oracle.adf.view.faces.model.UploadedFile, ADF Faces sets an UploadedFile object on the model.

The oracle.adf.view.faces.model.UploadedFile API describes the contents of a single file. It lets you get at the actual byte stream of the file, as well as the file's name, its MIME type, and its size. The UploadedFile might be stored as a file in the file system, or it might be stored in memory; the API hides that difference.

ADF Faces limits the size of acceptable incoming requests to avoid denial-of-service attacks that might attempt to fill a hard drive or flood memory with uploaded files. By default, only the first 100 kilobytes in any one request are stored in memory. Once that has been filled, disk space is used. Again, by default, that is limited to 2,000 kilobytes of disk storage for any one request for all files combined. The AdfFacesFilter throws an EOFException once the default disk storage and memory limits are reached. To change the default values, see Section 11.6.4, "Configuring File Uploading Initialization Parameters".

11.6.3 What You May Need to Know About ADF Faces File Upload

Consider the following if you're using ADF Faces file upload:

  • Most applications don't need to replace the default UploadedFileProcessor instance, but if your application needs to support uploading of very large files, you may wish to replace the default processor with a custom UploadedFileProcessor implementation. For more information see Section 11.6.5, "Configuring a Custom Uploaded File Processor".

  • The ADF Faces Filter ensures that the UploadedFile content is cleaned up after the request is complete. Thus, you cannot cache UploadedFile objects across requests. If you need to keep a file, you must copy it into persistent storage before the request finishes.

11.6.4 Configuring File Uploading Initialization Parameters

During file uploading, ADF Faces temporarily stores incoming files either on disk or in memory. ADF Faces defaults to the application server's temporary directory, as provided by the javax.servlet.context.tempdir property. If that property is not set, the system java.io.tempdir property is used.

If you wish you can set a default temporary storage location, and default values for the amount of disk space and memory that can be used in any one file upload request. You can specify the following file upload context parameters in web.xml:

  • oracle.adf.view.faces.UPLOAD_TEMP_DIR—Specifies the directory where temporary files are to be stored during file uploading. Default is the user's temporary directory.

  • oracle.adf.view.faces.UPLOAD_MAX_DISK_SPACE—Specifies the maximum amount of disk space that can be used in a single request to store uploaded files. Default is 2000K.

  • oracle.adf.view.faces.UPLOAD_MAX_MEMORY—Specifies the maximum amount of memory that can be used in a single request to store uploaded files. Default is 100K.

Example 11-52 shows the context initialization parameters for file uploading that you use in web.xml.

Example 11-52 Context Parameters for File Uploading in the web.xml File

<context-param>
  <param-name>oracle.adf.view.faces.UPLOAD_TEMP_DIR</param-name>
  <param-value>/tmp/Adfuploads</param-value>
</context-param>

<context-param>
  <param-name>oracle.adf.view.faces.UPLOAD_MAX_DISK_SPACE</param-name>
  <param-value>10240000</param-value>
</context-param>

<context-param>
  <param-name>oracle.adf.view.faces.UPLOAD_MAX_MEMORY</param-name>
  <param-value>5120000</param-value>
</context-param>

Note:

The file upload initialization parameters are processed by the default UploadedFileProcessor only. If you replace the default processor with a custom UploadedFileProcessor implementation, the parameters are not processed.

11.6.5 Configuring a Custom Uploaded File Processor

Most applications don't need to replace the default UploadedFileProcessor instance provided by ADF Faces, but if your application needs to support uploading of very large files or rely heavily on file uploads, you may wish to replace the default processor with a custom UploadedFileProcessor implementation. For example, you could improve performance by using an implementation that immediately stores files in their final destination, instead of requiring ADF Faces to handle temporary storage during the request.

To replace the default processor, specify the custom implementation using the <uploaded-file-processor> element in adf-faces-config.xml. Example 11-53 shows the code for registering a custom UploadedFileProcessor implementation.

Example 11-53 Registering a Custom Uploaded File Processor in the adf-faces-config.xml File

<adf-faces-config xmlns="http://xmlns.oracle.com/adf/view/faces/config">
...
  <!-- Use my UploadFileProcessor class -->
  <uploaded-file-processor>
    com.mycompany.faces.myUploadedFileProcessor
  </uploaded-file-processor>
...
</adf-faces-config>

Tip:

Any file uploading initialization parameters specified in web.xml are processed by the default UploadedFileProcessor only. If you replace the default processor with a custom UploadedFileProcessor implementation, the file uploading parameters are not processed.

11.7 Creating Databound Dropdown Lists

ADF Faces selection list components include selectOneChoice and selectOneListbox, which work in the same way as standard JSF list components. ADF Faces list components, however, provide extra functionality such as support for label and message display, automatic form submission, and partial page rendering.

In the SRDemo application, the SRSearch page uses a selectOneChoice component to let users pick the type of service requests to perform a search on. With the selectOneChoice component, you can provide a static list of items for selection, or you can create a list that is populated dynamically. In either case, you use a f:selectItems tag to provide the items for display and selection.

11.7.1 How to Create a Dropdown List with a Fixed List of Values

The SRSearch page uses a selectOneChoice component to let users pick the type of service request to perform a search on. For example, instead of searching on all service requests, the user can refine the search on requests that have the status of open, pending, or closed. Figure 11-19 shows the search form in the SRDemo application where a selectOneChoice component is used.

Figure 11-19 SelectOneChoice Component for Selecting a Service Request Status

Status dropdown list for refining search on service requests

The search form is created using a method that takes parameters. For information about how to create a search form using parameters, see Section 10.8, "Creating Search Pages". The following procedure describes, without using parameters, how to create a dropdown list that is bound to a fixed list of values.

To create a dropdown list bound to a fixed list of values using the Data Control Palette:

  1. From the Data Control Palette, expand a business service method, and then expand a method return that returns a data collection. Drag and drop the data collection attribute you desire onto the page, and then choose Create > Single Selections > ADF Select One Choice from the context menu. The List Binding Editor displays, as illustrated in Figure 11-20.

    Using the service request status example, you would expand findAllServiceRequest(), then expand ServiceRequest, and drag and drop the status attribute. Because you want users to be able to search on a service request type, therefore you use the status attribute on the ServiceRequest data collection, which is a collection of all requests returned by the findAllServiceRequest() method.

    Figure 11-20 List Binding Editor with the Fixed List Option Selected

    List Binding Editor creating fixed list of values
  2. In the List Binding Editor, select Fixed List. Then select the status attribute from the Base Data Source Attribute dropdown list.

    The Fixed List option lets users choose a value from a predefined list, which is useful when you want to update a data object attribute with values that you code yourself, rather than getting the values from another data collection.

    When a value is selected from the list, Base Data Source Attribute is the attribute of the bound data collection that is to be updated to the selected value.

  3. Enter the following in the Set of Values box, pressing Enter to set a value before typing the next value:

    • Open

    • Pending

    • Closed

    The order in which you enter the values is the order in which the items are displayed in the selectOneChoice control at runtime.

  4. In the List Items section, select Include Labeled Item from the "No Selection" Item dropdown list. Then enter Any Status in the box next to it.

    The selectOneChoice component supports a null value, that is, if the user has not selected an item, the label of the item is shown as blank, and the value of the component defaults to an empty string. Instead of using blank or an empty string, you can specify a string to represent the null value. By default, the new string appears at the top of the list of values that is defined in step 3.

Tip:

In the SRDemo application, the findServiceRequestSearch(Integer, String, String) method contains the logic to find and return service records based on three parameters, one of which is statusParam. Each method parameter has an associated variable. For information about variable iterators and variables, see Section 10.8.2, "What Happens When You Use Parameter Methods".

If you created the search form using the method with parameters (as described in Section 10.8.1, "How to Create a Search Form"), delete the inputText component created for the Status field, and replace it with a selectOneChoice component by dragging and dropping statusParam from the Data Control Palette. In the List Binding Editor, for the Base Data Source Attribute, select the variable name findServiceRequestSearch_statusParam.

11.7.2 What Happens When You Create a Dropdown List Bound to a Fixed List

When you drag and drop from the Data Control Palette, JDeveloper does many things for you. For a full description of what happens and what is created when you use the Data Control Palette, see Section 5.2.3, "What Happens When You Use the Data Control Palette".

Example 11-54 shows the code for the selectOneChoice component after you've completed the List Binding Editor.

Example 11-54 SelectOneChoice Component After You Complete Binding

<af:selectOneChoice value="#{bindings.ServiceRequeststatus.inputValue}"
                    label="#{bindings.ServiceRequeststatus.label}: ">
  <f:selectItems value="#{bindings.ServiceRequeststatus.items}"/>
</af:selectOneChoice>

The f:selectItems tag, which provides the list of items for selection, is bound to the items property on the ServiceRequeststatus list binding object in the binding container.

In the page definition file (for example, SRSearchPageDef.xml), JDeveloper adds the list binding object definition in the bindings element, as shown in Example 11-55.

Example 11-55 List Binding Object for the Fixed Dropdown List in the Page Definition File

<bindings>
  ...
  <list id="ServiceRequeststatus" IterBinding="findAllServiceRequestIter"
        ListOperMode="0" StaticList="true" NullValueFlag="1">
    <AttrNames>
      <Item Value="status"/>
    </AttrNames>
    <ValueList>
      <Item Value="Any Status"/>
      <Item Value="Open"/>
      <Item Value="Pending"/>
      <Item Value="Closed"/>
    </ValueList>
  </list>
  ...
</bindings>

The id attribute specifies the name of the list binding object. The IterBinding attribute specifies the iterator binding object, which exposes and iterates over the collection returned by the findAllServiceRequest() method. The AttrNames element defines the attribute returned by the iterator. The ValueList element specifies the fixed list of values to be displayed for selection at runtime.

For more information about the page definition file and ADF data binding expressions, see Section 5.5, "Working with Page Definition Files" and Section 5.6, "Creating ADF Data Binding EL Expressions".

11.7.3 How to Create a Dropdown List with a Dynamic List of Values

Instead of getting values from a static list, you can populate a selectOneChoice component with values dynamically at runtime. The steps for creating a dropdown list bound to a dynamic list are almost the same as those for creating a dropdown list bound to a fixed list, with the exception that you define two data sources—one for the list data collection that provides the dynamic list of values, and the other for the base data collection that is to be updated based on the user's selection.

To create a dropdown list bound to a dynamic list of values using the Data Control Palette:

  1. From the Data Control Palette, expand a business service method, and then expand a method return that returns a data collection. Next, expand an accessor return that returns a detail collection. Drag and drop the attribute you desire onto the page, and then choose Create > Single Selections > ADF Select One Choice from the context menu. The List Binding Editor displays, as illustrated in Figure 11-21.

    For example, if users want to be able to pick a product before searching on service requests, you might expand findAllServiceRequest(), followed by ServiceRequest, and product. Then drag and drop the name attribute. Because you want users to be able to search service requests based on a product name, therefore you use the name attribute on the Product detail collection.

    Note:

    The list and base data collections do not have to form a master-detail relationship, but the items in the list data collection must be the same type as the base data collection attribute.

    Figure 11-21 List Binding Editor with the Dynamic List Option Selected

    List Binding Editor for creating dynamic list of values
  2. In the List Binding Editor, select Dynamic List.

  3. In the Base Data Source dropdown list, select the data collection that is to be updated with the list value selected by a user. For example, ServiceRequest: SRPublicFacade.findAllServiceRequest.product.

  4. In the List Data Source dropdown list, select the data collection that provides the list of values dynamically. For example, Product: SRPublicFacade findAllProduct.

  5. In the mapping area, select name from Base Data Source Attribute, and name from List Data Source Attribute. This maps the list source attribute to the base source attribute you want to update.

  6. In the List Items section, select name from the Display Attribute dropdown list. This populates the values users see in the list.

11.7.4 What Happens When You Create a Dropdown List Bound to a Dynamic List

When you drag and drop from the Data Control Palette, JDeveloper does many things for you. For a full description of what happens and what is created when you use the Data Control Palette, see Section 5.2.3, "What Happens When You Use the Data Control Palette".

Example 11-56 shows the code for the selectOneChoice component after you've completed the List Binding Editor.

Example 11-56 SelectOneChoice Component After You Complete Binding

<af:selectOneChoice value="#{bindings.Productname.inputValue}"
                    label="#{bindings.Productname.label}">
  <f:selectItems value="#{bindings.Productname.items}"/>
</af:selectOneChoice>

The f:selectItems tag, which provides the list of items for selection, is bound to the items property on the Productname list binding object in the binding container. For further descriptions about ADF data binding expressions, see Section 5.6, "Creating ADF Data Binding EL Expressions".

In the page definition file (for example, SRDemopage.xml), JDeveloper adds the list binding object definition into the bindings element, as shown in Example 11-57.

Example 11-57 List Binding Object for the Dynamic Dropdown List in the Page Definition File

<bindings>
  ...
  <list id="Productname" IterBinding="productIterator" StaticList="false"
        ListOperMode="0" ListIter="findAllProductIter"..>
    <AttrNames>
      <Item Value="name"/>
    </AttrNames>
    <ListAttrNames>
      <Item Value="name"/>
    </ListAttrNames>
    <ListDisplayAttrNames>
      <Item Value="name"/>
    </ListDisplayAttrNames>
  </list>
  ...
</bindings>

The id attribute specifies the name of the list binding object. The IterBinding attribute specifies the iterator binding object, which exposes and iterates over the collection returned by the findAllProduct() method. The AttrNames element defines the base data source attribute returned by the iterator. The ListAttrNames element defines the list data source attribute that is mapped to the base data source attribute returned by the iterator. The ListDisplayAttrNames element defines the list data source attribute that populates the values users see in the list.

For complete information about page definition files, see Section 5.5, "Working with Page Definition Files".

11.7.5 How to Use Variables with Dropdown Lists

Sometimes you might want to use a variable with a selectOneChoice component to hold the value of the item selected by a user. On the SRSkills page (as shown later in Figure 11-25), a manager selects a staff member name from the dropdown list to display the member's assigned product skills in the shuttle component. The selectOneChoice component in the SRSkills page populates a variable in the page definition file when the user makes a selection.

The following procedure shows how to manually add a variable to a page definition file.

To create a variable iterator and variable in a page definition file:

  1. Open the page definition file (for example, <pageName>PageDef.xml) for the JSF page in which a selectOneChoice component will be used.

  2. In the Structure window, right-click the topmost node and choose Insert inside <pageName>PageDef > executables to add the executables node, if not added already.

  3. In the Structure window, right-click executables and choose Insert inside executables > variableIterator to add the variables node, if not added already.

  4. In the Structure window, right-click variables and choose Insert inside variables > variable.

  5. In the Insert Variable dialog, enter a name and type for the variable. For example, you might enter someStaffIdVar for the name, and java.lang.Integer for the type, if you want the variable to hold data about the selected staff member.

Example 11-58 shows the page definition file after you've created a variable.

Example 11-58 Variable Iterator and Variable in the Page Definition File

<?xml version="1.0" encoding="UTF-8" ?>
<pageDefinition xmlns="http://xmlns.oracle.com/adfm/uimodel"
                version="10.1.3.36.61" id="app_management_untitled4PageDef"
                Package="oracle.srdemo.view.pageDefs">
  <executables>
    <variableIterator id="variables">
      <variable Name="someStaffIdVar" Type="java.lang.Integer"/>
    </variableIterator>
  </executables>
</pageDefinition>

For more information about variable iterators and variables, see Section 5.5.2.2, "Binding Objects Defined in the executables Element".

The next procedure shows how to use the variable to create a dropdown list that lets users select a staff member's name from a dynamic list.

To create a dynamic dropdown list:

  1. Open the JSF page in which you want to add a selectOneChoice component.

  2. From the Data Control Palette, expand findAllStaff() > User. Drag and drop the userId attribute to the page, and then choose Create > Single Selections > ADF Select One Choice from the context menu.

  3. In the List Binding Editor, select variables from the Base Data Source dropdown list.

  4. Select Dynamic List.

  5. From the List Data Source dropdown list, select User: SRPublicFacade:findAllStaff.

  6. In the mapping area, select someStaffIdVar from the Base Data Source Attribute dropdown list, and userId from the List Data Source Attribute dropdown list.

  7. In the List Items section, from the Display Attribute dropdown list, select Select Multiple, and add firstName and lastName to the Attributes to Display list in the Select Multiple Display Attributes dialog.

  8. From the "No Selection" Item dropdown list, select Include Labeled Item.

11.8 Creating a Databound Shuttle

The selectManyShuttle and selectOrderShuttle components render two list boxes, and buttons that allow the user to select multiple items from the leading (or "available") list box and move or shuttle the items over to the trailing (or "selected") list box, and vice versa. Figure 11-22 shows an example of a rendered selectManyShuttle component. You can specify any text you want for the headers that display above the list boxes.

Figure 11-22 Shuttle (SelectManyShuttle) Component

Shuttle showing available list and selected list of items

Note:

In addition to using the supplied Move and Remove buttons to shuttle items from one list to the other, you can also double-click an item in either list. Double-clicking an item in one list moves the item to the other list. For example, if you double-click an item in the leading list, the item is automatically moved to the trailing list, and vice versa.

The only difference between selectManyShuttle and selectOrderShuttle is that in the selectOrderShuttle component, the user can reorder the items in the trailing list box by using the up and down arrow buttons on the side, as shown in Figure 11-23.

Figure 11-23 Shuttle Component (SelectOrderShuttle) with Reorder Buttons

Shuttle with reorder buttons on the side

11.8.1 How to Create a Databound Shuttle

In the SRDemo application, the SRSkills page uses a selectManyShuttle component to let managers assign product skills to a technician. Figure 11-24 shows the SRSkills page created for the sample application. The leading list box on the left displays products such as washing machines and dryers; the trailing list box on the right displays the products that a technician is skilled at servicing.

Figure 11-24 SelectManyShuttle Component on the SRSkills Page

Technician skills page for assigning skills with shuttle

To review and change product skill assignments, a manager first selects a technician's name from the dropdown list above the shuttle component. The application then displays the technician's existing skill assignments in the trailing list, as shown in Figure 11-25.

Figure 11-25 Shuttle Component with the Trailing List Populated

Technician skills page showing assigned skills in shuttle

Below the leading and trailing lists are optional boxes for displaying a description of a product. To view a description of a product, the manager can select an item from either list box, and the application displays the product's description in the box below the list.

To add new skill assignments, the manager selects the products from the leading list (Available Products) and then clicks the Move button.

To remove skills from the Assigned Skills list, the manager selects the products from the trailing list and then clicks the Remove button.

Like other ADF Faces selection list components, the selectManyShuttle component can use the f:selectItems tag to provide the list of items available for display and selection in the leading list.

Before you can bind the f:selectItems tag, create a class that maintains a list of the valid products (skills) for the shuttle, and the indexes of the products that are assigned to (selected for) a technician. The class should use the page's binding container to get the product master list for populating the shuttle's leading list. Example 11-59 shows the SkillsHelper class that is created to manage the population and selection state of the shuttle component on the SRSkills page.

Example 11-59 SkillsHelper Class

package oracle.srdemo.view;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
...
public class SkillsHelper {
    private BindingContainer bindings;
    private List<Product> productMasterList;
    private List <SelectItem> allProducts;
    private HashMap productLookUp;
    private int[] selectedProducts;
    private int skillsFor;
    private boolean skillsChanged = false;
    public List<SelectItem> getAllProducts() {
        if (allProducts == null) {
            OperationBinding oper = getBindings().getOperationBinding("findAllProduct");
            productMasterList = (List<Product>)oper.execute();
            int cap = productMasterList.size();
            allProducts = new ArrayList(cap);
            productLookUp = new HashMap(cap);
            //for(Product prod: products) {
            for (int i=0;i<cap;i++){
                Product prod = productMasterList.get(i);
                SelectItem item = new SelectItem(i, prod.getName(),prod.getDescription());
                allProducts.add(item);
                productLookUp.put(prod.getProdId(), i);
            }
        }
        
        return allProducts;
    }
 
    public void setAllProducts(List<SelectItem> allProducts) {
        this.allProducts = allProducts;
    }
 
    public void setSelectedProducts(int[] selectedProducts) {
        skillsChanged = true;
        this.selectedProducts = selectedProducts;
    }
 
    public int[] getSelectedProducts() {
        Integer currentTechnician = (Integer)ADFUtils.getBoundAttributeValue(getBindings(),"currentTechnician");
        if (currentTechnician != null){
            if (skillsFor != currentTechnician.intValue()){
                skillsFor = currentTechnician.intValue();
                skillsChanged = false;
                OperationBinding getAssignedSkillsOp = getBindings().getOperationBinding("findExpertiseByUserId");
                List<ExpertiseArea> skills = (List<ExpertiseArea>)getAssignedSkillsOp.execute();
                selectedProducts = new int[skills.size()];
                for (int i=0;i<skills.size();i++){
                    Integer lookup = (Integer)productLookUp.get(skills.get(i).getProdId());
                    selectedProducts[i] = lookup.intValue();
                }   
            }
        }
 
        return selectedProducts;
    }
 
    public List<Integer> getSelectedProductIds(){
        ArrayList prodIdList = new ArrayList(selectedProducts.length);
        for (int i:selectedProducts){
            prodIdList.add(productMasterList.get(i).getProdId());
        }
        return prodIdList;
    }
    public void setBindings(BindingContainer bindings) {
        this.bindings = bindings;
    }
 
    public BindingContainer getBindings() {
        return bindings;
    }
 
    public void setSkillsChanged(boolean skillsChanged) {
        this.skillsChanged = skillsChanged;
    }
 
    public boolean isSkillsChanged() {
        return skillsChanged;
    }
}

The methods of interest in the SkillsHelper class are getAllProducts() and getSelectedProducts().

The getAllProducts() method is the method that populates the shuttle's leading list. The first time this method is called, the findAllProduct() method on the SRPublicFacade session bean is invoked, and the list of products is cached in an array list of SelectItem objects. The getAllProducts() method also maintains a hashmap that enables reverse lookup of the list item index number based on the product ID.

The getSelectedProducts() method returns an array of int values, defining the list of items that appear on the shuttle's trailing list. This method also checks whether the currently selected technician (from the dropdown list above the shuttle) has changed. If the currently selected technician has changed, the findExpertiseByUserId() method on the SRAdminFacade session bean is invoked, and the new current technician's list of skills is retrieved and displayed in the trailing list of the shuttle.

The SkillsHelper class is maintained as a session scoped managed bean named skillsHelper. Example 11-60 shows the managed beans configured for working with the shuttle component in the SRDemo application.

Example 11-60 Managed Beans for the Shuttle Component in the faces-config.xml File

<managed-bean>
  <managed-bean-name>skillsHelper</managed-bean-name>
  <managed-bean-class>oracle.srdemo.view.SkillsHelper</managed-bean-class>
  <managed-bean-scope>session</managed-bean-scope>
  <managed-property>
    <property-name>bindings</property-name>
    <value>#{data.SRSkillsPageDef}</value>
  </managed-property>    
</managed-bean>
<managed-bean>
  <managed-bean-name>backing_SRSkills</managed-bean-name>
  <managed-bean-class>oracle.srdemo.view.backing.SRSkills</managed-bean-class>
  <managed-bean-scope>request</managed-bean-scope>
</managed-bean>

All the bindings of the SRSkills page are defined in the file app_management_SRSkillsPageDef.xml, a reference of which is injected into the SkillsHelper class. Example 11-61 shows the page definition file for the SRSkills page.

Example 11-61 Page Definition File for the SRSkills Page

<?xml version="1.0" encoding="UTF-8" ?>
<pageDefinition xmlns="http://xmlns.oracle.com/adfm/uimodel"
                version="10.1.3.36.2" id="SRSkillsPageDef"
                Package="oracle.srdemo.view.pageDefs"..>
  <executables>
    <methodIterator id="findAllStaffIter" Binds="findAllStaff.result"
                    DataControl="SRPublicFacade" RangeSize="-1"
                    BeanClass="oracle.srdemo.model.entities.User"/>
    <methodIterator id="findAllProductIter" Binds="findAllProduct.result"
                    DataControl="SRPublicFacade" RangeSize="-1"
                    BeanClass="oracle.srdemo.model.entities.Product"/>
    <variableIterator id="variables">
      <variable Name="selectedStaffIdVar" Type="java.lang.Integer"/>
    </variableIterator>
  </executables>
  <bindings>
    <methodAction id="findAllStaff" InstanceName="SRPublicFacade.dataProvider"
                  DataControl="SRPublicFacade" MethodName="findAllStaff"
                  RequiresUpdateModel="true" Action="999"
                  ReturnName="SRPublicFacade.methodResults.SRPublicFacade_dataProvider_findAllStaff_result"/>
    <methodAction id="findAllProduct" InstanceName="SRPublicFacade.dataProvider"
                  DataControl="SRPublicFacade" MethodName="findAllProduct"
                  RequiresUpdateModel="true" Action="999"
                  ReturnName="SRPublicFacade.methodResults.SRPublicFacade_dataProvider_findAllProduct_result"/>
    <attributeValues IterBinding="variables" id="currentTechnician">
      <AttrNames>
        <Item Value="selectedStaffIdVar"/>
      </AttrNames>
    </attributeValues>
    <methodAction id="findExpertiseByUserId"
                  InstanceName="SRAdminFacade.dataProvider"
                  DataControl="SRAdminFacade" MethodName="findExpertiseByUserId"
                  RequiresUpdateModel="true" Action="999"
                  ReturnName="SRAdminFacade.methodResults.SRAdminFacade_dataProvider_findExpertiseByUserId_result">
      <NamedData NDName="userIdParam" 
                 NDType="java.lang.Integer"
                 NDValue="#{bindings.currentTechnician.inputValue}"/>
    </methodAction>
    <list id="findAllStaffList" StaticList="false" ListOperMode="0"
          IterBinding="variables" ListIter="findAllStaffIter" 
          NullValueFlag="1" NullValueId="findAllStaffList_null">
      <AttrNames>
        <Item Value="selectedStaffIdVar"/>
      </AttrNames>
      <ListAttrNames>
        <Item Value="userId"/>
      </ListAttrNames>
      <ListDisplayAttrNames>
        <Item Value="firstName"/>
        <Item Value="lastName"/>
      </ListDisplayAttrNames>
    </list>
    <methodAction id="updateStaffSkills"
                  InstanceName="SRAdminFacade.dataProvider"
                  DataControl="SRAdminFacade" MethodName="updateStaffSkills"
                  RequiresUpdateModel="true" Action="999">
      <NamedData NDName="userId"
                 NDValue="${bindings.currentTechnician.inputValue}"
                 NDType="java.lang.Integer"/>
      <NamedData NDName="prodIds" NDValue="${skillsHelper.selectedProductIds}"
                 NDType="java.util.List"/>
    </methodAction>
  </bindings>
</pageDefinition>

The next procedure assumes you've already created the relevant bindings, a class similar to the SkillsHelper class in Example 11-59, and configured the required managed beans in faces-config.xml, as shown in Example 11-60.

To create a shuttle component:

  1. From the ADF Faces Core page of the Component Palette, drag and drop SelectManyShuttle onto the page. JDeveloper displays the Insert SelectManyShuttle dialog, as illustrated in Figure 11-26.

    Figure 11-26 Insert SelectManyShuttle Dialog

    Insert SelectManyShuttle dialog for binding select items
  2. Select Bind to list (select items) and click Bind... to open the Expression Builder.

  3. In the Expression Builder, expand JSF Managed Beans > skills. Double-click allProducts to build the expression #{skillsHelper.allProducts}. Click OK.

    This binds the f:selectItems tag to the getAllProducts() method that populates the shuttle's leading list.

  4. In the Insert SelectManyShuttle dialog, click Common Properties. Click Bind... next to the Value field to open the Expression Builder again.

  5. In the Expression Builder, expand JSF Managed Beans > skills. Double-click selectedProducts to build the expression #{skillsHelper.selectedProducts}. Click OK.

    This binds the value attribute of the selectManyShuttle component to the getSelectedProducts() method that returns an array of int values, defining the list items on the shuttle's trailing list.

Example 11-62 shows the code for the selectManyShuttle component after you complete the Insert SelectManyShutle dialog.

Example 11-62 SelectManyShuttle Component in the SRSkills.jspx File

<af:selectManyShuttle value="#{skillsHelper.selectedProducts}"
                      ...
  <f:selectItems value="#{skillsHelper.allProducts}"/>
</af:selectManyShuttle>

For more information about using the shuttle component, see the ADF Faces Core tags at

http://www.oracle.com/technology/products/jdev/htdocs/partners/addins/exchange/jsf/doc/tagdoc/core/index.html

11.8.2 What Happens at Runtime

When the SRSkills page is first accessed, the variable iterator executes and instantiates its variable, selectedStaffIdVar. At this point, the variable does not contain a value. When the manager selects a name from the dropdown list, the variable is populated and the attribute binding can then provide the value for the findExpertiseByUserId() method's parameter userIdParam, using the EL expression for the value of the NamedData Element.

When the Save skill changes command button (see Figure 11-24) is clicked, the current technician's user ID and the associated array of product IDs (assigned skills) are retrieved and sent to the updateStaffSkills() method on the SRAdminFacade bean.

Example 11-63 shows the code for the commandButton component on the SRSkills.jspx page.

Example 11-63 CommandButton Component in the SRSkills.jspx File

<af:commandButton action="#{backing_SRSkills.saveSkillChanges_action}"
                  actionListener="#{bindings.updateStaffSkills.execute}"../>