001package com.github.gwtbootstrap.client.ui;
002
003import java.util.Collection;
004
005import com.github.gwtbootstrap.client.ui.base.MarkupWidget;
006import com.github.gwtbootstrap.client.ui.base.TextBoxBase;
007import com.google.gwt.core.client.JavaScriptObject;
008import com.google.gwt.core.client.JsArrayString;
009import com.google.gwt.core.client.Scheduler;
010import com.google.gwt.core.client.Scheduler.ScheduledCommand;
011import com.google.gwt.user.client.Element;
012import com.google.gwt.user.client.ui.MultiWordSuggestOracle;
013import com.google.gwt.user.client.ui.SuggestOracle;
014import com.google.gwt.user.client.ui.SuggestOracle.Callback;
015import com.google.gwt.user.client.ui.SuggestOracle.Request;
016import com.google.gwt.user.client.ui.SuggestOracle.Response;
017import com.google.gwt.user.client.ui.SuggestOracle.Suggestion;
018import com.google.gwt.user.client.ui.Widget;
019
020public class Typeahead extends MarkupWidget {
021
022    public interface UpdaterCallback {
023        String onSelection(Suggestion selectedSuggestion);
024    }
025    
026    private int displayItems = 8;
027
028    private int minLength = 1;
029
030    private final SuggestOracle oracle;
031    private Collection<? extends Suggestion> suggestions;
032
033    private UpdaterCallback updaterCallback;
034
035    /**
036     * Constructor for {@link Typeahead}. Creates a {@link MultiWordSuggestOracle} to use with this
037     */
038    public Typeahead() {
039        this(new MultiWordSuggestOracle());
040    }
041
042    /**
043     * Constructor for {@link Typeahead}.
044     *
045     * @param oracle the oracle for this <code>Typeahead</code>
046     */
047    public Typeahead(SuggestOracle oracle) {
048        this.oracle = oracle;
049        this.updaterCallback = createDefaultUpdaterCallback();
050    }
051
052    private UpdaterCallback createDefaultUpdaterCallback() {
053        return new UpdaterCallback() {
054            @Override
055            public String onSelection(Suggestion selectedSuggestion) {
056                return selectedSuggestion.getReplacementString();
057            }
058        };
059    }
060
061    /**
062     * {@inheritDoc}
063     */
064    @Override
065    public void setWidget(Widget w) {
066
067        if (!(w instanceof TextBoxBase || w instanceof com.google.gwt.user.client.ui.TextBoxBase)) {
068            throw new IllegalArgumentException("Typeahead should be set TextBoxBase childs");
069        }
070
071        super.setWidget(w);
072    }
073
074    @Override
075    public Widget asWidget() {
076
077        if (widget != null) {
078            Scheduler.get().scheduleDeferred(new ScheduledCommand() {
079                @Override
080                public void execute() {
081                    reconfigure();
082                }
083            });
084
085        }
086
087        return super.asWidget();
088    }
089
090    /**
091     * reconfigure setting.
092     */
093    public void reconfigure() {
094
095        if (widget == null) {
096            return;
097        }
098
099        removeDataIfExists(widget.getElement());
100
101        configure(widget.getElement(), displayItems, minLength);
102    }
103
104    /**
105     * Get the max number of items to display in the dropdown.
106     *
107     * @return the max number of items to display in the dropdown
108     */
109    public int getDisplayItemCount() {
110        return displayItems;
111    }
112
113    /**
114     * Set max number of items to display in the dropdown.
115     *
116     * @param displayItems the max number of items to display in the dropdown
117     */
118    public void setDisplayItemCount(int displayItems) {
119        this.displayItems = displayItems;
120    }
121
122    /**
123     * Get the minimum character length needed before triggering autocomplete suggestions.
124     *
125     * @return the minimum character length needed before triggering autocomplete suggestions
126     */
127    public int getMinLength() {
128        return minLength;
129    }
130
131    /**
132     * Set the minimum character length needed before triggering autocomplete suggestions.
133     *
134     * @param minLength The minimum character length needed before triggering autocomplete suggestions.
135     */
136    public void setMinLength(int minLength) {
137        this.minLength = minLength;
138    }
139
140    public void setUpdaterCallback(UpdaterCallback updaterCallback) {
141        this.updaterCallback = updaterCallback;
142    }
143
144    /**
145     * Get suggest oracle
146     *
147     * @return oracle
148     */
149    public SuggestOracle getSuggestOracle() {
150        return oracle;
151    }
152
153    private void query(String query, final JavaScriptObject process) {
154        Callback callback = new Callback() {
155            @Override
156            public void onSuggestionsReady(Request request, Response response) {
157                callback(process, response);
158            }
159        };
160
161        if (query != null && !query.isEmpty()) {
162            oracle.requestSuggestions(new Request(query, displayItems), callback);
163        } else {
164            oracle.requestDefaultSuggestions(new Request(), callback);
165        }
166    }
167
168    private void callback(final JavaScriptObject process, Response response) {
169        suggestions = response.getSuggestions();
170
171        JsArrayString jsArrayString = JavaScriptObject.createArray().cast();
172
173        for (Suggestion suggestion : suggestions) {
174            jsArrayString.push(suggestion.getDisplayString());
175        }
176        callProcess(jsArrayString, process);
177    }
178
179    private String updater(String item) {
180        for (Suggestion suggestion : suggestions) {
181            if (suggestion.getDisplayString().equals(item)) {
182                return this.updaterCallback.onSelection(suggestion);
183            }
184        }
185
186        return item;
187    }
188    
189    //@formatter:off
190    private native void callProcess(JsArrayString items ,JavaScriptObject process) /*-{
191        process(items);
192    }-*/;
193
194    private native void removeDataIfExists(Element element) /*-{
195        var $this = $wnd.jQuery(element);
196        $this.data("typeahead") && $this.removeData("typeahead");
197    }-*/;
198    
199    public native void configure(Element element,int items, int minLength) /*-{
200        var that = this;
201        $wnd.jQuery(element).typeahead({
202            "source" : function(query , process) {
203                that.@com.github.gwtbootstrap.client.ui.Typeahead::query(Ljava/lang/String;Lcom/google/gwt/core/client/JavaScriptObject;)(query , process);
204            },
205            "items" : items,
206            "minLength" : minLength,
207            "updater" : function(item) {
208                return that.@com.github.gwtbootstrap.client.ui.Typeahead::updater(Ljava/lang/String;)(item);
209            },
210            "highlighter" : function(item) {
211                return item;
212            }
213        });
214    }-*/;
215    //@formatter:on
216}