]> source.dussan.org Git - vaadin-framework.git/commitdiff
fixes #1121
authorMatti Tahvonen <matti.tahvonen@itmill.com>
Thu, 15 Nov 2007 06:44:25 +0000 (06:44 +0000)
committerMatti Tahvonen <matti.tahvonen@itmill.com>
Thu, 15 Nov 2007 06:44:25 +0000 (06:44 +0000)
svn changeset:2821/svn branch:trunk

src/com/itmill/toolkit/terminal/gwt/client/ui/IFilterSelect.java

index 41d5620fd9c96798e9ee6a121be71dbab31f23ed..5444614242eaf31f01e8e1b8e84767969246f8ea 100644 (file)
@@ -33,640 +33,660 @@ import com.itmill.toolkit.terminal.gwt.client.Util;
  * TODO needs major refactoring (to be extensible etc)
  */
 public class IFilterSelect extends Composite implements Paintable,
-               KeyboardListener, ClickListener, FocusListener {
-
-       public class FilterSelectSuggestion implements Suggestion, Command {
-
-               private String key;
-               private String caption;
-               private String iconUri;
-
-               public FilterSelectSuggestion(UIDL uidl) {
-                       this.key = uidl.getStringAttribute("key");
-                       this.caption = uidl.getStringAttribute("caption");
-                       if (uidl.hasAttribute("icon")) {
-                               this.iconUri = uidl.getStringAttribute("icon");
-                       }
-               }
-
-               public String getDisplayString() {
-                       StringBuffer sb = new StringBuffer();
-                       if (iconUri != null) {
-                               sb.append("<img src=\"");
-                               sb.append(iconUri);
-                               sb.append("\" alt=\"icon\" class=\"i-icon\" />");
-                       }
-                       sb.append(caption);
-                       return sb.toString();
-               }
-
-               public String getReplacementString() {
-                       return caption;
-               }
-
-               public int getOptionKey() {
-                       return Integer.parseInt(key);
-               }
-
-               public String getIconUri() {
-                       return iconUri;
-               }
-
-               public void execute() {
-                       IFilterSelect.this.onSuggestionSelected(this);
-               }
-       }
-
-       /**
-        * @author mattitahvonen
-        * 
-        */
-       public class SuggestionPopup extends PopupPanel implements
-                       PositionCallback, PopupListener {
-               private static final int EXTRASPACE = 8;
-
-               private static final String Z_INDEX = "30000";
-
-               private SuggestionMenu menu;
-
-               private Element up = DOM.createDiv();
-               private Element down = DOM.createDiv();
-               private Element status = DOM.createDiv();
-
-               private boolean isPagingEnabled = true;
-
-               private long lastAutoClosed;
-
-               SuggestionPopup() {
-                       super(true);
-                       this.menu = new SuggestionMenu();
-                       setWidget(menu);
-                       setStyleName(CLASSNAME + "-suggestpopup");
-                       DOM.setStyleAttribute(getElement(), "zIndex", Z_INDEX);
-
-                       Element root = getContainerElement();
-
-                       DOM.setInnerHTML(up, "<span>Prev</span>");
-                       DOM.sinkEvents(up, Event.ONCLICK);
-                       DOM.setInnerHTML(down, "<span>Next</span>");
-                       DOM.sinkEvents(down, Event.ONCLICK);
-                       DOM.insertChild(root, up, 0);
-                       DOM.appendChild(root, down);
-                       DOM.appendChild(root, status);
-                       DOM.setElementProperty(status, "className", CLASSNAME + "-status");
-
-                       this.addPopupListener(this);
-               }
-
-               public void showSuggestions(Collection currentSuggestions,
-                               int currentPage, int totalSuggestions) {
-                       menu.setSuggestions(currentSuggestions);
-                       int x = IFilterSelect.this.getAbsoluteLeft();
-                       int y = IFilterSelect.this.tb.getAbsoluteTop();
-                       y += IFilterSelect.this.tb.getOffsetHeight();
-                       this.setPopupPosition(x, y);
-                       int first = currentPage * PAGELENTH + 1;
-                       int last = first + currentSuggestions.size() - 1;
-                       DOM.setInnerText(status, first + "-" + last + "/"
-                                       + totalSuggestions);
-                       setPrevButtonActive(first > 1);
-                       setNextButtonActive(last < totalSuggestions);
-
-                       // clear previously fixed width
-                       menu.setWidth("");
-                       DOM.setStyleAttribute(DOM.getFirstChild(menu.getElement()),
-                                       "width", "");
-
-                       setPopupPositionAndShow(this);
-               }
-
-               private void setNextButtonActive(boolean b) {
-                       if (b) {
-                               DOM.sinkEvents(down, Event.ONCLICK);
-                               DOM.setElementProperty(down, "className", CLASSNAME
-                                               + "-nextpage");
-                       } else {
-                               DOM.sinkEvents(down, 0);
-                               DOM.setElementProperty(down, "className", CLASSNAME
-                                               + "-nextpage-off");
-                       }
-               }
-
-               private void setPrevButtonActive(boolean b) {
-                       if (b) {
-                               DOM.sinkEvents(up, Event.ONCLICK);
-                               DOM
-                                               .setElementProperty(up, "className", CLASSNAME
-                                                               + "-prevpage");
-                       } else {
-                               DOM.sinkEvents(up, 0);
-                               DOM.setElementProperty(up, "className", CLASSNAME
-                                               + "-prevpage-off");
-                       }
-
-               }
-
-               public void selectNextItem() {
-                       MenuItem cur = menu.getSelectedItem();
-                       int index = 1 + menu.getItems().indexOf(cur);
-                       if (menu.getItems().size() > index) {
-                               MenuItem newSelectedItem = (MenuItem) menu.getItems()
-                                               .get(index);
-                               menu.selectItem(newSelectedItem);
-                               tb.setText(newSelectedItem.getText());
-                               tb.setSelectionRange(lastFilter.length(), newSelectedItem
-                                               .getText().length()
-                                               - lastFilter.length());
-
-                       } else if (!clientSideFiltering && hasNextPage())
-                               filterOptions(currentPage + 1);
-               }
-
-               public void selectPrevItem() {
-                       MenuItem cur = menu.getSelectedItem();
-                       int index = -1 + menu.getItems().indexOf(cur);
-                       if (index > -1) {
-                               MenuItem newSelectedItem = (MenuItem) menu.getItems()
-                                               .get(index);
-                               menu.selectItem(newSelectedItem);
-                               tb.setText(newSelectedItem.getText());
-                               tb.setSelectionRange(lastFilter.length(), newSelectedItem
-                                               .getText().length()
-                                               - lastFilter.length());
-                       } else if (index == -1) {
-                               if (currentPage > 0)
-                                       filterOptions(currentPage - 1);
-                       } else {
-                               MenuItem newSelectedItem = (MenuItem) menu.getItems().get(
-                                               menu.getItems().size() - 1);
-                               menu.selectItem(newSelectedItem);
-                               tb.setText(newSelectedItem.getText());
-                               tb.setSelectionRange(lastFilter.length(), newSelectedItem
-                                               .getText().length()
-                                               - lastFilter.length());
-                       }
-               }
-
-               public void onBrowserEvent(Event event) {
-                       Element target = DOM.eventGetTarget(event);
-                       if (DOM.compare(target, up)
-                                       || DOM.compare(target, DOM.getChild(up, 0))) {
-                               filterOptions(currentPage - 1, lastFilter);
-                       } else if (DOM.compare(target, down)
-                                       || DOM.compare(target, DOM.getChild(down, 0))) {
-                               filterOptions(currentPage + 1, lastFilter);
-                       }
-                       tb.setFocus(true);
-               }
-
-               public void setPagingEnabled(boolean paging) {
-                       if (isPagingEnabled == paging)
-                               return;
-                       if (paging) {
-                               DOM.setStyleAttribute(this.down, "display", "block");
-                               DOM.setStyleAttribute(this.up, "display", "block");
-                               DOM.setStyleAttribute(this.status, "display", "block");
-                       } else {
-                               DOM.setStyleAttribute(this.down, "display", "none");
-                               DOM.setStyleAttribute(this.up, "display", "none");
-                               DOM.setStyleAttribute(this.status, "display", "none");
-                       }
-                       isPagingEnabled = paging;
-               }
-
-               /*
-                * (non-Javadoc)
-                * 
-                * @see com.google.gwt.user.client.ui.PopupPanel$PositionCallback#setPosition(int,
-                *      int)
-                */
-               public void setPosition(int offsetWidth, int offsetHeight) {
-
-                       int top = -1;
-                       int left = -1;
-
-                       // reset menu size and retrieve its "natural" size
-                       menu.setHeight("");
-                       offsetHeight = getOffsetHeight();
-
-                       int desiredWidth = IFilterSelect.this.getOffsetWidth();
-                       int naturalMenuWidth = DOM.getElementPropertyInt(DOM
-                                       .getFirstChild(menu.getElement()), "offsetWidth");
-                       if (naturalMenuWidth < desiredWidth) {
-                               menu.setWidth(desiredWidth + "px");
-                               DOM.setStyleAttribute(DOM.getFirstChild(menu.getElement()),
-                                               "width", "100%");
-                               naturalMenuWidth = desiredWidth;
-                       }
-                       if (Util.isIE()) {
-                               DOM.setStyleAttribute(getElement(), "width", naturalMenuWidth
-                                               + "px");
-                       }
-
-                       if (offsetHeight + getPopupTop() > Window.getClientHeight()) {
-                               top = Window.getClientHeight() - offsetHeight - EXTRASPACE / 2;
-                               if (top < 0)
-                                       top = 0;
-                       } else {
-                               top = getPopupTop();
-                       }
-
-                       // fetch real width (mac FF bugs here due GWT popups overflow:auto )
-                       offsetWidth = DOM.getElementPropertyInt(DOM.getFirstChild(menu
-                                       .getElement()), "offsetWidth");
-                       if (offsetWidth + getPopupLeft() > Window.getClientWidth()) {
-                               left = IFilterSelect.this.getAbsoluteLeft()
-                                               + IFilterSelect.this.getOffsetWidth() - offsetWidth;
-                               if (left < 0)
-                                       left = 0;
-                       } else {
-                               left = getPopupLeft();
-                       }
-                       setPopupPosition(left, top);
-
-               }
-
-               /**
-                * @return true if popup was just closed
-                */
-               public boolean isJustClosed() {
-                       long now = (new Date()).getTime();
-                       return (lastAutoClosed > 0 && (now - lastAutoClosed) < 200);
-               }
-
-               public void onPopupClosed(PopupPanel sender, boolean autoClosed) {
-                       if (autoClosed) {
-                               lastAutoClosed = (new Date()).getTime();
-                       }
-               }
-
-       }
-
-       public class SuggestionMenu extends MenuBar {
-
-               SuggestionMenu() {
-                       super(true);
-                       setStyleName(CLASSNAME + "-suggestmenu");
-               }
-
-               public void setSuggestions(Collection suggestions) {
-                       this.clearItems();
-                       Iterator it = suggestions.iterator();
-                       while (it.hasNext()) {
-                               FilterSelectSuggestion s = (FilterSelectSuggestion) it.next();
-                               MenuItem mi = new MenuItem(s.getDisplayString(), true, s);
-                               this.addItem(mi);
-                               if (s == currentSuggestion)
-                                       selectItem(mi);
-                       }
-               }
-
-               public void doSelectedItemAction() {
-                       MenuItem item = this.getSelectedItem();
-                       if (item != null
-                                       && item.getText().toLowerCase().startsWith(
-                                                       lastFilter.toLowerCase())) {
-                               doItemAction(item, true);
-                       } else if (allowNewItem) {
-                               String newItemValue = tb.getText();
-                               // check for exact match in menu
-                               if(this.getItems().size() == 1 ) {
-                                       MenuItem potentialExactMatch = (MenuItem) getItems().get(0);
-                                       if(potentialExactMatch.getText().equals(newItemValue)) {
-                                               this.selectItem(potentialExactMatch);
-                                               this.doSelectedItemAction();
-                                               return;
-                                       }
-                               } else {
-                                       if (!newItemValue.equals("")) {
-                                               client.updateVariable(paintableId, "newitem", newItemValue,
-                                                               true);
-                                       }
-                               }
-                       }
-                       suggestionPopup.hide();
-               }
-
-               public void setWidth(String width) {
-                       super.setWidth(width);
-                       // if(width != null && !width.equals("")) {
-                       // DOM.setStyleAttribute(DOM.getFirstChild(getElement()), "width",
-                       // "100%");
-                       // } else {
-                       // DOM.setStyleAttribute(DOM.getFirstChild(getElement()), "width",
-                       // "");
-                       // }
-               }
-       }
-
-       public static final int FILTERINGMODE_OFF = 0;
-       public static final int FILTERINGMODE_STARTSWITH = 1;
-       public static final int FILTERINGMODE_CONTAINS = 2;
-
-       private static final String CLASSNAME = "i-filterselect";
-
-       public static final int PAGELENTH = 10;
-
-       private final FlowPanel panel = new FlowPanel();
-
-       private final TextBox tb = new TextBox();
-
-       private final SuggestionPopup suggestionPopup = new SuggestionPopup();
-
-       private final HTML popupOpener = new HTML("");
-
-       private final Image selectedItemIcon = new Image();
-
-       private ApplicationConnection client;
-
-       private String paintableId;
-
-       private int currentPage;
-
-       private Collection currentSuggestions = new ArrayList();
-
-       private boolean immediate;
-
-       private String selectedOptionKey;
-
-       private boolean filtering = false;
-
-       private String lastFilter = "";
-
-       private int totalSuggestions;
-
-       private FilterSelectSuggestion currentSuggestion;
-
-       private boolean clientSideFiltering;
-
-       private ArrayList allSuggestions;
-       private int totalMatches;
-       private boolean allowNewItem;
-       private boolean nullSelectionAllowed;
-
-       public IFilterSelect() {
-               selectedItemIcon.setVisible(false);
-               panel.add(selectedItemIcon);
-               panel.add(tb);
-               panel.add(popupOpener);
-               initWidget(panel);
-               setStyleName(CLASSNAME);
-               tb.addKeyboardListener(this);
-               tb.setStyleName(CLASSNAME + "-input");
-               tb.addFocusListener(this);
-               popupOpener.setStyleName(CLASSNAME + "-button");
-               popupOpener.addClickListener(this);
-       }
-
-       public boolean hasNextPage() {
-               if (totalSuggestions > (this.currentPage + 1) * PAGELENTH)
-                       return true;
-               else
-                       return false;
-       }
-
-       public void filterOptions(int page) {
-               filterOptions(page, tb.getText());
-       }
-
-       public void filterOptions(int page, String filter) {
-               if (filter.equals(lastFilter) && currentPage == page) {
-                       if (!suggestionPopup.isAttached())
-                               suggestionPopup.showSuggestions(currentSuggestions,
-                                               currentPage, totalMatches);
-                       return;
-               }
-               if (!filter.equals(lastFilter)) {
-                       // we are on subsequant page and text has changed -> reset page
-                       page = 0;
-               }
-               if (clientSideFiltering) {
-                       currentSuggestions.clear();
-                       for (Iterator it = allSuggestions.iterator(); it.hasNext();) {
-                               FilterSelectSuggestion s = (FilterSelectSuggestion) it.next();
-                               String string = s.getDisplayString().toLowerCase();
-                               if (string.startsWith(filter.toLowerCase())) {
-                                       currentSuggestions.add(s);
-                               }
-                       }
-                       lastFilter = filter;
-                       currentPage = page;
-                       suggestionPopup.showSuggestions(currentSuggestions, page,
-                                       currentSuggestions.size());
-               } else {
-                       filtering = true;
-                       client.updateVariable(paintableId, "filter", filter, false);
-                       client.updateVariable(paintableId, "page", page, true);
-                       lastFilter = filter;
-                       currentPage = page;
-               }
-       }
-
-       public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
-               this.paintableId = uidl.getId();
-               this.client = client;
-
-               if (client.updateComponent(this, uidl, true))
-                       return;
-
-               immediate = uidl.hasAttribute("immediate");
-
-               nullSelectionAllowed = uidl.hasAttribute("nullselect");
-
-               if (true) {
-                       this.suggestionPopup.setPagingEnabled(true);
-                       clientSideFiltering = false;
-               } else {
-                       this.suggestionPopup.setPagingEnabled(false);
-                       clientSideFiltering = true;
-               }
-
-               allowNewItem = uidl.hasAttribute("allownewitem");
-
-               currentSuggestions.clear();
-               UIDL options = uidl.getChildUIDL(0);
-               totalSuggestions = uidl.getIntAttribute("totalitems");
-               totalMatches = uidl.getIntAttribute("totalMatches");
-
-               String captions = "";
-               if (clientSideFiltering) {
-                       allSuggestions = new ArrayList();
-               }
-               for (Iterator i = options.getChildIterator(); i.hasNext();) {
-                       UIDL optionUidl = (UIDL) i.next();
-                       FilterSelectSuggestion suggestion = new FilterSelectSuggestion(
-                                       optionUidl);
-                       currentSuggestions.add(suggestion);
-                       if (clientSideFiltering) {
-                               allSuggestions.add(suggestion);
-                       }
-                       if (!filtering && optionUidl.hasAttribute("selected")) {
-                               tb.setText(suggestion.getReplacementString());
-                               currentSuggestion = suggestion;
-                       }
-
-                       // Collect captions so we can calculate minimum width for textarea
-                       if (captions.length() > 0)
-                               captions += "|";
-                       captions += suggestion.getReplacementString();
-               }
-
-               if (filtering && lastFilter.equals(uidl.getStringVariable("filter"))) {
-                       suggestionPopup.showSuggestions(currentSuggestions, currentPage,
-                                       totalMatches);
-                       filtering = false;
-               }
-
-               // Calculate minumum textarea width
-               int minw = minWidth(captions);
-               Element spacer = DOM.createDiv();
-               DOM.setStyleAttribute(spacer, "width", minw + "px");
-               DOM.setStyleAttribute(spacer, "height", "0");
-               DOM.setStyleAttribute(spacer, "overflow", "hidden");
-               DOM.appendChild(panel.getElement(), spacer);
-
-               // Set columns (width) is given
-               if (uidl.hasAttribute("cols"))
-                       DOM.setStyleAttribute(getElement(), "width", uidl
-                                       .getIntAttribute("cols")
-                                       + "em");
-
-       }
-
-       public void onSuggestionSelected(FilterSelectSuggestion suggestion) {
-               currentSuggestion = suggestion;
-               String newKey;
-               if (suggestion.key.equals("")) {
-                       // "nullselection"
-                       newKey = "";
-               } else {
-                       // normal selection
-                       newKey = String.valueOf(suggestion.getOptionKey());
-               }
-               tb.setText(suggestion.getReplacementString());
-               setSelectedItemIcon(suggestion.getIconUri());
-               if (!newKey.equals(selectedOptionKey)) {
-                       selectedOptionKey = newKey;
-                       client.updateVariable(paintableId, "selected",
-                                       new String[] { selectedOptionKey }, immediate);
-                       lastFilter = tb.getText();
-               }
-               suggestionPopup.hide();
-       }
-
-       private void setSelectedItemIcon(String iconUri) {
-               if (iconUri == null) {
-                       selectedItemIcon.setVisible(false);
-               } else {
-                       selectedItemIcon.setUrl(iconUri);
-                       selectedItemIcon.setVisible(true);
-               }
-       }
-
-       public void onKeyDown(Widget sender, char keyCode, int modifiers) {
-               if (suggestionPopup.isAttached()) {
-                       switch (keyCode) {
-                       case KeyboardListener.KEY_DOWN:
-                               suggestionPopup.selectNextItem();
-                               DOM.eventPreventDefault(DOM.eventGetCurrentEvent());
-                               break;
-                       case KeyboardListener.KEY_UP:
-                               suggestionPopup.selectPrevItem();
-                               DOM.eventPreventDefault(DOM.eventGetCurrentEvent());
-                               break;
-                       case KeyboardListener.KEY_PAGEDOWN:
-                               if (hasNextPage())
-                                       filterOptions(currentPage + 1, lastFilter);
-                               break;
-                       case KeyboardListener.KEY_PAGEUP:
-                               if (currentPage > 0)
-                                       filterOptions(currentPage - 1, lastFilter);
-                               break;
-                       case KeyboardListener.KEY_ENTER:
-                       case KeyboardListener.KEY_TAB:
-                               suggestionPopup.menu.doSelectedItemAction();
-                               break;
-                       }
-               }
-       }
-
-       public void onKeyPress(Widget sender, char keyCode, int modifiers) {
-
-       }
-
-       public void onKeyUp(Widget sender, char keyCode, int modifiers) {
-               switch (keyCode) {
-               case KeyboardListener.KEY_ENTER:
-               case KeyboardListener.KEY_TAB:
-                       ; // NOP
-                       break;
-               case KeyboardListener.KEY_DOWN:
-               case KeyboardListener.KEY_UP:
-               case KeyboardListener.KEY_PAGEDOWN:
-               case KeyboardListener.KEY_PAGEUP:
-                       if (suggestionPopup.isAttached()) {
-                               break;
-                       } else {
-                               // open popup as from gadget
-                               filterOptions(0, "");
-                               tb.selectAll();
-                               break;
-                       }
-               default:
-                       filterOptions(currentPage);
-                       break;
-               }
-       }
-
-       /**
-        * Listener for popupopener
-        */
-       public void onClick(Widget sender) {
-               // ask suggestionPopup if it was just closed, we are using GWT Popup's
-               // auto close feature
-               if (!suggestionPopup.isJustClosed()) {
-                       filterOptions(0, "");
-               }
-               DOM.eventPreventDefault(DOM.eventGetCurrentEvent());
-               tb.selectAll();
-               tb.setFocus(true);
-       }
-
-       /*
-        * Calculate minumum width for FilterSelect textarea
-        */
-       private native int minWidth(String captions) /*-{
-               if(!captions || captions.length <= 0)
-                       return 0;
-               captions = captions.split("|");
-               var d = $wnd.document.createElement("div");
-               var html = "";
-               for(var i=0; i < captions.length; i++) {
-                       html += "<div>" + captions[i] + "</div>";
-                       // TODO apply same CSS classname as in suggestionmenu
-               }
-               d.style.position = "absolute";
-               d.style.top = "0";
-               d.style.left = "0";
-               d.style.visibility = "hidden";
-               d.innerHTML = html;
-               $wnd.document.body.appendChild(d);
-               var w = d.offsetWidth;
-               $wnd.document.body.removeChild(d);
-               return w;
-       }-*/;
-
-       public void onFocus(Widget sender) {
-               // NOP
-       }
-
-       public void onLostFocus(Widget sender) {
-               if(currentSuggestion == null || !tb.getText().equals(currentSuggestion.getReplacementString())) {
-                       if(currentSuggestion != null) {
-                               tb.setText(currentSuggestion.getDisplayString());
-                       } else {
-                               tb.setText("-");
-                       }
-               }
-       }
+        KeyboardListener, ClickListener, FocusListener {
+
+    public class FilterSelectSuggestion implements Suggestion, Command {
+
+        private String key;
+        private String caption;
+        private String iconUri;
+
+        public FilterSelectSuggestion(UIDL uidl) {
+            key = uidl.getStringAttribute("key");
+            caption = uidl.getStringAttribute("caption");
+            if (uidl.hasAttribute("icon")) {
+                iconUri = uidl.getStringAttribute("icon");
+            }
+        }
+
+        public String getDisplayString() {
+            StringBuffer sb = new StringBuffer();
+            if (iconUri != null) {
+                sb.append("<img src=\"");
+                sb.append(iconUri);
+                sb.append("\" alt=\"icon\" class=\"i-icon\" />");
+            }
+            sb.append(caption);
+            return sb.toString();
+        }
+
+        public String getReplacementString() {
+            return caption;
+        }
+
+        public int getOptionKey() {
+            return Integer.parseInt(key);
+        }
+
+        public String getIconUri() {
+            return iconUri;
+        }
+
+        public void execute() {
+            onSuggestionSelected(this);
+        }
+    }
+
+    /**
+     * @author mattitahvonen
+     * 
+     */
+    public class SuggestionPopup extends PopupPanel implements
+            PositionCallback, PopupListener {
+        private static final int EXTRASPACE = 8;
+
+        private static final String Z_INDEX = "30000";
+
+        private SuggestionMenu menu;
+
+        private Element up = DOM.createDiv();
+        private Element down = DOM.createDiv();
+        private Element status = DOM.createDiv();
+
+        private boolean isPagingEnabled = true;
+
+        private long lastAutoClosed;
+
+        SuggestionPopup() {
+            super(true);
+            menu = new SuggestionMenu();
+            setWidget(menu);
+            setStyleName(CLASSNAME + "-suggestpopup");
+            DOM.setStyleAttribute(getElement(), "zIndex", Z_INDEX);
+
+            Element root = getContainerElement();
+
+            DOM.setInnerHTML(up, "<span>Prev</span>");
+            DOM.sinkEvents(up, Event.ONCLICK);
+            DOM.setInnerHTML(down, "<span>Next</span>");
+            DOM.sinkEvents(down, Event.ONCLICK);
+            DOM.insertChild(root, up, 0);
+            DOM.appendChild(root, down);
+            DOM.appendChild(root, status);
+            DOM.setElementProperty(status, "className", CLASSNAME + "-status");
+
+            addPopupListener(this);
+        }
+
+        public void showSuggestions(Collection currentSuggestions,
+                int currentPage, int totalSuggestions) {
+            menu.setSuggestions(currentSuggestions);
+            int x = IFilterSelect.this.getAbsoluteLeft();
+            int y = tb.getAbsoluteTop();
+            y += tb.getOffsetHeight();
+            setPopupPosition(x, y);
+            int first = currentPage * PAGELENTH + 1;
+            int last = first + currentSuggestions.size() - 1;
+            DOM.setInnerText(status, first + "-" + last + "/"
+                    + totalSuggestions);
+            setPrevButtonActive(first > 1);
+            setNextButtonActive(last < totalSuggestions);
+
+            // clear previously fixed width
+            menu.setWidth("");
+            DOM.setStyleAttribute(DOM.getFirstChild(menu.getElement()),
+                    "width", "");
+
+            setPopupPositionAndShow(this);
+        }
+
+        private void setNextButtonActive(boolean b) {
+            if (b) {
+                DOM.sinkEvents(down, Event.ONCLICK);
+                DOM.setElementProperty(down, "className", CLASSNAME
+                        + "-nextpage");
+            } else {
+                DOM.sinkEvents(down, 0);
+                DOM.setElementProperty(down, "className", CLASSNAME
+                        + "-nextpage-off");
+            }
+        }
+
+        private void setPrevButtonActive(boolean b) {
+            if (b) {
+                DOM.sinkEvents(up, Event.ONCLICK);
+                DOM
+                        .setElementProperty(up, "className", CLASSNAME
+                                + "-prevpage");
+            } else {
+                DOM.sinkEvents(up, 0);
+                DOM.setElementProperty(up, "className", CLASSNAME
+                        + "-prevpage-off");
+            }
+
+        }
+
+        public void selectNextItem() {
+            MenuItem cur = menu.getSelectedItem();
+            int index = 1 + menu.getItems().indexOf(cur);
+            if (menu.getItems().size() > index) {
+                MenuItem newSelectedItem = (MenuItem) menu.getItems()
+                        .get(index);
+                menu.selectItem(newSelectedItem);
+                tb.setText(newSelectedItem.getText());
+                tb.setSelectionRange(lastFilter.length(), newSelectedItem
+                        .getText().length()
+                        - lastFilter.length());
+
+            } else if (!clientSideFiltering && hasNextPage()) {
+                filterOptions(currentPage + 1);
+            }
+        }
+
+        public void selectPrevItem() {
+            MenuItem cur = menu.getSelectedItem();
+            int index = -1 + menu.getItems().indexOf(cur);
+            if (index > -1) {
+                MenuItem newSelectedItem = (MenuItem) menu.getItems()
+                        .get(index);
+                menu.selectItem(newSelectedItem);
+                tb.setText(newSelectedItem.getText());
+                tb.setSelectionRange(lastFilter.length(), newSelectedItem
+                        .getText().length()
+                        - lastFilter.length());
+            } else if (index == -1) {
+                if (currentPage > 0) {
+                    filterOptions(currentPage - 1);
+                }
+            } else {
+                MenuItem newSelectedItem = (MenuItem) menu.getItems().get(
+                        menu.getItems().size() - 1);
+                menu.selectItem(newSelectedItem);
+                tb.setText(newSelectedItem.getText());
+                tb.setSelectionRange(lastFilter.length(), newSelectedItem
+                        .getText().length()
+                        - lastFilter.length());
+            }
+        }
+
+        public void onBrowserEvent(Event event) {
+            Element target = DOM.eventGetTarget(event);
+            if (DOM.compare(target, up)
+                    || DOM.compare(target, DOM.getChild(up, 0))) {
+                filterOptions(currentPage - 1, lastFilter);
+            } else if (DOM.compare(target, down)
+                    || DOM.compare(target, DOM.getChild(down, 0))) {
+                filterOptions(currentPage + 1, lastFilter);
+            }
+            tb.setFocus(true);
+        }
+
+        public void setPagingEnabled(boolean paging) {
+            if (isPagingEnabled == paging) {
+                return;
+            }
+            if (paging) {
+                DOM.setStyleAttribute(down, "display", "block");
+                DOM.setStyleAttribute(up, "display", "block");
+                DOM.setStyleAttribute(status, "display", "block");
+            } else {
+                DOM.setStyleAttribute(down, "display", "none");
+                DOM.setStyleAttribute(up, "display", "none");
+                DOM.setStyleAttribute(status, "display", "none");
+            }
+            isPagingEnabled = paging;
+        }
+
+        /*
+         * (non-Javadoc)
+         * 
+         * @see com.google.gwt.user.client.ui.PopupPanel$PositionCallback#setPosition(int,
+         *      int)
+         */
+        public void setPosition(int offsetWidth, int offsetHeight) {
+
+            int top = -1;
+            int left = -1;
+
+            // reset menu size and retrieve its "natural" size
+            menu.setHeight("");
+            offsetHeight = getOffsetHeight();
+
+            int desiredWidth = IFilterSelect.this.getOffsetWidth();
+            int naturalMenuWidth = DOM.getElementPropertyInt(DOM
+                    .getFirstChild(menu.getElement()), "offsetWidth");
+            if (naturalMenuWidth < desiredWidth) {
+                menu.setWidth(desiredWidth + "px");
+                DOM.setStyleAttribute(DOM.getFirstChild(menu.getElement()),
+                        "width", "100%");
+                naturalMenuWidth = desiredWidth;
+            }
+            if (Util.isIE()) {
+                DOM.setStyleAttribute(getElement(), "width", naturalMenuWidth
+                        + "px");
+            }
+
+            if (offsetHeight + getPopupTop() > Window.getClientHeight()
+                    + Window.getScrollTop()) {
+                top = Window.getClientHeight() + Window.getScrollTop()
+                        - offsetHeight - EXTRASPACE
+                        - IFilterSelect.this.getOffsetHeight();
+                if (top < 0) {
+                    top = 0;
+                }
+            } else {
+                top = getPopupTop();
+            }
+
+            // fetch real width (mac FF bugs here due GWT popups overflow:auto )
+            offsetWidth = DOM.getElementPropertyInt(DOM.getFirstChild(menu
+                    .getElement()), "offsetWidth");
+            if (offsetWidth + getPopupLeft() > Window.getClientWidth()
+                    + Window.getScrollLeft()) {
+                left = IFilterSelect.this.getAbsoluteLeft()
+                        + IFilterSelect.this.getOffsetWidth()
+                        + Window.getScrollLeft() - offsetWidth;
+                if (left < 0) {
+                    left = 0;
+                }
+            } else {
+                left = getPopupLeft();
+            }
+            setPopupPosition(left, top);
+
+        }
+
+        /**
+         * @return true if popup was just closed
+         */
+        public boolean isJustClosed() {
+            long now = (new Date()).getTime();
+            return (lastAutoClosed > 0 && (now - lastAutoClosed) < 200);
+        }
+
+        public void onPopupClosed(PopupPanel sender, boolean autoClosed) {
+            if (autoClosed) {
+                lastAutoClosed = (new Date()).getTime();
+            }
+        }
+
+    }
+
+    public class SuggestionMenu extends MenuBar {
+
+        SuggestionMenu() {
+            super(true);
+            setStyleName(CLASSNAME + "-suggestmenu");
+        }
+
+        public void setSuggestions(Collection suggestions) {
+            clearItems();
+            Iterator it = suggestions.iterator();
+            while (it.hasNext()) {
+                FilterSelectSuggestion s = (FilterSelectSuggestion) it.next();
+                MenuItem mi = new MenuItem(s.getDisplayString(), true, s);
+                this.addItem(mi);
+                if (s == currentSuggestion) {
+                    selectItem(mi);
+                }
+            }
+        }
+
+        public void doSelectedItemAction() {
+            MenuItem item = getSelectedItem();
+            if (item != null
+                    && item.getText().toLowerCase().startsWith(
+                            lastFilter.toLowerCase())) {
+                doItemAction(item, true);
+            } else if (allowNewItem) {
+                String newItemValue = tb.getText();
+                // check for exact match in menu
+                if (getItems().size() == 1) {
+                    MenuItem potentialExactMatch = (MenuItem) getItems().get(0);
+                    if (potentialExactMatch.getText().equals(newItemValue)) {
+                        selectItem(potentialExactMatch);
+                        doSelectedItemAction();
+                        return;
+                    }
+                } else {
+                    if (!newItemValue.equals("")) {
+                        client.updateVariable(paintableId, "newitem",
+                                newItemValue, true);
+                    }
+                }
+            }
+            suggestionPopup.hide();
+        }
+
+        public void setWidth(String width) {
+            super.setWidth(width);
+            // if(width != null && !width.equals("")) {
+            // DOM.setStyleAttribute(DOM.getFirstChild(getElement()), "width",
+            // "100%");
+            // } else {
+            // DOM.setStyleAttribute(DOM.getFirstChild(getElement()), "width",
+            // "");
+            // }
+        }
+    }
+
+    public static final int FILTERINGMODE_OFF = 0;
+    public static final int FILTERINGMODE_STARTSWITH = 1;
+    public static final int FILTERINGMODE_CONTAINS = 2;
+
+    private static final String CLASSNAME = "i-filterselect";
+
+    public static final int PAGELENTH = 10;
+
+    private final FlowPanel panel = new FlowPanel();
+
+    private final TextBox tb = new TextBox();
+
+    private final SuggestionPopup suggestionPopup = new SuggestionPopup();
+
+    private final HTML popupOpener = new HTML("");
+
+    private final Image selectedItemIcon = new Image();
+
+    private ApplicationConnection client;
+
+    private String paintableId;
+
+    private int currentPage;
+
+    private Collection currentSuggestions = new ArrayList();
+
+    private boolean immediate;
+
+    private String selectedOptionKey;
+
+    private boolean filtering = false;
+
+    private String lastFilter = "";
+
+    private int totalSuggestions;
+
+    private FilterSelectSuggestion currentSuggestion;
+
+    private boolean clientSideFiltering;
+
+    private ArrayList allSuggestions;
+    private int totalMatches;
+    private boolean allowNewItem;
+    private boolean nullSelectionAllowed;
+
+    public IFilterSelect() {
+        selectedItemIcon.setVisible(false);
+        panel.add(selectedItemIcon);
+        panel.add(tb);
+        panel.add(popupOpener);
+        initWidget(panel);
+        setStyleName(CLASSNAME);
+        tb.addKeyboardListener(this);
+        tb.setStyleName(CLASSNAME + "-input");
+        tb.addFocusListener(this);
+        popupOpener.setStyleName(CLASSNAME + "-button");
+        popupOpener.addClickListener(this);
+    }
+
+    public boolean hasNextPage() {
+        if (totalSuggestions > (currentPage + 1) * PAGELENTH) {
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    public void filterOptions(int page) {
+        filterOptions(page, tb.getText());
+    }
+
+    public void filterOptions(int page, String filter) {
+        if (filter.equals(lastFilter) && currentPage == page) {
+            if (!suggestionPopup.isAttached()) {
+                suggestionPopup.showSuggestions(currentSuggestions,
+                        currentPage, totalMatches);
+            }
+            return;
+        }
+        if (!filter.equals(lastFilter)) {
+            // we are on subsequant page and text has changed -> reset page
+            page = 0;
+        }
+        if (clientSideFiltering) {
+            currentSuggestions.clear();
+            for (Iterator it = allSuggestions.iterator(); it.hasNext();) {
+                FilterSelectSuggestion s = (FilterSelectSuggestion) it.next();
+                String string = s.getDisplayString().toLowerCase();
+                if (string.startsWith(filter.toLowerCase())) {
+                    currentSuggestions.add(s);
+                }
+            }
+            lastFilter = filter;
+            currentPage = page;
+            suggestionPopup.showSuggestions(currentSuggestions, page,
+                    currentSuggestions.size());
+        } else {
+            filtering = true;
+            client.updateVariable(paintableId, "filter", filter, false);
+            client.updateVariable(paintableId, "page", page, true);
+            lastFilter = filter;
+            currentPage = page;
+        }
+    }
+
+    public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
+        paintableId = uidl.getId();
+        this.client = client;
+
+        if (client.updateComponent(this, uidl, true)) {
+            return;
+        }
+
+        immediate = uidl.hasAttribute("immediate");
+
+        nullSelectionAllowed = uidl.hasAttribute("nullselect");
+
+        if (true) {
+            suggestionPopup.setPagingEnabled(true);
+            clientSideFiltering = false;
+        } else {
+            suggestionPopup.setPagingEnabled(false);
+            clientSideFiltering = true;
+        }
+
+        allowNewItem = uidl.hasAttribute("allownewitem");
+
+        currentSuggestions.clear();
+        UIDL options = uidl.getChildUIDL(0);
+        totalSuggestions = uidl.getIntAttribute("totalitems");
+        totalMatches = uidl.getIntAttribute("totalMatches");
+
+        String captions = "";
+        if (clientSideFiltering) {
+            allSuggestions = new ArrayList();
+        }
+        for (Iterator i = options.getChildIterator(); i.hasNext();) {
+            UIDL optionUidl = (UIDL) i.next();
+            FilterSelectSuggestion suggestion = new FilterSelectSuggestion(
+                    optionUidl);
+            currentSuggestions.add(suggestion);
+            if (clientSideFiltering) {
+                allSuggestions.add(suggestion);
+            }
+            if (!filtering && optionUidl.hasAttribute("selected")) {
+                tb.setText(suggestion.getReplacementString());
+                currentSuggestion = suggestion;
+            }
+
+            // Collect captions so we can calculate minimum width for textarea
+            if (captions.length() > 0) {
+                captions += "|";
+            }
+            captions += suggestion.getReplacementString();
+        }
+
+        if (filtering && lastFilter.equals(uidl.getStringVariable("filter"))) {
+            suggestionPopup.showSuggestions(currentSuggestions, currentPage,
+                    totalMatches);
+            filtering = false;
+        }
+
+        // Calculate minumum textarea width
+        int minw = minWidth(captions);
+        Element spacer = DOM.createDiv();
+        DOM.setStyleAttribute(spacer, "width", minw + "px");
+        DOM.setStyleAttribute(spacer, "height", "0");
+        DOM.setStyleAttribute(spacer, "overflow", "hidden");
+        DOM.appendChild(panel.getElement(), spacer);
+
+        // Set columns (width) is given
+        if (uidl.hasAttribute("cols")) {
+            DOM.setStyleAttribute(getElement(), "width", uidl
+                    .getIntAttribute("cols")
+                    + "em");
+        }
+
+    }
+
+    public void onSuggestionSelected(FilterSelectSuggestion suggestion) {
+        currentSuggestion = suggestion;
+        String newKey;
+        if (suggestion.key.equals("")) {
+            // "nullselection"
+            newKey = "";
+        } else {
+            // normal selection
+            newKey = String.valueOf(suggestion.getOptionKey());
+        }
+        tb.setText(suggestion.getReplacementString());
+        setSelectedItemIcon(suggestion.getIconUri());
+        if (!newKey.equals(selectedOptionKey)) {
+            selectedOptionKey = newKey;
+            client.updateVariable(paintableId, "selected",
+                    new String[] { selectedOptionKey }, immediate);
+            lastFilter = tb.getText();
+        }
+        suggestionPopup.hide();
+    }
+
+    private void setSelectedItemIcon(String iconUri) {
+        if (iconUri == null) {
+            selectedItemIcon.setVisible(false);
+        } else {
+            selectedItemIcon.setUrl(iconUri);
+            selectedItemIcon.setVisible(true);
+        }
+    }
+
+    public void onKeyDown(Widget sender, char keyCode, int modifiers) {
+        if (suggestionPopup.isAttached()) {
+            switch (keyCode) {
+            case KeyboardListener.KEY_DOWN:
+                suggestionPopup.selectNextItem();
+                DOM.eventPreventDefault(DOM.eventGetCurrentEvent());
+                break;
+            case KeyboardListener.KEY_UP:
+                suggestionPopup.selectPrevItem();
+                DOM.eventPreventDefault(DOM.eventGetCurrentEvent());
+                break;
+            case KeyboardListener.KEY_PAGEDOWN:
+                if (hasNextPage()) {
+                    filterOptions(currentPage + 1, lastFilter);
+                }
+                break;
+            case KeyboardListener.KEY_PAGEUP:
+                if (currentPage > 0) {
+                    filterOptions(currentPage - 1, lastFilter);
+                }
+                break;
+            case KeyboardListener.KEY_ENTER:
+            case KeyboardListener.KEY_TAB:
+                suggestionPopup.menu.doSelectedItemAction();
+                break;
+            }
+        }
+    }
+
+    public void onKeyPress(Widget sender, char keyCode, int modifiers) {
+
+    }
+
+    public void onKeyUp(Widget sender, char keyCode, int modifiers) {
+        switch (keyCode) {
+        case KeyboardListener.KEY_ENTER:
+        case KeyboardListener.KEY_TAB:
+            ; // NOP
+            break;
+        case KeyboardListener.KEY_DOWN:
+        case KeyboardListener.KEY_UP:
+        case KeyboardListener.KEY_PAGEDOWN:
+        case KeyboardListener.KEY_PAGEUP:
+            if (suggestionPopup.isAttached()) {
+                break;
+            } else {
+                // open popup as from gadget
+                filterOptions(0, "");
+                tb.selectAll();
+                break;
+            }
+        default:
+            filterOptions(currentPage);
+            break;
+        }
+    }
+
+    /**
+     * Listener for popupopener
+     */
+    public void onClick(Widget sender) {
+        // ask suggestionPopup if it was just closed, we are using GWT Popup's
+        // auto close feature
+        if (!suggestionPopup.isJustClosed()) {
+            filterOptions(0, "");
+        }
+        DOM.eventPreventDefault(DOM.eventGetCurrentEvent());
+        tb.selectAll();
+        tb.setFocus(true);
+    }
+
+    /*
+     * Calculate minumum width for FilterSelect textarea
+     */
+    private native int minWidth(String captions) /*-{
+                       if(!captions || captions.length <= 0)
+                               return 0;
+                       captions = captions.split("|");
+                       var d = $wnd.document.createElement("div");
+                       var html = "";
+                       for(var i=0; i < captions.length; i++) {
+                               html += "<div>" + captions[i] + "</div>";
+                               // TODO apply same CSS classname as in suggestionmenu
+                       }
+                       d.style.position = "absolute";
+                       d.style.top = "0";
+                       d.style.left = "0";
+                       d.style.visibility = "hidden";
+                       d.innerHTML = html;
+                       $wnd.document.body.appendChild(d);
+                       var w = d.offsetWidth;
+                       $wnd.document.body.removeChild(d);
+                       return w;
+                   }-*/;
+
+    public void onFocus(Widget sender) {
+        // NOP
+    }
+
+    public void onLostFocus(Widget sender) {
+        if (currentSuggestion == null
+                || !tb.getText().equals(
+                        currentSuggestion.getReplacementString())) {
+            if (currentSuggestion != null) {
+                tb.setText(currentSuggestion.getDisplayString());
+            } else {
+                tb.setText("-");
+            }
+        }
+    }
 }