Auto-Completion Textbox

Author: Oliver Albers 

How to use it:

final AutoCompleteTextBox box = new AutoCompleteTextBox();
box.setCompletionItems(new SimpleAutoCompletionItems(
            new String[]{ "apple", "ape", "anything", "else"}));

Alternatively, you can create your own implementation of CompletionItems interface (see below) to get a list of items using RPC.

Updated 05/27/06. New version is up. Now with AutoCompleteTextArea. 

Please leave your questions or comments here.

Source code:

CompletionItems.java:

/*
Auto-Completion Textbox for GWT
Copyright (C) 2006 Oliver Albers http://gwt.components.googlepages.com/

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/

package com.gwt.components.client;

public interface CompletionItems {
        /**
         * Returns an array of all completion items matching
         * @param match The user-entered text all compleition items have to match
         * @return      Array of strings
         */
        public String[] getCompletionItems(String match);

AutoCompleteTextBox.java:

/*
Auto-Completion Textbox for GWT
Copyright (C) 2006 Oliver Albers http://gwt.components.googlepages.com/

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/

package com.gwt.components.client;

import com.google.gwt.user.client.ui.ChangeListener;
import com.google.gwt.user.client.ui.KeyboardListener;
import com.google.gwt.user.client.ui.ListBox;
import com.google.gwt.user.client.ui.PopupPanel;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.Widget;



public class AutoCompleteTextBox extends TextBox
    implements KeyboardListener, ChangeListener {
   
  protected PopupPanel choicesPopup = new PopupPanel(true);
  protected ListBox choices = new ListBox();
  protected CompletionItems items = new SimpleAutoCompletionItems(new String[]{});
  protected boolean popupAdded = false;
  protected boolean visible = false;
   
  /**
   * Default Constructor
   *
   */
  public AutoCompleteTextBox()
  {
    super();
    this.addKeyboardListener(this);
    choices.addChangeListener(this);
    this.setStyleName("AutoCompleteTextBox");
       
    choicesPopup.add(choices);
    choicesPopup.addStyleName("AutoCompleteChoices");
       
    choices.setStyleName("list");
  }

  /**
   * Sets an "algorithm" returning completion items
   * You can define your own way how the textbox retrieves autocompletion items
   * by implementing the CompletionItems interface and setting the according object
   * @see SimpleAutoCompletionItem
   * @param items CompletionItem implementation
   */
  public void setCompletionItems(CompletionItems items)
  {
    this.items = items;
  }
   
  /**
   * Returns the used CompletionItems object
   * @return CompletionItems implementation
   */
  public CompletionItems getCompletionItems()
  {
    return this.items;
  }
   
  /**
   * Not used at all
   */
  public void onKeyDown(Widget arg0, char arg1, int arg2) {
  }

  /**
   * Not used at all
   */
  public void onKeyPress(Widget arg0, char arg1, int arg2) {
  }

  /**
   * A key was released, start autocompletion
   */
  public void onKeyUp(Widget arg0, char arg1, int arg2) {
    if(arg1 == KEY_DOWN)
    {
      int selectedIndex = choices.getSelectedIndex();
      selectedIndex++;
      if(selectedIndex > choices.getItemCount())
      {
        selectedIndex = 0;
      }
      choices.setSelectedIndex(selectedIndex);
           
      return;
    }
       
    if(arg1 == KEY_UP)
    {
      int selectedIndex = choices.getSelectedIndex();
      selectedIndex--;
      if(selectedIndex < 0)
      {
        selectedIndex = choices.getItemCount();
      }
      choices.setSelectedIndex(selectedIndex);
           
      return;        
    }
       
    if(arg1 == KEY_ENTER)
    {
      if(visible)
      {
        complete();
      }
           
      return;
    }
       
    if(arg1 == KEY_ESCAPE)
    {
      choices.clear();
      choicesPopup.hide();
      visible = false;
           
      return;
    }
       
    String text = this.getText();
    String[] matches = new String[]{};
    if(text.length() > 0)
    {
      matches = items.getCompletionItems(text);
    }
       
    if(matches.length > 0)
    {
      choices.clear();
           
      for(int i = 0; i < matches.length; i++)
      {
        choices.addItem((String) matches[i]);
      }
           
      // if there is only one match and it is what is in the
      // text field anyways there is no need to show autocompletion
      if(matches.length == 1 && matches[0].compareTo(text) == 0)
      {
        choicesPopup.hide();
      } else {
        choices.setSelectedIndex(0);
        choices.setVisibleItemCount(matches.length + 1);
               
        if(!popupAdded)
        {
          RootPanel.get().add(choicesPopup);
          popupAdded = true;
        }
        choicesPopup.show();
        visible = true;
        choicesPopup.setPopupPosition(this.getAbsoluteLeft(),
        this.getAbsoluteTop() + this.getOffsetHeight());
        //choicesPopup.setWidth(this.getOffsetWidth() + "px");
        choices.setWidth(this.getOffsetWidth() + "px");
      }

    } else {
      visible = false;
      choicesPopup.hide();
    }
  }

  /**
   * A mouseclick in the list of items
   */
  public void onChange(Widget arg0) {
    complete();
  }
 
  public void onClick(Widget arg0) {
    complete();
  }
   
  // add selected item to textbox
  protected void complete()
  {
    if(choices.getItemCount() > 0)
    {
      this.setText(choices.getItemText(choices.getSelectedIndex()));
    }
       
    choices.clear();
    choicesPopup.hide();
  }
}
 

AutoCompleteTextArea.java:

/*
Auto-Completion Textbox for GWT
Copyright (C) 2006 Oliver Albers http://gwt.components.googlepages.com/

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/

package com.gwt.components.client;

import com.google.gwt.user.client.ui.ChangeListener;
import com.google.gwt.user.client.ui.KeyboardListener;
import com.google.gwt.user.client.ui.ListBox;
import com.google.gwt.user.client.ui.PopupPanel;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.TextArea;
import com.google.gwt.user.client.ui.Widget;

public class AutoCompleteTextArea extends TextArea
    implements KeyboardListener, ChangeListener {
   
  protected PopupPanel choicesPopup = new PopupPanel(true);
  protected ListBox choices = new ListBox();
  protected CompletionItems items = new SimpleAutoCompletionItems(new String[]{});
  protected boolean popupAdded = false;
  protected String typedText = "";
  protected boolean visible = false;
   
  protected int posy = -1;
   
  /**
   * Default Constructor
   *
   */
  public AutoCompleteTextArea()
  {
    super();
    this.addKeyboardListener(this);
    choices.addChangeListener(this);
    this.setStyleName("AutoCompleteTextArea");
       
    choicesPopup.add(choices);
    choicesPopup.addStyleName("AutoCompleteChoices");
       
    choices.setStyleName("list");
  }

  /**
   * Sets an "algorithm" returning completion items
   * You can define your own way how the textbox retrieves autocompletion items
   * by implementing the CompletionItems interface and setting the according object
   * @see SimpleAutoCompletionItem
   * @param items CompletionItem implementation
   */
  public void setCompletionItems(CompletionItems items)
  {
    this.items = items;
  }
   
  /**
   * Returns the used CompletionItems object
   * @return CompletionItems implementation
   */
  public CompletionItems getCompletionItems()
  {
    return this.items;
  }
   
  public void onKeyDown(Widget sender, char keyCode, int modifiers) {
  }

  public void onKeyPress(Widget sender, char keyCode, int modifiers) {
  }

  public void onKeyUp(Widget sender, char keyCode, int modifiers) {
    if(keyCode == KEY_DOWN)
    {
      int selectedIndex = choices.getSelectedIndex();
      selectedIndex++;
      if(selectedIndex > choices.getItemCount())
      {
        selectedIndex = 0;
      }
      choices.setSelectedIndex(selectedIndex);
           
      return;
    }
       
    if(keyCode == KEY_UP)
    {
      int selectedIndex = choices.getSelectedIndex();
      selectedIndex--;
      if(selectedIndex < 0)
      {
        selectedIndex = choices.getItemCount();
      }
      choices.setSelectedIndex(selectedIndex);
           
      return;        
    }
       
    if(keyCode == KEY_ENTER)
    {
      if(visible)
      {
        complete();
      }
           
      return;
    }
       
    if(keyCode == KEY_ESCAPE)
    {
      choices.clear();
      choicesPopup.hide();
      visible = false;
           
      return;
    }
       
    String text = this.getText();
    String[] matches = new String[]{};
       
    String[] words = text.split(" |\n|\r");
    text = words[words.length - 1];
    typedText = text;
       
    if(text.length() > 0)
    {
      matches = items.getCompletionItems(text);
    }
       
    if(matches.length > 0)
    {
      choices.clear();
           
      for(int i = 0; i < matches.length; i++)
      {
        choices.addItem((String) matches[i]);
      }
           
      // if there is only one match and it is what is in the
      // text field anyways there is no need to show autocompletion
      if(matches.length == 1 && matches[0].compareTo(text) == 0)
      {
        choicesPopup.hide();
      } else {
        choices.setSelectedIndex(0);
        choices.setVisibleItemCount(matches.length + 1);
               
        if(!popupAdded)
        {
          RootPanel.get().add(choicesPopup);
          popupAdded = true;
        }
        choicesPopup.show();
        visible = true;
        int nposy = this.getAbsoluteTop() + this.getOffsetHeight();
        if(posy < 0 || nposy > posy)
        {
          posy = nposy;
        }
        choicesPopup.setPopupPosition(this.getAbsoluteLeft(), posy);
        //choicesPopup.setWidth(this.getOffsetWidth() + "px");
        choices.setWidth(this.getOffsetWidth() + "px");
      }

    } else {
      choicesPopup.hide();
      visible = false;
    }
  }

  public void onChange(Widget sender) {
    complete();
  }

  // add selected item to textarea
  protected void complete()
  {
    if(choices.getItemCount() > 0)
    {
      String text = this.getText();
      text = text.substring(0, text.length() - typedText.length() - 1);
      text += choices.getItemText(choices.getSelectedIndex());
      this.setText(text);
      this.setFocus(true);
    }
       
    choices.clear();
    choicesPopup.hide();
  }
}
 

SimpleAutoCompletionItems.java:

/*
Auto-Completion Textbox for GWT
Copyright (C) 2006 Oliver Albers http://gwt.components.googlepages.com/

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/

package com.gwt.components.client;

import java.util.ArrayList;

public class SimpleAutoCompletionItems implements CompletionItems {
  private String[] completions;

  public SimpleAutoCompletionItems(String[] items)
  {
    completions = items;
  }

  public String[] getCompletionItems(String match) {
    ArrayList matches = new ArrayList();
    for (int i = 0; i < completions.length; i++) {
      if (completions[i].toLowerCase().startsWith(match.toLowerCase())) {
        matches.add(completions[i]);
      }
    }
    String[] returnMatches = new String[matches.size()];
    for(int i = 0; i < matches.size(); i++)
    {
      returnMatches[i] = (String)matches.get(i);
    }
    return returnMatches;
  }
}