2121
2222import java .util .ArrayList ;
2323import java .util .Arrays ;
24- import java .util .HashMap ;
2524import java .util .List ;
26- import java .util .Map ;
2725import java .util .Optional ;
2826import java .util .concurrent .atomic .AtomicInteger ;
2927import java .util .stream .Stream ;
3836import com .vaadin .flow .component .HasStyle ;
3937import com .vaadin .flow .component .ItemLabelGenerator ;
4038import com .vaadin .flow .component .Tag ;
41- import com .vaadin .flow .component .UI ;
4239import com .vaadin .flow .component .dependency .JavaScript ;
4340import com .vaadin .flow .component .dependency .NpmPackage ;
4441import com .vaadin .flow .data .binder .HasDataProvider ;
@@ -71,7 +68,6 @@ public class ChipField<T> extends AbstractField<ChipField<T>, List<T>>
7168 public static final String CHIP_LABEL = "event.detail.chipLabel" ;
7269
7370 private DataProvider <T , ?> availableItems = DataProvider .ofCollection (new ArrayList <T >());
74- private final Map <String , T > selectedItems = new HashMap <>();
7571 private ItemLabelGenerator <T > itemLabelGenerator ;
7672 private SerializableFunction <String , T > newItemHandler ;
7773
@@ -93,30 +89,24 @@ private void configure() {
9389 getElement ().addEventListener ("chip-created" , e -> {
9490 JsonObject eventData = e .getEventData ();
9591 String chipLabel = eventData .get (CHIP_LABEL ).asString ();
96- Stream <T > streamItems = availableItems .fetch (new Query <>());
97- Optional <T > newItem = streamItems .filter (item -> itemLabelGenerator .apply (item ).equals (chipLabel )).findFirst ();
98- if (newItem .isPresent ()) {
99- selectedItems .put (chipLabel , newItem .get ());
100- setValue (new ArrayList <>(selectedItems .values ()));
101- } else {
92+ T newItem = findItemByLabel (chipLabel ).orElseGet (() -> {
10293 if (isAllowAdditionalItems ()) {
10394 if (newItemHandler == null ) {
10495 throw new IllegalStateException ("You need to setup a NewItemHandler" );
10596 }
106- T item = this .newItemHandler .apply (chipLabel );
107- selectedItems .put (chipLabel , item );
108- setValue (new ArrayList <>(selectedItems .values ()));
97+ return this .newItemHandler .apply (chipLabel );
10998 } else {
11099 throw new IllegalStateException (
111100 "Adding new items is not allowed, but still receiving new items (not present in DataProvider) from client-side. Probably wrong configuration." );
112101 }
113- }
102+ });
103+ addSelectedItem (newItem , true );
114104 }).addEventData (CHIP_LABEL );
105+
115106 getElement ().addEventListener ("chip-removed" , e -> {
116107 JsonObject eventData = e .getEventData ();
117108 String chipLabel = eventData .get (CHIP_LABEL ).asString ();
118- T itemToRemove = selectedItems .remove (chipLabel );
119- getValue ().remove (itemToRemove );
109+ findItemByLabel (chipLabel ).ifPresent (item -> removeSelectedItem (item , true ));
120110 }).addEventData (CHIP_LABEL );
121111 getElement ().addEventListener ("chip-clicked" , e -> {
122112 }).addEventData (CHIP_LABEL );
@@ -140,20 +130,21 @@ protected void onAttach(AttachEvent attachEvent) {
140130 configure ();
141131 }
142132
143- private void appendClientChipWithoutEvent (String label ) {
144- String function = "(function _appendChipWithoutEvent() {" + "if ($0.allowDuplicates) {"
145- + "$0.push('items', $1);" + "} else if ($0.items.indexOf($1) == -1) {"
146- + "$0.push('items', $1);}" + "$0.required = false;"
147- + "$0.autoValidate = false;" + "$0._value = '';" + "})()" ;
148- UI .getCurrent ().getPage ().executeJs (function , getElement (), label );
133+ @ Override
134+ protected void setPresentationValue (List <T > newPresentationValue ) {
135+ setClientChipWithoutEvent (newPresentationValue .stream ().map (itemLabelGenerator ).toArray (String []::new ));
136+ }
137+
138+ private Optional <T > findItemByLabel (String label ) {
139+ return availableItems .fetch (new Query <>()).filter (item -> itemLabelGenerator .apply (item ).equals (label )).findFirst ();
149140 }
150141
151- private void removeClientChipWithoutEvent (String label ) {
152- String function = "(function _removeChipByLabel() {"
153- + "const index = $0.items.indexOf($1);" + "if (index != -1 ) {"
154- + "$0.items.splice('availableItems ', index, 1);}"
155- + "})()" ;
156- UI . getCurrent ().getPage (). executeJs (function , getElement (), label );
142+ private void setClientChipWithoutEvent (String [] labels ) {
143+ getElement (). executeJs ( "this.splice('items', 0, this.items.length);" );
144+ for ( String label : labels ) {
145+ getElement (). executeJs ( "this.push('items ', $0);" , label );
146+ }
147+ getElement ().executeJs ("this.required = false; this.autoValidate = false; this._value = '';" );
157148 }
158149
159150 public void setAvailableItems (List <T > items ) {
@@ -290,32 +281,47 @@ public void setNewItemHandler(SerializableFunction<String, T> handler) {
290281 this .setAllowAdditionalItems (true );
291282 }
292283
293- @ Override
294- protected void setPresentationValue (List <T > newPresentationValue ) {
295- }
296-
297284 @ Override
298285 public void setDataProvider (DataProvider <T , ?> dataProvider ) {
299286 this .availableItems = dataProvider ;
300287 }
301288
302289 public void addSelectedItem (T newItem ) {
303- if ( availableItems . fetch ( new Query <>()). noneMatch ( item -> item . equals (newItem )) && ! isAllowAdditionalItems ()) {
304- throw new UnsupportedOperationException (
305- "Cannot select item '" + newItem + "', because is not present in DataProvider, and adding new items is not permitted." );
290+ String label = itemLabelGenerator . apply (newItem );
291+ if ( isAllowAdditionalItems ()) {
292+ addSelectedItem ( findItemByLabel ( label ). orElse ( newItem ), false );
306293 } else {
307- getValue ().add (newItem );
308- this .selectedItems .put (itemLabelGenerator .apply (newItem ), newItem );
309- this .appendClientChipWithoutEvent (itemLabelGenerator .apply (newItem ));
310- fireEvent (new ChipCreatedEvent <>(this , false , itemLabelGenerator .apply (newItem )));
294+ addSelectedItem (findItemByLabel (label ).orElseThrow (() -> new UnsupportedOperationException (
295+ "Cannot select item '" + newItem + "', because is not present in DataProvider, and adding new items is not permitted." )), false );
296+ }
297+ }
298+
299+ private void addSelectedItem (T newItem , boolean fromClient ) {
300+ List <T > value = getValue ();
301+ if (!value .contains (newItem )) {
302+ value = new ArrayList <>(value );
303+ value .add (newItem );
304+ setModelValue (value , fromClient );
305+ if (!fromClient ) {
306+ setPresentationValue (value );
307+ fireEvent (new ChipCreatedEvent <>(this , fromClient , itemLabelGenerator .apply (newItem )));
308+ }
311309 }
312310 }
313311
314312 public void removeSelectedItem (T itemToRemove ) {
315- getValue ().remove (itemToRemove );
316- this .selectedItems .remove (itemLabelGenerator .apply (itemToRemove ), itemToRemove );
317- this .removeClientChipWithoutEvent (itemLabelGenerator .apply (itemToRemove ));
318- fireEvent (new ChipRemovedEvent <>(this , false , itemLabelGenerator .apply (itemToRemove )));
313+ removeSelectedItem (itemToRemove , false );
314+ }
315+
316+ private void removeSelectedItem (T itemToRemove , boolean fromClient ) {
317+ List <T > value = new ArrayList <>(getValue ());
318+ if (value .remove (itemToRemove )) {
319+ setModelValue (value , fromClient );
320+ if (!fromClient ) {
321+ setPresentationValue (value );
322+ fireEvent (new ChipRemovedEvent <>(this , fromClient , itemLabelGenerator .apply (itemToRemove )));
323+ }
324+ }
319325 }
320326
321327 @ DomEvent ("chip-clicked" )
0 commit comments