An Advanced Custom Search Form For ADF
Monday, February 26, 2007 1:17:51 PM
I recently read a question on the JDeveloper forum about an advanced search. This intrigued me, and so I set about implementing this.
Required: I used the HR schema for my sample, so you need to have a connection to HR from JDeveloper to run it. The advanced search itself, however, is not tied to any particular schema and has been made to be as generic as possible.
Firsly, let me show what an example search form will look like using this method:
The first drop down box has been populated with field labels, based on a managed property that you define as a list of field names. The second drop down box has two options: Is and Is Not, and the third drop down box has four different operators: Like, Equal To, Greater Than and Less Than. To perform a search on one field you would select the field label from the first list, select whether you want the field to match or not match the parameters from the second list, select the relevant operator from the third list and enter a value in the input box. Once the search button is clicked, the associated table will update to show the results.
The other two buttons are there to add and remove a search criteria. If you click the add criteria button, another row will be added with a drop down box before it containing two items: And and Or. This allows you to select how the search method will treat the two criteria (whether the records must match both, or just one).
Here's an example of the search form with two criteria and the results shown in the table below:

Most of the work of this search form is done by a backing bean named SearchCriteriaModelAdapter. There are just a few things to set up in the faces-config.xml and on the page itself, to make it work. Before I get into the code that lies behind all this, I'll show you what set up is actually needed.
In the faces-config.xml file, you need to add a session-scoped managed bean that has the class SearchCriteriaModelAdpater. You then need to add two managed properties: searchIteratorName that contains the name of the iterator binding (in the case of my sample EmployeesSearchIterator) and searchAttrNames that contains a list of attribute names (e.g. FirstName and LastName). That's it for the bean. On the page you need to do a bit more.
Above is a code sample showing what is needed to set up the search form. The managed bean is called searchCriteria in this example. There is a forEach component that is mapped to the model property of the bean. This property is a list of criteria, each of which contain values defining the search attribute name, the operator in use, the attribute value, whether it should be an and conjuction or an or conjunction and whether it should match the condition or not. Each of these properties are mapped to the values of four selectOneChoice components and an inputText component. The items in the attribute and operator selectOneChoice components are mapped to another two attributes from the bean (attrList and operatorList respectively). The first of these is constructed from the list of attribute names defined in the faces-config.xml, while the other is hard-coded in the backing bean class (this could be extended to include other operators). The three commandButton components each have a corresponding action listener method in the backing bean.
Now I'll talk about the code in the backing bean class. The SearchCriteriaModelAdapter class has two attributes that are set in the faces-config (searchIteratorName and searchAttrNames) and three others accessible from EL (model, attrList and operatorList). The operatorList is the most simple to explain - it is a list containing a few SelectItems corresponding to the different operators available. This maps directly to the selectItems component on the page. The attrList property is constructed from the list of attribute names defined in the searchAttrNames property. The code below shows how the label for each attribute is found and then added to a SelectItem instance.
The getViewObject method uses the searchIteratorName property to find the DCBindingIterator and from that, the ViewObject. ADFUtils is a class that is distributed with the SRDemo sample and it is used here un-altered. Once this method is called once, the attrList is set up for the whole session.
The model is the main property of this class and the code below shows how it is constructed.
At the start it is simply populated with a "blank" criteria. The SearchCriteriaItem class is a simple bean with five properties for storing the attribute name, the operator, the attribute value, whether to use an and conjuction or and or conjunction, and whether to use a not conjunction. A blank criteria in this case defines default values for all attributes other than the attribute value, which really is blank. This means that the top item in each of the drop down boxes will be selected, which is probably what the user wants. You could change this so that you can define which attribute name or which operator to use as the defaults. You could even define a default value to go in the input field.
The methods to add and remove a criteria row are quite simple too.
The removeCriteria method ensures that if the user removes the last criteria, there will always be a blank criteria put in its place.
The updateView method is called when the search button is click and it is the main method in the class.
Using all the values from each of the SearchCriteriaItems in the model, this method constructs a ViewCriteria object and applies it to the view of the specified iterator. The clearCache method is called so that the view and its iterator are refreshed.
Since each SearchCriteriaItem object created has default values given to it, you can make the search form as simple or as complex as you want. For example, you could simply not include drop downs for And/Or or Is/Is Not, since the default values of these are probably what most users will want (it's the default behaviour of a normal search form).

That's all there is to it. I have created a sample application which has two pages - one with the full advanced search form and the other with the simpler form described above. You can download this below.
CustomSearch.zip
CustomSearch-11g.zip (11g version)
Required: I used the HR schema for my sample, so you need to have a connection to HR from JDeveloper to run it. The advanced search itself, however, is not tied to any particular schema and has been made to be as generic as possible.
Firsly, let me show what an example search form will look like using this method:
The first drop down box has been populated with field labels, based on a managed property that you define as a list of field names. The second drop down box has two options: Is and Is Not, and the third drop down box has four different operators: Like, Equal To, Greater Than and Less Than. To perform a search on one field you would select the field label from the first list, select whether you want the field to match or not match the parameters from the second list, select the relevant operator from the third list and enter a value in the input box. Once the search button is clicked, the associated table will update to show the results.
The other two buttons are there to add and remove a search criteria. If you click the add criteria button, another row will be added with a drop down box before it containing two items: And and Or. This allows you to select how the search method will treat the two criteria (whether the records must match both, or just one).
Here's an example of the search form with two criteria and the results shown in the table below:

Most of the work of this search form is done by a backing bean named SearchCriteriaModelAdapter. There are just a few things to set up in the faces-config.xml and on the page itself, to make it work. Before I get into the code that lies behind all this, I'll show you what set up is actually needed.
In the faces-config.xml file, you need to add a session-scoped managed bean that has the class SearchCriteriaModelAdpater. You then need to add two managed properties: searchIteratorName that contains the name of the iterator binding (in the case of my sample EmployeesSearchIterator) and searchAttrNames that contains a list of attribute names (e.g. FirstName and LastName). That's it for the bean. On the page you need to do a bit more.
<af:panelForm partialTriggers="Add Remove">
<af:forEach items="#{searchCriteria.model}" var="criteria"
varStatus="vs">
<af:panelHorizontal halign="center">
<af:objectSpacer width="50" height="10"
rendered="#{vs.count == 1}"/>
<af:selectOneChoice value="#{criteria.andConjunction}"
rendered="#{vs.count > 1}">
<af:selectItem label="And" value="true"/>
<af:selectItem label="Or" value="false"/>
</af:selectOneChoice>
<af:selectOneChoice value="#{criteria.attrName}">
<f:selectItems value="#{searchCriteria.attrList}"/>
</af:selectOneChoice>
<af:selectOneChoice value="#{criteria.notConjunction}">
<af:selectItem label="Is" value="false"/>
<af:selectItem label="Is Not" value="true"/>
</af:selectOneChoice>
<af:selectOneChoice value="#{criteria.operator}">
<f:selectItems value="#{searchCriteria.operatorList}"/>
</af:selectOneChoice>
<af:inputText value="#{criteria.attrValue}"/>
</af:panelHorizontal>
</af:forEach>
<f:facet name="footer">
<h:panelGroup>
<af:commandButton text="Add criteria"
actionListener="#{searchCriteria.addCriteria}"
id="Add" partialSubmit="true"/>
<af:commandButton text="Remove criteria"
actionListener="#{searchCriteria.removeCriteria}"
id="Remove" partialSubmit="true"/>
<af:commandButton text="Search"
actionListener="#{searchCriteria.updateView}"
id="Search" partialSubmit="true"/>
</h:panelGroup>
</f:facet>
</af:panelForm>
Above is a code sample showing what is needed to set up the search form. The managed bean is called searchCriteria in this example. There is a forEach component that is mapped to the model property of the bean. This property is a list of criteria, each of which contain values defining the search attribute name, the operator in use, the attribute value, whether it should be an and conjuction or an or conjunction and whether it should match the condition or not. Each of these properties are mapped to the values of four selectOneChoice components and an inputText component. The items in the attribute and operator selectOneChoice components are mapped to another two attributes from the bean (attrList and operatorList respectively). The first of these is constructed from the list of attribute names defined in the faces-config.xml, while the other is hard-coded in the backing bean class (this could be extended to include other operators). The three commandButton components each have a corresponding action listener method in the backing bean.
Now I'll talk about the code in the backing bean class. The SearchCriteriaModelAdapter class has two attributes that are set in the faces-config (searchIteratorName and searchAttrNames) and three others accessible from EL (model, attrList and operatorList). The operatorList is the most simple to explain - it is a list containing a few SelectItems corresponding to the different operators available. This maps directly to the selectItems component on the page. The attrList property is constructed from the list of attribute names defined in the searchAttrNames property. The code below shows how the label for each attribute is found and then added to a SelectItem instance.
public List getAttrList()
{
if (attrList == null)
{
attrList = new ArrayList();
ViewObject view = getViewObject();
for (int i = 0; i < searchAttrNames.size(); i++)
{
String searchAttrName = (String) searchAttrNames.get(i);
AttributeDef attrDef = view.findAttributeDef(searchAttrName);
if (attrDef != null)
{
String label =
attrDef.getUIHelper().getLabel(ADFUtils.getDCBindingContainer().getLocaleContext());
SelectItem item = new SelectItem(searchAttrName, label);
attrList.add(item);
}
}
}
return attrList;
}
The getViewObject method uses the searchIteratorName property to find the DCBindingIterator and from that, the ViewObject. ADFUtils is a class that is distributed with the SRDemo sample and it is used here un-altered. Once this method is called once, the attrList is set up for the whole session.
The model is the main property of this class and the code below shows how it is constructed.
public List getModel()
{
if (model == null)
{
model = new ArrayList();
model.add(getBlankItem());
}
return model;
}
private SearchCriteriaItem getBlankItem()
{
return new SearchCriteriaItem((String)searchAttrNames.get(0), "like", "", true, false);
}
At the start it is simply populated with a "blank" criteria. The SearchCriteriaItem class is a simple bean with five properties for storing the attribute name, the operator, the attribute value, whether to use an and conjuction or and or conjunction, and whether to use a not conjunction. A blank criteria in this case defines default values for all attributes other than the attribute value, which really is blank. This means that the top item in each of the drop down boxes will be selected, which is probably what the user wants. You could change this so that you can define which attribute name or which operator to use as the defaults. You could even define a default value to go in the input field.
The methods to add and remove a criteria row are quite simple too.
public void addCriteria(ActionEvent event)
{
model.add(getBlankItem());
}
public void removeCriteria(ActionEvent event)
{
model.remove(model.size() - 1);
if (model.size() == 0) model.add(getBlankItem());
}
The removeCriteria method ensures that if the user removes the last criteria, there will always be a blank criteria put in its place.
The updateView method is called when the search button is click and it is the main method in the class.
public void updateView(ActionEvent event)
{
ViewObject view = getViewObject();
ViewCriteria criteria = view.createViewCriteria();
for (int i = 0; i < model.size(); i++)
{
SearchCriteriaItem item = (SearchCriteriaItem)model.get(i);
if (!item.getAttrValue().equals(""))
{
String attrName = item.getAttrName();
String operator = item.getOperator();
String attrValue = item.getAttrValue();
ViewCriteriaRow row = criteria.createViewCriteriaRow();
row.setAttribute(attrName, operatorMap.get(operator) + " '" + attrValue + "'");
if (item.isAndConjunction())
{
if (item.isNotConjunction()) row.setConjunction(ViewCriteriaRow.VCROW_CONJ_NOT | ViewCriteriaRow.VCROW_CONJ_AND);
else row.setConjunction(ViewCriteriaRow.VCROW_CONJ_AND);
}
else
{
if (item.isNotConjunction()) row.setConjunction(ViewCriteriaRow.VCROW_CONJ_NOT | ViewCriteriaRow.VCROW_CONJ_OR);
}
criteria.addElement(row);
}
}
view.applyViewCriteria(criteria);
view.clearCache();
}
Using all the values from each of the SearchCriteriaItems in the model, this method constructs a ViewCriteria object and applies it to the view of the specified iterator. The clearCache method is called so that the view and its iterator are refreshed.
Since each SearchCriteriaItem object created has default values given to it, you can make the search form as simple or as complex as you want. For example, you could simply not include drop downs for And/Or or Is/Is Not, since the default values of these are probably what most users will want (it's the default behaviour of a normal search form).

That's all there is to it. I have created a sample application which has two pages - one with the full advanced search form and the other with the simpler form described above. You can download this below.
CustomSearch.zip
CustomSearch-11g.zip (11g version)







