Wednesday 21 October 2015

Implement a custom autosuggest filter for an ADF table

The purpose is to create a custom filter with autosuggestion feature on an ADF table column.

The idea is to have a couple of buttons in the table header to make the filter visible, and to disable and clear the filter itself:


After the filter is enabled, the text box appears, and as soon as the user starts typing, the autosuggestion kicks in:



This is what we need on the page/fragment:

A button to activate the filter (i.e. make the filter visible):

<af:commandButton text="Filter"
   id="cbFilter" rendered="true" partialSubmit="true"
   disabled="#{pageFlowScope.filter}" styleClass="btn">
 <af:setActionListener from="true" to="#{pageFlowScope.filter}"/>
</af:commandButton>

A button to deactivate and clear the filter:

<af:commandButton text="#{vehicleBundle.SELECTIVE_TABLE_FILTER_DISABLE}"
     id="cbDFilter" rendered="true"
     partialSubmit="true"
     actionListener="#{viewScope.vehicleBean.resetTableFilter}"
     disabled="#{!pageFlowScope.filter}"
     styleClass="btn">
  <af:setActionListener from="false"
      to="#{pageFlowScope.filter}"/>
</af:commandButton>

This is the table containing the filter: in the column we want to filter, we have an af:inputText component with autosuggest behavior operation, and two buttons to perform the filtering and clear the filter.
<af:table value="#{bindings.vehicleList1.collectionModel}" var="row"
     rows="#{bindings.vehicleList1.rangeSize}" fetchSize="5"
     varStatus="vs" partialTriggers=":cbDFilter :cbFilter"
     filterModel="#{bindings.vehicleListQuery.queryDescriptor}"
     queryListener="#{bindings.vehicleListQuery.processQuery}"
     filterVisible="#{pageFlowScope.filter}" id="listVehicles" [ . . . ]>
[ . . .]
<af:column filterable="true" filterFeatures="caseInsensitive"
     headerText="#{vehicleBundle.VEHICLE_DESC}" id="cVD">
 <f:facet name="filter">
  <af:panelGroupLayout id="pgAS" layout="vertical">
    <af:outputText value="#{vehicleBundle.FILTER_VEHICLES}" id="oFMsg"/>
    <af:panelGroupLayout id="pgFL" layout="horizontal">
    <af:inputText id="itFL"
         value="#{vs.filterCriteria.vehicleDescription}"
         autoSubmit="true" usage="search">
     <af:autoSuggestBehavior suggestItems="#{viewScope.vehicleBean.suggestedVehicles}"/>
    </af:inputText>
    <af:commandButton partialSubmit="true" id="cbexecute" text="Filter"/>
    <af:commandButton partialSubmit="true" id="cbclear" text="Clear"
          actionListener="#{viewScope.vehicleBean.resetTableFilter}"/>
    </af:panelGroupLayout>
  </af:panelGroupLayout>
 </f:facet>
</af:column>

In this case, we need the item type returned from the iterator to expose an attribute called 'vehicleDescription'.
  Since the table is supposed to show a collection of Vehicle objects, the Vehicle class needs to have a 'vehicleDescription' property (or a getter method called 'getVehicleDescription').

  So the page definition for this page/fragment will contain something like this:

<tree IterBinding="vehiclesIterator" id="vehicleList1">
      <nodeDefinition DefName="com.test.Vehicle"
                      Name="vehicleList10">
        <AttrNames>
          <Item Value="id"/>
          <Item Value="vciIdentifier"/>
          [. . .]
          <Item Value="vehicleDescription"/>
        </AttrNames>
      </nodeDefinition>
    </tree>

 [. . .]
 
 <searchRegion Binds="vehiclesIterator" Criteria=""
     Customizer="oracle.jbo.uicli.binding.JUSearchBindingCustomizer"
     id="vehicleListQuery"/>
 
Here are the methods needed:
public List suggestedVehicles(FacesContext facesContext,
                                 AutoSuggestUIHints autoSuggestUIHints) {
        // Add event code here...
        List<SelectItem> suggestions =
            autoSuggestIterator("vehiclesIterator",
                                         "vehicleDescription",
                                         autoSuggestUIHints.getSubmittedValue());
        return suggestions;
    }

    public void resetTableFilter(ActionEvent actionEvent) {
        // Add event code here...
        resetTableFilter("listVehicles");
    }
 
    public List<SelectItem> autoSuggestIterator(String iterator, String listValue, String input){
        // Add event code here...
        DCIteratorBinding binding = ADFUtils.findIterator(iterator);
        int rangeSize = binding.getRangeSize();
        binding.setRangeSize(1000);
        List suggestionList = ADFUtils.attributeListForIterator(iterator, listValue);
        List<SelectItem> suggestions = new ArrayList<SelectItem>();

        for (int i = 0; i < suggestionList.size(); i++) {
            if (suggestionList.get(i).toString().toUpperCase().contains(input.toUpperCase())) {
                suggestions.add(new SelectItem(suggestionList.get(i)));
            }
        }
        binding.setRangeSize(rangeSize);
        return suggestions;
    }
 
   public List attributeListForIterator(DCIteratorBinding iter,
                                         String valueAttrName) {
        List attributeList = new ArrayList();
        for (Row r : iter.getAllRowsInRange()) {
            attributeList.add(r.getAttribute(valueAttrName));
        }
        return attributeList;
    }
 
   public void resetTableFilter(String tableId) {

        UIComponent uiComponent = JSFUtils.findComponentInRoot(tableId);
        RichTable table = (RichTable)uiComponent;

        FilterableQueryDescriptor queryDescriptor =
            (FilterableQueryDescriptor)table.getFilterModel();
        if (queryDescriptor != null &&
            queryDescriptor.getFilterCriteria() != null) {
            queryDescriptor.getFilterCriteria().clear();
            table.queueEvent(new QueryEvent(table, queryDescriptor));
        }
    }