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