001/* 002 * #%L 003 * GwtMaterial 004 * %% 005 * Copyright (C) 2015 - 2017 GwtMaterialDesign 006 * %% 007 * Licensed under the Apache License, Version 2.0 (the "License"); 008 * you may not use this file except in compliance with the License. 009 * You may obtain a copy of the License at 010 * 011 * http://www.apache.org/licenses/LICENSE-2.0 012 * 013 * Unless required by applicable law or agreed to in writing, software 014 * distributed under the License is distributed on an "AS IS" BASIS, 015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 016 * See the License for the specific language governing permissions and 017 * limitations under the License. 018 * #L% 019 */ 020package gwt.material.design.client.ui; 021 022import com.google.gwt.core.client.GWT; 023import com.google.gwt.dom.client.Document; 024import com.google.gwt.dom.client.OptionElement; 025import com.google.gwt.dom.client.SelectElement; 026import com.google.gwt.event.dom.client.DomEvent; 027import com.google.gwt.event.logical.shared.ValueChangeEvent; 028import com.google.gwt.i18n.client.HasDirection.Direction; 029import com.google.gwt.user.client.ui.FormPanel; 030import com.google.gwt.user.client.ui.HasConstrainedValue; 031import com.google.gwt.user.client.ui.ListBox; 032import gwt.material.design.client.base.*; 033import gwt.material.design.client.base.mixin.ErrorMixin; 034import gwt.material.design.client.base.mixin.ReadOnlyMixin; 035import gwt.material.design.client.base.mixin.ToggleStyleMixin; 036import gwt.material.design.client.constants.CssName; 037import gwt.material.design.client.js.JsMaterialElement; 038import gwt.material.design.client.ui.html.Label; 039import gwt.material.design.jquery.client.api.JQueryElement; 040 041import java.util.ArrayList; 042import java.util.Collection; 043import java.util.LinkedList; 044import java.util.List; 045 046import static gwt.material.design.client.js.JsMaterialElement.$; 047//@formatter:off 048 049/** 050 * <p>Material ListBox is another dropdown component that will set / get the value depends on the selected index 051 * <h3>UiBinder Usage:</h3> 052 * <p> 053 * <pre> 054 * {@code 055 * <m:MaterialListBox ui:field="lstBox" /> 056 * } 057 * </pre> 058 * <h3>Java Usage:</h3> 059 * <p> 060 * <pre> 061 * {@code 062 * // functions 063 * lstBox.setSelectedIndex(2); 064 * lstBox.getSelectedIndex(); 065 * lstBox.addValueChangeHandler(handler); 066 * } 067 * </pre> 068 * </p> 069 * 070 * @author kevzlou7979 071 * @author Ben Dol 072 * @see <a href="http://gwtmaterialdesign.github.io/gwt-material-demo/#forms">Material ListBox</a> 073 * @see <a href="https://material.io/guidelines/components/menus.html">Material Design Specification</a> 074 */ 075//@formatter:on 076public class MaterialListValueBox<T> extends AbstractValueWidget<T> implements JsLoader, HasPlaceholder, 077 HasConstrainedValue<T>, HasReadOnly { 078 079 private final ListBox listBox = new ListBox(); 080 private final Label label = new Label(); 081 protected final List<T> values = new ArrayList<>(); 082 private KeyFactory<T, String> keyFactory = Object::toString; 083 private MaterialLabel errorLabel = new MaterialLabel(); 084 085 private ToggleStyleMixin<ListBox> toggleOldMixin; 086 private ReadOnlyMixin<MaterialListValueBox<T>, ListBox> readOnlyMixin; 087 private ErrorMixin<AbstractValueWidget, MaterialLabel> errorMixin; 088 089 private String emptyPlaceHolder = null; 090 091 public MaterialListValueBox() { 092 super(Document.get().createDivElement(), CssName.INPUT_FIELD, CssName.LISTBOX_WRAPPER); 093 } 094 095 @Override 096 protected void onLoad() { 097 super.onLoad(); 098 099 add(listBox); 100 add(label); 101 add(errorLabel); 102 103 registerHandler(addValueChangeHandler(valueChangeEvent -> { 104 if (isToggleReadOnly()) { 105 setReadOnly(true); 106 } 107 })); 108 109 load(); 110 } 111 112 @Override 113 public void load() { 114 JQueryElement listBoxElement = $(listBox.getElement()); 115 JsMaterialElement.$(listBox.getElement()).material_select( 116 () -> $("input.select-dropdown").trigger("close", true)); 117 listBoxElement.change((e, param) -> { 118 try { 119 ValueChangeEvent.fire(this, getValue()); 120 } catch (IndexOutOfBoundsException ex) { 121 GWT.log("ListBox value change handler threw an exception.", ex); 122 } 123 return true; 124 }); 125 126 JQueryElement selectDropdown = listBoxElement.siblings("input.select-dropdown"); 127 selectDropdown.mousedown((event, o) -> { 128 $("input[data-activates!='" + listBoxElement.attr("data-activates") + "'].select-dropdown").trigger("close", true); 129 return true; 130 }); 131 132 selectDropdown.blur((e, param1) -> { 133 DomEvent.fireNativeEvent(Document.get().createBlurEvent(), this); 134 return true; 135 }); 136 137 selectDropdown.focus((e, param1) -> { 138 DomEvent.fireNativeEvent(Document.get().createFocusEvent(), this); 139 return true; 140 }); 141 } 142 143 @Override 144 protected void onUnload() { 145 super.onUnload(); 146 147 unload(); 148 } 149 150 @Override 151 public void unload() { 152 if (listBox != null && listBox.isAttached()) { 153 $(listBox.getElement()).siblings("input.select-dropdown").off("mousedown"); 154 $(listBox.getElement()).off("change"); 155 $(listBox.getElement()).material_select("destroy"); 156 } 157 } 158 159 @Override 160 public void reload() { 161 if (isAttached()) { 162 unload(); 163 load(); 164 } 165 } 166 167 public void add(T value) { 168 addItem(value); 169 } 170 171 /** 172 * Adds an item to the list box, specifying its direction. This method has 173 * the same effect as 174 * <pre>addItem(value, dir, item)</pre> 175 * 176 * @param value the item's value, to be submitted if it is part of a 177 * {@link FormPanel}; cannot be <code>null</code> 178 * @param dir the item's direction 179 */ 180 public void addItem(T value, Direction dir) { 181 addItem(value, dir, true); 182 } 183 184 /** 185 * Adds an item to the list box, specifying its direction. This method has 186 * the same effect as 187 * <pre>addItem(value, dir, item)</pre> 188 * 189 * @param value the item's value, to be submitted if it is part of a 190 * {@link FormPanel}; cannot be <code>null</code> 191 * @param dir the item's direction 192 * @param reload perform a 'material select' reload to update the DOM. 193 */ 194 public void addItem(T value, Direction dir, boolean reload) { 195 values.add(value); 196 listBox.addItem(keyFactory.generateKey(value), dir); 197 198 if (reload) { 199 reload(); 200 } 201 } 202 203 /** 204 * Adds an item to the list box. This method has the same effect as 205 * <pre>addItem(value, item)</pre> 206 * 207 * @param value the item's value, to be submitted if it is part of a 208 * {@link FormPanel}; cannot be <code>null</code> 209 */ 210 public void addItem(T value) { 211 addItem(value, true); 212 } 213 214 /** 215 * Adds an item to the list box. This method has the same effect as 216 * <pre>addItem(value, item)</pre> 217 * 218 * @param value the item's value, to be submitted if it is part of a 219 * {@link FormPanel}; cannot be <code>null</code> 220 * @param reload perform a 'material select' reload to update the DOM. 221 */ 222 public void addItem(T value, boolean reload) { 223 values.add(value); 224 listBox.addItem(keyFactory.generateKey(value)); 225 226 if (reload) { 227 reload(); 228 } 229 } 230 231 /** 232 * Adds an item to the list box, specifying an initial value for the item. 233 * 234 * @param value the item's value, to be submitted if it is part of a 235 * {@link FormPanel}; cannot be <code>null</code> 236 * @param text the text of the item to be added 237 */ 238 public void addItem(T value, String text) { 239 addItem(value, text, true); 240 } 241 242 /** 243 * Adds an item to the list box, specifying an initial value for the item. 244 * 245 * @param value the item's value, to be submitted if it is part of a 246 * {@link FormPanel}; cannot be <code>null</code> 247 * @param text the text of the item to be added 248 * @param reload perform a 'material select' reload to update the DOM. 249 */ 250 public void addItem(T value, String text, boolean reload) { 251 values.add(value); 252 listBox.addItem(text, keyFactory.generateKey(value)); 253 254 if (reload) { 255 reload(); 256 } 257 } 258 259 /** 260 * Adds an item to the list box, specifying its direction and an initial 261 * value for the item. 262 * 263 * @param value the item's value, to be submitted if it is part of a 264 * {@link FormPanel}; cannot be <code>null</code> 265 * @param dir the item's direction 266 * @param text the text of the item to be added 267 */ 268 public void addItem(T value, Direction dir, String text) { 269 addItem(value, dir, text, true); 270 } 271 272 /** 273 * Adds an item to the list box, specifying its direction and an initial 274 * value for the item. 275 * 276 * @param value the item's value, to be submitted if it is part of a 277 * {@link FormPanel}; cannot be <code>null</code> 278 * @param dir the item's direction 279 * @param text the text of the item to be added 280 * @param reload perform a 'material select' reload to update the DOM. 281 */ 282 public void addItem(T value, Direction dir, String text, boolean reload) { 283 values.add(value); 284 listBox.addItem(text, dir, keyFactory.generateKey(value)); 285 286 if (reload) { 287 reload(); 288 } 289 } 290 291 /** 292 * Inserts an item into the list box. Has the same effect as 293 * <pre>insertItem(value, item, index)</pre> 294 * 295 * @param value the item's value, to be submitted if it is part of a 296 * {@link FormPanel}. 297 * @param index the index at which to insert it 298 */ 299 public void insertItem(T value, int index) { 300 insertItemInternal(value, index, true); 301 } 302 303 /** 304 * Inserts an item into the list box. Has the same effect as 305 * <pre>insertItem(value, item, index)</pre> 306 * 307 * @param value the item's value, to be submitted if it is part of a 308 * {@link FormPanel}. 309 * @param index the index at which to insert it 310 * @param reload perform a 'material select' reload to update the DOM. 311 */ 312 public void insertItem(T value, int index, boolean reload) { 313 index += getIndexOffset(); 314 insertItemInternal(value, index, reload); 315 } 316 protected void insertItemInternal(T value, int index, boolean reload) { 317 values.add(index, value); 318 listBox.insertItem(keyFactory.generateKey(value), index); 319 320 if (reload) { 321 reload(); 322 } 323 } 324 325 /** 326 * Inserts an item into the list box, specifying its direction. Has the same 327 * effect as 328 * <pre>insertItem(value, dir, item, index)</pre> 329 * 330 * @param value the item's value, to be submitted if it is part of a 331 * {@link FormPanel}. 332 * @param dir the item's direction 333 * @param index the index at which to insert it 334 */ 335 public void insertItem(T value, Direction dir, int index) { 336 insertItemInternal(value, dir, index, true); 337 } 338 339 /** 340 * Inserts an item into the list box, specifying its direction. Has the same 341 * effect as 342 * <pre>insertItem(value, dir, item, index)</pre> 343 * 344 * @param value the item's value, to be submitted if it is part of a 345 * {@link FormPanel}. 346 * @param dir the item's direction 347 * @param index the index at which to insert it 348 * @param reload perform a 'material select' reload to update the DOM. 349 */ 350 public void insertItem(T value, Direction dir, int index, boolean reload) { 351 index += getIndexOffset(); 352 insertItemInternal(value, dir, index, reload); 353 } 354 protected void insertItemInternal(T value, Direction dir, int index, boolean reload) { 355 values.add(index, value); 356 listBox.insertItem(keyFactory.generateKey(value), dir, index); 357 358 if (reload) { 359 reload(); 360 } 361 } 362 363 /** 364 * Inserts an item into the list box, specifying an initial value for the 365 * item. Has the same effect as 366 * <pre>insertItem(value, null, item, index)</pre> 367 * 368 * @param value the item's value, to be submitted if it is part of a 369 * {@link FormPanel}. 370 * @param text the text of the item to be inserted 371 * @param index the index at which to insert it 372 */ 373 public void insertItem(T value, String text, int index) { 374 insertItemInternal(value, text, index, true); 375 } 376 377 /** 378 * Inserts an item into the list box, specifying an initial value for the 379 * item. Has the same effect as 380 * <pre>insertItem(value, null, item, index)</pre> 381 * 382 * @param value the item's value, to be submitted if it is part of a 383 * {@link FormPanel}. 384 * @param text the text of the item to be inserted 385 * @param index the index at which to insert it 386 * @param reload perform a 'material select' reload to update the DOM. 387 */ 388 public void insertItem(T value, String text, int index, boolean reload) { 389 index += getIndexOffset(); 390 insertItemInternal(value, text, index, reload); 391 } 392 protected void insertItemInternal(T value, String text, int index, boolean reload) { 393 values.add(index, value); 394 listBox.insertItem(text, keyFactory.generateKey(value), index); 395 396 if (reload) { 397 reload(); 398 } 399 } 400 401 /** 402 * Inserts an item into the list box, specifying its direction and an 403 * initial value for the item. If the index is less than zero, or greater 404 * than or equal to the length of the list, then the item will be appended 405 * to the end of the list. 406 * 407 * @param value the item's value, to be submitted if it is part of a 408 * {@link FormPanel}. 409 * @param dir the item's direction. If {@code null}, the item is displayed 410 * in the widget's overall direction, or, if a direction 411 * estimator has been set, in the item's estimated direction. 412 * @param text the text of the item to be inserted 413 * @param index the index at which to insert it 414 */ 415 public void insertItem(T value, Direction dir, String text, int index) { 416 insertItemInternal(value, dir, text, index, true); 417 } 418 419 /** 420 * Inserts an item into the list box, specifying its direction and an 421 * initial value for the item. If the index is less than zero, or greater 422 * than or equal to the length of the list, then the item will be appended 423 * to the end of the list. 424 * 425 * @param value the item's value, to be submitted if it is part of a 426 * {@link FormPanel}. 427 * @param dir the item's direction. If {@code null}, the item is displayed 428 * in the widget's overall direction, or, if a direction 429 * estimator has been set, in the item's estimated direction. 430 * @param text the text of the item to be inserted 431 * @param index the index at which to insert it 432 * @param reload perform a 'material select' reload to update the DOM. 433 */ 434 public void insertItem(T value, Direction dir, String text, int index, boolean reload) { 435 index += getIndexOffset(); 436 insertItemInternal(value, dir, text, index, reload); 437 } 438 protected void insertItemInternal(T value, Direction dir, String text, int index, boolean reload) { 439 values.add(index, value); 440 listBox.insertItem(keyFactory.generateKey(value), dir, text, index); 441 442 if (reload) { 443 reload(); 444 } 445 } 446 447 /** 448 * Removes the item at the specified index. 449 * 450 * @param index the index of the item to be removed 451 * @throws IndexOutOfBoundsException if the index is out of range 452 */ 453 public void removeItem(int index) { 454 removeItemInternal(index, true); 455 } 456 457 /** 458 * Removes the item at the specified index. 459 * 460 * @param index the index of the item to be removed 461 * @param reload perform a 'material select' reload to update the DOM. 462 * @throws IndexOutOfBoundsException if the index is out of range 463 */ 464 public void removeItem(int index, boolean reload) { 465 index += getIndexOffset(); 466 removeItemInternal(index, reload); 467 } 468 protected void removeItemInternal(int index, boolean reload) { 469 values.remove(index); 470 listBox.removeItem(index); 471 472 if (reload) { 473 reload(); 474 } 475 } 476 477 /** 478 * Removes a value from the list box. Nothing is done if the value isn't on 479 * the list box. 480 * 481 * @param value the value to be removed from the list 482 */ 483 public void removeValue(T value) { 484 removeValue(value, true); 485 } 486 487 /** 488 * Removes a value from the list box. Nothing is done if the value isn't on 489 * the list box. 490 * 491 * @param value the value to be removed from the list 492 * @param reload perform a 'material select' reload to update the DOM. 493 */ 494 public void removeValue(T value, boolean reload) { 495 int idx = getIndex(value); 496 if (idx >= 0) { 497 removeItemInternal(idx, reload); 498 } 499 } 500 501 @Override 502 public void reset() { 503 super.reset(); 504 clear(); 505 } 506 507 /** 508 * Removes all items from the list box. 509 */ 510 @Override 511 public void clear() { 512 values.clear(); 513 listBox.clear(); 514 if(emptyPlaceHolder != null) { 515 insertEmptyPlaceHolder(emptyPlaceHolder); 516 } 517 reload(); 518 } 519 520 @Override 521 public void setPlaceholder(String placeholder) { 522 label.setText(placeholder); 523 } 524 525 @Override 526 public String getPlaceholder() { 527 return label.getText(); 528 } 529 530 public OptionElement getOptionElement(int index) { 531 return getSelectElement().getOptions().getItem(index); 532 } 533 534 protected SelectElement getSelectElement() { 535 return listBox.getElement().cast(); 536 } 537 538 /** 539 * Sets whether this list allows multiple selections. 540 * 541 * @param multipleSelect <code>true</code> to allow multiple selections 542 */ 543 public void setMultipleSelect(boolean multipleSelect) { 544 listBox.setMultipleSelect(multipleSelect); 545 } 546 547 /** 548 * Gets whether this list allows multiple selection. 549 * 550 * @return <code>true</code> if multiple selection is allowed 551 */ 552 public boolean isMultipleSelect() { 553 return listBox.isMultipleSelect(); 554 } 555 556 public void setEmptyPlaceHolder(String value) { 557 if(value == null) { 558 // about to un-set emptyPlaceHolder 559 if(emptyPlaceHolder != null) { 560 // emptyPlaceHolder is about to change from null to non-null 561 if(isEmptyPlaceHolderListed()) { 562 // indeed first item is actually emptyPlaceHolder 563 removeEmptyPlaceHolder(); 564 } else { 565 GWT.log("WARNING: emptyPlaceHolder is set but not listed.", new IllegalStateException()); 566 } 567 } // else no change 568 } else { 569 if(!value.equals(emptyPlaceHolder)) { 570 // adding emptyPlaceHolder 571 insertEmptyPlaceHolder(value); 572 } // else no change 573 } 574 575 emptyPlaceHolder = value; 576 } 577 578 public String getEmptyPlaceHolder() { 579 return emptyPlaceHolder; 580 } 581 582 @Override 583 public void setAcceptableValues(Collection<T> values) { 584 clear(); 585 values.forEach(this::addItem); 586 } 587 588 @Override 589 public T getValue() { 590 int selectedIndex = listBox.getSelectedIndex(); 591 if (selectedIndex >= 0) { 592 return values.get(selectedIndex); 593 } 594 return null; 595 } 596 597 @Override 598 public void setValue(T value) { 599 setValue(value, false); 600 } 601 602 @Override 603 public void setValue(T value, boolean fireEvents) { 604 int index = values.indexOf(value); 605 if (index >= 0) { 606 T before = getValue(); 607 setSelectedIndexInternal(index); 608 609 if (fireEvents) { 610 ValueChangeEvent.fireIfNotEqual(this, before, value); 611 } 612 } 613 } 614 615 public boolean isOld() { 616 return getToggleOldMixin().isOn(); 617 } 618 619 public void setOld(boolean old) { 620 getToggleOldMixin().setOn(old); 621 } 622 623 /** 624 * Sets the value associated with the item at a given index. This value can 625 * be used for any purpose, but is also what is passed to the server when 626 * the list box is submitted as part of a {@link FormPanel}. 627 * 628 * @param index the index of the item to be set 629 * @param value the item's new value; cannot be <code>null</code> 630 * @throws IndexOutOfBoundsException if the index is out of range 631 */ 632 public void setValue(int index, String value) { 633 index += getIndexOffset(); 634 listBox.setValue(index, value); 635 reload(); 636 } 637 638 @Override 639 public void setTitle(String title) { 640 listBox.setTitle(title); 641 } 642 643 /** 644 * Sets whether an individual list item is selected. 645 * 646 * @param index the index of the item to be selected or unselected 647 * @param selected <code>true</code> to select the item 648 * @throws IndexOutOfBoundsException if the index is out of range 649 */ 650 public void setItemSelected(int index, boolean selected) { 651 index += getIndexOffset(); 652 setItemSelectedInternal(index, selected); 653 } 654 private void setItemSelectedInternal(int index, boolean selected) { 655 listBox.setItemSelected(index, selected); 656 reload(); 657 } 658 659 /** 660 * Sets the text associated with the item at a given index. 661 * 662 * @param index the index of the item to be set 663 * @param text the item's new text 664 * @throws IndexOutOfBoundsException if the index is out of range 665 */ 666 public void setItemText(int index, String text) { 667 index += getIndexOffset(); 668 listBox.setItemText(index, text); 669 reload(); 670 } 671 672 /** 673 * Sets the text associated with the item at a given index. 674 * 675 * @param index the index of the item to be set 676 * @param text the item's new text 677 * @param dir the item's direction. 678 * @throws IndexOutOfBoundsException if the index is out of range 679 */ 680 public void setItemText(int index, String text, Direction dir) { 681 index += getIndexOffset(); 682 listBox.setItemText(index, text, dir); 683 reload(); 684 } 685 686 public void setName(String name) { 687 listBox.setName(name); 688 } 689 690 /** 691 * Sets the currently selected index. 692 * <p> 693 * After calling this method, only the specified item in the list will 694 * remain selected. For a ListBox with multiple selection enabled, see 695 * {@link #setItemSelected(int, boolean)} to select multiple items at a 696 * time. 697 * 698 * @param index the index of the item to be selected 699 */ 700 public void setSelectedIndex(int index) { 701 index += getIndexOffset(); 702 setSelectedIndexInternal(index); 703 } 704 protected void setSelectedIndexInternal(int index) { 705 listBox.setSelectedIndex(index); 706 reload(); 707 } 708 709 /** 710 * Sets the number of items that are visible. If only one item is visible, 711 * then the box will be displayed as a drop-down list. 712 * 713 * @param visibleItems the visible item count 714 */ 715 public void setVisibleItemCount(int visibleItems) { 716 listBox.setVisibleItemCount(visibleItems); 717 } 718 719 /** 720 * Gets the number of items present in the list box. 721 * 722 * @return the number of items 723 */ 724 public int getItemCount() { 725 return listBox.getItemCount(); 726 } 727 728 /** 729 * Gets the text associated with the item at the specified index. 730 * 731 * @param index the index of the item whose text is to be retrieved 732 * @return the text associated with the item 733 * @throws IndexOutOfBoundsException if the index is out of range 734 */ 735 public String getItemText(int index) { 736 index += getIndexOffset(); 737 return listBox.getItemText(index); 738 } 739 740 /** 741 * Gets the text for currently selected item. If multiple items are 742 * selected, this method will return the text of the first selected item. 743 * 744 * @return the text for selected item, or {@code null} if none is selected 745 */ 746 public String getSelectedItemText() { 747 return listBox.getSelectedItemText(); 748 } 749 750 public String getName() { 751 return listBox.getName(); 752 } 753 754 /** 755 * Gets the currently-selected item. If multiple items are selected, this 756 * method will return the first selected item ({@link #isItemSelected(int)} 757 * can be used to query individual items). 758 * 759 * @return the selected index, or <code>-1</code> if none is selected 760 */ 761 public int getSelectedIndex() { 762 int selectedIndex = getSelectedIndexInternal(); 763 if(selectedIndex >= 0) { 764 selectedIndex -= getIndexOffset(); 765 } 766 return selectedIndex; 767 } 768 protected int getSelectedIndexInternal() { 769 return listBox.getSelectedIndex(); 770 } 771 772 /** 773 * Gets the value associated with the item at a given index. 774 * 775 * @param index the index of the item to be retrieved 776 * @return the item's associated value 777 * @throws IndexOutOfBoundsException if the index is out of range 778 */ 779 public T getValue(int index) { 780 return getValueInternal(index + getIndexOffset()); 781 } 782 protected T getValueInternal(int index) { 783 return values.get(index); 784 } 785 786 /** 787 * Gets the value for currently selected item. If multiple items are 788 * selected, this method will return the value of the first selected item. 789 * 790 * @return the value for selected item, or {@code null} if none is selected 791 */ 792 public T getSelectedValue() { 793 try { 794 return values.get(getSelectedIndexInternal()); 795 } catch (IndexOutOfBoundsException ex) { 796 return null; 797 } 798 } 799 800 /** 801 * Gets the number of items that are visible. If only one item is visible, 802 * then the box will be displayed as a drop-down list. 803 * 804 * @return the visible item count 805 */ 806 public int getVisibleItemCount() { 807 return listBox.getVisibleItemCount(); 808 } 809 810 /** 811 * Determines whether an individual list item is selected. 812 * 813 * @param index the index of the item to be tested 814 * @return <code>true</code> if the item is selected 815 * @throws IndexOutOfBoundsException if the index is out of range 816 */ 817 public boolean isItemSelected(int index) { 818 return listBox.isItemSelected(index + getIndexOffset()); 819 } 820 821 822 @Override 823 public void setEnabled(boolean enabled) { 824 listBox.setEnabled(enabled); 825 reload(); 826 } 827 828 @Override 829 public boolean isEnabled() { 830 return listBox.isEnabled(); 831 } 832 833 /** 834 * Use your own key factory for value keys. 835 */ 836 public void setKeyFactory(KeyFactory<T, String> keyFactory) { 837 this.keyFactory = keyFactory; 838 } 839 840 @Override 841 public void setReadOnly(boolean value) { 842 getReadOnlyMixin().setReadOnly(value); 843 if (!value) { 844 $(listBox.getElement()).material_select("destroy"); 845 $(listBox.getElement()).material_select(); 846 } 847 } 848 849 @Override 850 public boolean isReadOnly() { 851 return getReadOnlyMixin().isReadOnly(); 852 } 853 854 @Override 855 public void setToggleReadOnly(boolean toggle) { 856 getReadOnlyMixin().setToggleReadOnly(toggle); 857 } 858 859 @Override 860 public boolean isToggleReadOnly() { 861 return getReadOnlyMixin().isToggleReadOnly(); 862 } 863 864 public ListBox getListBox() { 865 return listBox; 866 } 867 868 @Override 869 public ErrorMixin<AbstractValueWidget, MaterialLabel> getErrorMixin() { 870 if (errorMixin == null) { 871 errorMixin = new ErrorMixin<>(this, errorLabel, this, label); 872 } 873 return errorMixin; 874 } 875 876 public Label getLabel() { 877 return label; 878 } 879 880 public MaterialLabel getErrorLabel() { 881 return errorLabel; 882 } 883 884 /** 885 * Returns all selected values of the list box, or empty array if none. 886 * 887 * @return the selected values of the list box 888 */ 889 public String[] getItemsSelected() { 890 List<String> selected = new LinkedList<>(); 891 for (int i = getIndexOffset(); i < listBox.getItemCount(); i++) { 892 if (listBox.isItemSelected(i)) { 893 selected.add(listBox.getValue(i)); 894 } 895 } 896 return selected.toArray(new String[selected.size()]); 897 } 898 899 /** 900 * Sets the currently selected value. 901 * <p> 902 * After calling this method, only the specified item in the list will 903 * remain selected. For a ListBox with multiple selection enabled, see 904 * {@link #setValueSelected(T, boolean)} to select multiple items at a 905 * time. 906 * 907 * @param value the value of the item to be selected 908 */ 909 public void setSelectedValue(T value) { 910 int idx = getIndex(value); 911 if (idx >= 0) { 912 setSelectedIndexInternal(idx); 913 } 914 } 915 916 /** 917 * Sets whether an individual list value is selected. 918 * 919 * @param value the value of the item to be selected or unselected 920 * @param selected <code>true</code> to select the item 921 */ 922 public void setValueSelected(T value, boolean selected) { 923 int idx = getIndex(value); 924 if (idx >= 0) { 925 setItemSelectedInternal(idx, selected); 926 } 927 } 928 929 /** 930 * Gets the index of the specified value. 931 * 932 * @param value the value of the item to be found 933 * @return the index of the value 934 */ 935 public int getIndex(T value) { 936 int count = getItemCount(); 937 for (int i = 0; i < count; i++) { 938 if (getValueInternal(i).equals(value)) { 939 return i; 940 } 941 } 942 return -1; 943 } 944 945 public ReadOnlyMixin<MaterialListValueBox<T>, ListBox> getReadOnlyMixin() { 946 if (readOnlyMixin == null) { 947 readOnlyMixin = new ReadOnlyMixin<>(this, listBox); 948 } 949 return readOnlyMixin; 950 } 951 952 protected ToggleStyleMixin<ListBox> getToggleOldMixin() { 953 if (toggleOldMixin == null) { 954 toggleOldMixin = new ToggleStyleMixin<>(listBox, "browser-default"); 955 } 956 return toggleOldMixin; 957 } 958 959 /** 960 * Checks whether {@link #emptyPlaceHolder} is added/present in both {@link #listBox} and {@link #values} at 0 index. 961 * 962 * @return is {@link #emptyPlaceHolder} added/present in both {@link #listBox} and {@link #values}? 963 */ 964 protected boolean isEmptyPlaceHolderListed() { 965 return emptyPlaceHolder.equals(listBox.getValue(0)) && 966 values.get(0) == null; 967 } 968 969 protected void insertEmptyPlaceHolder(String emptyPlaceHolder) { 970 listBox.insertItem(emptyPlaceHolder, 0); 971 values.add(0, null); 972 getOptionElement(0).setDisabled(true); 973 } 974 975 protected void removeEmptyPlaceHolder() { 976 // indeed the first item/value is emptyPlaceHolder 977 listBox.removeItem(0); 978 values.remove(0); 979 980 OptionElement currentPlaceholder = getOptionElement(0); 981 if (currentPlaceholder != null) { 982 currentPlaceholder.setDisabled(false); 983 } 984 } 985 986 /** 987 * @return index increased by number of special items/values at the start (e.g. {@link #emptyPlaceHolder}) 988 */ 989 protected int getIndexOffset() { 990 return emptyPlaceHolder != null && isEmptyPlaceHolderListed() ? 1 : 0; 991 } 992}