001package gwt.material.design.client.ui; 002 003/* 004 * #%L 005 * GwtMaterial 006 * %% 007 * Copyright (C) 2015 GwtMaterialDesign 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 com.google.gwt.core.client.GWT; 024import com.google.gwt.dom.client.Document; 025import com.google.gwt.dom.client.Element; 026import com.google.gwt.dom.client.OptionElement; 027import com.google.gwt.dom.client.SelectElement; 028import com.google.gwt.editor.client.EditorError; 029import com.google.gwt.editor.client.HasEditorErrors; 030import com.google.gwt.event.dom.client.BlurEvent; 031import com.google.gwt.event.dom.client.BlurHandler; 032import com.google.gwt.event.dom.client.HasBlurHandlers; 033import com.google.gwt.event.logical.shared.HasValueChangeHandlers; 034import com.google.gwt.event.logical.shared.ValueChangeEvent; 035import com.google.gwt.event.logical.shared.ValueChangeHandler; 036import com.google.gwt.event.shared.HandlerRegistration; 037import com.google.gwt.i18n.client.HasDirection.Direction; 038import com.google.gwt.user.client.ui.FormPanel; 039import com.google.gwt.user.client.ui.HasConstrainedValue; 040import com.google.gwt.user.client.ui.ListBox; 041import gwt.material.design.client.base.*; 042import gwt.material.design.client.base.error.ErrorHandler; 043import gwt.material.design.client.base.error.ErrorHandlerType; 044import gwt.material.design.client.base.error.HasErrorHandler; 045import gwt.material.design.client.base.mixin.BlankValidatorMixin; 046import gwt.material.design.client.base.mixin.ErrorHandlerMixin; 047import gwt.material.design.client.base.mixin.ToggleStyleMixin; 048import gwt.material.design.client.base.validator.HasValidators; 049import gwt.material.design.client.base.validator.ValidationChangedEvent.ValidationChangedHandler; 050import gwt.material.design.client.base.validator.Validator; 051import gwt.material.design.client.ui.html.Label; 052import gwt.material.design.client.ui.html.Option; 053 054import java.util.*; 055 056//@formatter:off 057 058/** 059 * <p>Material ListBox is another dropdown component that will set / get the value depends on the selected index 060 * <h3>UiBinder Usage:</h3> 061 * 062 * <pre> 063 * {@code 064 * <m:MaterialListBox ui:field="lstBox" /> 065 * } 066 * </pre> 067 * <h3>Java Usage:</h3> 068 * 069 * <pre> 070 * {@code 071 * // functions 072 * lstBox.setSelectedIndex(2); 073 * lstBox.getSelectedIndex(); 074 * lstBox.addValueChangeHandler(handler); 075 * } 076 * </pre> 077 * </p> 078 * 079 * @author kevzlou7979 080 * @author Ben Dol 081 * @see <a href="http://gwt-material-demo.herokuapp.com/#forms">Material ListBox</a> 082 */ 083//@formatter:on 084public class MaterialListValueBox<T> extends MaterialWidget implements HasId, HasGrid, HasColors, HasPlaceholder, 085 HasValueChangeHandlers<T>, HasConstrainedValue<T>, HasEditorErrors<T>, HasErrorHandler, HasValidators<T>, 086 HasBlurHandlers { 087 088 private final ListBox listBox = new ListBox(); 089 private final Label lblName = new Label(); 090 091 private boolean initialized; 092 093 protected final List<T> values = new ArrayList<>(); 094 095 private ToggleStyleMixin<ListBox> toggleOldMixin; 096 private final ErrorHandlerMixin<T> errorHandlerMixin = new ErrorHandlerMixin<>(this); 097 private final BlankValidatorMixin<MaterialListValueBox<T>, T> validatorMixin = new BlankValidatorMixin<>(this, 098 errorHandlerMixin.getErrorHandler()); 099 100 public MaterialListValueBox() { 101 super(Document.get().createDivElement(), "input-field"); 102 add(listBox); 103 add(lblName); 104 toggleOldMixin = new ToggleStyleMixin<>(listBox, "browser-default"); 105 } 106 107 @Override 108 public void onLoad() { 109 super.onLoad(); 110 if (!initialized) { 111 initialized = true; 112 createInternalChangeHandler(listBox.getElement()); 113 initializeMaterial(listBox.getElement()); 114 } 115 } 116 117 @Override 118 public void setPlaceholder(String placeholder) { 119 lblName.setText(placeholder); 120 121 if (initialized && placeholder != null) { 122 initializeMaterial(listBox.getElement()); 123 } 124 } 125 126 @Override 127 public String getPlaceholder() { 128 return lblName.getText(); 129 } 130 131 public OptionElement getOptionElement(int index) { 132 return getSelectElement().getOptions().getItem(index); 133 } 134 135 /** 136 * Removes all items from the list box. 137 */ 138 @Override 139 public void clear() { 140 listBox.clear(); 141 if (initialized) { 142 // reinitialize 143 initializeMaterial(listBox.getElement()); 144 } 145 } 146 147 protected SelectElement getSelectElement() { 148 return listBox.getElement().cast(); 149 } 150 151 protected void onChangeInternal() { 152 try { 153 ValueChangeEvent.fire(this, values.get(getSelectedIndex())); 154 } catch (IndexOutOfBoundsException ex) { 155 GWT.log("onChangeInternal threw an exception", ex); 156 } 157 } 158 159 /** 160 * Creates the internal change handler needed to trigger change events for 161 * Materialize CSS change events. 162 */ 163 protected native void createInternalChangeHandler(Element element) /*-{ 164 var that = this; 165 var callback = $entry(function() { 166 that.@gwt.material.design.client.ui.MaterialListValueBox::onChangeInternal()(); 167 }); 168 169 $wnd.jQuery(element).change(callback); 170 }-*/; 171 172 /** 173 * Initializes the Materialize CSS list box. Should be 174 * called every time the contents of the list box 175 * changes, to keep the Materialize CSS design updated. 176 */ 177 protected native void initializeMaterial(Element element) /*-{ 178 $wnd.jQuery(element).material_select(); 179 }-*/; 180 181 /** 182 * Re initialize the material listbox component 183 */ 184 public void reinitialize() { 185 initializeMaterial(getElement()); 186 } 187 188 /** 189 * Sets whether this list allows multiple selections. 190 * 191 * @param multipleSelect <code>true</code> to allow multiple selections 192 */ 193 public void setMultipleSelect(boolean multipleSelect) { 194 listBox.setMultipleSelect(multipleSelect); 195 if (initialized) { 196 initializeMaterial(listBox.getElement()); 197 } 198 } 199 200 /** 201 * Gets whether this list allows multiple selection. 202 * 203 * @return <code>true</code> if multiple selection is allowed 204 */ 205 public boolean isMultipleSelect() { 206 return listBox.isMultipleSelect(); 207 } 208 209 public void setEmptyPlaceHolder(String value) { 210 listBox.insertItem(value, 0); 211 212 getOptionElement(0).setDisabled(true); 213 214 if (initialized) { 215 initializeMaterial(listBox.getElement()); 216 } 217 } 218 219 @Override 220 public HandlerRegistration addValueChangeHandler(final ValueChangeHandler<T> handler) { 221 return addHandler(new ValueChangeHandler<T>() { 222 @Override 223 public void onValueChange(ValueChangeEvent<T> event) { 224 if(isEnabled()){ 225 handler.onValueChange(event); 226 } 227 } 228 }, ValueChangeEvent.getType()); 229 } 230 231 @Override 232 public void setAcceptableValues(Collection<T> values) { 233 this.values.clear(); 234 clear(); 235 236 for(T value : values) { 237 addValue(value); 238 } 239 } 240 241 @Override 242 public T getValue() { 243 return values.get(getSelectedIndex()); 244 } 245 246 @Override 247 public void setValue(T value) { 248 setValue(value, true); 249 } 250 251 @Override 252 public void setValue(T value, boolean fireEvents) { 253 int index = getIndex(value.toString()); 254 if(index > 0 && values.contains(value)) { 255 T before = getValue(); 256 setSelectedIndex(index); 257 258 if (fireEvents) { 259 ValueChangeEvent.fireIfNotEqual(this, before, value); 260 } 261 } 262 } 263 264 public Option addValue(T value) { 265 if(!values.contains(value)) { 266 values.add(value); 267 Option opt = new Option(value.toString()); 268 add(opt); 269 return opt; 270 } else { 271 GWT.log("Cannot add duplicate value: " + value); 272 } 273 return null; 274 } 275 276 public boolean isOld() { 277 return toggleOldMixin.isOn(); 278 } 279 280 public void setOld(boolean old) { 281 toggleOldMixin.setOn(old); 282 } 283 284 // delegate methods 285 286 /** 287 * Inserts an item into the list box, specifying its direction and an 288 * initial value for the item. If the index is less than zero, or greater 289 * than or equal to the length of the list, then the item will be appended 290 * to the end of the list. 291 * 292 * @param item 293 * the text of the item to be inserted 294 * @param dir 295 * the item's direction. If {@code null}, the item is displayed 296 * in the widget's overall direction, or, if a direction 297 * estimator has been set, in the item's estimated direction. 298 * @param value 299 * the item's value, to be submitted if it is part of a 300 * {@link FormPanel}. 301 * @param index 302 * the index at which to insert it 303 */ 304 public void insertItem(String item, Direction dir, String value, int index) { 305 listBox.insertItem(item, dir, value, index); 306 if (initialized) { 307 // reinitialize 308 initializeMaterial(listBox.getElement()); 309 } 310 } 311 312 /** 313 * Sets the value associated with the item at a given index. This value can 314 * be used for any purpose, but is also what is passed to the server when 315 * the list box is submitted as part of a {@link FormPanel}. 316 * 317 * @param index 318 * the index of the item to be set 319 * @param value 320 * the item's new value; cannot be <code>null</code> 321 * @throws IndexOutOfBoundsException 322 * if the index is out of range 323 */ 324 public void setValue(int index, String value) { 325 listBox.setValue(index, value); 326 if (initialized) { 327 // reinitialize 328 initializeMaterial(listBox.getElement()); 329 } 330 } 331 332 @Override 333 public void setTitle(String title) { 334 listBox.setTitle(title); 335 if (initialized) { 336 // reinitialize 337 initializeMaterial(listBox.getElement()); 338 } 339 } 340 341 /** 342 * Adds an item to the list box, specifying its direction. This method has 343 * the same effect as 344 * 345 * <pre> 346 * addItem(item, dir, item) 347 * </pre> 348 * 349 * @param item 350 * the text of the item to be added 351 * @param dir 352 * the item's direction 353 */ 354 public void addItem(String item, Direction dir) { 355 listBox.addItem(item, dir); 356 if (initialized) { 357 // reinitialize 358 initializeMaterial(listBox.getElement()); 359 } 360 } 361 362 /** 363 * Adds an item to the list box. This method has the same effect as 364 * 365 * <pre> 366 * addItem(item, item) 367 * </pre> 368 * 369 * @param item 370 * the text of the item to be added 371 */ 372 public void addItem(String item) { 373 listBox.addItem(item); 374 if (initialized) { 375 // reinitialize 376 initializeMaterial(listBox.getElement()); 377 } 378 } 379 380 /** 381 * Adds an item to the list box, specifying an initial value for the item. 382 * 383 * @param item 384 * the text of the item to be added 385 * @param value 386 * the item's value, to be submitted if it is part of a 387 * {@link FormPanel}; cannot be <code>null</code> 388 */ 389 public void addItem(String item, String value) { 390 listBox.addItem(item, value); 391 if (initialized) { 392 // reinitialize 393 initializeMaterial(listBox.getElement()); 394 } 395 } 396 397 /** 398 * Adds an item to the list box, specifying its direction and an initial 399 * value for the item. 400 * 401 * @param item 402 * the text of the item to be added 403 * @param dir 404 * the item's direction 405 * @param value 406 * the item's value, to be submitted if it is part of a 407 * {@link FormPanel}; cannot be <code>null</code> 408 */ 409 public void addItem(String item, Direction dir, String value) { 410 listBox.addItem(item, dir, value); 411 if (initialized) { 412 // reinitialize 413 initializeMaterial(listBox.getElement()); 414 } 415 } 416 417 /** 418 * Inserts an item into the list box. Has the same effect as 419 * 420 * <pre> 421 * insertItem(item, item, index) 422 * </pre> 423 * 424 * @param item 425 * the text of the item to be inserted 426 * @param index 427 * the index at which to insert it 428 */ 429 public void insertItem(String item, int index) { 430 listBox.insertItem(item, index); 431 if (initialized) { 432 // reinitialize 433 initializeMaterial(listBox.getElement()); 434 } 435 } 436 437 /** 438 * Inserts an item into the list box, specifying its direction. Has the same 439 * effect as 440 * 441 * <pre> 442 * insertItem(item, dir, item, index) 443 * </pre> 444 * 445 * @param item 446 * the text of the item to be inserted 447 * @param dir 448 * the item's direction 449 * @param index 450 * the index at which to insert it 451 */ 452 public void insertItem(String item, Direction dir, int index) { 453 listBox.insertItem(item, dir, index); 454 if (initialized) { 455 // reinitialize 456 initializeMaterial(listBox.getElement()); 457 } 458 } 459 460 /** 461 * Inserts an item into the list box, specifying an initial value for the 462 * item. Has the same effect as 463 * 464 * <pre> 465 * insertItem(item, null, value, index) 466 * </pre> 467 * 468 * @param item 469 * the text of the item to be inserted 470 * @param value 471 * the item's value, to be submitted if it is part of a 472 * {@link FormPanel}. 473 * @param index 474 * the index at which to insert it 475 */ 476 public void insertItem(String item, String value, int index) { 477 listBox.insertItem(item, value, index); 478 if (initialized) { 479 // reinitialize 480 initializeMaterial(listBox.getElement()); 481 } 482 } 483 484 /** 485 * Sets whether an individual list item is selected. 486 * 487 * @param index 488 * the index of the item to be selected or unselected 489 * @param selected 490 * <code>true</code> to select the item 491 * @throws IndexOutOfBoundsException 492 * if the index is out of range 493 */ 494 public void setItemSelected(int index, boolean selected) { 495 listBox.setItemSelected(index, selected); 496 if (initialized) { 497 // reinitialize 498 initializeMaterial(listBox.getElement()); 499 } 500 } 501 502 /** 503 * Sets the text associated with the item at a given index. 504 * 505 * @param index 506 * the index of the item to be set 507 * @param text 508 * the item's new text 509 * @throws IndexOutOfBoundsException 510 * if the index is out of range 511 */ 512 public void setItemText(int index, String text) { 513 listBox.setItemText(index, text); 514 if (initialized) { 515 // reinitialize 516 initializeMaterial(listBox.getElement()); 517 } 518 } 519 520 /** 521 * Sets the text associated with the item at a given index. 522 * 523 * @param index 524 * the index of the item to be set 525 * @param text 526 * the item's new text 527 * @param dir 528 * the item's direction. 529 * @throws IndexOutOfBoundsException 530 * if the index is out of range 531 */ 532 public void setItemText(int index, String text, Direction dir) { 533 listBox.setItemText(index, text, dir); 534 if (initialized) { 535 // reinitialize 536 initializeMaterial(listBox.getElement()); 537 } 538 } 539 540 public void setName(String name) { 541 listBox.setName(name); 542 if (initialized) { 543 // reinitialize 544 initializeMaterial(listBox.getElement()); 545 } 546 } 547 548 /** 549 * Sets the currently selected index. 550 * 551 * After calling this method, only the specified item in the list will 552 * remain selected. For a ListBox with multiple selection enabled, see 553 * {@link #setItemSelected(int, boolean)} to select multiple items at a 554 * time. 555 * 556 * @param index 557 * the index of the item to be selected 558 */ 559 public void setSelectedIndex(int index) { 560 listBox.setSelectedIndex(index); 561 if (initialized) { 562 // reinitialize 563 initializeMaterial(listBox.getElement()); 564 } 565 } 566 567 /** 568 * Sets the number of items that are visible. If only one item is visible, 569 * then the box will be displayed as a drop-down list. 570 * 571 * @param visibleItems 572 * the visible item count 573 */ 574 public void setVisibleItemCount(int visibleItems) { 575 listBox.setVisibleItemCount(visibleItems); 576 if (initialized) { 577 // reinitialize 578 initializeMaterial(listBox.getElement()); 579 } 580 } 581 582 /** 583 * Gets the number of items present in the list box. 584 * 585 * @return the number of items 586 */ 587 public int getItemCount() { 588 return listBox.getItemCount(); 589 } 590 591 /** 592 * Gets the text associated with the item at the specified index. 593 * 594 * @param index 595 * the index of the item whose text is to be retrieved 596 * @return the text associated with the item 597 * @throws IndexOutOfBoundsException 598 * if the index is out of range 599 */ 600 public String getItemText(int index) { 601 return listBox.getItemText(index); 602 } 603 604 /** 605 * Gets the text for currently selected item. If multiple items are 606 * selected, this method will return the text of the first selected item. 607 * 608 * @return the text for selected item, or {@code null} if none is selected 609 */ 610 public String getSelectedItemText() { 611 return listBox.getSelectedItemText(); 612 } 613 614 public String getName() { 615 return listBox.getName(); 616 } 617 618 /** 619 * Gets the currently-selected item. If multiple items are selected, this 620 * method will return the first selected item ({@link #isItemSelected(int)} 621 * can be used to query individual items). 622 * 623 * @return the selected index, or <code>-1</code> if none is selected 624 */ 625 public int getSelectedIndex() { 626 return listBox.getSelectedIndex(); 627 } 628 629 /** 630 * Gets the value associated with the item at a given index. 631 * 632 * @param index 633 * the index of the item to be retrieved 634 * @return the item's associated value 635 * @throws IndexOutOfBoundsException 636 * if the index is out of range 637 */ 638 public String getValue(int index) { 639 return listBox.getValue(index); 640 } 641 642 /** 643 * Gets the value for currently selected item. If multiple items are 644 * selected, this method will return the value of the first selected item. 645 * 646 * @return the value for selected item, or {@code null} if none is selected 647 */ 648 public String getSelectedValue() { 649 return listBox.getSelectedValue(); 650 } 651 652 /** 653 * Gets the number of items that are visible. If only one item is visible, 654 * then the box will be displayed as a drop-down list. 655 * 656 * @return the visible item count 657 */ 658 public int getVisibleItemCount() { 659 return listBox.getVisibleItemCount(); 660 } 661 662 /** 663 * Determines whether an individual list item is selected. 664 * 665 * @param index 666 * the index of the item to be tested 667 * @return <code>true</code> if the item is selected 668 * @throws IndexOutOfBoundsException 669 * if the index is out of range 670 */ 671 public boolean isItemSelected(int index) { 672 return listBox.isItemSelected(index); 673 } 674 675 /** 676 * Removes the item at the specified index. 677 * 678 * @param index 679 * the index of the item to be removed 680 * @throws IndexOutOfBoundsException 681 * if the index is out of range 682 */ 683 public void removeItem(int index) { 684 listBox.removeItem(index); 685 if (initialized) { 686 initializeMaterial(listBox.getElement()); 687 } 688 } 689 690 // utility methods 691 692 /** 693 * Returns all selected values of the list box, or empty array if none. 694 * 695 * @return the selected values of the list box 696 */ 697 public String[] getItemsSelected() { 698 List<String> selected = new LinkedList<>(); 699 for (int i = 0; i < listBox.getItemCount(); i++) { 700 if (listBox.isItemSelected(i)) { 701 selected.add(listBox.getValue(i)); 702 } 703 } 704 return selected.toArray(new String[selected.size()]); 705 } 706 707 /** 708 * Sets the currently selected value. 709 * 710 * After calling this method, only the specified item in the list will 711 * remain selected. For a ListBox with multiple selection enabled, see 712 * {@link #setValueSelected(String, boolean)} to select multiple items at a 713 * time. 714 * 715 * @param value 716 * the value of the item to be selected 717 */ 718 public void setSelectedValue(String value) { 719 int idx = getIndex(value); 720 if (idx >= 0) { 721 setSelectedIndex(idx); 722 } 723 } 724 725 /** 726 * Gets the index of the specified value. 727 * 728 * @param value 729 * the value of the item to be found 730 * @return the index of the value 731 */ 732 public int getIndex(String value) { 733 int count = getItemCount(); 734 for (int i = 0; i < count; i++) { 735 String v = getValue(i); 736 if (v.equals(value)) { 737 return i; 738 } 739 } 740 return -1; 741 } 742 743 /** 744 * Sets whether an individual list value is selected. 745 * 746 * @param value 747 * the value of the item to be selected or unselected 748 * @param selected 749 * <code>true</code> to select the item 750 */ 751 public void setValueSelected(String value, boolean selected) { 752 int idx = getIndex(value); 753 if (idx >= 0) { 754 setItemSelected(idx, selected); 755 } 756 } 757 758 /** 759 * Removes a value from the list box. Nothing is done if the value isn't on 760 * the list box. 761 * 762 * @param value 763 * the value to be removed from the list 764 */ 765 public void removeValue(String value) { 766 int idx = getIndex(value); 767 if (idx >= 0) { 768 removeItem(idx); 769 } 770 } 771 772 @Override 773 public void setEnabled(boolean enabled) { 774 listBox.setEnabled(enabled); 775 if (initialized) { 776 // reinitialize 777 initializeMaterial(listBox.getElement()); 778 } 779 } 780 781 @Override 782 public void showErrors(List<EditorError> errors) { 783 errorHandlerMixin.showErrors(errors); 784 } 785 786 @Override 787 public ErrorHandler getErrorHandler() { 788 return errorHandlerMixin.getErrorHandler(); 789 } 790 791 @Override 792 public void setErrorHandler(ErrorHandler errorHandler) { 793 errorHandlerMixin.setErrorHandler(errorHandler); 794 } 795 796 @Override 797 public ErrorHandlerType getErrorHandlerType() { 798 return errorHandlerMixin.getErrorHandlerType(); 799 } 800 801 @Override 802 public void setErrorHandlerType(ErrorHandlerType errorHandlerType) { 803 errorHandlerMixin.setErrorHandlerType(errorHandlerType); 804 } 805 806 @Override 807 public void addValidator(Validator<T> validator) { 808 validatorMixin.addValidator(validator); 809 } 810 811 @Override 812 public boolean isValidateOnBlur() { 813 return validatorMixin.isValidateOnBlur(); 814 } 815 816 @Override 817 public boolean removeValidator(Validator<T> validator) { 818 return validatorMixin.removeValidator(validator); 819 } 820 821 @Override 822 public void reset() { 823 validatorMixin.reset(); 824 } 825 826 @Override 827 public void setValidateOnBlur(boolean validateOnBlur) { 828 validatorMixin.setValidateOnBlur(validateOnBlur); 829 } 830 831 @Override 832 public void setValidators(@SuppressWarnings("unchecked") Validator<T>... validators) { 833 validatorMixin.setValidators(validators); 834 } 835 836 @Override 837 public boolean validate() { 838 return validatorMixin.validate(); 839 } 840 841 @Override 842 public boolean validate(boolean show) { 843 return validatorMixin.validate(show); 844 } 845 846 @Override 847 public com.google.web.bindery.event.shared.HandlerRegistration addValidationChangedHandler(ValidationChangedHandler handler) { 848 return validatorMixin.addValidationChangedHandler(handler); 849 } 850 851 @Override 852 public HandlerRegistration addBlurHandler(final BlurHandler handler) { 853 return addDomHandler(new BlurHandler() { 854 @Override 855 public void onBlur(BlurEvent event) { 856 if(isEnabled()) { 857 handler.onBlur(event); 858 } 859 } 860 }, BlurEvent.getType()); 861 } 862}