Introduction
When using the sort icon in an ADF Faces table, the sorting will be performed using the column value. In cases where you use a UI widget that does not display the column value but a different display value like in a drop-down list, this sorting might appear to be wrong. This article describes a technique to fix this sorting issue.Main Article
If you use a model-based LOV and you use display type "choice", then ADF nicely displays the display value, even if the table is read-only. In the screen shot below, you see the RegionName attribute displayed instead of the RegionId. This is accomplished by the model-based LOV, I did not modify the Countries view object to include a join with Regions. Also note the sort icon, the table is sorted by RegionId. This sorting typically results in a bug reported by your test team. Europe really shouldn't come before America when sorting ascending, right?
public SortCriteria[] getSortCriteria() { String orderBy = getOrderByClause(); if (orderBy!=null ) { boolean descending = false; if (orderBy.endsWith(" DESC")) { descending = true; orderBy = orderBy.substring(0,orderBy.length()-5); } // extract column name, is part after the dot int dotpos = orderBy.lastIndexOf("."); String columnName = orderBy.substring(dotpos+1); // loop over attributes and find matching attribute AttributeDef orderByAttrDef = null; for (AttributeDef attrDef : getAttributeDefs()) { if (columnName.equals(attrDef.getColumnName())) { orderByAttrDef = attrDef; break; } } if (orderByAttrDef!=null && "choice".equals(orderByAttrDef.getProperty("CONTROLTYPE")) && orderByAttrDef.getListBindingDef()!=null) { String orderbyAttr = orderByAttrDef.getName(); String[] displayAttrs = orderByAttrDef.getListBindingDef().getListDisplayAttrNames(); String[] listAttrs = orderByAttrDef.getListBindingDef().getListAttrNames(); // if first list display attributes is not the same as first list attribute, than the value // displayed is different from the value copied back to the order by attribute, in which case we need to // use our custom comparator if (displayAttrs!=null && listAttrs!=null && displayAttrs.length>0 && !displayAttrs[0].equals(listAttrs[0])) { SortCriteriaImpl sc1 = new SortCriteriaImpl(orderbyAttr, descending); SortCriteria[] sc = new SortCriteriaImpl[]{sc1}; return sc; } } } return super.getSortCriteria(); }If this method returns a sort criteria array, then the framework will call the sort method on the view object. Note that we loop over all orderBy columns as there might be multiple columns when using the Advanced Sort option in the panelCollection View menu, as shown below.


public Comparator getRowComparator() { return new LovDisplayAttributeRowComparator(getSortCriteria()); }The custom comparator class extends the default RowComparator class and overrides the method compareRows and looks up the choice display value to compare the two rows. The complete code of this class is included in the sample application With this code in place, clicking on the Region sort icon nicely sorts the countries by RegionName, as you can see below.

<list StaticList="false" Uses="LOV_RegionId" IterBinding="CountriesView1Iterator" id="RegionId"/>We need this "current row" list binding because the implicit list binding used by the item in the table is not accessible outside a table row, we cannot use the expression #{row.bindings.RegionId} in the table filter facet. 2. Create a Map-style managed bean with the get method retrieving the list binding as key, and returning the list of SelectItems. To return this list, we take the list of selectItems contained by the list binding and replace the index number that is normally used as key value with the actual attribute value that is set by the choice list. Here is the code of the get method:
public Object get(Object key) { if (key instanceof FacesCtrlListBinding) { // we need to cast to internal class FacesCtrlListBinding rather than JUCtrlListBinding to // be able to call getItems method. To prevent this import, we could evaluate an EL expression // to get the list of items FacesCtrlListBinding lb = (FacesCtrlListBinding) key; if (cachedFilterLists.containsKey(lb.getName())) { return cachedFilterLists.get(lb.getName()); } List<SelectItem> items = (List<SelectItem>)lb.getItems(); if (items==null || items.size()==0) { return items; } List<SelectItem> newItems = new ArrayList<SelectItem>(); JUCtrlValueDef def = ((JUCtrlValueDef)lb.getDef()); String valueAttr = def.getFirstAttrName(); // the items list has an index number as value, we need to replace this with the actual // value of the attribute that is copied back by the choice list for (int i = 0; i < items.size(); i++) { SelectItem si = (SelectItem) items.get(i); Object value = lb.getValueFromList(i); if (value instanceof Row) { Row row = (Row) value; si.setValue(row.getAttribute(valueAttr)); } else { // this is the "empty" row, set value to empty string so all rows will be returned // as user no longer wants to filter on this attribute si.setValue(""); } newItems.add(si); } cachedFilterLists.put(lb.getName(), newItems); return newItems; } return null; }Note that we added caching to speed up performance, and to handle the situation where table filters or search criteria are set such that no rows are retrieved in the table. When there are no rows, there is no current row and the getItems method on the list binding will return no items. An alternative approach to create the list of SelectItems would be to retrieve the iterator binding from the list binding and loop over the rows in the iterator binding rowset. Then we wouldn't need the import of the ADF internal oracle.adfinternal.view.faces.model.binding.FacesCtrlListBinding class, but then we need to figure out the display attributes from the list binding definition, and possible separate them with a dash if multiple display attributes are defined in the LOV. Doable but less reuse and more work. 3. Inside the filter facet for the column create an af:selectOneChoice with the value property of the f:selectItems tag referencing the get method of the managed bean:
<f:facet name="filter"> <af:selectOneChoice id="soc0" autoSubmit="true" value="#{vs.filterCriteria.RegionId}"> <!-- attention: the RegionId list binding must be created manually in the page definition! --> <f:selectItems id="si0" value="#{viewScope.TableFilterChoiceList[bindings.RegionId]}"/> </af:selectOneChoice> </f:facet>Note that the managed bean is defined in viewScope for the caching to take effect. Here is a screen shot of the table filter in action:

All content listed on this page is the property of Oracle Corp. Redistribution now allowed without written permission