001 package org.gwtbootstrap3.extras.typeahead.client.ui;
002
003 /*
004 * #%L
005 * GwtBootstrap3
006 * %%
007 * Copyright (C) 2013 - 2014 GwtBootstrap3
008 * %%
009 * Licensed under the Apache License, Version 2.0 (the "License");
010 * you may not use this file except in compliance with the License.
011 * You may obtain a copy of the License at
012 *
013 * http://www.apache.org/licenses/LICENSE-2.0
014 *
015 * Unless required by applicable law or agreed to in writing, software
016 * distributed under the License is distributed on an "AS IS" BASIS,
017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
018 * See the License for the specific language governing permissions and
019 * limitations under the License.
020 * #L%
021 */
022
023 import com.google.gwt.core.client.JavaScriptObject;
024 import com.google.gwt.core.client.JsArray;
025 import com.google.gwt.dom.client.Element;
026 import com.google.gwt.event.shared.HandlerRegistration;
027 import com.google.gwt.user.client.Event;
028 import org.gwtbootstrap3.client.ui.TextBox;
029 import org.gwtbootstrap3.extras.typeahead.client.base.Dataset;
030 import org.gwtbootstrap3.extras.typeahead.client.base.Suggestion;
031 import org.gwtbootstrap3.extras.typeahead.client.events.*;
032
033 import java.util.Arrays;
034 import java.util.Collection;
035
036 /**
037 * Twitter typeahead.js
038 *
039 * https://github.com/twitter/typeahead.js
040 *
041 * @author Florian Kremser <florian.kremser@sage.com>
042 */
043 public class Typeahead<T> extends TextBox {
044 private Collection<? extends Dataset<T>> datasets;
045 private boolean highlight = false;
046 private boolean hint = true;
047 private int minLength = 1;
048
049 public Typeahead() {
050 }
051
052 /**
053 * A typeahead is composed of one or more datasets. When an end-user
054 * modifies the value of a typeahead, each dataset will attempt to
055 * render suggestions for the new value.
056 *
057 * @param dataset a dataset for providing suggestions
058 */
059 public Typeahead(final Dataset<T> dataset) {
060 setDatasets(dataset);
061 }
062
063 public Typeahead(final Collection<? extends Dataset<T>> datasets) {
064 setDatasets(datasets);
065 }
066
067 public void setDatasets(final Dataset<T> dataset) {
068 this.datasets = Arrays.asList(dataset);
069 }
070
071 public void setDatasets(final Collection<? extends Dataset<T>> datasets) {
072 this.datasets = datasets;
073 }
074
075 @Override
076 public void setValue(final String value, final boolean fireEvents) {
077 setValueNative(getElement(), value);
078 super.setValue(value, fireEvents);
079 }
080
081 /**
082 * If {@code true}, when suggestions are rendered, pattern matches for the
083 * current query in text nodes will be wrapped in a {@code strong} element
084 * with the {@code tt-highlight} class. Defaults to {@code false}.
085 *
086 * @param highlight {@code true} to highlight pattern matches in suggestions
087 */
088 public void setHighlight(final boolean highlight) {
089 this.highlight = highlight;
090 }
091
092 /**
093 * If {@code false}, the typeahead will not show a hint. Defaults to {@code true}.
094 *
095 * @param hint {@code true} to show a hint
096 */
097 public void setHint(final boolean hint) {
098 this.hint = hint;
099 }
100
101 /**
102 * The minimum character length needed before suggestions start getting
103 * rendered. Defaults to 1.
104 *
105 * @param minLength minimum required input length for matching
106 */
107 public void setMinLength(final int minLength) {
108 this.minLength = minLength;
109 }
110
111 public HandlerRegistration addTypeaheadOpenedHandler(final TypeaheadOpenedHandler<T> handler) {
112 return addHandler(handler, TypeaheadOpenedEvent.getType());
113 }
114
115 public HandlerRegistration addTypeaheadClosedHandler(final TypeaheadClosedHandler<T> handler) {
116 return addHandler(handler, TypeaheadClosedEvent.getType());
117 }
118
119 public HandlerRegistration addTypeaheadCursorChangededHandler(final TypeaheadCursorChangedHandler<T> handler) {
120 return addHandler(handler, TypeaheadCursorChangedEvent.getType());
121 }
122
123 public HandlerRegistration addTypeaheadAutoCompletedHandler(final TypeaheadAutoCompletedHandler<T> handler) {
124 return addHandler(handler, TypeaheadAutoCompletedEvent.getType());
125 }
126
127 public HandlerRegistration addTypeaheadSelectedHandler(final TypeaheadSelectedHandler<T> handler) {
128 return addHandler(handler, TypeaheadSelectedEvent.getType());
129 }
130
131 /**
132 * Triggered when the dropdown menu of the typeahead is opened.
133 *
134 * @param event the event
135 */
136 private void onOpened(final Event event) {
137 TypeaheadOpenedEvent.fire(this, event);
138 }
139
140 /**
141 * Triggered when the dropdown menu of the typeahead is closed.
142 *
143 * @param event the event
144 */
145 private void onClosed(final Event event) {
146 TypeaheadClosedEvent.fire(this, event);
147 }
148
149 /**
150 * Triggered when the dropdown menu cursor is moved to a different suggestion.
151 *
152 * @param event the event
153 * @param suggestion the suggestion object
154 */
155 private void onCursorChanged(final Event event, final Suggestion<T> suggestion) {
156 TypeaheadCursorChangedEvent.fire(this, suggestion, event);
157 }
158
159 /**
160 * Triggered when the query is autocompleted. Autocompleted means the query was changed to the hint.
161 *
162 * @param event the event
163 * @param suggestion the suggestion object
164 */
165 private void onAutoCompleted(final Event event, final Suggestion<T> suggestion) {
166 TypeaheadAutoCompletedEvent.fire(this, suggestion, event);
167 }
168
169 /**
170 * Triggered when a suggestion from the dropdown menu is selected.
171 *
172 * @param event the event
173 * @param suggestion the suggestion object
174 */
175 private void onSelected(final Event event, final Suggestion<T> suggestion) {
176 TypeaheadSelectedEvent.fire(this, suggestion, event);
177 }
178
179 public void reconfigure() {
180 remove(getElement());
181 configure();
182 }
183
184 @Override
185 protected void onLoad() {
186 super.onLoad();
187 configure();
188 }
189
190 @Override
191 protected void onUnload() {
192 super.onUnload();
193 remove(getElement());
194 }
195
196 protected void configure() {
197 JsArray<JavaScriptObject> datasetJSOs = JsArray.createArray().cast();
198 for (Dataset<T> dataset : datasets) {
199 JavaScriptObject jso = toJSO(dataset);
200 datasetJSOs.push(jso);
201 }
202 configure(getElement(), highlight, hint, minLength, datasetJSOs);
203 }
204
205 private native JavaScriptObject toJSO(Dataset<T> dataset) /*-{
206 var emptyTemplate = dataset.@org.gwtbootstrap3.extras.typeahead.client.base.Dataset::getEmptyTemplate()();
207 var headerTemplate = dataset.@org.gwtbootstrap3.extras.typeahead.client.base.Dataset::getHeaderTemplate()();
208 var footerTemplate = dataset.@org.gwtbootstrap3.extras.typeahead.client.base.Dataset::getFooterTemplate()();
209 var suggestionTemplate = dataset.@org.gwtbootstrap3.extras.typeahead.client.base.Dataset::getSuggestionTemplate()();
210
211 var findMatches = function () {
212 return function (query, cb) {
213 var scb = @org.gwtbootstrap3.extras.typeahead.client.base.SuggestionCallback::new(Lcom/google/gwt/core/client/JavaScriptObject;)(cb);
214 return dataset.@org.gwtbootstrap3.extras.typeahead.client.base.Dataset::findMatches(Ljava/lang/String;Lorg/gwtbootstrap3/extras/typeahead/client/base/SuggestionCallback;)(query, scb);
215 };
216 };
217
218 return {
219 name: dataset.@org.gwtbootstrap3.extras.typeahead.client.base.Dataset::name,
220 source: findMatches(),
221 templates: {
222 empty: (emptyTemplate != null ? function (query) {
223 return emptyTemplate.@org.gwtbootstrap3.extras.typeahead.client.base.Template::render()();
224 } : undefined),
225 header: (headerTemplate != null ? function (query) {
226 return headerTemplate.@org.gwtbootstrap3.extras.typeahead.client.base.Template::render()();
227 } : undefined),
228 footer: (footerTemplate != null ? function (query) {
229 return footerTemplate.@org.gwtbootstrap3.extras.typeahead.client.base.Template::render()();
230 } : undefined),
231 suggestion: (suggestionTemplate != null ? function (suggestion) {
232 return suggestionTemplate.@org.gwtbootstrap3.extras.typeahead.client.base.SuggestionTemplate::render(Lorg/gwtbootstrap3/extras/typeahead/client/base/Suggestion;)(suggestion);
233 } : undefined)
234 }
235
236 };
237 }-*/;
238
239 private native void configure(
240 Element e, boolean highlight, boolean hint, int minLength, JsArray<JavaScriptObject> datasets) /*-{
241 var that = this;
242 $wnd.jQuery(e).typeahead({
243 highlight: highlight,
244 hint: hint,
245 minLength: minLength
246 },
247 datasets)
248 .on('typeahead:opened', function (e) {
249 that.@org.gwtbootstrap3.extras.typeahead.client.ui.Typeahead::onOpened(Lcom/google/gwt/user/client/Event;)(e);
250 })
251 .on('typeahead:closed', function (e) {
252 that.@org.gwtbootstrap3.extras.typeahead.client.ui.Typeahead::onClosed(Lcom/google/gwt/user/client/Event;)(e);
253 })
254 .on('typeahead:cursorchanged', function (e, value, datasetName) {
255 that.@org.gwtbootstrap3.extras.typeahead.client.ui.Typeahead::onCursorChanged(Lcom/google/gwt/user/client/Event;Lorg/gwtbootstrap3/extras/typeahead/client/base/Suggestion;)(e, value);
256 })
257 .on('typeahead:autocompleted', function (e, value, datasetName) {
258 that.@org.gwtbootstrap3.extras.typeahead.client.ui.Typeahead::onAutoCompleted(Lcom/google/gwt/user/client/Event;Lorg/gwtbootstrap3/extras/typeahead/client/base/Suggestion;)(e, value);
259 })
260 .on('typeahead:selected', function (e, value, datasetName) {
261 that.@org.gwtbootstrap3.extras.typeahead.client.ui.Typeahead::onSelected(Lcom/google/gwt/user/client/Event;Lorg/gwtbootstrap3/extras/typeahead/client/base/Suggestion;)(e, value);
262 });
263 }-*/;
264
265 private native void remove(Element e) /*-{
266 $wnd.jQuery(e).typeahead('destroy');
267 }-*/;
268
269 private native void setValueNative(Element e, String value) /*-{
270 $wnd.jQuery(e).typeahead('val', value);
271 }-*/;
272 }