DOWNLOAD
 Oracle JDeveloper and ADF 11g
 Sample Code
   TAGS
rea, adf, All
Developer: Rich Enterprise Apps

Implementing Cell Highlighting in JSF-based Rich Enterprise Apps (Part 2)

by Lucas Jellema ACE Director

Help your end-users digest information by implementing cell highlighting in your JavaServer Faces app.

Published November 2008

In Part 1 of this article, you experienced the implementation of cell highlighting in Oracle Application Development Framework (ADF) Faces applications, using fairly simple methods in a straightforward manner. As you learned, with very little effort, Oracle ADF applications can be extended with highlighting functionality typically found in tools such as Oracle Discoverer, Microsoft Excel, and Oracle Application Express.

Figure
Figure 1 The final result of Part 1 - Specifying two Salary threshold values for Cell Highlighting

However, this implementation is fairly limited: it supports only a predefined number of thresholds for which users can dynamically set the values and the colors. Every value range is identified by the designated color; for example, salaries below 2,000, between 2,000 and 3,000, and over 3,000 are highlighted in green, orange, and red, respectively.

In Part 2, you will add more power for end users: it describes how to add end-user functionality to dynamically add, update, and remove thresholds—supporting as many thresholds as could possibly be desired. You will add support for multicolumn highlighting in the same table, allowing the Hiredate column to be highlighted based on work experience (green being less experienced, obviously) while the Salary column's highlighting indicates the employees' income category.

It then goes beyond the simple threshold pattern to also provide expression-based filtering. The end user can set up one or multiple expressions, such as JOB <> 'MANAGER and SAL > 3000 or JOB = 'CLERK' or SAL < 1500, that may refer to all attributes in the data records. Every filter expression can be associated with a color. The first expression a record satisfies determines the color of the cell. It is like executing multiple queries simultaneously and getting the results displayed in parallel.

Preparation

Ideally, you've already read and worked your way through Part 1. If you skipped it, you'll need to prepare by downloading and installing Oracle JDeveloper 11g and downloading and opening an Oracle JDeveloper start application. Part 1 describes how you can work against either a database or a JavaBeans-backed data store. In this part, too, you will work only within Oracle ADF Faces, ignoring the implementation of the model components.

  1. Start Oracle JDeveloper.
  2. Load the provided Oracle JDeveloper application ADFCellHighlightingPart2_StartingPoint. It contains a model-view-controller (MVC) project with the situation reached at the end of Part 1 (and shown in Figure 1 above).
The Model project contains three classes: HrmManager, EmpManager (a singleton), and Emp. The Emp and EmpManager classes have been generated from the EMP table to produce an offline (nondatabase) datasource. The HrmManager class has been published as DataControl. As a result, the emps collection is available on the Data Control palette.

The ViewController project contains the EmpTable.jspx page, its associated PageDefinition, and the ThresholdHighlightBean class.

Step 1: Adding Thresholds on the Fly

In the situation reached at the end of Part 1, you allowed the user to set the values for each of the thresholds and to specify the colors used for highlighting, but the number of thresholds is predefined and fixed. You will now build on top of that functionality by enabling the creation and removal of thresholds on the fly. It is up to the user whether thresholds apply to a certain column and, if so, how many.

Figure
Figure 2 Dynamically specifying Cell Highlighting Thresholds

Managed Beans and Underlying Threshold Logic

Thresholds are added and removed via calls to a managed bean, based on the DynamicThresholdManager class. This bean maintains a list of thresholds. A threshold is a simple bean with two properties: the lower limit and the color. Every cell with a value over or equal to the lower limit gets the color associated with the threshold, unless the value is higher than other thresholds farther up the value range.

The DynamicThresholdManager class has the addThreshold() and removeThreshold() methods—with obvious functionality—as well as applyHighlighting(), which is invoked when all the currently defined thresholds should be applied to the cells in the column. Each of these methods is called as part of a partial page rendering cycle and either causes the table to refresh or the highlight pop-up panel to appear in the column header.

When the cells are rendered, they need to know what background color to display as well. The contentStyle property is used, as it was before, but it now contains an expression language (EL) expression that enlists the help of a managed bean to determine the color, based on the value a cell contains and on all the thresholds the user specifies.

The EL expression on the contentStyle attribute for the inputText component for Sal looks like this:

<af:inputText value="#{row.sal}"
        contentStyle="background-color:#{EmpHighlighterController.reset[row.sal].color}">
        ...
</af:inputText>
The EmpHighlighterController is a managed bean, based on the ThresholdHighlighterController class, that extends from HashMap. The EL expression used here causes the get(Object key) method on the EmpHighlighterController bean to be called several times (because that method returns the bean itself) with the key values "reset," "row.sal" (the salary value), and "color." That last call instructs the bean to return the color to be used for painting the background of the cell. The second call (the one which passes row.sal in) made the current cell's Salary value available to the EmpHighlighterController bean, to use for determining the color in that last call.

The get method in the ThresholdHighlighterController class is implemented like this:

private static final String reset = "RESET";
    private static final String color = "COLOR";
    public Object get(Object key) {
        if (key instanceof String) {
            String command = (String)key;
            if (reset.equalsIgnoreCase(command)) {
                operands = new ArrayList();
                return this;
            }
            else if (color.equalsIgnoreCase((String)key)) {
                return dynamicThresholdManager.getColor(operands.get(0));
            }
        }
        else {
            operands.add(key);
            return this;
        }
        // return the object itself to allow additional calls to be made to this method
        return this;
    }
It recognizes two instructions, through the string values reset and color. Upon the "reset" instruction, it will clear the list of operands. When the "color" instruction is received, it will call the getColor() method on the dynamicThresholdManager.

The crucial method in the whole scheme obviously is the getColor() method in the dynamicThresholdManager. This method compares the value passed in from the cell with the lower-limit values associated with all thresholds and returns the color for the lowest threshold the value surpasses." Here is a visual overview:

Figure
Figure 3 The cell's background color is determined in a call to the EmpHighlighterController managed bean. This bean has been injected with the EmpThresholdManager bean that holds the thresholds and calculates the cell's color.

The configuration of the managed beans in adfc-config.xml has injected the EmpThresholdManager into EmpHighlighterController.
<managed-bean>
    <managed-bean-name>EmpSalThresholdManager</managed-bean-name>
    <managed-bean-class>otn.cellhighlighting.jsf.DynamicThresholdManager</managed-bean-class>
    <managed-bean-scope>session</managed-bean-scope>
</managed-bean>
<managed-bean>
    <managed-bean-name>EmpHighlighterController</managed-bean-name>
    <managed-bean-class>otn.cellhighlighting.jsf.ThresholdHighlighterController</managed-bean-class>
    <managed-bean-scope>request</managed-bean-scope>
    <managed-property>
    <property-name>dynamicThresholdManager</property-name>
    <property-class>otn.cellhighlighting.jsf.DynamicThresholdManager</property-class>
    <value>#{EmpSalThresholdManager}</value>
    </managed-property>
</managed-bean>
Then, for each cell in the Salary column, the EmpHighlighterController is called again with the value in the cell. The EmpHighlighterController bean calls the getColor() method on the EmpThresholdManager stored in its private dynamicThresholdManager property and returns the response from that method to the cell. In the cell, it is used to set the background color property in the contentStyle.

Cell Highlight Settings Panel for Configuring Thresholds

The thresholds are specified by the user through the popup panel that appears when the little icon in the column header clicked upon. This is configured in the column's header facet using an image with a showPopupBehavior associated with the click action like this:

<af:column sortProperty="sal" sortable="true">
         <f:facet name="header">
           <af:panelGroupLayout layout="horizontal">
             <af:outputText value="#{bindings.emps.hints.sal.label}"/>
             <af:image shortDesc="Open Cell Highlight Settings Window"
                       source="/highlightIcon.gif">
               <af:showPopupBehavior popupId=":CellHighlightThresholdSettings"
                                     triggerType="click"/>
             </af:image>
           </af:panelGroupLayout>
         </f:facet>
         <af:inputText ...
</af:column>
The showPopupBehavior refers to the popup defined elsewhere in the page. The popup panel is somewhat changed from the implementation in Part 1 since it now needs to cater for a dynamic collection of thresholds.

Figure
Figure 4 The Popup for adding (and removing) thresholds

Inside the panel is always the initial color selector—even when no threshold is defined, the color for the cells can be specified. In addition to that basic selector, there is a button panel where new thresholds can be added and the current highlight settings can be applied.
<af:popup id="CellHighlightThresholdSettings">
       <af:panelWindow title="Highlight Settings for Employees Table"
                       id="cellHighlightingSettingsPanel">
         <af:panelGroupLayout id="thresholdPanel" layout="horizontal" valign="top"
                    partialTriggers="addThreshold">              
           <af:panelLabelAndMessage label="Initial Color"
                                    labelStyle="vertical-align: top;">
             <af:inputColor id="initialColor" chooseId="chooseInitial"
                            value="#{EmpSalThresholdManager.initialColor}"
                            compact="true"/>
             <af:chooseColor id="chooseInitial" defaultVisible="false"
                             lastUsedVisible="true"/>
           </af:panelLabelAndMessage>
           <af:iterator ... over all thresholds
               ... enter threshold value and color
           </af:iterator>
         </af:panelGroupLayout>
         <af:panelGroupLayout id="hdbuttongroup" layout="horizontal">
           <af:commandButton text="Add Threshhold" partialSubmit="true"
                             action="#{EmpSalThresholdManager.addThreshold}"
                             id="addThreshold"/>
           <af:commandButton text="Apply Highlighting" partialSubmit="true"
                             action="#{EmpSalThresholdManager.applyHighlighting}"
                             id="doHighlight">
             <af:setActionListener from="#{'::emptable'}"
                                   to="#{EmpSalThresholdManager.containerToRefresh}"/>
           </af:commandButton>
         </af:panelGroupLayout>
       </af:panelWindow>
</af:popup>
The initial color that can be set in the panel is stored in the initialColor property of the EmpSalThresholdManager bean—where it will be used later in the getColor() method.

The two buttons in the button panel both link to action methods in the EmpSalThresholdManager bean. One of them adds a threshold:

 List<Threshold> thresholds = new ArrayList();
    public String addThreshold() {
        Threshold threshold = new Threshold();
        Double thresholdValue = Double.POSITIVE_INFINITY;
        // if there are already thresholds, take the last one (plus 1) as starting point
        if (getThresholds().size() > 0) {
            thresholdValue =
                    (getThresholds().get(getThresholds().size() - 1).getLowLimit()) +
                    1;
        }
        threshold.setLowLimit(thresholdValue);
        thresholds.add(threshold);
        return null;
    }
       
Note how the Add Threshold button has partialSubmit set to true—meaning that clicking the button starts a partial page refresh cycle. Since its addThreshold ID value is included in the partialTriggers attribute of the thresholdPanel PanelGroupLayout, the ppr causes the highlight panel's contents to be refreshed when the button is clicked and the server-side processing has completed. The panel will contain a new, empty threshold after this refresh.

As for the second button, Apply Highlighting, it calls the applyHighlighting() method on the EmpSalThresholdManager. It also tells this bean that the ID of the container it should refresh is ::emptable. This piece of information is conveyed through the setActionListener that sends this string to the containerToRefresh property on the bean.

public String applyHighlighting() {
       refreshContainer();
       return null;
   }
Apply Highlighting is simple: it only needs to tell ADF to refresh the entire table at the end of the partial page rendering cycle. All information has already been collected.

The refreshContainer method is tasked with programmatically adding the table as a target for the partial page refresh:

private void refreshContainer() {
        AdfFacesContext adfContext = AdfFacesContext.getCurrentInstance();
        UIComponent highlightSettingsPanel =
            FacesContext.getCurrentInstance().getViewRoot().findComponent(getContainerToRefresh());
        if (highlightSettingsPanel != null) {
            adfContext.addPartialTarget(highlightSettingsPanel);
        }
    }
So what does it look like when more thresholds are added?

Figure
Figure 5 Creating as many thresholds for Salary cell highlighting as you need

And how is that implemented on the page—which JavaServer Faces (JSF) components take care of rendering these threshold controls?

Well, for each threshold, you need an inputText to enter the threshold value, a inputColor component, and a Delete command link to help the user get rid of a threshold. And you need to iterate over all thresholds already defined. Here's the JSF code for this:

<af:iterator value="#{EmpSalThresholdManager.thresholds}"
             var="threshold" varStatus="rowStatus">
  <af:panelGroupLayout layout="vertical">
    <af:panelLabelAndMessage label="Threshold #{rowStatus.index+1}"
                             labelStyle="vertical-align: top;">
      <af:panelGroupLayout layout="horizontal">
        <af:inputText value="#{threshold.lowLimit}" columns="6">
          <f:convertNumber pattern="####.##" type="number"/>
        </af:inputText>
        <af:commandLink id="deleteThreshold"
                        action="#{EmpSalThresholdManager.removeThreshold}"
                        partialSubmit="true">
          <af:image source="/delete.jpeg"
                    inlineStyle="height:15px;"/>
          <af:setActionListener from="#{rowStatus.index}"
                                to="#{EmpSalThresholdManager.deletedThresholdIndex}"/>
          <af:setActionListener from="#{'cellHighlightingSettingsPanel'}"
                                to="#{EmpSalThresholdManager.containerToRefresh}"/>
        </af:commandLink>
      </af:panelGroupLayout>
      <af:spacer height="20"/>
      <af:inputColor id="sic1" chooseId="chooseThresholdColor"
                     value="#{threshold.color}" compact="true"/>
      <af:chooseColor id="chooseThresholdColor"
                      defaultVisible="false"
                      lastUsedVisible="true"/>
     </af:panelLabelAndMessage>
    <af:spacer width="10" height="10"/>
  </af:panelGroupLayout>
</af:iterator>
The af:iterator component iterates over all elements in the EmpSalThresholdManager.thresholds collection. The necessary components are stamped out for every threshold element. The command link for deleting the threshold calls the removeThreshold() method on the EmpSalThresholdManager bean. It passes the index number of the current threshold so the bean knows which threshold to get rid of—as well as the identifier of the panelWindow—so that after the threshold is removed, the panel can immediately be refreshed:
public String removeThreshold() {
        thresholds.remove(getDeletedThresholdIndex().intValue());
        refreshContainer();
        return null;
    }

Applying the Appropriate Color to the Cell

At this point, we are missing just one element. We can add thresholds, edit them, and remove them. We can open the highlight panel, make it disappear, and apply the highlight settings. However, we have not yet discussed the getColor() method that determines which color should be used as the background for a certain cell, depending on its value.

contentStyle="background-color:#{EmpHighlighterController.reset[row.sal].color}"
It is up to the EmpHighlighterController bean, when it receives the "color" instruction immediately after receiving the cell's value, to call getColor() on the EmpSalThresholdManager:
public String getColor(Object fieldvalue) {
        String color = initialColor();
        //TODO: make sure that the thresholds are in an ascending order
        for (Threshold threshold : getThresholds()) {
            // if this threshold is below the current row value, the previous threshold's color is the one to return
            if (isInsideThreshold(fieldvalue, threshold.getLowLimit())) {
                break;
            }
            color = getColorString(threshold.getColor());
        } // for
        return color;
    }
The getColorString() method is a helper method that turns an AWT Color object into an RGB string representation that is used in Cascading Style Sheets (CSS) lingo:
protected String getColorString(Color rgbColor) {
        return rgbColor==null?"white":"rgb(" + rgbColor.getRed() + "," + rgbColor.getGreen() + "," +
            rgbColor.getBlue() + ")";

    }
The isInsideThreshold method is called to determine whether fieldvalue is below the threshold value:
public boolean isInsideThreshold(Object value, Double thresholdValue) {
    return (thresholdValue == null) || ((Double)value < thresholdValue);
}
In this case, this method is written on the assumption that the value and the thresholdValue will be double values. In this current situation, this method will not support cells with string-, date-, or custom type-based values. For salaries, it will do its job just fine, though.

Summarizing the algorithm very briefly: the cell passes its value, the getColor() method iterates over all thresholds until it finds the first one whose value is higher than the cell value, and the cell will be highlighted with the color of the threshold below that one.

Figure
Figure 6 Complete control over the number of thresholds, the threshold values, and the highlight color

Note: the unspoken assumption here is that the user will create the thresholds in increasing order. Ideally, the code in the DynamicThresholdManager class should somehow ensure that the thresholds in the thresholds list are sorted by ascending threshold value. I will leave that (surely you know what's coming next) as an exercise for the reader.

Step 2: Multicolumn Highlighting

With all the setup we did in the previous section—two managed beans, a panelWindow with some content, and an contentStyle attribute on the cell's inputText—it should now be easy to support cell highlighting on a second column in the table. You will now enable the user to highlight Hiredate cells, according to date-based thresholds.

In Figure 7 below, Hiredate cells are being highlighted before and after January 1, 1982. The salary cells in the same records are highlighted by the threshold values 2100 and 4000. The color coding enables you to, for example, quickly spot relatively inexperienced employees with a medium or high salary.

Figure
Figure 7 Cell Highlighting simultaneously in multiple columns

Preparing for Reuse with a Bounded Task Flow

We will see how we can use the modifications we made to the EmpTable page in the previous step as the foundation for a reusable highlight settings panel—implemented with an Oracle ADF Faces Bounded Task Flow. When you have this Task Flow implemented, you can embed it time again in multiple columns in many different pages. And you can very quickly add cell highlighting to the Hiredate column. All you need to do next is create and configure two very simple managed beans for the backing logic.

The popup with panelWindow you have added to support cell-highlighting for the Salary column is hardly specific to that column. You could use a very similar popup for other columns such as the Hiredate column. You only need a few tiny changes—such as catering for date values rather than numeric thresholds. However, the page quickly becomes messy and the longer-term maintenance effort increases unnecessarily. With a little effort, you can create a reusable component that saves us a lot of effort in the long run, and will prevents some embarrassment over rather ugly code in the near future.

Oracle ADF 11g introduced the Task Flow: a mechanism for creating reusable components. The Bounded Task Flow allows you to create a component that can be reused as embedded component inside other pages—exactly the thing that we need. A bounded task flow is embedded using the af:region tag. Our popup in the EmpTable.jspx page will become something like this when we use the Bounded Task Flow:

<af:popup id="CellHighlightThresholdSettings">
      <af:panelWindow title="Highlight Settings for Employees Table"
                         id="cellHighlightingSettingsPanel">
           <af:region value="#{bindings.cellhighlightthresholdspanel1.regionModel}"
                      id="cellh1"/>
      </af:panelWindow>
</af:popup>
In the page definition for the EmpTable page, you specify the input parameters you pass to the Bounded Task Flow through the regionModel. These input parameters allow you to tune the task flow's content and behavior to the specific situation in which it is used.

Create the Bounded Task Flow: The Reusable CellHighlightThresholdsPanel

Now, go through these steps:

  1. Create a new bounded task flow called cellhighlight-thresholdspanel.

    Figure
    Figure 8 Creating the Bounded Task Flow cellhighlight-thresholdspanel

  2. Specify parameters thresholdManager—to pass the specific DynamicThresholdManager bean to use for managing the thresholds—and tableWithHighlightingBinder to pass a reference to the Rich Table for which the highlighting is defined in this instance of the task flow.

    Figure
    Figure 9 Specifying the parameters (name, class, and location) for the Bounded Task Flow

  3. Add a single View activity to the Task Flow, called cellhighlightThresholdsPanel.

    Figure
    Figure 10 Add cellHighlightThresholdsPanel View activity to Bounded Task Flow

  4. Edit the cellhighlightThresholdsPanel .jsff fragment by double clicking the View activity icon in the task flow visual editor. Perform these steps:
    1. Copy all contents of the cellHighlightingSettingsPanel panelWindow in EmpTable.jspx to the jsp:root tag in the cellhighlightThresholdsPanel .jsff.
    2. Replace all occurrences of EmpSalThresholdManager (the Salary column specific bean) with pageFlowScope.thresholdManager (a reference to the bean passed into the task flow).
    3. Modify the setActionListener element on the doHighlight command button; it passes a reference to the RichTable to refresh with the latest highlighting settings using the TableBinder parameter that was passed into the Task Flow from the page that embeds it:
      <af:setActionListener from="#{pageFlowScope.tableWithHighlightingBinder.tableWithHighlighting}"
                                  to="#{pageFlowScope.thresholdManager.tableWithHighlighting}"/>
      

Embedding the Bounded Task Flow in the EmpTable Page

  1. Clear the contents of the panelWindow cellHighlightingSettingsPanel nested in the popup in the EmpTable.jspx page.

  2. From the Application Navigator, drag the Bounded Task Flow cellhighlight-thresholdspanel to the PanelWindow in EmpTable.jspx.

    Figure
    Figure 11 Dropping the Bounded Task Flow into the popup's PanelWindow

    The Visual Editor will now show the contents of the popup like this:

    Figure
    Figure 12 The popup after dropping the Bounded Task Flow

    Note the af:region element that has been created—it's a reference to the taskFlow element cellhighlightthresholdspanel1 in the Page Definition file that describes the usage of the Bounded Task Flow cellhighlight-thresholdspanel that you created previously.

  3. The last step is the configuration of the input parameters passed to the Bounded Task Flow from this usage on behalf of the Salary column. Go to the Page Definition editor for page EmpTablePageDef.xml.

    Figure
    Figure 13 Editing the taskFlow (usage) in the EmpTable's Page Definition

    Click on the Edit icon for the taskFlow executable. Now provide the EL expressions that set the values for the two inputParameters. The thresholdManager bean in this case is the #{EmpSalThresholdManager} that you have used and seen before. The other parameter, tableWithHighlightingBinder, is new. It is a way of passing a reference to a RichTable component to the Task Flow that allows the Task Flow to refresh that component in a Partial Page Refresh later on (when the Apply Highlighting button is pressed).

    Figure
    Figure 14 Specifying the values for the input parameters to pass into the Task Flow

    Inside the Bounded Task Flow, there is no knowledge of the emptable RichTable in the page that happens to embed the task flow. So instead, you'll tell the task flow about this table to refresh. The approach for passing that information:

    1. Create a class TableBinder with a single property and its accessors:
      package otn.cellhighlighting.jsf;
      import oracle.adf.view.rich.component.rich.data.RichTable;
      public class TableBinder {
        private RichTable tableWithHighlighting;
      
    2. Configure a managed bean for the emptable:
      <managed-bean>
          <managed-bean-name>EmpTableBinder</managed-bean-name>
          <managed-bean-class>otn.cellhighlighting.jsf.TableBinder</managed-bean-class>
          <managed-bean-scope>session</managed-bean-scope>
      </managed-bean>
      
    3. And bind the emptable to this bean:
      <af:table id="emptable" ....
                        binding="#{EmpTableBinder.tableWithHighlighting}">
      
    With these steps, the setActionListener on the doHighlight button hopefully starts to make sense: it passes the Richtable reference to the thresholdManager.
    <af:commandButton text="Apply Highlighting" partialSubmit="true"
                          action="#{pageFlowScope.thresholdManager.applyHighlighting}"
                          id="doHighlight">
          <af:setActionListener from="#{pageFlowScope.tableWithHighlightingBinder.tableWithHighlighting}"
                                to="#{pageFlowScope.thresholdManager.tableWithHighlighting}"/>
    </af:commandButton>
    
    The DynamicThresholdManager class has undergone a few small changes to accommodate this approach:
    public class DynamicThresholdManager {
        private RichTable tableWithHighlighting;
    ...
           public String applyHighlighting() {
            refreshTableWithHighlighting();
            return null;
        }
        private void refreshTableWithHighlighting() {
            AdfFacesContext adfContext = AdfFacesContext.getCurrentInstance();
            adfContext.addPartialTarget(this.tableWithHighlighting);
        }
        public void setTableWithHighlighting(RichTable tableWithHighlighting) {
            this.tableWithHighlighting = tableWithHighlighting;
        }
    ...
    

    Figure
    Figure 15 How it all hangs together: column has icon that on click brings up a popup with panelWindow that contains region referenced to bounded taskflow consisting of a page fragment

When you run the EmpTable page now, you should not notice any difference with before when the popup contents were specified in the EmpTable.jspx page itself.

Reaping the Benefits: Time for Reuse

So far we have not achieved any new functionality. We can run the EmpTable page and get exactly the same behavior we had at the end of Step 1. However, we are also all set for reuse. Adding cell highlighting to the Hiredate column has now become a very easy job. This is where the work on the Hiredate column really starts.

For cell highlighting to be enabled in the Hiredate column, the column header needs to contain an icon to have a popup activated in the same way the Salary column does. So copy the header facet in the Salary column, and paste it into the Hiredate column. Then modify the facet to:

<f:facet name="header">
        <af:panelGroupLayout layout="horizontal">
                <af:outputText value="#{bindings.emps.hints.hiredate.label}"/>
                <af:image shortDesc="Open Cell Highlight Settings Window"
                          source="/highlightIcon.gif">
                  <af:showPopupBehavior popupId=":HiredateCellHighlightThresholdSettings"
                                        triggerType="click"/>
                </af:image>
        </af:panelGroupLayout>
</f:facet>
Notice the two small but essential changes in the facet definition: the hiredate label should be used in the outputText and the popupId reference in the showPopupBehavior should be HiredateCellHighlightThresholdSettings (this popup is not yet created).

Also add the contentStyle attribute to the inputText for Hiredate; this attribute contains the expression that retrieves the cell's background color from the EmpHiredateHighlighterController:

<af:inputText value="#{row.hiredate}"
      contentStyle="background-color:#{EmpHiredateHighlighterController.reset[row.hiredate].color}">
      ...
      <f:convertDateTime pattern="#{bindings.EmpManageremps.formats.hiredate}"/>
  </af:inputText>
</af:column>
Add the following popup element:
<af:popup id="HiredateCellHighlightThresholdSettings">
          <af:panelWindow title="Highlight Settings for Hiredate in Employees Table"
                          id="hiredateCellHighlightingSettingsPanel">
          </af:panelWindow>
</af:popup>
Now drag the bounded task flow cellhighlight-thresholdspanel from the application navigator and drop it in the panelWindow. Provide values for the Input Parameters for the Task Flow:

Figure
Figure 16 Set the values for the parameters to pass to the Task Flow for the Hiredate usage

The entry in the PageDefinition file for the EmpTable that is created for this task flow reference looks like this:
<taskFlow id="cellhighlightthresholdspanel2"
              taskFlowId="/WEB-INF/cellhighlight-thresholdspanel.xml#cellhighlight-thresholdspanel"
              xmlns="http://xmlns.oracle.com/adf/controller/binding">
      <parameters>
        <parameter id="thresholdManager" value="#{EmpHiredateThresholdManager}"
                   xmlns="http://xmlns.oracle.com/adfm/uimodel"/>
        <parameter id="tableWithHighlightingBinder" value="#{EmpTableBinder}"
                   xmlns="http://xmlns.oracle.com/adfm/uimodel"/>
      </parameters>
</taskFlow>

Backing Beans and Classes

The DynamicThresholdManager class we used before supports only double values in its isInsideThreshold method. You need to extend this class to also deal with date values. Extend the Threshold class with a dateValue property. Create a new class called EmpHiredateThresholdManager, with overrides of two methods to support date values:

public class EmpHiredateThresholdManager extends DynamicThresholdManager {
    public EmpHiredateThresholdManager() {
    }

    public boolean isInsideThreshold(Object value, Threshold threshold) {
        long thresholdDate = threshold.dateValue.getTime();
        long valueDate = ((Timestamp)value).getTime();
        return (thresholdDate > valueDate);
    }


    public String addThreshold() {
        Threshold threshold = new Threshold();
        Date thresholdValue = new Date();
        // if there are already thresholds, take the last one (plus 1) as starting point
        if (getThresholds().size() > 0) {
            thresholdValue =
                    super.getThresholds().get(super.getThresholds().size() -
                                              1).getDateValue();
        }
        threshold.setDateValue(thresholdValue);
        thresholds.add(threshold);
        return null;
    }
}
To support cell highlighting in the Hiredate column, you need to configure two new managed beans in adfc-config.xml:
<managed-bean>
    <managed-bean-name>EmpHiredateThresholdManager</managed-bean-name>
    <managed-bean-class>otn.cellhighlighting.jsf.EmpHiredateThresholdManager</managed-bean-class>
    <managed-bean-scope>session</managed-bean-scope>
</managed-bean>
<managed-bean>
    <managed-bean-name>EmpHiredateHighlighterController</managed-bean-name>
    <managed-bean-class>otn.cellhighlighting.jsf.ThresholdHighlighterController</managed-bean-class>
    <managed-bean-scope>request</managed-bean-scope>
    <managed-property>
    <property-name>dynamicThresholdManager</property-name>
    <property-class>otn.cellhighlighting.jsf.DynamicThresholdManager</property-class>
    <value>#{EmpHiredateThresholdManager}</value>
    </managed-property>
</managed-bean>
That's all. You can run the EmpTable page and open a popup window for specifying Cell Highlighting settings for the Hiredate column just like you can for Salary:

Figure
Figure 17 Now you can bring up the popup window for specifying highlight settings for both the Hiredate and the Salary column.

Well, almost that is. The threshold values for Hiredate obviously are dates, not numbers. And for entering dates, you should use the inputDate component, not a plain inputText. So the final step is to make the bounded task flow aware of the type of threshold value you are after so it can respond by showing the correct input component.

First add a property dateValue to the Threshold bean. Then specify a new Input Parameter on the Bounded Taskflow. This parameter is called thresholdDataType and it is used to pass a String that contains the values "number" or "date", depending on the data type of the column for which the task flow should maintain the thresholds.

Open the page fragment for the View activity cellhighlightThresholdsPanel in the editor. Add a switcher component to deal with different thresholdDataType values:

<af:switcher facetName="#{pageFlowScope.thresholdDataType}"
                         defaultFacet="number">
              <f:facet name="number">
                <af:inputText value="#{threshold.lowLimit}" columns="6">
                  <f:convertNumber pattern="####.##" type="number"/>
                </af:inputText>
              </f:facet>
              <f:facet name="date">
                <af:inputDate value="#{threshold.dateValue}" >
                  <af:convertDateTime pattern="d-MMM-yyyy"/> 
                </af:inputDate>
              </f:facet>
</af:switcher>
The switcher examines the value of #{pageFlowScope.thresholdDataType} which amounts to the value of the thresholdDataType input parameter. The value is used to select the switcher's to render. When no value is passed, the default is taken which is 'number'. The facet with name equal to 'date' is shown when the input parameter has the value 'date'—and that is the value you will assign for the task flow usage for the Hiredate column.

Go to the file EmpTablePageDef and edit the taskflow element with id equals cellhighlightthresholdspanel2. Add the parameter element.

<parameter id="thresholdDataType" value="#{'date'}"
                   xmlns="http://xmlns.oracle.com/adfm/uimodel"/>
You now have a new popup for the Hiredate column that you can bring up to edit the Hiredate cell highlighting thresholds.

Figure
Figure 18 Managing the thresholds for the Hiredate column using the InputDate component

At this point the user can set up thresholds for Hiredate as well as for Salary by configuring two managed beans, including a region, and adding a few snippets to attributes on JSF components.

Adding threshold-based cell highlighting to other columns should now be fairly easy. Try it, for example, for the Deptno column. The steps are:

  1. Configure two managed beans.
  2. Create a popup with a child panelWindow.
  3. Add an icon to the column header with a showPopupBehavior that links it to the popup.
  4. Drag the bounded task flow to the panelWindow inside the popup and specify the two input parameters.
  5. Set the contentStyle property on the cell's inputText component.

Step 3: Expression-Based Highlighting

The finishing touch is the addition of expression-based highlighting. Cell highlighting can be based on user-defined formulas that may refer to all attributes in a record (even nondisplayed ones). This enables you to clearly mark employees who are SALESMAN and earn more than 1500 (using the expression [sal]> 1500 && [job]=='SALESMAN') or who work in department 20 and are either CLERK or ANALYST and earn less than 2500 (expression: [deptno] ==20 && ([job]== 'CLERK' or [job]== 'ANALYST') && [sal] < 2500).

Figure
Figure 19 Expression Based Cell Highlighting: two expressions applied to the Ename column

A reference to an attribute in the record consists of the name of the attribute in square brackets, such as [sal] and [job]. Before the expression gets interpreted, the attribute reference is replaced by a string representation of its value. The resulting string is evaluated by the JSF EL evaluator. All operators supported by JSF Expression Language—such as +, �, <, !=, ==, and, or, ()—as well as references to managed beans and ternary expressions can be used in the expressions.

To allow for a little ability to use date values in any meaningful way, ExpressionManager translates dates according to the fixed format DD-MM-YYYY and also supports functions to extract the year or month from a date. The following quite ridiculous expression is still a valid one:

[hiredate].month==1?[job]=='CLERK' : [hiredate].year < 1982 and [sal]>2800
...which states that the expression should evaluate to true (the cell will be highlighted) when the employee is either a clerk hired in January or someone hired in a different month of a year prior to 1982 and earns more than 2800. And believe it or not, five guys fit this bill.

Figure
Figure 20 Using special date operators month and year for date-based expressions

Note: you may want to add extra ViewObject attributes such as ManagerJob, NumberOfSubordinates, or SalaryRankInDepartment for use in the expressions.

With the work you have done previously on threshold-based cell highlighting in multiple columns, you have laid a solid foundation on which you can now build the expression-based highlighting.

Creating New Classes and Configuring Managed Beans

The DynamicThresholdManager class you used to hold the thresholds and implement the getColor() method that determines the color for each cell needs to be extended to deal with expressions. To do that create the otn.cellhighlighting.jsf.DynamicHighlightExpressionManager class, which extends from DynamicThresholdManager.

Add a property expression to the Threshold bean, of type String.

The addThreshold() method is overridden in DynamicHighlightExpressionManager. Note how the expression is initially set to false—a valid EL expression that will never evaluate to true.

public String addThreshold() {
        Threshold threshold = new Threshold();
        threshold.setExpression("false");
        thresholds.add(threshold);
        return null;
    }
Also override the getColor() method to check the result of evaluating the expression for a particular row instead of dealing with thresholds and a single attribute value:
public String getColor(Object input) {
        // check whether the input (a Row object)
        // confirms with one of the expressions
        // if so, the color associated with the expression is returned
        String color = null;
        for (Threshold threshold : getThresholds()) {
            if (evaluateExpression(threshold.getExpression(), (Row)input)) {
                color = getColorString(threshold.getColor());
                return color;
            }
        }
        return null;
    }
The real expression "magic" is in the evaluateExpression(String expression, Row row) method. This method replaces every attribute placeholder ([attribute name]) with the actual value of that attribute in the current row. Then the resulting expression evaluates to either true or false. The resulting Boolean is returned from the method.

Figure
Figure 21 Determining cell color with expression highlighting. The row is passed to the HighlightController bean, which evaluates the expressions and returns the color for the first one that evaluates to true.

Most of the code lines in evaluateExpression are required to deal with attributes of various datatypes. The bare essence of the method, handling only long values, looks like this:
    private boolean evaluateExpression(String expression, Row row) {
        // replace [attribute name] with 'value of the attribute in the row'
        if (expression != null && expression != "") {
            String exp = expression;
            // iterate over all attributes in row; make for each the suitable replacement
            int i = 0;
            for (String att : row.getAttributeNames()) {
                AttributeDef attdef =
                    row.getStructureDef().getAttributeDef(i++);
                if (attdef.getJavaType().getName().equals("java.lang.Long")) {
                    Long value = (Long)row.getAttribute(attdef.getName());
                    if (value != null)
                        exp =
exp.replace("[" + attdef.getName() + "]", value.toString());
                    else
                        exp =
exp.replace("[" + attdef.getName() + "]", "8747436436");
                }
                if (attdef.getJavaType().getName().equals("oracle.jbo.domain.Date")) {
                    oracle.jbo.domain.Date value =
                        (oracle.jbo.domain.Date)row.getAttribute(attdef.getName());
                    if (value != null) {
                        java.text.SimpleDateFormat displayDateFormat =
                            new java.text.SimpleDateFormat("dd-MM-yyyy");

                        exp =
exp.replace("[" + attdef.getName() + "]", displayDateFormat.format(value.dateValue()));
                    } else
                        exp =
exp.replace("[" + attdef.getName() + "]", "8747436436");
                }
                ...support for other data types such as String, Double, Date & Timestamp, Number
            }
            return (Boolean)getExpressionValue("#{" + exp + "}");
        }
        return false;
    }
The helper method getExpressionValue does the actual evaluation of strings that contain an EL expression, returning the outcome of the evaluation:
public Object getExpressionValue(String el) {
    FacesContext ctx = FacesContext.getCurrentInstance();
    Application app = ctx.getApplication();
    Object result = app.evaluateExpressionGet(ctx, el, Object.class);
    return result;
}
It turns out that the variable exposed by the Oracle ADF collection model used in the table component is not a Row object, even though Oracle JDeveloper called the variable row when you dropped the collection as a table on the page.
  <af:table id="emptable"
      value="#{bindings.emps.collectionModel}" var="row"
      ...
You need a little logic to get at the Row we need for the evaluateExpression method. To this end, modify the get() in the ThresholdHighlighterController class method. Add support for a key of type FacesCtrlHierNodeBinding—the type of the variable in the loop over the collection model, from which the real Row can be extracted:
public Object get(Object key) {
    if (key instanceof FacesCtrlHierNodeBinding) {
        FacesCtrlHierNodeBinding ding = (FacesCtrlHierNodeBinding)key;
        operands.add(ding.getRow());
    } else if (key instanceof String) {
   ...
Two managed beans must be configured to support Ename's expression-based cell highlighting. One is ExpressionManager, and the other is HighlighterController:
<managed-bean>
  <managed-bean-name>EmpEnameExpressionManager</managed-bean-name>
  <managed-bean-class>otn.cellhighlighting.jsf.DynamicHighlightExpressionManager</managed-bean-class>
  <managed-bean-scope>session</managed-bean-scope>
</managed-bean>
<managed-bean>
  <managed-bean-name>EmpEnameHighlighterController</managed-bean-name>
  <managed-bean-class>otn.cellhighlighting.jsf.ThresholdHighlighterController</managed-bean-class>
  <managed-bean-scope>request</managed-bean-scope>
  <managed-property>
  <property-name>dynamicThresholdManager</property-name>
  <property-class>otn.cellhighlighting.jsf.DynamicThresholdManager</property-class>
  <value>#{EmpEnameExpressionManager}</value>
  </managed-property>
</managed-bean>
The extra support for date values—the year and month operators—is implemented through a little piece of additional code in the evaluateExpression method:
if (attdef.getJavaType().getName().equals("java.sql.Timestamp")) {
     Date value = 
         (Date)row.getAttribute(attdef.getName());
     if (value != null) {
         exp = 
         exp.replace("[" + attdef.getName() + "].month", monthDateFormat.format(value));
         exp = 
         exp.replace("[" + attdef.getName() + "].year", yearDateFormat.format(value));
         exp = 
         exp.replace("[" + attdef.getName() + "]", "'"+displayDateFormat.format(value)+"'");
     } else
         exp = 
 exp.replace("[" + attdef.getName() + "]", "'8747436436'");
 }
where the DateFormat variables are class-level constants:
  private static java.text.SimpleDateFormat displayDateFormat = 
             new java.text.SimpleDateFormat("dd-MM-yyyy");
  private static java.text.SimpleDateFormat yearDateFormat = 
             new java.text.SimpleDateFormat("yyyy");
  private static java.text.SimpleDateFormat monthDateFormat = 
             new java.text.SimpleDateFormat("MM");

Changes in JSF Pages

The Bounded Task Flow needs to support Expressions. We add the following facet to the switcher component we introduced earlier on:

            
<f:facet name="expression">
   <af:inputText value="#{threshold.expression}" rows="3" />                
</f:facet>
This means that if the value of the thresholdDataType input parameter passed into the Bounded Task Flow is equal to "expression", that an inputText is displayed with three rows that gets and sets its value from and to the expression property in the Threshold bean.

For expressions, we will not have an initial color; a color is applied only when an expression evaluates to true for a certain row. The rendered attribute of the initial color, panelLabelAndMessage, is set to have it rendered only when the datatype is not equal to "expression".

<af:panelLabelAndMessage label="Initial Color#{pageFlowScope.thresholdDataType}"
                   labelStyle="vertical-align: top;"
                   rendered="#{pageFlowScope.thresholdDataType==null or pageFlowScope.thresholdDataType!='expression'}">
The label on the button that now says "Add Threshold" should also cater for expressions:
<af:commandButton text="Add #{pageFlowScope.thresholdDataType=='expression'?'Expression':'Threshhold'}" partialSubmit="true"
                  action="#{pageFlowScope.thresholdManager.addThreshold}"
                  id="addThreshold"/>
On the EmpTable.jspx page, we have to add a popup element with panelWindow and once again drop the Bounded Task Flow on that panelWindow. The code in the page is like this:
<af:popup id="EnameCellHighlightThresholdSettings">
          <af:panelWindow title="Highlight Settings for Ename in Employees Table"
                          id="enameCellHighlightingSettingsPanel">
            <af:region value="#{bindings.cellhighlightthresholdspanel3.regionModel}"
                       id="cellh3"/>
          </af:panelWindow>
</af:popup> 
The column header for the Ename column refers to this popup:
<af:column sortProperty="ename" sortable="true">
            <f:facet name="header">
              <af:panelGroupLayout layout="horizontal">
                <af:outputText value="#{bindings.emps.hints.ename.label}"/>
                <af:image shortDesc="Open Cell Highlight Settings Window"
                          source="/highlightIcon.gif">
                  <af:showPopupBehavior popupId=":EnameCellHighlightThresholdSettings"
                                        triggerType="click"/>
                </af:image>
              </af:panelGroupLayout>
</f:facet>
To have the color produced by the EmpEnameHighlighterController actually result in cell highlighting, you need to specify the contentStyle attribute for the Ename inputText component:
<af:inputText value="#{row.ename}" simple="true"
     contentStyle="background-color:#{EmpEnameHighlighterController.reset[row].color}"
With these changes in place, you can run EmpTable.jspx. The Ename column now has the highlight icon too. Positioning the cursor over the icon will bring up the highlight settings panel, this time for specifying expressions.

Conclusion

In Part 1 of this two-part article on Oracle ADF cell highlighting, you learned how to rapidly add static highlighting to Oracle ADF Faces data tables. With EL expressions, getting the highlighting to work was easy. You then extended the functionality with more end-user control: backed by a managed bean, you made both the threshold values and the associated colors dynamic and provided users with a panel for manipulating those settings and embedded this panel in a popup to be displayed only when the user needs it, making for a nicer user interface.

Part 2 extended the highlight functionality in several ways. We added support for multicolumn highlighting in the same table, allowing the Hiredate column highlighting to reflect work experience (green being less experienced) and the Salary column to indicate the employees' income category as well as dynamic threshold definitions: users can add as many thresholds as they see fit, instead of the two supported in the first installment. Finally, and most interestingly, you looked at the addition of expression-based highlighting. Cell highlighting can be based on formulas that may refer to all attributes in a record (even nondisplayed ones).

The work you have done boils down to just a small number of very simple Java classes and a Bounded Task Flow that are all fairly generic. To add cell highlighting—threshold-based as well as expression-based—to your own applications, you only have to include these classes in your project, add the Bounded Task Flow, and configure the required managed beans. You should be able to add this functionality to a column in any of your Oracle ADF tables in a matter of minutes.

As always, there are several ways to improve the results achieved so far. Some obvious refinements:

  • Persistent storage of thresholds across sessions (when the user would like to use the same color scheme over and over again, it would be useful to store the definitions of thresholds and/or expressions in a persistent way and make these definitions available in subsequent sessions)
  • More sophisticated expressions with, for example, additional operators and functions as well as support for other datatypes
  • Better-looking pop-up panels and better handling of partial page refresh (show/hide panels)
  • Row highlighting in addition to cell highlighting
  • Sort or filter (client side) by color

This article has demonstrated the power of JSF's expression language and showed some useful Ajax/partial page rendering techniques. You have also experienced some of the richness of Oracle ADF 11g as well as the repeated (re)use of a bounded task flow.

 Read Part 1 of this series




Lucas Jellema is CTO of AMIS in Nieuwegein, The Netherlands, and an Oracle ACE Director (Developer Tools). He participates in projects typically involving ADF and SOA. In addition he is a frequent presenter at conferences like Oracle OpenWorld and JavaOne as well as a frequent blogger.