001package org.gwtbootstrap3.client.ui; 002 003/* 004 * #%L 005 * GwtBootstrap3 006 * %% 007 * Copyright (C) 2015 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 023import java.util.Collection; 024import java.util.List; 025 026import org.gwtbootstrap3.client.ui.base.HasAutoComplete; 027import org.gwtbootstrap3.client.ui.base.HasId; 028import org.gwtbootstrap3.client.ui.base.HasPlaceholder; 029import org.gwtbootstrap3.client.ui.base.HasResponsiveness; 030import org.gwtbootstrap3.client.ui.base.HasSize; 031import org.gwtbootstrap3.client.ui.base.ValueBoxBase; 032import org.gwtbootstrap3.client.ui.base.helper.StyleHelper; 033import org.gwtbootstrap3.client.ui.base.mixin.BlankValidatorMixin; 034import org.gwtbootstrap3.client.ui.base.mixin.EnabledMixin; 035import org.gwtbootstrap3.client.ui.base.mixin.ErrorHandlerMixin; 036import org.gwtbootstrap3.client.ui.base.mixin.IdMixin; 037import org.gwtbootstrap3.client.ui.constants.DeviceSize; 038import org.gwtbootstrap3.client.ui.constants.InputSize; 039import org.gwtbootstrap3.client.ui.constants.Styles; 040import org.gwtbootstrap3.client.ui.form.error.ErrorHandler; 041import org.gwtbootstrap3.client.ui.form.error.ErrorHandlerType; 042import org.gwtbootstrap3.client.ui.form.error.HasErrorHandler; 043import org.gwtbootstrap3.client.ui.form.validator.HasBlankValidator; 044import org.gwtbootstrap3.client.ui.form.validator.HasValidators; 045import org.gwtbootstrap3.client.ui.form.validator.ValidationChangedEvent.ValidationChangedHandler; 046import org.gwtbootstrap3.client.ui.form.validator.Validator; 047 048import com.google.gwt.core.client.Scheduler; 049import com.google.gwt.core.client.Scheduler.ScheduledCommand; 050import com.google.gwt.dom.client.Element; 051import com.google.gwt.dom.client.Style.Display; 052import com.google.gwt.dom.client.Style.Unit; 053import com.google.gwt.editor.client.EditorError; 054import com.google.gwt.editor.client.HasEditorErrors; 055import com.google.gwt.event.logical.shared.ResizeEvent; 056import com.google.gwt.event.logical.shared.ResizeHandler; 057import com.google.gwt.user.client.Timer; 058import com.google.gwt.user.client.Window; 059import com.google.gwt.user.client.ui.MultiWordSuggestOracle; 060import com.google.gwt.user.client.ui.PopupPanel; 061import com.google.gwt.user.client.ui.SuggestOracle; 062import com.google.gwt.user.client.ui.SuggestOracle.Suggestion; 063import com.google.gwt.user.client.ui.Widget; 064import com.google.web.bindery.event.shared.HandlerRegistration; 065 066/** 067 * Wrapper for a {@link com.google.gwt.user.client.ui.SuggestBox}.<br/> 068 * <br/> 069 * The default style is inherited from the {@link Styles#DROPDOWN_MENU}. Styling of the suggestions items need 070 * a bit of css in order to be pleasing to the eye. 071 * 072 * <pre> 073 * {@code 074 * .dropdown-menu .item { 075 * padding: 5px; 076 * } 077 * 078 * .dropdown-menu .item-selected { 079 * background-color: #eee; 080 * } 081 * 082 * } 083 * </pre> 084 * 085 * @author Steven Jardine 086 */ 087public class SuggestBox extends com.google.gwt.user.client.ui.SuggestBox implements HasId, HasResponsiveness, HasPlaceholder, 088 HasAutoComplete, HasSize<InputSize>, HasEditorErrors<String>, HasErrorHandler, HasValidators<String>, 089 HasBlankValidator<String> { 090 091 static class CustomSuggestionDisplay extends DefaultSuggestionDisplay { 092 093 private ResizeHandler popupResizeHandler = null; 094 095 public CustomSuggestionDisplay() { 096 super(); 097 final PopupPanel popup = getPopupPanel(); 098 popup.setStyleName(Styles.DROPDOWN_MENU); 099 popup.getElement().getStyle().setDisplay(Display.BLOCK); 100 } 101 102 /** 103 * Resize the popup panel to the size of the suggestBox and place it below the SuggestBox. This is not 104 * ideal but works better in a mobile environment. 105 * 106 * @param box the box the SuggestBox. 107 */ 108 private void resizePopup(final com.google.gwt.user.client.ui.SuggestBox box) { 109 Scheduler.get().scheduleDeferred(new ScheduledCommand() { 110 @Override 111 public void execute() { 112 PopupPanel panel = getPopupPanel(); 113 if (box.isAttached()) 114 { 115 Element e = box.getElement(); 116 panel.setWidth((e.getAbsoluteRight() - e.getAbsoluteLeft() - 2) + Unit.PX.getType()); 117 panel.setPopupPosition(e.getAbsoluteLeft(), e.getAbsoluteBottom()); 118 } 119 else 120 { 121 panel.hide(); 122 } 123 } 124 }); 125 } 126 127 /** {@inheritDoc} */ 128 @Override 129 protected void showSuggestions(final com.google.gwt.user.client.ui.SuggestBox suggestBox, 130 final Collection<? extends Suggestion> suggestions, final boolean isDisplayStringHTML, 131 final boolean isAutoSelectEnabled, final SuggestionCallback callback) { 132 super.showSuggestions(suggestBox, suggestions, isDisplayStringHTML, isAutoSelectEnabled, callback); 133 resizePopup(suggestBox); 134 if (popupResizeHandler == null) { 135 popupResizeHandler = new ResizeHandler() { 136 private Timer timer = new Timer() { 137 public void run() { 138 resizePopup(suggestBox); 139 } 140 }; 141 142 @Override 143 public void onResize(ResizeEvent event) { 144 timer.schedule(250); 145 } 146 }; 147 Window.addResizeHandler(popupResizeHandler); 148 } 149 // Try and set the z-index of the popup to the same as the SuggestBox. 150 if (!suggestBox.getElement().getStyle().getZIndex().equals("")) { 151 try { 152 getPopupPanel().getElement().getStyle() 153 .setZIndex(Integer.valueOf(suggestBox.getElement().getStyle().getZIndex())); 154 } catch (Exception e) { 155 // Do nothing. We tried.... 156 } 157 } 158 } 159 } 160 161 private final EnabledMixin<SuggestBox> enabledMixin = new EnabledMixin<SuggestBox>(this); 162 163 private final ErrorHandlerMixin<String> errorHandlerMixin = new ErrorHandlerMixin<String>(this); 164 165 private final IdMixin<SuggestBox> idMixin = new IdMixin<SuggestBox>(this); 166 167 private final BlankValidatorMixin<SuggestBox, String> validatorMixin = new BlankValidatorMixin<SuggestBox, String>(this, 168 errorHandlerMixin.getErrorHandler()); 169 170 /** 171 * Constructor for {@link SuggestBox}. Creates a {@link MultiWordSuggestOracle} and {@link TextBox} to use 172 * with this {@link SuggestBox}. 173 */ 174 public SuggestBox() { 175 this(new MultiWordSuggestOracle()); 176 } 177 178 /** 179 * Constructor for {@link SuggestBox}. Creates a {@link TextBox} to use with this {@link SuggestBox}. 180 * 181 * @param oracle the oracle for this <code>SuggestBox</code> 182 */ 183 public SuggestBox(SuggestOracle oracle) { 184 this(oracle, new TextBox()); 185 } 186 187 /** 188 * Constructor for {@link SuggestBox}. The text box will be removed from it's current location and wrapped 189 * by the {@link SuggestBox}. 190 * 191 * @param oracle supplies suggestions based upon the current contents of the text widget 192 * @param box the text widget 193 */ 194 public SuggestBox(SuggestOracle oracle, ValueBoxBase<String> box) { 195 this(oracle, box, new CustomSuggestionDisplay()); 196 } 197 198 /** 199 * Constructor for {@link SuggestBox}. The text box will be removed from it's current location and wrapped 200 * by the {@link SuggestBox}. 201 * 202 * @param oracle supplies suggestions based upon the current contents of the text widget 203 * @param box the text widget 204 * @param suggestDisplay the class used to display suggestions 205 */ 206 public SuggestBox(SuggestOracle oracle, ValueBoxBase<String> box, SuggestionDisplay suggestDisplay) { 207 super(oracle, box, suggestDisplay); 208 setStyleName(Styles.FORM_CONTROL); 209 } 210 211 /** {@inheritDoc} */ 212 @Override 213 public HandlerRegistration addValidationChangedHandler(ValidationChangedHandler handler) { 214 return validatorMixin.addValidationChangedHandler(handler); 215 } 216 217 /** {@inheritDoc} */ 218 @Override 219 public void addValidator(Validator<String> validator) { 220 validatorMixin.addValidator(validator); 221 } 222 223 /** {@inheritDoc} */ 224 @Override 225 public boolean getAllowBlank() { 226 return validatorMixin.getAllowBlank(); 227 } 228 229 /** {@inheritDoc} */ 230 @Override 231 public String getAutoComplete() { 232 return getElement().getAttribute(AUTO_COMPLETE); 233 } 234 235 /** {@inheritDoc} */ 236 @Override 237 public ErrorHandler getErrorHandler() { 238 return errorHandlerMixin.getErrorHandler(); 239 } 240 241 /** {@inheritDoc} */ 242 @Override 243 public ErrorHandlerType getErrorHandlerType() { 244 return errorHandlerMixin.getErrorHandlerType(); 245 } 246 247 /** 248 * {@inheritDoc} 249 */ 250 @Override 251 public String getId() { 252 return idMixin.getId(); 253 } 254 255 /** {@inheritDoc} */ 256 @Override 257 public String getPlaceholder() { 258 return getElement().getAttribute(PLACEHOLDER); 259 } 260 261 /** {@inheritDoc} */ 262 @Override 263 public InputSize getSize() { 264 return InputSize.fromStyleName(getStyleName()); 265 } 266 267 @Override 268 public boolean getValidateOnBlur() { 269 return validatorMixin.getValidateOnBlur(); 270 } 271 272 /** {@inheritDoc} */ 273 @Override 274 public boolean isEnabled() { 275 return enabledMixin.isEnabled(); 276 } 277 278 @Override 279 protected void onAttach() { 280 super.onAttach(); 281 // Try and set the z-index. 282 Integer zIndex = null; 283 Widget widget = this; 284 while (zIndex == null && widget != null) { 285 if (!widget.getElement().getStyle().getZIndex().equals("")) { 286 try { 287 zIndex = Integer.valueOf(widget.getElement().getStyle().getZIndex()); 288 zIndex += 10; 289 } catch (Exception e) { 290 zIndex = null; 291 } 292 } 293 widget = widget.getParent(); 294 } 295 if (zIndex != null) { 296 getElement().getStyle().setZIndex(zIndex); 297 } 298 } 299 300 /** {@inheritDoc} */ 301 @Override 302 public boolean removeValidator(Validator<String> validator) { 303 return validatorMixin.removeValidator(validator); 304 } 305 306 @Override 307 public void reset() { 308 validatorMixin.reset(); 309 } 310 311 @Override 312 public void setAllowBlank(boolean allowBlank) { 313 validatorMixin.setAllowBlank(allowBlank); 314 } 315 316 /** {@inheritDoc} */ 317 @Override 318 public void setAutoComplete(final boolean autoComplete) { 319 getElement().setAttribute(AUTO_COMPLETE, autoComplete ? ON : OFF); 320 } 321 322 /** {@inheritDoc} */ 323 @Override 324 public void setEnabled(final boolean enabled) { 325 enabledMixin.setEnabled(enabled); 326 } 327 328 /** {@inheritDoc} */ 329 @Override 330 public void setErrorHandler(ErrorHandler handler) { 331 errorHandlerMixin.setErrorHandler(handler); 332 validatorMixin.setErrorHandler(handler); 333 } 334 335 /** {@inheritDoc} */ 336 @Override 337 public void setErrorHandlerType(ErrorHandlerType type) { 338 errorHandlerMixin.setErrorHandlerType(type); 339 } 340 341 /** {@inheritDoc} */ 342 @Override 343 public void setHiddenOn(final DeviceSize deviceSize) { 344 StyleHelper.setHiddenOn(this, deviceSize); 345 } 346 347 /** {@inheritDoc} */ 348 @Override 349 public void setId(final String id) { 350 idMixin.setId(id); 351 } 352 353 /** {@inheritDoc} */ 354 @Override 355 public void setPlaceholder(final String placeHolder) { 356 getElement().setAttribute(PLACEHOLDER, placeHolder != null ? placeHolder : ""); 357 } 358 359 /** {@inheritDoc} */ 360 @Override 361 public void setSize(final InputSize size) { 362 StyleHelper.addUniqueEnumStyleName(this, InputSize.class, size); 363 } 364 365 @Override 366 public void setValidateOnBlur(boolean validateOnBlur) { 367 validatorMixin.setValidateOnBlur(validateOnBlur); 368 } 369 370 @Override 371 public void setValidators(@SuppressWarnings("unchecked") Validator<String>... validators) { 372 validatorMixin.setValidators(validators); 373 } 374 375 /** {@inheritDoc} */ 376 @Override 377 public void setVisibleOn(final DeviceSize deviceSize) { 378 StyleHelper.setVisibleOn(this, deviceSize); 379 } 380 381 /** {@inheritDoc} */ 382 @Override 383 public void showErrors(List<EditorError> errors) { 384 errorHandlerMixin.showErrors(errors); 385 } 386 387 /** {@inheritDoc} */ 388 @Override 389 public boolean validate() { 390 return validatorMixin.validate(); 391 } 392 393 /** {@inheritDoc} */ 394 @Override 395 public boolean validate(boolean show) { 396 return validatorMixin.validate(show); 397 } 398 399}