001package gwt.material.design.addins.client.autocomplete; 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.dom.client.Document; 024import com.google.gwt.event.dom.client.*; 025import com.google.gwt.event.logical.shared.*; 026import com.google.gwt.event.shared.HandlerRegistration; 027import com.google.gwt.user.client.DOM; 028import com.google.gwt.user.client.ui.*; 029import com.google.gwt.user.client.ui.SuggestOracle.Suggestion; 030import gwt.material.design.addins.client.MaterialAddins; 031import gwt.material.design.addins.client.autocomplete.constants.AutocompleteType; 032import gwt.material.design.client.MaterialDesignBase; 033import gwt.material.design.client.base.*; 034import gwt.material.design.client.base.mixin.CssTypeMixin; 035import gwt.material.design.client.base.mixin.ErrorMixin; 036import gwt.material.design.client.base.mixin.FocusableMixin; 037import gwt.material.design.client.base.mixin.ProgressMixin; 038import gwt.material.design.client.constants.IconType; 039import gwt.material.design.client.constants.ProgressType; 040import gwt.material.design.client.ui.MaterialChip; 041import gwt.material.design.client.ui.MaterialLabel; 042import gwt.material.design.client.ui.MaterialToast; 043import gwt.material.design.client.ui.html.Label; 044import gwt.material.design.client.ui.html.ListItem; 045import gwt.material.design.client.ui.html.UnorderedList; 046 047import java.util.*; 048import java.util.Map.Entry; 049 050//@formatter:off 051 052/** 053 * <p> 054 * Use GWT Autocomplete to search for matches from local or remote data sources. 055 * We used MultiWordSuggestOracle to populate the list to be added on the 056 * autocomplete values. 057 * </p> 058 * 059 * <h3>XML Namespace Declaration</h3> 060 * <pre> 061 * {@code 062 * xmlns:ma='urn:import:gwt.material.design.addins.client' 063 * } 064 * </pre> 065 * 066 * <h3>UiBinder Usage:</h3> 067 * <pre> 068 * {@code 069 * <ma:autocomplete.MaterialAutoComplete ui:field="autocomplete" placeholder="States" />} 070 * </pre> 071 * 072 * <h3>Java Usage:</h3> 073 * 074 * <p> 075 * To use your domain object inside the MaterialAutoComplete, for example, an object 076 * "User", you can subclass the {@link gwt.material.design.addins.client.autocomplete.base.MaterialSuggestionOracle} and {@link Suggestion}, like this: 077 * </p> 078 * <p><pre> 079 * public class UserOracle extends MaterialSuggestionOracle { 080 * private List<User> contacts = new LinkedList<>(); 081 * 082 * public void addContacts(List<User> users){ 083 * contacts.addAll(users); 084 * } 085 * 086 * {@literal @}Override 087 * public void requestSuggestions(final Request request, final Callback callback) { 088 * Response resp = new Response(); 089 * if (contacts.isEmpty()){ 090 * callback.onSuggestionsReady(request, resp); 091 * return; 092 * } 093 * String text = request.getQuery(); 094 * text = text.toLowerCase(); 095 * 096 * List<UserSuggestion> list = new ArrayList<>(); 097 * 098 * /{@literal *} 099 * {@literal *} Finds the contacts that meets the criteria. Note that since the 100 * {@literal *} requestSuggestions method is asynchronous, you can fetch the 101 * {@literal *} results from the server instead of using a local contacts List. 102 * {@literal *}/ 103 * for (User contact : contacts){ 104 * if (contact.getName().toLowerCase().contains(text)){ 105 * list.add(new UserSuggestion(contact)); 106 * } 107 * } 108 * resp.setSuggestions(list); 109 * callback.onSuggestionsReady(request, resp); 110 * } 111 * } 112 * 113 * public class UserSuggestion implements SuggestOracle.Suggestion { 114 * 115 * private User user; 116 * 117 * public UserSuggestion(User user){ 118 * this.user = user; 119 * } 120 * 121 * {@literal @}Override 122 * public String getDisplayString() { 123 * return getReplacementString(); 124 * } 125 * 126 * {@literal @}Override 127 * public String getReplacementString() { 128 * return user.getName(); 129 * } 130 * 131 * public User getUser() { 132 * return user; 133 * } 134 * } 135 * </pre></p> 136 * <p> 137 * And then use the UserOracle like this: 138 * </p> 139 * <p><pre> 140 * //Constructor 141 * MaterialAutoComplete userAutoComplete = new MaterialAutoComplete(new UserOracle()); 142 * 143 * //How to get the selected User objects 144 * public List<User> getSelectedUsers(){ 145 * List<? extends Suggestion> values = userAutoComplete.getValue(); 146 * List<User> users = new ArrayList<>(values.size()); 147 * for (Suggestion value : values) { 148 * if (value instanceof UserSuggestion){ 149 * UserSuggestion us = (UserSuggestion) value; 150 * User user = us.getUser(); 151 * users.add(user); 152 * } 153 * } 154 * return users; 155 * } 156 * </pre></p> 157 * 158 * @author kevzlou7979 159 * @author gilberto-torrezan 160 * @see <a href="http://gwtmaterialdesign.github.io/gwt-material-demo/#!autocomplete">Material AutoComplete</a> 161 */ 162// @formatter:on 163public class MaterialAutoComplete extends MaterialWidget implements HasError, HasPlaceholder, 164 HasValue<List<? extends Suggestion>>, HasProgress, HasKeyUpHandlers, HasType<AutocompleteType>, HasSelectionHandlers<Suggestion> { 165 166 static { 167 if(MaterialAddins.isDebug()) { 168 MaterialDesignBase.injectCss(MaterialAutocompleteDebugClientBundle.INSTANCE.autocompleteCssDebug()); 169 } else { 170 MaterialDesignBase.injectCss(MaterialAutocompleteClientBundle.INSTANCE.autocompleteCss()); 171 } 172 } 173 174 private Map<Suggestion, Widget> suggestionMap = new LinkedHashMap<>(); 175 private Label label = new Label(); 176 177 private List<ListItem> itemsHighlighted = new ArrayList<>(); 178 private FlowPanel panel = new FlowPanel(); 179 private UnorderedList list = new UnorderedList(); 180 private SuggestOracle suggestions; 181 private TextBox itemBox = new TextBox(); 182 private SuggestBox box; 183 private int limit = 0; 184 private MaterialLabel lblError = new MaterialLabel(); 185 private final ProgressMixin<MaterialAutoComplete> progressMixin = new ProgressMixin<>(this); 186 187 private String selectedChipStyle = "blue white-text"; 188 private boolean directInputAllowed = true; 189 private MaterialChipProvider chipProvider = new DefaultMaterialChipProvider(); 190 191 private final ErrorMixin<MaterialAutoComplete, MaterialLabel> errorMixin = new ErrorMixin<>(this, 192 lblError, list); 193 194 private FocusableMixin<MaterialWidget> focusableMixin; 195 196 public final CssTypeMixin<AutocompleteType, MaterialAutoComplete> typeMixin = new CssTypeMixin<>(this); 197 198 /** 199 * Use MaterialAutocomplete to search for matches from local or remote data 200 * sources. 201 */ 202 public MaterialAutoComplete() { 203 super(Document.get().createDivElement(), "autocomplete", "input-field"); 204 add(panel); 205 } 206 207 public MaterialAutoComplete(AutocompleteType type) { 208 this(); 209 setType(type); 210 } 211 212 /** 213 * Use MaterialAutocomplete to search for matches from local or remote data 214 * sources. 215 * 216 * @see #setSuggestions(SuggestOracle) 217 */ 218 public MaterialAutoComplete(SuggestOracle suggestions) { 219 this(); 220 generateAutoComplete(suggestions); 221 } 222 223 /** 224 * Generate and build the List Items to be set on Auto Complete box. 225 */ 226 protected void generateAutoComplete(SuggestOracle suggestions) { 227 list.setStyleName("multiValueSuggestBox-list"); 228 this.suggestions = suggestions; 229 final ListItem item = new ListItem(); 230 231 item.setStyleName("multiValueSuggestBox-input-token"); 232 box = new SuggestBox(suggestions, itemBox); 233 setLimit(this.limit); 234 String autocompleteId = DOM.createUniqueId(); 235 itemBox.getElement().setId(autocompleteId); 236 237 item.add(box); 238 item.add(label); 239 list.add(item); 240 241 list.addDomHandler(new ClickHandler() { 242 @Override 243 public void onClick(ClickEvent event) { 244 box.showSuggestionList(); 245 } 246 }, ClickEvent.getType()); 247 248 itemBox.addBlurHandler(new BlurHandler() { 249 @Override 250 public void onBlur(BlurEvent event) { 251 if(getValue().size() > 0) { 252 label.addStyleName("active"); 253 } 254 } 255 }); 256 257 itemBox.addKeyDownHandler(new KeyDownHandler() { 258 public void onKeyDown(KeyDownEvent event) { 259 boolean itemsChanged = false; 260 261 switch (event.getNativeKeyCode()) { 262 case KeyCodes.KEY_ENTER: 263 if (directInputAllowed) { 264 String value = itemBox.getValue(); 265 if (value != null && !(value = value.trim()).isEmpty()) { 266 gwt.material.design.client.base.Suggestion directInput = new gwt.material.design.client.base.Suggestion(); 267 directInput.setDisplay(value); 268 directInput.setSuggestion(value); 269 itemsChanged = addItem(directInput); 270 itemBox.setValue(""); 271 itemBox.setFocus(true); 272 } 273 } 274 break; 275 276 case KeyCodes.KEY_BACKSPACE: 277 if (itemBox.getValue().trim().isEmpty()) { 278 if (itemsHighlighted.isEmpty()) { 279 if (suggestionMap.size() > 0) { 280 281 ListItem li = (ListItem) list.getWidget(list.getWidgetCount() - 2); 282 MaterialChip p = (MaterialChip) li.getWidget(0); 283 284 boolean removable = true; 285 286 Set<Entry<Suggestion, Widget>> entrySet = suggestionMap.entrySet(); 287 for (Entry<Suggestion, Widget> entry : entrySet) { 288 if (p.equals(entry.getValue())) { 289 if (chipProvider.isChipRemovable(entry.getKey())){ 290 suggestionMap.remove(entry.getKey()); 291 itemsChanged = true; 292 } 293 else { 294 removable = false; 295 } 296 break; 297 } 298 } 299 300 if (removable){ 301 list.remove(li); 302 } 303 } 304 } 305 } 306 307 case KeyCodes.KEY_DELETE: 308 if (itemBox.getValue().trim().isEmpty()) { 309 for (ListItem li : itemsHighlighted) { 310 MaterialChip p = (MaterialChip) li.getWidget(0); 311 312 boolean removable = true; 313 314 Set<Entry<Suggestion, Widget>> entrySet = suggestionMap.entrySet(); 315 for (Entry<Suggestion, Widget> entry : entrySet) { 316 if (p.equals(entry.getValue())) { 317 if (chipProvider.isChipRemovable(entry.getKey())){ 318 suggestionMap.remove(entry.getKey()); 319 itemsChanged = true; 320 } 321 else { 322 removable = false; 323 } 324 break; 325 } 326 } 327 328 if (removable){ 329 li.removeFromParent(); 330 } 331 } 332 itemsHighlighted.clear(); 333 } 334 itemBox.setFocus(true); 335 break; 336 } 337 338 if (itemsChanged) { 339 ValueChangeEvent.fire(MaterialAutoComplete.this, getValue()); 340 } 341 } 342 }); 343 344 itemBox.addClickHandler(new ClickHandler() { 345 @Override 346 public void onClick(ClickEvent event) { 347 box.showSuggestionList(); 348 } 349 }); 350 351 box.addSelectionHandler(new SelectionHandler<Suggestion>() { 352 public void onSelection(SelectionEvent<Suggestion> selectionEvent) { 353 Suggestion selectedItem = selectionEvent.getSelectedItem(); 354 itemBox.setValue(""); 355 if (addItem(selectedItem)) { 356 ValueChangeEvent.fire(MaterialAutoComplete.this, getValue()); 357 } 358 itemBox.setFocus(true); 359 } 360 }); 361 362 panel.add(list); 363 panel.getElement().setAttribute("onclick", 364 "document.getElementById('" + autocompleteId + "').focus()"); 365 panel.add(lblError); 366 box.setFocus(true); 367 } 368 369 /** 370 * Adding the item value using Material Chips added on auto complete box 371 */ 372 protected boolean addItem(final Suggestion suggestion) { 373 SelectionEvent.fire(MaterialAutoComplete.this, suggestion); 374 if (getLimit() > 0) { 375 if (suggestionMap.size() >= getLimit()) { 376 return false; 377 } 378 } 379 380 if (suggestionMap.containsKey(suggestion)) { 381 return false; 382 } 383 384 final ListItem displayItem = new ListItem(); 385 displayItem.setStyleName("multiValueSuggestBox-token"); 386 387 if (getType() == AutocompleteType.TEXT){ 388 suggestionMap.clear(); 389 itemBox.setText(suggestion.getDisplayString()); 390 displayItem.add(itemBox); 391 } 392 else { 393 final MaterialChip chip = chipProvider.getChip(suggestion); 394 if (chip == null) { 395 return false; 396 } 397 398 chip.addClickHandler(new ClickHandler() { 399 public void onClick(ClickEvent clickEvent) { 400 if (chipProvider.isChipSelectable(suggestion)){ 401 if (itemsHighlighted.contains(displayItem)) { 402 chip.removeStyleName(selectedChipStyle); 403 itemsHighlighted.remove(displayItem); 404 } else { 405 chip.addStyleName(selectedChipStyle); 406 itemsHighlighted.add(displayItem); 407 } 408 } 409 } 410 }); 411 412 if (chip.getIcon() != null){ 413 chip.getIcon().addClickHandler(new ClickHandler() { 414 public void onClick(ClickEvent clickEvent) { 415 if (chipProvider.isChipRemovable(suggestion)){ 416 suggestionMap.remove(suggestion); 417 list.remove(displayItem); 418 itemsHighlighted.remove(displayItem); 419 ValueChangeEvent.fire(MaterialAutoComplete.this, getValue()); 420 box.showSuggestionList(); 421 } 422 } 423 }); 424 } 425 426 suggestionMap.put(suggestion, chip); 427 displayItem.add(chip); 428 } 429 430 list.insert(displayItem, list.getWidgetCount() - 1); 431 return true; 432 } 433 434 /** 435 * Clear the chip items on the autocomplete box 436 */ 437 public void clear() { 438 itemBox.setValue(""); 439 440 Collection<Widget> values = suggestionMap.values(); 441 for (Widget widget : values) { 442 Widget parent = widget.getParent(); 443 if (parent instanceof ListItem) { 444 parent.removeFromParent(); 445 } 446 } 447 suggestionMap.clear(); 448 449 clearErrorOrSuccess(); 450 } 451 452 @Override 453 protected FocusableMixin<MaterialWidget> getFocusableMixin() { 454 if(focusableMixin == null) { focusableMixin = new FocusableMixin<>(new MaterialWidget(itemBox.getElement())); } 455 return focusableMixin; 456 } 457 458 /** 459 * @return the item values on autocomplete 460 * @see #getValue() 461 */ 462 public List<String> getItemValues() { 463 Set<Suggestion> keySet = suggestionMap.keySet(); 464 List<String> values = new ArrayList<>(keySet.size()); 465 for (Suggestion suggestion : keySet) { 466 values.add(suggestion.getReplacementString()); 467 } 468 return values; 469 } 470 471 /** 472 * @param itemValues 473 * the itemsSelected to set 474 * @see #setValue(List) 475 */ 476 public void setItemValues(List<String> itemValues) { 477 if (itemValues == null) { 478 clear(); 479 return; 480 } 481 List<Suggestion> list = new ArrayList<>(itemValues.size()); 482 for (String value : itemValues) { 483 Suggestion suggestion = new gwt.material.design.client.base.Suggestion(value, value); 484 list.add(suggestion); 485 } 486 setValue(list); 487 } 488 489 /** 490 * @return the itemsHighlighted 491 */ 492 public List<ListItem> getItemsHighlighted() { 493 return itemsHighlighted; 494 } 495 496 /** 497 * @param itemsHighlighted 498 * the itemsHighlighted to set 499 */ 500 public void setItemsHighlighted(List<ListItem> itemsHighlighted) { 501 this.itemsHighlighted = itemsHighlighted; 502 } 503 504 /** 505 * @return the suggestion oracle 506 */ 507 public SuggestOracle getSuggestions() { 508 return suggestions; 509 } 510 511 /** 512 * Sets the SuggestOracle to be used to provide suggestions. Also setups the 513 * component with the needed event handlers and UI elements. 514 * 515 * @param suggestions 516 * the suggestion oracle to set 517 */ 518 public void setSuggestions(SuggestOracle suggestions) { 519 this.suggestions = suggestions; 520 generateAutoComplete(suggestions); 521 } 522 523 public void setSuggestions(SuggestOracle suggestions, AutocompleteType type) { 524 setType(type); 525 setSuggestions(suggestions); 526 } 527 528 public int getLimit() { 529 return limit; 530 } 531 532 public void setLimit(int limit) { 533 this.limit = limit; 534 if (this.box != null) { 535 this.box.setLimit(limit); 536 } 537 } 538 539 @Override 540 public String getPlaceholder() { 541 return label.getText(); 542 } 543 544 @Override 545 public void setPlaceholder(String placeholder) { 546 label.setText(placeholder); 547 } 548 549 @Override 550 public void setError(String error) { 551 errorMixin.setError(error); 552 } 553 554 @Override 555 public void setSuccess(String success) { 556 errorMixin.setSuccess(success); 557 } 558 559 @Override 560 public void setHelperText(String helperText) { 561 errorMixin.setHelperText(helperText); 562 } 563 564 @Override 565 public void clearErrorOrSuccess() { 566 errorMixin.clearErrorOrSuccess(); 567 } 568 569 /** 570 * Gets the current {@link MaterialChipProvider}. By default, the class uses 571 * an instance of {@link DefaultMaterialChipProvider}. 572 */ 573 public MaterialChipProvider getChipProvider() { 574 return chipProvider; 575 } 576 577 /** 578 * Sets a {@link MaterialChipProvider} that can customize how the 579 * {@link MaterialChip} is created for each selected {@link Suggestion}. 580 */ 581 public void setChipProvider(MaterialChipProvider chipProvider) { 582 this.chipProvider = chipProvider; 583 } 584 585 /** 586 * When set to <code>false</code>, only {@link Suggestion}s from the 587 * SuggestionOracle are accepted. Direct input create by the user is 588 * ignored. By default, direct input is allowed. 589 */ 590 public void setDirectInputAllowed(boolean directInputAllowed) { 591 this.directInputAllowed = directInputAllowed; 592 } 593 594 /** 595 * @return if {@link Suggestion}s created by direct input from the user 596 * should be allowed. By default directInputAllowed is 597 * <code>true</code>. 598 */ 599 public boolean isDirectInputAllowed() { 600 return directInputAllowed; 601 } 602 603 /** 604 * Sets the style class applied to chips when they are selected. 605 * <p> 606 * Defaults to "blue white-text". 607 * </p> 608 * 609 * @param selectedChipStyle 610 * The class or classes to be applied to selected chips 611 */ 612 public void setSelectedChipStyle(String selectedChipStyle) { 613 this.selectedChipStyle = selectedChipStyle; 614 } 615 616 /** 617 * Returns the style class applied to chips when they are selected. 618 * <p> 619 * Defaults to "blue white-text". 620 * </p> 621 */ 622 public String getSelectedChipStyle() { 623 return selectedChipStyle; 624 } 625 626 @Override 627 public void showProgress(ProgressType type) { 628 progressMixin.showProgress(ProgressType.INDETERMINATE); 629 } 630 631 @Override 632 public void setPercent(double percent) { 633 progressMixin.setPercent(percent); 634 } 635 636 @Override 637 public void hideProgress() { 638 progressMixin.hideProgress(); 639 } 640 641 @Override 642 public HandlerRegistration addKeyUpHandler(final KeyUpHandler handler) { 643 return itemBox.addKeyUpHandler(new KeyUpHandler() { 644 @Override 645 public void onKeyUp(KeyUpEvent event) { 646 if(isEnabled()){ 647 handler.onKeyUp(event); 648 } 649 } 650 }); 651 } 652 653 @Override 654 public void setType(AutocompleteType type) { 655 typeMixin.setType(type); 656 } 657 658 @Override 659 public AutocompleteType getType() { 660 return typeMixin.getType(); 661 } 662 663 @Override 664 public HandlerRegistration addSelectionHandler(final SelectionHandler<Suggestion> handler) { 665 return addHandler(new SelectionHandler<Suggestion>() { 666 @Override 667 public void onSelection(SelectionEvent<Suggestion> event) { 668 if(isEnabled()){ 669 handler.onSelection(event); 670 } 671 } 672 }, SelectionEvent.getType()); 673 } 674 675 /** 676 * Interface that defines how a {@link MaterialChip} is created, given a 677 * {@link Suggestion}. 678 * 679 * @see gwt.material.design.addins.client.autocomplete.MaterialAutoComplete#setChipProvider(MaterialChipProvider) 680 */ 681 public static interface MaterialChipProvider { 682 683 /** 684 * Creates and returns a {@link MaterialChip} based on the selected 685 * {@link Suggestion}. 686 * 687 * @param suggestion 688 * the selected {@link Suggestion} 689 * 690 * @return the created MaterialChip, or <code>null</code> if the 691 * suggestion should be ignored. 692 */ 693 MaterialChip getChip(Suggestion suggestion); 694 695 /** 696 * Returns whether the chip defined by the suggestion should be selected when the user clicks on it. 697 * 698 * <p> 699 * Selecion of chips is used to batch remove suggestions, for example. 700 * </p> 701 * 702 * @param suggestion 703 * the selected {@link Suggestion} 704 * 705 * @see MaterialAutoComplete#setSelectedChipStyle(String) 706 */ 707 boolean isChipSelectable(Suggestion suggestion); 708 709 /** 710 * Returns whether the chip defined by the suggestion should be removed from the autocomplete when clicked on its icon. 711 * 712 * <p> 713 * Override this method returning <code>false</code> to implement your own logic when the user clicks on the chip icon. 714 * </p> 715 * 716 * @param suggestion 717 * the selected {@link Suggestion} 718 */ 719 boolean isChipRemovable(Suggestion suggestion); 720 } 721 722 /** 723 * Default implementation of the {@link MaterialChipProvider} interface, 724 * used by the {@link gwt.material.design.addins.client.autocomplete.MaterialAutoComplete}. 725 * 726 * <p> 727 * By default all chips are selectable and removable. The default {@link IconType} used by the chips provided is the {@link IconType#CLOSE}. 728 * </p> 729 * 730 * @see gwt.material.design.addins.client.autocomplete.MaterialAutoComplete#setChipProvider(MaterialChipProvider) 731 */ 732 public static class DefaultMaterialChipProvider implements MaterialChipProvider { 733 734 @Override 735 public MaterialChip getChip(Suggestion suggestion) { 736 final MaterialChip chip = new MaterialChip(); 737 738 String imageChip = suggestion.getDisplayString(); 739 String textChip = imageChip; 740 741 String s = "<img src=\""; 742 if (imageChip.contains(s)) { 743 int ix = imageChip.indexOf(s) + s.length(); 744 imageChip = imageChip.substring(ix, imageChip.indexOf("\"", ix + 1)); 745 chip.setUrl(imageChip); 746 textChip = textChip.replaceAll("[<](/)?img[^>]*[>]", ""); 747 } 748 chip.setText(textChip); 749 chip.setIconType(IconType.CLOSE); 750 751 return chip; 752 } 753 754 @Override 755 public boolean isChipRemovable(Suggestion suggestion) { 756 return true; 757 } 758 759 @Override 760 public boolean isChipSelectable(Suggestion suggestion) { 761 return true; 762 } 763 } 764 765 @Override 766 public HandlerRegistration addValueChangeHandler(final ValueChangeHandler<List<? extends Suggestion>> handler) { 767 return addHandler(new ValueChangeHandler<List<? extends Suggestion>>() { 768 @Override 769 public void onValueChange(ValueChangeEvent<List<? extends Suggestion>> event) { 770 if(isEnabled()){ 771 handler.onValueChange(event); 772 } 773 } 774 }, ValueChangeEvent.getType()); 775 } 776 777 /** 778 * Returns the selected {@link Suggestion}s. Modifications to the list are 779 * not propagated to the component. 780 * 781 * @return the list of selected {@link Suggestion}s, or empty if none was 782 * selected (never <code>null</code>). 783 */ 784 @Override 785 public List<? extends Suggestion> getValue() { 786 return new ArrayList<>(suggestionMap.keySet()); 787 } 788 789 @Override 790 public void setValue(List<? extends Suggestion> value) { 791 setValue(value, false); 792 } 793 794 @Override 795 public void setValue(List<? extends Suggestion> value, boolean fireEvents) { 796 clear(); 797 if (value != null) { 798 for (Suggestion suggestion : value) { 799 addItem(suggestion); 800 } 801 } 802 if (fireEvents) { 803 ValueChangeEvent.fire(this, getValue()); 804 } 805 } 806 807 @Override 808 public void setEnabled(boolean enabled) { 809 super.setEnabled(enabled); 810 itemBox.setEnabled(enabled); 811 } 812}