So I've been on a webapp of mine that does RPC, Google Maps, MVP, uses
a ton of stock and homebrewn widgets and plenty of other things,
touching most of the GWT API and, like in every other GWT App I've
done so far it also uses SuggestBox. And like in every other GWT App
I've written, I come to battle the usual cross browser compatibility
issues, double events firing, display issues etc [1] that haunt
SuggestBox.

A discouraging look at the SOYC report reveals that a whooping 17% of
the code volume is courtesy of SuggestBox & friends, so I decide to
write a (simpler) version (something like they [2] are doing) which
results in a 15% reduction of the gzipped code.

On a side note: this made me really appreciate the ingenuity of the
original SuggestBox author - there is a myriad of details one needs to
take care of, for instance keeping track of focus/blur events between
the listbox and textbox.


[1]
https://code.google.com/p/google-web-toolkit/issues/list?can=2&q=suggestbox&colspec=ID+Type+Status+Owner+Milestone+Summary+Stars&cells=tiles

[2] http://www.airbnb.com/

[3]

import java.util.ArrayList;
import java.util.List;

import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.event.dom.client.BlurEvent;
import com.google.gwt.event.dom.client.BlurHandler;
import com.google.gwt.event.dom.client.ChangeEvent;
import com.google.gwt.event.dom.client.ChangeHandler;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.FocusEvent;
import com.google.gwt.event.dom.client.FocusHandler;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.dom.client.KeyDownEvent;
import com.google.gwt.event.dom.client.KeyDownHandler;
import com.google.gwt.event.dom.client.KeyEvent;
import com.google.gwt.event.dom.client.KeyPressEvent;
import com.google.gwt.event.dom.client.KeyPressHandler;
import com.google.gwt.event.logical.shared.HasValueChangeHandlers;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.HasText;
import com.google.gwt.user.client.ui.ListBox;
import com.google.gwt.user.client.ui.SuggestOracle;
import com.google.gwt.user.client.ui.SuggestOracle.Request;
import com.google.gwt.user.client.ui.SuggestOracle.Response;
import com.google.gwt.user.client.ui.SuggestOracle.Suggestion;
import com.google.gwt.user.client.ui.TextBox;

public class SuggestBox extends Composite implements BlurHandler,
KeyDownHandler, ChangeHandler, ClickHandler,
                SuggestOracle.Callback, FocusHandler, HasText,
HasValueChangeHandlers<String> {

        private TextBox textbox = new TextBox();
        private ListBox listOfSuggestions = new ListBox();
        private SuggestOracle oracle;
        private FlowPanel panel = new FlowPanel();
        private Timer timer;
        private boolean hasFocus;
        private String lastAnnouncedValue = "";

        public SuggestBox(SuggestOracle oracle) {
                initWidget(panel);
                this.oracle = oracle;
                panel.add(textbox);
                panel.add(listOfSuggestions);
                setStyleName("SuggestBox");

                listOfSuggestions.setVisibleItemCount(10);
                //textbox.addKeyPressHandler(this);
                textbox.addKeyDownHandler(this);
                textbox.addFocusHandler(this);
                listOfSuggestions.addFocusHandler(this);
                listOfSuggestions.addChangeHandler(this);
                listOfSuggestions.addClickHandler(this);
                listOfSuggestions.addBlurHandler(this);
                textbox.addBlurHandler(this);
                hideListOfSuggestions();
        }

        private void hideListOfSuggestions() {
                listOfSuggestions.removeStyleName("hidden");
                listOfSuggestions.addStyleName("hidden");
        }

        private void showListOfSuggestions() {
        
listOfSuggestions.getElement().getStyle().setLeft(textbox.getAbsoluteLeft(),
Unit.PX);
        
listOfSuggestions.getElement().getStyle().setTop(textbox.getAbsoluteTop()
+ textbox.getOffsetHeight(), Unit.PX);
                listOfSuggestions.removeStyleName("hidden");
        }

        private boolean isListOfSuggestionsShowing() {
                return !listOfSuggestions.getStyleName().contains("hidden");
        }

        private void showSuggestions(String text) {
                oracle.requestSuggestions(new SuggestOracle.Request(text), 
this);
        }

        private void showSuggestionsForCurrentText() {
                if (timer != null)
                        timer.cancel();
                timer = new Timer() {
                        @Override
                        public void run() {
                                showSuggestions(textbox.getText());
                        }
                };
                timer.schedule(1);
        }

        private void putCurrentSelectionFromSuggestionsIntoTextBox() {
                String value =
listOfSuggestions.getItemText(listOfSuggestions.getSelectedIndex());
                textbox.setValue(value);
        }

        private boolean handleControls(KeyEvent event) {
                switch (event.getNativeEvent().getKeyCode()) {
                case KeyCodes.KEY_DOWN:
                        if (!isListOfSuggestionsShowing())
                                return false;
                        int index = listOfSuggestions.getSelectedIndex();
                        index = (index + 1) % listOfSuggestions.getItemCount();
                        listOfSuggestions.setSelectedIndex(index);
                        return true;
                case KeyCodes.KEY_UP:
                        if (!isListOfSuggestionsShowing())
                                return false;
                        index = listOfSuggestions.getSelectedIndex();
                        index = index - 1;
                        if (index < 0)
                                index = listOfSuggestions.getItemCount() + 
index;
                        listOfSuggestions.setSelectedIndex(index);
                        return true;
                case KeyCodes.KEY_ENTER:
                        if (isListOfSuggestionsShowing()) {
                                putCurrentSelectionFromSuggestionsIntoTextBox();
                                hideListOfSuggestions();
                        }
                        announceChangedValue();
                        return true;
                }
                return false;
        }

        private void pickCurrentSuggestionFromList() {
        
textbox.setText(listOfSuggestions.getItemText(listOfSuggestions.getSelectedIndex()));
                hideListOfSuggestions();
                textbox.setFocus(true);
        }

        @Override
        public void onChange(ChangeEvent event) {
                if (event.getSource() == textbox) {
                        return;
                }
                if (event.getSource() == listOfSuggestions) {
                        pickCurrentSuggestionFromList();
                        announceChangedValue();
                        return;
                }
        }

        @Override
        public void onSuggestionsReady(Request request, Response response) {
                listOfSuggestions.clear();
                for (Suggestion suggestion : response.getSuggestions())
                        
listOfSuggestions.addItem(suggestion.getReplacementString());
                if (listOfSuggestions.getItemCount() > 0)
                        showListOfSuggestions();
                else
                        hideListOfSuggestions();
                if (!response.getSuggestions().isEmpty())
                        listOfSuggestions.setSelectedIndex(0);
        }

        @Override
        public void onFocus(FocusEvent event) {
                if (event.getSource() == textbox)
                        textbox.selectAll();
                hasFocus = true;
        }

        @Override
        public void onClick(ClickEvent event) {
                GWT.log(event.toString());
                if (event.getSource() == listOfSuggestions) {
                        pickCurrentSuggestionFromList();
                        announceChangedValue();
                }
        }

        @Override
        public void onBlur(BlurEvent event) {
                defocusWidgetIfneccessary();
                hasFocus = false;
        }

        private void defocusWidgetIfneccessary() {
                new Timer() {

                        @Override
                        public void run() {
                                if (!hasFocus)
                                        hideListOfSuggestions();
                                announceChangedValue();
                        }
                }.schedule(100);
        }

        @Override
        public String getText() {
                return textbox.getText();
        }

        @Override
        public void setText(String text) {
                textbox.setText(text);
        }

        private void announceChangedValue(){
                String currentValue = getText();
                if (lastAnnouncedValue.equals(currentValue))
                        return;
                lastAnnouncedValue = currentValue;
                ValueChangeEvent.fire(this, currentValue);
                textbox.selectAll();
        }

        @Override
        public HandlerRegistration
addValueChangeHandler(ValueChangeHandler<String> handler) {
                return addHandler(handler, ValueChangeEvent.getType());
        }

        @Override
        public void onKeyDown(KeyDownEvent event) {
                boolean wasHandled = handleControls(event);
                if (!wasHandled)
                        showSuggestionsForCurrentText();
        }
}

-- 
You received this message because you are subscribed to the Google Groups 
"Google Web Toolkit" group.
To post to this group, send email to google-web-tool...@googlegroups.com.
To unsubscribe from this group, send email to 
google-web-toolkit+unsubscr...@googlegroups.com.
For more options, visit this group at 
http://groups.google.com/group/google-web-toolkit?hl=en.

Reply via email to