Using a SelectMany… component in a JHeadstart Advanced Search Form – search for all CLERKS & SALESMEN

JHeadstart is capable of generating so called advanced search forms. These allow the end user of the ADF application created using JHeadstart to enter various search criteria on which records are to filtered. It is ridiculously easy for example to generate a search form with a dropdownlist for a field that has a domain defined for it (or a lookup relation). The JHeadstart runtime code translates the entries in the search form into ViewCritera on the underlying ADF BC ViewObject and executes the query.

However, JHeadstart does not currently support the option of Multi Select search fields: fields that allow selection of a set of values. The example below shows such a multi-select search item: we want to search for all employee that have one of the selected jobs:

Using a SelectMany... component in a JHeadstart Advanced Search Form - search for all CLERKS & SALESMEN multiSearch1

In this article, I will demonstrate how you can implement this functionality.
Basically, the steps required for implementing multi-select search items are:

  1. extend the JhsSearchBean and add an ArrayList property as well as an override for the createArgumentListForAdvancedSearch() method
  2. modify the page-beans.xml file to use our subclass instead of JhsSearchBean
  3. extend the JhsApplicationModuleImpl – override the advancedSearch() method – a very small modification: the method should not transform quotes to double qoutes in case of the IN operator
  4. convert the selectOneChoice element to a selectManyListBox (or another multi select element) and bind the value attribute to the new ArrayList property on the extended search bean

Step 1 – Extend the JhsSearchBean

The class oracle.jheadstart.controller.jsf.bean.JhsSearchBean is used in JHeadstart based application to hold all values associated with the advanced search form on a page as well as intermediate for performing the query itself. Currently, it does not have any support for a multiselect search property – one that does not have a scalar value but instead an ArrayList collection of values. We need to extend the search bean with some small additions:

Create a property jobs with associated getter and setter:

public class MySearchBean extends JhsSearchBean   {
    
    public MySearchBean() {
    }

    private Object jobs;

    public void setJobs(Object jobs) {
      super.getCriteria().put("Job",jobs);
      this.jobs = jobs;
    }

    public Object getJobs() {
        return super.getCriteria().get("Job");
    }
}

Then override the createArgumentListForAdvancedSearch() method. This method creates an ArrayList of QueryCondition objects that is passed to the ADF BC ApplicationModuleImpl’s advancedSearch method that then uses these QueryConditions for creating ViewCriteria for the ViewObject.

We need to add additional QueryCondition(s) for our multi-select items, after calling the super’s createArgumentListForAdvancedSearch():

    protected ArrayList createArgumentListForAdvancedSearch()
    {
      ArrayList<QueryCondition> qcs = super.createArgumentListForAdvancedSearch();
      if (getJobs()!=null) {
        DCIteratorBinding iterBinding = getIterBinding();
        ViewObject vo =
          iterBinding.getRowSetIterator().getRowSet().getViewObject();
        AttributeDef def = vo.findAttributeDef("Job");
        String inCondition ="(";
        // create the IN condition from the Strings in the Jobs ArrayList
        // example: ('CLERK', 'PRESIDENT')
        for (String s:(ArrayList<String>)getJobs()) {
            inCondition=inCondition+"'"+s+"'"+",";
        }
        QueryCondition qa =
          new QueryCondition(def, inCondition.substring(0,inCondition.lastIndexOf(","))+")");
        qa.setOperator("IN");
        qa.setJavaType(ArrayList.class);
        qcs.add(qa);
      }
      return qcs;    
    } 

One obvious downside of this approach is that it is entirely specific, suited to only handle the jobs item. A more generic approach is wanted. However, the obvious ones did not fly – for reasons yet beyond me. I ran into nasty errors such as:

javax.faces.convert.ConverterException: Unsupported model type.
	at oracle.adfinternal.view.faces.renderkit.core.xhtml.SimpleSelectManyRenderer._throwUnsupportedModelType(SimpleSelectManyRenderer.java:573)
	at oracle.adfinternal.view.faces.renderkit.core.xhtml.SimpleSelectManyRenderer.getDefaultConverter(SimpleSelectManyRenderer.java:143)
	at oracle.adfinternal.view.faces.renderkit.core.xhtml.SimpleSelectManyRenderer.encodeAllAsElement(SimpleSelectManyRenderer.java:298)
	at oracle.adfinternal.view.faces.renderkit.core.xhtml.FormElementRenderer.encodeAll(FormElementRenderer.java:48)

when I attempted approaches like:

for (String key:(Set<String>)myMap.keySet()) {    
      ArrayList al = (ArrayList)myMap.get(key);
          DCIteratorBinding iterBinding = getIterBinding();
            ViewObject vo =
              iterBinding.getRowSetIterator().getRowSet().getViewObject();
            AttributeDef def = vo.findAttributeDef(key);
            String inCondition ="(";
            for (String s:(ArrayList<String>)al) {
                inCondition=inCondition+"'"+s+"'"+",";
            }
            
            QueryCondition qa =
              new QueryCondition(def, inCondition.substring(0,inCondition.lastIndexOf(","))+")");
              qa.setOperator("IN");
              qa.setJavaType(ArrayList.class);
qcs.add(qa);

      }

where myMap is a private HashMap with accessors, linked to the selectManyListBox through: value="#{searchEmp.myMap.Job}

Step 2 – Modify the page-beans.xml file to use our subclass instead of JhsSearchBean

The JHeadstart Application Generator generates a faces-config file to set up the beans for the generated Faces page, in our case Emp-beans.xml. In this file, the standard JhsSearchBean class is used for instantiating the search bean for the Employees page. We have to change this config file to use our own extended searchbean class:

<managed-bean>
  <managed-bean-name>searchEmp</managed-bean-name>
  <managed-bean-class>nl.amis.search.MySearchBean</managed-bean-class>
  <managed-bean-scope>session</managed-bean-scope>
  <managed-property>
    <property-name>bindings</property-name>
    <value>#{data.EmpPageDef}</value>
  </managed-property>
  <managed-property>
...

Step 3 – Extend
the JhsApplicationModuleImpl

Our ApplicationModuleImpl class extends from JhsApplicationModuleImpl. That class contains the advancedSearch() method, that we have to override. The issue with the method as it currently is, is that it translates single quotes to double quotes – which is okay for query operators like LIKE, >, <, = etc. However, not for the IN operator that we want to use. There are no hooks (template design pattern) for this method, so unfortunatetly the override consists of a copy and paste of the entire advancedSearch() method with the following minuscule change:

// escape quotes in value, even if it is su[posed to be a number, to
// prevent sql error
// THE ONLY CHANGE IN THE ADVANCEDSEARCH method is the next lineE
if (!"IN".equalsIgnoreCase(operator)) // only this line needs to be added!
  value = StringUtils.substitute(value,"'","''");

Step 4 – Convert the
selectOneChoice element to a SelectManyListBox

The final step is to convert the selectOneChoice element that was generated for the Job search item into a selectManyListBox (or selectManyCheckBox or one of the other SelectManyElements). We then tie the value attribute of the selectManyListBox to the jobs property on the search bean. And that is all we have to set up:

<!-- The only changes are in the first two line: af:selectManyListBox instead of selectSingleChoice and value="#(searchEmp.jobs" instead of "#{searchEmp.criteria.EmpJob} -->
<af:selectManyListbox id="SearchEmpJob" label="Job"
                      value="#{searchEmp.jobs}">
    <af:forEach var="row2"
                items="#{bindings.JobsDomain.rangeSet}">
        <af:selectItem id="SiAsEmpJob" label="#{row2.Job}"
                       value="#{row2.Job}"/>
    </af:forEach>
</af:selectManyListbox>

 

2 Comments

  1. Serafeim Karapatis November 3, 2011
  2. javier May 30, 2008