001package org.gwtbootstrap3.extras.select.client.ui; 002 003/* 004 * #%L 005 * GwtBootstrap3 006 * %% 007 * Copyright (C) 2016 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 */ 022import static org.gwtbootstrap3.extras.select.client.ui.SelectOptions.CONTAINER; 023import static org.gwtbootstrap3.extras.select.client.ui.SelectOptions.DROPDOWN_ALIGN_RIGHT; 024import static org.gwtbootstrap3.extras.select.client.ui.SelectOptions.DROPUP_AUTO; 025import static org.gwtbootstrap3.extras.select.client.ui.SelectOptions.HEADER; 026import static org.gwtbootstrap3.extras.select.client.ui.SelectOptions.HIDE_DISABLED; 027import static org.gwtbootstrap3.extras.select.client.ui.SelectOptions.LIVE_SEARCH; 028import static org.gwtbootstrap3.extras.select.client.ui.SelectOptions.LIVE_SEARCH_NORMALIZE; 029import static org.gwtbootstrap3.extras.select.client.ui.SelectOptions.LIVE_SEARCH_PLACEHOLDER; 030import static org.gwtbootstrap3.extras.select.client.ui.SelectOptions.LIVE_SEARCH_STYLE; 031import static org.gwtbootstrap3.extras.select.client.ui.SelectOptions.MOBILE; 032import static org.gwtbootstrap3.extras.select.client.ui.SelectOptions.SELECT_ON_TAB; 033import static org.gwtbootstrap3.extras.select.client.ui.SelectOptions.SHOW_CONTENT; 034import static org.gwtbootstrap3.extras.select.client.ui.SelectOptions.SHOW_ICON; 035import static org.gwtbootstrap3.extras.select.client.ui.SelectOptions.SHOW_SUBTEXT; 036import static org.gwtbootstrap3.extras.select.client.ui.SelectOptions.SIZE; 037import static org.gwtbootstrap3.extras.select.client.ui.SelectOptions.STYLE; 038import static org.gwtbootstrap3.extras.select.client.ui.SelectOptions.WIDTH; 039import static org.gwtbootstrap3.extras.select.client.ui.SelectOptions.WINDOW_PADDING; 040 041import java.util.ArrayList; 042import java.util.HashMap; 043import java.util.List; 044import java.util.Map; 045import java.util.Map.Entry; 046 047import org.gwtbootstrap3.client.ui.base.ComplexWidget; 048import org.gwtbootstrap3.client.ui.base.HasSize; 049import org.gwtbootstrap3.client.ui.base.HasType; 050import org.gwtbootstrap3.client.ui.base.mixin.AttributeMixin; 051import org.gwtbootstrap3.client.ui.base.mixin.EnabledMixin; 052import org.gwtbootstrap3.client.ui.constants.ButtonSize; 053import org.gwtbootstrap3.client.ui.constants.ButtonType; 054import org.gwtbootstrap3.client.ui.constants.Styles; 055import org.gwtbootstrap3.extras.select.client.ui.constants.DropdownAlignRight; 056import org.gwtbootstrap3.extras.select.client.ui.constants.LiveSearchStyle; 057import org.gwtbootstrap3.extras.select.client.ui.constants.MenuSize; 058import org.gwtbootstrap3.extras.select.client.ui.constants.SelectStyles; 059import org.gwtbootstrap3.extras.select.client.ui.constants.SelectWidth; 060import org.gwtbootstrap3.extras.select.client.ui.event.HasAllSelectHandlers; 061import org.gwtbootstrap3.extras.select.client.ui.event.HiddenEvent; 062import org.gwtbootstrap3.extras.select.client.ui.event.HiddenHandler; 063import org.gwtbootstrap3.extras.select.client.ui.event.HideEvent; 064import org.gwtbootstrap3.extras.select.client.ui.event.HideHandler; 065import org.gwtbootstrap3.extras.select.client.ui.event.LoadedEvent; 066import org.gwtbootstrap3.extras.select.client.ui.event.LoadedHandler; 067import org.gwtbootstrap3.extras.select.client.ui.event.RefreshedEvent; 068import org.gwtbootstrap3.extras.select.client.ui.event.RefreshedHandler; 069import org.gwtbootstrap3.extras.select.client.ui.event.RenderedEvent; 070import org.gwtbootstrap3.extras.select.client.ui.event.RenderedHandler; 071import org.gwtbootstrap3.extras.select.client.ui.event.ShowEvent; 072import org.gwtbootstrap3.extras.select.client.ui.event.ShowHandler; 073import org.gwtbootstrap3.extras.select.client.ui.event.ShownEvent; 074import org.gwtbootstrap3.extras.select.client.ui.event.ShownHandler; 075 076import com.google.gwt.core.client.JavaScriptObject; 077import com.google.gwt.core.client.JsArrayNumber; 078import com.google.gwt.core.client.JsonUtils; 079import com.google.gwt.core.client.ScriptInjector; 080import com.google.gwt.dom.client.Document; 081import com.google.gwt.dom.client.Element; 082import com.google.gwt.dom.client.NodeList; 083import com.google.gwt.dom.client.OptionElement; 084import com.google.gwt.dom.client.SelectElement; 085import com.google.gwt.editor.client.IsEditor; 086import com.google.gwt.editor.client.LeafValueEditor; 087import com.google.gwt.editor.client.adapters.TakesValueEditor; 088import com.google.gwt.event.logical.shared.ValueChangeEvent; 089import com.google.gwt.event.logical.shared.ValueChangeHandler; 090import com.google.gwt.event.shared.HandlerRegistration; 091import com.google.gwt.user.client.ui.Focusable; 092import com.google.gwt.user.client.ui.HasEnabled; 093import com.google.gwt.user.client.ui.HasValue; 094import com.google.gwt.user.client.ui.Widget; 095import com.google.gwt.user.client.ui.impl.FocusImpl; 096 097/** 098 * Bootstrap select widget base 099 * 100 * @param <T> select value type 101 * 102 * @see http://silviomoreto.github.io/bootstrap-select/ 103 * @author Xiaodong Sun 104 */ 105public abstract class SelectBase<T> extends ComplexWidget implements HasValue<T>, HasEnabled, Focusable, 106 HasType<ButtonType>, HasSize<ButtonSize>, IsEditor<LeafValueEditor<T>>, HasAllSelectHandlers<T> { 107 108 private LeafValueEditor<T> editor; 109 private ButtonType type; 110 private ButtonSize size; 111 112 /** 113 * Default language: {@link SelectLanguage#EN} 114 */ 115 private static final SelectLanguage DEFAULT_LANGUAGE = SelectLanguage.EN; 116 117 /** 118 * Language; defaults to {@value #DEFAULT_LANGUAGE} 119 */ 120 private SelectLanguage language = DEFAULT_LANGUAGE; 121 122 protected final SelectElement selectElement; 123 protected final Map<OptionElement, Option> itemMap = new HashMap<>(0); 124 protected final AttributeMixin<SelectBase<T>> attrMixin = new AttributeMixin<>(this); 125 private final EnabledMixin<SelectBase<T>> enabledMixin = new EnabledMixin<>(this); 126 private final FocusImpl focusImpl = FocusImpl.getFocusImplForWidget(); 127 128 /** 129 * Initialize options 130 */ 131 protected SelectOptions options = SelectOptions.newOptions(); 132 133 protected SelectBase() { 134 this.selectElement = Document.get().createSelectElement(); 135 setElement(selectElement); 136 setStyleName(SelectStyles.SELECT_PICKER); 137 addStyleName(Styles.FORM_CONTROL); 138 } 139 140 /** 141 * Returns <code>true</code> if multiple selection is allowed. 142 * 143 * @return <code>true</code> if multiple selection is allowed 144 */ 145 public abstract boolean isMultiple(); 146 147 @Override 148 protected void onLoad() { 149 super.onLoad(); 150 // Inject the language JS is necessary 151 if (language.getJs() != null) { 152 ScriptInjector.fromString(language.getJs().getText()) 153 .setWindow(ScriptInjector.TOP_WINDOW).inject(); 154 } 155 initialize(getElement(), options); 156 bindSelectEvents(getElement()); 157 } 158 159 @Override 160 protected void onUnload() { 161 super.onUnload(); 162 unbindSelectEvents(getElement()); 163 command(getElement(), SelectCommand.DESTROY); 164 } 165 166 @Override 167 public void add(Widget child) { 168 super.add(child); 169 updateItemMap(child, true); 170 } 171 172 @Override 173 public void insert(Widget child, int beforeIndex) { 174 super.insert(child, beforeIndex); 175 updateItemMap(child, true); 176 } 177 178 @Override 179 public boolean remove(Widget w) { 180 boolean removed = super.remove(w); 181 if (removed) { 182 updateItemMap(w, false); 183 } 184 return removed; 185 } 186 187 void updateItemMap(Widget widget, boolean toAdd) { 188 // Option ==> update with this option 189 if (widget instanceof Option) { 190 Option option = (Option) widget; 191 if (toAdd) 192 itemMap.put(option.getSelectElement(), option); 193 else 194 itemMap.remove(option.getSelectElement()); 195 } else if (widget instanceof OptGroup) { 196 // OptGroup ==> update with all optGroup options 197 OptGroup optGroup = (OptGroup) widget; 198 if (toAdd) 199 itemMap.putAll(optGroup.getItemMap()); 200 else 201 for (Entry<OptionElement, Option> entry : optGroup.getItemMap().entrySet()) { 202 OptionElement optElem = entry.getKey(); 203 itemMap.remove(optElem); 204 } 205 } 206 } 207 208 /** 209 * Set the select language. 210 * 211 * @param language 212 */ 213 public void setLanguage(final SelectLanguage language) { 214 this.language = (language == null) ? DEFAULT_LANGUAGE : language; 215 } 216 217 /** 218 * Returns the select language. 219 * 220 * @return 221 */ 222 public SelectLanguage getLanguage() { 223 return language; 224 } 225 226 /** 227 * Sets a container to which the select will be appended. 228 * 229 * @param container specific element or selector, e.g., "body", ".my-container" 230 */ 231 public void setContainer(final String container) { 232 if (container != null) 233 attrMixin.setAttribute(CONTAINER, container); 234 else 235 attrMixin.removeAttribute(CONTAINER); 236 } 237 238 /** 239 * Sets the handler to get the text displayed when selectedTextFormat 240 * is <code>count</code> or <code>count > #</code>, or <code>null</code> 241 * to use the default text: <code>X of Y selected</code>. 242 * 243 * @param handler 244 */ 245 public void setCountSelectedTextHandler(final CountSelectedTextHandler handler) { 246 options.setCountSelectedTextHandler(handler); 247 } 248 249 /** 250 * Sets the drop-down menu right alignment.<br> 251 * <br> 252 * Defaults to {@link DropdownAlignRight#FALSE}. 253 * 254 * @param dropdownAlignRight 255 * @see DropdownAlignRight 256 */ 257 public void setDropdownAlignRight(final DropdownAlignRight dropdownAlignRight) { 258 if (dropdownAlignRight != null) 259 attrMixin.setAttribute(DROPDOWN_ALIGN_RIGHT, dropdownAlignRight.getValue()); 260 else 261 attrMixin.removeAttribute(DROPDOWN_ALIGN_RIGHT); 262 } 263 264 /** 265 * Checks to see which has more room, above or below. 266 * If the drop-up has enough room to fully open normally, 267 * but there is more room above, the drop-up still opens 268 * normally. Otherwise, it becomes a drop-up. 269 * If dropupAuto is set to <code>false</code>, drop-ups 270 * must be called manually.<br> 271 * <br> 272 * Defaults to <code>true</code>. 273 * 274 * @param dropupAuto 275 */ 276 public void setDropupAuto(final boolean dropupAuto) { 277 if (!dropupAuto) 278 attrMixin.setAttribute(DROPUP_AUTO, Boolean.toString(false)); 279 else 280 attrMixin.removeAttribute(DROPUP_AUTO); 281 } 282 283 /** 284 * If drop-up auto is set to <code>false</code>, force to make 285 * the select a drop-up menu if set to <code>true</code>. 286 * 287 * @param forceDropup 288 * @see #setDropupAuto(boolean) 289 */ 290 public void setForceDropup(final boolean forceDropup) { 291 if (forceDropup) { 292 addStyleName(SelectStyles.DROPUP); 293 } else { 294 removeStyleName(SelectStyles.DROPUP); 295 } 296 } 297 298 /** 299 * Adds a header to the top of the menu; includes 300 * a close button by default.<br> 301 * <br> 302 * No header by default. 303 * 304 * @param header 305 */ 306 public void setHeader(final String header) { 307 if (header != null) 308 attrMixin.setAttribute(HEADER, header); 309 else 310 attrMixin.removeAttribute(HEADER); 311 } 312 313 /** 314 * Removes disabled options and optgroups from the menu<br> 315 * <br> 316 * Defaults to <code>false</code>. 317 * 318 * @param hideDisabled 319 */ 320 public void setHideDisabled(final boolean hideDisabled) { 321 if (hideDisabled) 322 attrMixin.setAttribute(HIDE_DISABLED, Boolean.toString(true)); 323 else 324 attrMixin.removeAttribute(HIDE_DISABLED); 325 } 326 327 /** 328 * When set to <code>true</code>, adds a search box to the 329 * top of the select picker drop-down.<br> 330 * <br> 331 * Defaults to <code>false</code>. 332 * 333 * @param liveSearch 334 */ 335 public void setLiveSearch(final boolean liveSearch) { 336 if (liveSearch) 337 attrMixin.setAttribute(LIVE_SEARCH, Boolean.toString(true)); 338 else 339 attrMixin.removeAttribute(LIVE_SEARCH); 340 } 341 342 /** 343 * Setting liveSearchNormalize to <code>true</code> allows for 344 * accent-insensitive searching.<br> 345 * <br> 346 * Defaults to <code>false</code>. 347 * 348 * @param liveSearchNormalize 349 */ 350 public void setLiveSearchNormalize(final boolean liveSearchNormalize) { 351 if (liveSearchNormalize) 352 attrMixin.setAttribute(LIVE_SEARCH_NORMALIZE, Boolean.toString(true)); 353 else 354 attrMixin.removeAttribute(LIVE_SEARCH_NORMALIZE); 355 } 356 357 /** 358 * Set live search style.<br> 359 * <br> 360 * Defaults to {@link LiveSearchStyle#CONTAINS}. 361 * 362 * @param liveSearchStyle 363 * @see LiveSearchStyle 364 */ 365 public void setLiveSearchStyle(final LiveSearchStyle liveSearchStyle) { 366 if (liveSearchStyle != null) 367 attrMixin.setAttribute(LIVE_SEARCH_STYLE, liveSearchStyle.getValue()); 368 else 369 attrMixin.removeAttribute(LIVE_SEARCH_STYLE); 370 } 371 372 /** 373 * Set a placeholder to the live search input.<br> 374 * <br> 375 * Defaults to <code>null</code>. 376 * 377 * @param liveSearchPlaceholder 378 */ 379 public void setLiveSearchPlaceholder(final String liveSearchPlaceholder) { 380 if (liveSearchPlaceholder != null) 381 attrMixin.setAttribute(LIVE_SEARCH_PLACEHOLDER, liveSearchPlaceholder); 382 else 383 attrMixin.removeAttribute(LIVE_SEARCH_PLACEHOLDER); 384 } 385 386 /** 387 * When set to <code>true</code>, enables the device's native 388 * menu for select menus.<br> 389 * <br> 390 * Defaults to <code>false</code>. 391 * 392 * @param mobile 393 */ 394 public void setMobile(final boolean mobile) { 395 if (mobile) 396 attrMixin.setAttribute(MOBILE, Boolean.toString(true)); 397 else 398 attrMixin.removeAttribute(MOBILE); 399 } 400 401 /** 402 * When set to <code>true</code>, treats the tab character like the 403 * <code>enter</code> or <code>Space</code> characters within the 404 * select picker drop-down.<br> 405 * <br> 406 * Defaults to <code>false</code>. 407 * 408 * @param selectOnTab 409 */ 410 public void setSelectOnTab(final boolean selectOnTab) { 411 if (selectOnTab) 412 attrMixin.setAttribute(SELECT_ON_TAB, Boolean.toString(true)); 413 else 414 attrMixin.removeAttribute(SELECT_ON_TAB); 415 } 416 417 /** 418 * When set to <code>true</code>, display custom HTML associated with 419 * selected option(s) in the button. When set to <code>false</code>, 420 * the option value will be displayed instead.<br> 421 * <br> 422 * Defaults to <code>true</code>. 423 * 424 * @param showContent 425 */ 426 public void setShowContent(final boolean showContent) { 427 if (!showContent) 428 attrMixin.setAttribute(SHOW_CONTENT, Boolean.toString(false)); 429 else 430 attrMixin.removeAttribute(SHOW_CONTENT); 431 } 432 433 /** 434 * When set to <code>true</code>, display icon(s) associated with 435 * selected option(s) in the button.<br> 436 * <br> 437 * Defaults to <code>true</code>. 438 * 439 * @param showIcon 440 */ 441 public void setShowIcon(final boolean showIcon) { 442 if (!showIcon) 443 attrMixin.setAttribute(SHOW_ICON, Boolean.toString(false)); 444 else 445 attrMixin.removeAttribute(SHOW_ICON); 446 } 447 448 /** 449 * When set to <code>true</code>, display sub-text associated with a 450 * selected option in the button.<br> 451 * <br> 452 * Defaults to <code>false</code>. 453 * 454 * @param showSubtext 455 */ 456 public void setShowSubtext(final boolean showSubtext) { 457 if (showSubtext) 458 attrMixin.setAttribute(SHOW_SUBTEXT, Boolean.toString(true)); 459 else 460 attrMixin.removeAttribute(SHOW_SUBTEXT); 461 } 462 463 /** 464 * Specifies the number of visible menu items.<br> 465 * <br> 466 * Defaults to {@link MenuSize#AUTO}. 467 * 468 * @param size 469 * @see MenuSize 470 */ 471 public void setMenuSize(final MenuSize size) { 472 if (size != null) 473 attrMixin.setAttribute(SIZE, size.getValue()); 474 else 475 attrMixin.removeAttribute(SIZE); 476 } 477 478 /** 479 * The menu will show the given number of items, even if 480 * the drop-down is cut off.<br> 481 * <br> 482 * Defaults to {@link MenuSize#AUTO}. 483 * 484 * @param size 485 */ 486 public void setFixedMenuSize(final int size) { 487 attrMixin.setAttribute(SIZE, Integer.toString(size)); 488 } 489 490 /** 491 * Sets the {@link ButtonType} of the select.<br> 492 * <br> 493 * <b>IMPORTANT</b>: This method will override the style set by 494 * {@link #setStyle(String)}. 495 */ 496 @Override 497 public void setType(final ButtonType type) { 498 this.type = type; 499 updateStyle(); 500 } 501 502 /** 503 * Returns the {@link ButtonType} of the select; may be <code>null</code>. 504 * 505 * @return the {@link ButtonType} of the select 506 */ 507 @Override 508 public ButtonType getType() { 509 return type; 510 } 511 512 /** 513 * Sets the {@link ButtonSize} of the select.<br> 514 * <br> 515 * <b>IMPORTANT</b>: This method will override the style set by 516 * {@link #setStyle(String)}. 517 */ 518 @Override 519 public void setSize(final ButtonSize size) { 520 this.size = size; 521 updateStyle(); 522 } 523 524 /** 525 * Returns the {@link ButtonSize} of the select; may be <code>null</code>. 526 * 527 * @return the {@link ButtonSize} of the select 528 */ 529 @Override 530 public ButtonSize getSize() { 531 return size; 532 } 533 534 private void updateStyle() { 535 StringBuilder sb = new StringBuilder(); 536 if (type != null) { 537 sb.append(type.getCssName()); 538 } 539 if (size != null) { 540 if (!sb.toString().isEmpty()) { 541 sb.append(" "); 542 } 543 sb.append(size.getCssName()); 544 } 545 setStyle(sb.toString()); 546 } 547 548 /** 549 * Set the customized style name to the select.<br> 550 * <br> 551 * Defaults to <code>null</code>.<br> 552 * <br> 553 * <b>IMPORTANT</b>: This method will override the style set by 554 * {@link #setType(ButtonType)} and/or {@link #setSize(ButtonSize)}. 555 * 556 * @param styleName 557 */ 558 public void setStyle(final String styleName) { 559 if (styleName != null) 560 attrMixin.setAttribute(STYLE, styleName); 561 else 562 attrMixin.removeAttribute(STYLE); 563 } 564 565 /** 566 * Returns the style name applied to the select. 567 * 568 * @return 569 */ 570 public String getStyle() { 571 return attrMixin.getAttribute(STYLE); 572 } 573 574 /** 575 * Set the default placeholder text when nothing is selected. 576 * This works for both multiple and standard select boxes.<br> 577 * <br> 578 * Defaults to <code>null</code>. 579 * 580 * @param title 581 * @see #setTitle(String) 582 */ 583 public void setPlaceholder(final String placeholder) { 584 setTitle(placeholder); 585 } 586 587 /** 588 * Set the specified width to the select.<br> 589 * <br> 590 * Defaults to {@link SelectWidth#NONE}. 591 * 592 * @param width 593 * @see #setWidth(String) 594 * @see SelectWidth 595 */ 596 public void setSelectWidth(final SelectWidth width) { 597 setWidth((width != null) ? width.getValue() : null); 598 } 599 600 /** 601 * Set the select width witch is forced inline to the given value. 602 * 603 * @param cssWidth a CSS width with units, e.g. 100px 604 */ 605 @Override 606 public void setWidth(final String cssWidth) { 607 if (cssWidth != null) 608 attrMixin.setAttribute(WIDTH, cssWidth); 609 else 610 attrMixin.removeAttribute(WIDTH); 611 } 612 613 /** 614 * Sets the window padding to all sides. This is useful in cases where 615 * the window has areas that the drop-down menu should not cover - for 616 * instance a fixed header. 617 * 618 * @param padding 619 */ 620 public void setWindowPadding(final int padding) { 621 attrMixin.setAttribute(WINDOW_PADDING, Integer.toString(padding)); 622 } 623 624 /** 625 * Sets the window padding to top, right, bottom, and right sides. This 626 * is useful in cases where the window has areas that the drop-down menu 627 * should not cover - for instance a fixed header. 628 * 629 * @param top 630 * @param right 631 * @param bottom 632 * @param left 633 */ 634 public void setWindowPaddingTopRightBottomLeft(final int top, final int right, 635 final int bottom, final int left) { 636 JsArrayNumber array = JavaScriptObject.createArray(4).cast(); 637 array.push(top); 638 array.push(right); 639 array.push(bottom); 640 array.push(left); 641 attrMixin.setAttribute(WINDOW_PADDING, JsonUtils.stringify(array)); 642 } 643 644 /** 645 * Set to <code>true</code> to add the Bootstrap menu arrow. 646 * 647 * @param showMenuArrow 648 */ 649 public void setShowMenuArrow(final boolean showMenuArrow) { 650 if (showMenuArrow) { 651 addStyleName(SelectStyles.SHOW_MENU_ARROW); 652 } else { 653 removeStyleName(SelectStyles.SHOW_MENU_ARROW); 654 } 655 } 656 657 @Override 658 public LeafValueEditor<T> asEditor() { 659 if (editor == null) { 660 editor = TakesValueEditor.of(this); 661 } 662 return editor; 663 } 664 665 @Override 666 public void setValue(final T value) { 667 setValue(value, false); 668 } 669 670 @Override 671 public void setValue(final T value, final boolean fireEvents) { 672 673 T oldValue = fireEvents ? getValue() : null; 674 675 setSelectedValue(value); 676 677 if (fireEvents) { 678 T newValue = getValue(); 679 ValueChangeEvent.fireIfNotEqual(this, oldValue, newValue); 680 } 681 } 682 683 /** 684 * Fires {@link ValueChangeEvent} with the current value. 685 */ 686 private void onValueChange() { 687 T newValue = getValue(); 688 ValueChangeEvent.fire(this, newValue); 689 } 690 691 /** 692 * Selects the given value. If the value is <code>null</code> 693 * or does not match any option, no option will be selected. 694 * 695 * @param value 696 */ 697 protected abstract void setSelectedValue(final T value); 698 699 @Override 700 public void setEnabled(boolean enabled) { 701 enabledMixin.setEnabled(enabled); 702 refresh(); 703 } 704 705 @Override 706 public boolean isEnabled() { 707 return enabledMixin.isEnabled(); 708 } 709 710 @Override 711 public int getTabIndex() { 712 return focusImpl.getTabIndex(getFocusElement()); 713 } 714 715 @Override 716 public void setAccessKey(char key) { 717 focusImpl.setAccessKey(getFocusElement(), key); 718 } 719 720 @Override 721 public void setFocus(boolean focused) { 722 if (focused) { 723 focusImpl.focus(getFocusElement()); 724 } else { 725 focusImpl.blur(getFocusElement()); 726 } 727 } 728 729 @Override 730 public void setTabIndex(int index) { 731 focusImpl.setTabIndex(getFocusElement(), index); 732 } 733 734 private Element getFocusElement() { 735 if (!isAttached()) { 736 return selectElement; 737 } 738 return getElement().getParentElement().getFirstChildElement(); 739 } 740 741 /** 742 * Returns the number of items present in the select. 743 * 744 * @return the number of items 745 */ 746 public int getItemCount() { 747 return selectElement.getOptions().getLength(); 748 } 749 750 /** 751 * Returns the item list. 752 * 753 * @return the item list 754 */ 755 public List<Option> getItems() { 756 List<Option> selectedItems = new ArrayList<>(0); 757 NodeList<OptionElement> items = selectElement.getOptions(); 758 for (int i = 0; i < items.getLength(); i++) { 759 OptionElement item = items.getItem(i); 760 Option option = itemMap.get(item); 761 if (option != null) 762 selectedItems.add(option); 763 } 764 return selectedItems; 765 } 766 767 /** 768 * Returns <code>true</code> if the item at the given index is selected.<br> 769 * <br> 770 * <b>Note</b>: if the item at the given index is a divider, this method 771 * always returns <code>false</code>. 772 * 773 * @param index 774 * @return 775 */ 776 public boolean isItemSelected(final int index) { 777 checkIndex(index); 778 OptionElement item = selectElement.getOptions().getItem(index); 779 Option option = itemMap.get(item); 780 return option != null && option.isSelected(); 781 } 782 783 /** 784 * Returns the {@link Option} at the given index. 785 * 786 * @param index 787 * @return 788 */ 789 public Option getItem(final int index) { 790 checkIndex(index); 791 OptionElement item = selectElement.getOptions().getItem(index); 792 return itemMap.get(item); 793 } 794 795 private void checkIndex(final int index) { 796 int max = getItemCount(); 797 if (index < 0 || index >= max) { 798 throw new IndexOutOfBoundsException("Index should be in [0, " + max + "]"); 799 } 800 } 801 802 @Override 803 public HandlerRegistration addValueChangeHandler(ValueChangeHandler<T> handler) { 804 return addHandler(handler, ValueChangeEvent.getType()); 805 } 806 807 @Override 808 public HandlerRegistration addLoadedHandler(LoadedHandler handler) { 809 return addHandler(handler, LoadedEvent.getType()); 810 } 811 812 @Override 813 public HandlerRegistration addShowHandler(ShowHandler handler) { 814 return addHandler(handler, ShowEvent.getType()); 815 } 816 817 @Override 818 public HandlerRegistration addShownHandler(ShownHandler handler) { 819 return addHandler(handler, ShownEvent.getType()); 820 } 821 822 @Override 823 public HandlerRegistration addHideHandler(HideHandler handler) { 824 return addHandler(handler, HideEvent.getType()); 825 } 826 827 @Override 828 public HandlerRegistration addHiddenHandler(HiddenHandler handler) { 829 return addHandler(handler, HiddenEvent.getType()); 830 } 831 832 @Override 833 public HandlerRegistration addRenderedHandler(RenderedHandler handler) { 834 return addHandler(handler, RenderedEvent.getType()); 835 } 836 837 @Override 838 public HandlerRegistration addRefreshedHandler(RefreshedHandler handler) { 839 return addHandler(handler, RefreshedEvent.getType()); 840 } 841 842 /** 843 * Force a re-render of the bootstrap-select UI. This is useful if you programmatically 844 * change any underlying values that affect the layout of the element. 845 */ 846 public void render() { 847 if (isAttached()) 848 command(getElement(), SelectCommand.RENDER); 849 } 850 851 /** 852 * Toggles the select menu open/closed. 853 */ 854 public void toggle() { 855 if (isAttached()) 856 command(getElement(), SelectCommand.TOGGLE); 857 } 858 859 /** 860 * Enables the device's native menu for select menus. 861 */ 862 public void mobile() { 863 if (isAttached()) 864 command(getElement(), SelectCommand.MOBILE); 865 } 866 867 /** 868 * WHEN CHANGING ANY SETTINGS CALL REFRESH AFTER!! 869 */ 870 public void refresh() { 871 if (isAttached()) 872 command(getElement(), SelectCommand.REFRESH); 873 } 874 875 /** 876 * Shows the select. This only affects the visibility of 877 * the select itself. 878 */ 879 public void show() { 880 if (isAttached()) 881 command(getElement(), SelectCommand.SHOW); 882 else 883 super.setVisible(true); 884 } 885 886 /** 887 * Hides the select. This only affects the visibility of 888 * the select itself. 889 */ 890 public void hide() { 891 if (isAttached()) 892 command(getElement(), SelectCommand.HIDE); 893 else 894 super.setVisible(false); 895 } 896 897 @Override 898 public void setVisible(boolean visible) { 899 if (visible) 900 show(); 901 else 902 hide(); 903 } 904 905 @Override 906 public boolean isVisible() { 907 if (isAttached()) { 908 return isVisible(selectElement.getParentElement()); 909 } 910 return super.isVisible(); 911 } 912 913 private native void initialize(Element e, SelectOptions options) /*-{ 914 $wnd.jQuery(e).selectpicker(options); 915 }-*/; 916 917 /** 918 * Binds the select events. 919 * 920 * @param e 921 */ 922 private native void bindSelectEvents(Element e) /*-{ 923 var select = this; 924 $wnd.jQuery(e).on(@org.gwtbootstrap3.extras.select.client.ui.event.HasAllSelectHandlers::LOADED_EVENT, function(event) { 925 @org.gwtbootstrap3.extras.select.client.ui.event.LoadedEvent::fire(Lorg/gwtbootstrap3/extras/select/client/ui/event/HasLoadedHandlers;)(select); 926 }); 927 $wnd.jQuery(e).on(@org.gwtbootstrap3.extras.select.client.ui.event.HasAllSelectHandlers::CHANGED_EVENT, function(event, clickedIndex, newValue, oldValue) { 928 select.@org.gwtbootstrap3.extras.select.client.ui.SelectBase::onValueChange()(); 929 }); 930 $wnd.jQuery(e).on(@org.gwtbootstrap3.extras.select.client.ui.event.HasAllSelectHandlers::SHOW_EVENT, function(event) { 931 @org.gwtbootstrap3.extras.select.client.ui.event.ShowEvent::fire(Lorg/gwtbootstrap3/extras/select/client/ui/event/HasShowHandlers;)(select); 932 }); 933 $wnd.jQuery(e).on(@org.gwtbootstrap3.extras.select.client.ui.event.HasAllSelectHandlers::SHOWN_EVENT, function(event) { 934 @org.gwtbootstrap3.extras.select.client.ui.event.ShownEvent::fire(Lorg/gwtbootstrap3/extras/select/client/ui/event/HasShownHandlers;)(select); 935 }); 936 $wnd.jQuery(e).on(@org.gwtbootstrap3.extras.select.client.ui.event.HasAllSelectHandlers::HIDE_EVENT, function(event) { 937 @org.gwtbootstrap3.extras.select.client.ui.event.HideEvent::fire(Lorg/gwtbootstrap3/extras/select/client/ui/event/HasHideHandlers;)(select); 938 }); 939 $wnd.jQuery(e).on(@org.gwtbootstrap3.extras.select.client.ui.event.HasAllSelectHandlers::HIDDEN_EVENT, function(event) { 940 @org.gwtbootstrap3.extras.select.client.ui.event.HiddenEvent::fire(Lorg/gwtbootstrap3/extras/select/client/ui/event/HasHiddenHandlers;)(select); 941 }); 942 $wnd.jQuery(e).on(@org.gwtbootstrap3.extras.select.client.ui.event.HasAllSelectHandlers::RENDERED_EVENT, function(event) { 943 @org.gwtbootstrap3.extras.select.client.ui.event.RenderedEvent::fire(Lorg/gwtbootstrap3/extras/select/client/ui/event/HasRenderedHandlers;)(select); 944 }); 945 $wnd.jQuery(e).on(@org.gwtbootstrap3.extras.select.client.ui.event.HasAllSelectHandlers::REFRESHED_EVENT, function(event) { 946 @org.gwtbootstrap3.extras.select.client.ui.event.RefreshedEvent::fire(Lorg/gwtbootstrap3/extras/select/client/ui/event/HasRefreshedHandlers;)(select); 947 }); 948 }-*/; 949 950 /** 951 * Unbinds the select events. 952 * 953 * @param e 954 */ 955 private native void unbindSelectEvents(Element e) /*-{ 956 $wnd.jQuery(e).off(@org.gwtbootstrap3.extras.select.client.ui.event.HasAllSelectHandlers::LOADED_EVENT); 957 $wnd.jQuery(e).off(@org.gwtbootstrap3.extras.select.client.ui.event.HasAllSelectHandlers::CHANGED_EVENT); 958 $wnd.jQuery(e).off(@org.gwtbootstrap3.extras.select.client.ui.event.HasAllSelectHandlers::SHOW_EVENT); 959 $wnd.jQuery(e).off(@org.gwtbootstrap3.extras.select.client.ui.event.HasAllSelectHandlers::SHOWN_EVENT); 960 $wnd.jQuery(e).off(@org.gwtbootstrap3.extras.select.client.ui.event.HasAllSelectHandlers::HIDE_EVENT); 961 $wnd.jQuery(e).off(@org.gwtbootstrap3.extras.select.client.ui.event.HasAllSelectHandlers::HIDDEN_EVENT); 962 $wnd.jQuery(e).off(@org.gwtbootstrap3.extras.select.client.ui.event.HasAllSelectHandlers::RENDERED_EVENT); 963 $wnd.jQuery(e).off(@org.gwtbootstrap3.extras.select.client.ui.event.HasAllSelectHandlers::REFRESHED_EVENT); 964 }-*/; 965 966 protected native void command(Element e, String command) /*-{ 967 $wnd.jQuery(e).selectpicker(command); 968 }-*/; 969}