Skip to content

Easy Auto Completion with ICEfaces and Glazed Lists

June 7, 2010

This is my first blog post ever, so please excuse the formatting. Here it goes… Using the ICEfaces selectInputText component, combined with Glazed Lists, I was able to put together a simple auto completion implementation.

First, I created a class called SelectItemModel that handles the data behind JSF SelectItem based components (e.g. selectOneMenu).  I am not going to talk much about this, other than it allowed me to abstract out some of the SelectItem component behavior for the data represented by a list of SelectItems.


public class SelectItemModel
{
    private Map modelObjectMap = new HashMap();
    
    private List selectItemList = new ArrayList();
    
    private String selectedItemName;
    
    private String[] selectedItemNames;
    
    public void clear()
    {
        modelObjectMap.clear();
        selectItemList.clear();
        selectedItemName = null;
        selectedItemNames = null;
    }
    
    public void addSelectItem(String name, String label, T object)
    {
        if (!modelObjectMap.containsKey(name))
        {
            selectItemList.add(new SelectItem(name, label));
        }
        
        modelObjectMap.put(name, object);
    }

    public void addSelectItem(String name, T object)
    {
        if (!modelObjectMap.containsKey(name))
        {
            selectItemList.add(new SelectItem(name));
        }
        
        modelObjectMap.put(name, object);
    }
    
    public void addSelectTitleItem(String name)
    {
        selectItemList.add(0, new SelectItem(name));
    }
    
    public void sortAscending()
    {
        Collections.sort(selectItemList, new Comparator()
        {

            public int compare(SelectItem o1, SelectItem o2)
            {
                String obj1 = o1.getLabel();
                String obj2 = o2.getLabel();
                
                return obj1.compareTo(obj2);
            }
            
        });
    }
    
    public void sortDescending()
    {
        Collections.sort(selectItemList, new Comparator()
        {

            public int compare(SelectItem o1, SelectItem o2)
            {
                String obj1 = o1.getLabel();
                String obj2 = o2.getLabel();
                
                return obj2.compareTo(obj1);
            }
            
        });
    }
    
    public int getListCount()
    {
        return selectItemList.size();
    }
    
    /**
     * @return the selectedItemName
     */
    public String getSelectedItemName()
    {
        return selectedItemName;
    }

    /**
     * @param selectedItemName the selectedItemName to set
     */
    public void setSelectedItemName(String selectedItemName)
    {
        this.selectedItemName = selectedItemName;
    }

    /**
     * @return the selectItemList
     */
    public List getSelectItemList()
    {
        return selectItemList;
    }
    
    public Collection getModelObjects()
    {
        return modelObjectMap.values();
    }

    public T getModelObjectForName(String name)
    {
        return modelObjectMap.get(name);
    }

    /**
     * @return the selectedItemNames
     */
    public String[] getSelectedItemNames()
    {
        return selectedItemNames;
    }

    /**
     * @param selectedItemNames the selectedItemNames to set
     */
    public void setSelectedItemNames(String[] selectedItemNames)
    {
        this.selectedItemNames = selectedItemNames;
    }
   
    public List getSelectItemListAsStrings()
    {
        List stringValues = new ArrayList(selectItemList.size());
        for (SelectItem item : selectItemList)
        {
            stringValues.add((String) item.getValue());
        }
        
        return stringValues;
    }
    
}



Next I created an AutoCompleteBean class that is used by the ICEfaces selectInputText component. This class handles managing of the TextChangeEvent and does the filtering of the data model using Glazed Lists. Glazed Lists is an excellent framework for doing collection filtering using text matching. I had used it in the past in a Swing application and found it plugged nicely into how the ICEfaces selectInputText component is submitting text value changes to the server side to update the backing bean. You can configure the text matching to be either a “starts with” or a “contains” matching.


public class AutoCompleteBean
{
    public enum AutoCompleteMode {
        startsWith,
        contains
    }
    
    private TextMatcherEditor matcherEditor;
    
    private SelectItemModel selectItemModel = new SelectItemModel();
    
    private List autoCompleteList;
    
    private String searchValue;
    
    public AutoCompleteBean(SelectItemModel aSelectItemModel, AutoCompleteMode mode)
    {        
        this.selectItemModel = aSelectItemModel;
        matcherEditor = new TextMatcherEditor(new TextFilterator() {

            public void getFilterStrings(List stringList, Object obj)
            {
                SelectItem item = (SelectItem) obj;
                stringList.add(item.getValue().toString());
            }
               
           });
        if (mode.equals(AutoCompleteMode.startsWith))
        {
            matcherEditor.setMode(TextMatcherEditor.STARTS_WITH);
        }
        else if (mode.equals(AutoCompleteMode.contains))
        {
            matcherEditor.setMode(TextMatcherEditor.CONTAINS);
        }
        
        
        EventList eventList = GlazedLists.eventList(selectItemModel.getSelectItemList());
        autoCompleteList = new FilterList(eventList, matcherEditor);
    }
    
    public T getResult()
    {
        if(searchValue == null)
            return null;
        
        T result = selectItemModel.getModelObjectForName(searchValue);
        if (result == null)
        {
            result = selectItemModel.getModelObjectForName(searchValue.toUpperCase());
        }
        
        return result;
    }
    
    public T getResult(String key)
    {
        if(key == null)
            return null;
        
        T result = selectItemModel.getModelObjectForName(key);
        if (result == null)
        {
            result = selectItemModel.getModelObjectForName(key.toUpperCase());
        }
        
        return result;
    }
    
    public void autoCompleteTextValueChanged(TextChangeEvent event)
    {
        String value = event.getNewValue().toString();
        searchValue = value;
        matcherEditor.setFilterText(new String[]{value});
    }
    
    /**
     * @return the searchValue
     */
    public String getSearchValue()
    {
        return searchValue;
    }

    /**
     * @param searchValue the searchValue to set
     */
    public void setSearchValue(String searchValue)
    {
        this.searchValue = searchValue;
    }

    /**
     * @return the autoCompleteList
     */
    public List getAutoCompleteList()
    {
        return autoCompleteList;
    }

    /**
     * @param autoCompleteList the autoCompleteList to set
     */
    public void setAutoCompleteList(List autoCompleteList)
    {
        this.autoCompleteList = autoCompleteList;
    }
    
}



In the following code, I configure the AutoCompleteBean in my JSF managed bean.


public class AutoCompleteController { private AutoCompleteBean autoCompleteBean; public AutoCompleteController() { SelectItemModel selectItemModel = new SelectItemModel(); selectItemModel.addSelectItem("Bob", "Bob"); selectItemModel.addSelectItem("Tim", "Tim"); selectItemModel.addSelectItem("Scott", "Scott"); selectItemModel.addSelectItem("John", "John"); selectItemModel.addSelectItem("Jim", "Jim"); selectItemModel.addSelectItem("Andrew", "Andrew"); selectItemModel.addSelectItem("Gavin", "Gavin"); selectItemModel.addSelectItem("Mary", "Mary"); selectItemModel.addSelectItem("Mike", "Mike"); selectItemModel.addSelectItem("Becky", "Becky"); selectItemModel.addSelectItem("Brad", "Brad"); autoCompleteBean = new AutoCompleteBean(selectItemModel, AutoCompleteMode.startsWith); } /** * @return the autoCompleteBean */ public AutoCompleteBean getAutoCompleteBean() { return autoCompleteBean; } /** * @param autoCompleteBean the autoCompleteBean to set */ public void setAutoCompleteBean(AutoCompleteBean autoCompleteBean) { this.autoCompleteBean = autoCompleteBean; } }



And here is the code for your JSF page where you will be displaying the selecInputText component.


<ice:selectInputText
	id="autoComp"
	partialSubmit="true"
	value="#{autoCompleteController.autoCompleteBean.searchValue}"
	textChangeListener="#{autoCompleteController.autoCompleteBean.autoCompleteTextValueChanged}"
	required="true"
	rows="10">
	<f:selectItems value="#{autoCompleteController.autoCompleteBean.autoCompleteList}" />
</ice:selectInputText>



That’s it! So far it seems to be working well.

Advertisements

From → ICEfaces

Leave a Comment

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: