Recently I had to implement a selectManyShuttle Component with a filter on it. Filtering a selectManyShuttle Component has been described many times so that is no rocket science. However, this implementation asked for a keystroke filter. The user enters a value, and the shuttle is filtered immediately.
In this post I describe how to implement this feature.
Explaining the Data Model and Setting up the selectManyShuttle Component.
I use a standard selectManyShuttle component based on two tables: The Employees table (from HR schema) and a new table called SelectedEmployees. This new table only has one column containing the employeeId. The selectManyShuttle in this post is used to shuttle employees into the selectedEmployees table. I will not explain how I implemented the saving of the selected values (but if you need to know I can provide you with the workspace).
The code for the shuttle component is pretty standard.
<af:selectManyShuttle value="#{pageFlowScope.filterBean.selectedValues}" id="sms1" partialTriggers="fltr"> <f:selectItems value="#{pageFlowScope.filterBean.allItems}" id="si1"/> </af:selectManyShuttle>
The selectManyShuttle gets its values from ‘allItems’ in a pageFlowScoped bean called ‘filterBean’. In this bean I use some methods from the ADF Utils class in order to populate the shuttle. These methods need the information about iterators and attributes in order to determine shuttle data. The used iterators of course need to be present in the binding container. Take notice of the resetShuttle property that I use to repopulate the shuttle after saving.
package nl.amis.technology.shuttlealternatives.view.beans; ..... public class FilterBean { String allItemsIteratorName = "allEmployeesIterator"; String allItemsValueAttrName = "EmployeeId"; String allItemsDisplayAttrName = "FullName"; String allItemsDescriptionAttrName = "FullName"; String selectedValuesIteratorName = "SelectedEmployeesIterator"; String selectedValuesValueAttrName = "EmployeeId"; List selectedValues; List allItems; List unfilteredList; private boolean resetShuttle = true; //false; ................................ public List getAllItems() { if (resetShuttle) { allItems = ADFUtils.selectItemsForIterator(allItemsIteratorName, allItemsValueAttrName, allItemsDisplayAttrName, allItemsDescriptionAttrName); setResetShuttle(false); setUnfilteredList(allItems); } return allItems; }
The selected values are ‘stored’/’retrieved’ from ‘selectedValues’ in the same bean.
public List getSelectedValues() { if (selectedValues == null || refreshSelectedList) { selectedValues = ADFUtils.attributeListForIterator(selectedValuesIteratorName, selectedValuesValueAttrName); } return selectedValues; }
Adding the Filter Field.
The filter field is a simple inputText component. The actual trick of invoking the filter is done by the implementation of a clientListener and a serverListener. These two are hooked up to each other by a javascript function.
<af:resource type="javascript"> function catchKeyStroke(event) { component = event.getSource(); AdfCustomEvent.queue(component, "filterShuttle", {filterKey:component.getSubmittedValue()}, true); event.cancel(); } </af:resource>
Every keystroke in the inputText is handled by the catchKeyStroke() function, and forwarded to the filterShuttle serverListener. The filterShuttle serverListener invokes a method in a managed bean to do the actual filtering.
<af:inputText id="fltr" value="" label="Filter...." binding="#{backingBeanScope.shuttlePageBackingBean.filterfield}"> <af:clientListener method="catchKeyStroke" type="keyUp"/> <af:serverListener type="filterShuttle" method="#{pageFlowScope.filterBean.handleRequest}"/> </af:inputText>
Implementing the Actual Filter Functionality.
Filtering of the shuttle is done in a couple of steps. First (in handleRequest() ) I have to handle the request done by the serverListener. In this method I can figure out the entered value by getting the value of the ‘filterKey’ parameter.
public void handleRequest(ClientEvent event) { String searchString = (String)event.getParameters().get("filterKey"); List<SelectItem> newFilteredList = filterValues(searchString); setAllItems(newFilteredList); setResetShuttle(false); }
Next is the call to the filterValues() method. This method compares the items in the shuttle to the value entered by the user. Lets implement this method. First I just filter the shuttle entries based on the occurrence of the filterCriteria in the label of the item.
I create a new selectItem for every entry that qualifies the criteria, and add this selectItem to a “filteredList”.
After filtering I return the filtered list to the calling method.
public ArrayList<SelectItem> filterValues(String filterCriteria) { ArrayList<SelectItem> filteredList = new ArrayList<SelectItem>(); for (int i = 0; i < unfilteredList.size(); i++) { if (((SelectItem)unfilteredList.get(i)).getLabel().contains(filterCriteria)) { SelectItem si = new SelectItem(); si = (SelectItem)unfilteredList.get(i); filteredList.add(si); } return filteredList; }
This works fine, however there is one issue. The way the selectManyShuttle works is that the ‘left’ or ‘leading’ side holds all available values, and the values for the ‘right’ or ‘trailing’ side are removed from the ‘left’ and added to the ‘right’. So every time I filter the shuttle, the already selected item are also filtered. So if these do not qualify, they will not show up on the ‘right’. Therefor for every value that does not qualify, I have to check if it is a value that is already part of the selectedEmployees. If it is, I have to add it to the shuttle even tough it does not qualify. So I added an ‘else’ too the code above in order to keep already selected values in there. This can only be done based on the value field.
else { // here I need to add already selected items that do not match filtercriteria !! // This needs to be done because they are on the right (selected) side, and that is not being filtered ! for (int ij = 0; ij < selectedValues.size(); ij++) { if (selectedValues.get(ij).toString().equalsIgnoreCase(((SelectItem)unfilteredList.get(i)).getValue().toString())) { SelectItem si = new SelectItem(); si = (SelectItem)unfilteredList.get(i); filteredList.add(si); } } }
When you run it now, the shuttle with its filter works like a charm.
Other Shuttle Resources.
A sample workspace can be downloaded here.
ADF Faces Rich Client Shuttle Documentation.
ADF Code Corner Shuttle Example.
Find Selected Values in a SelectManyShuttle
Hi Luc,
This is a great post. I’m trying it and I’m facing 2 small issues (I confess I haven’t investigated too much, simply copy/paste for the time being):
setResetShuttle(
true) is never performed. I guess this should be done if the inputText is empty. Right?
refreshSelectedList is never declared or set anywhere in your example. Should we set it with valueChangeListener on the Shuttle?
I’ll further test and post my findings.
Regards,
Thomas.