Skip to content

Commit afb3ef1

Browse files
committed
fix: keep model and presentation values in sync
Close #25 Close #27
1 parent a8a010f commit afb3ef1

File tree

1 file changed

+49
-43
lines changed

1 file changed

+49
-43
lines changed

src/main/java/com/flowingcode/vaadin/addons/chipfield/ChipField.java

Lines changed: 49 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,7 @@
2121

2222
import java.util.ArrayList;
2323
import java.util.Arrays;
24-
import java.util.HashMap;
2524
import java.util.List;
26-
import java.util.Map;
2725
import java.util.Optional;
2826
import java.util.concurrent.atomic.AtomicInteger;
2927
import java.util.stream.Stream;
@@ -38,7 +36,6 @@
3836
import com.vaadin.flow.component.HasStyle;
3937
import com.vaadin.flow.component.ItemLabelGenerator;
4038
import com.vaadin.flow.component.Tag;
41-
import com.vaadin.flow.component.UI;
4239
import com.vaadin.flow.component.dependency.JavaScript;
4340
import com.vaadin.flow.component.dependency.NpmPackage;
4441
import 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

Comments
 (0)