From 42bb0a4a65b3d14f0c88ae4359d3ac30f295640d Mon Sep 17 00:00:00 2001 From: Jean-Christophe Gueriaud Date: Wed, 6 Oct 2021 15:02:26 +0300 Subject: [PATCH 1/4] Add custom filter and create MultiSelect --- lookup-field-flow-demo/pom.xml | 2 +- .../lookupfield/CreateItemView.java | 30 + .../lookupfield/CustomFilterView.java | 34 + .../lookupfield/MainLayout.java | 5 +- .../lookupfield/MultipleView.java | 28 + .../filter/CustomFilterString.java | 44 ++ lookup-field-flow/pom.xml | 7 +- .../lookupfield/AbstractLookupField.java | 656 ++++++++++++++++++ .../lookupfield/LookupField.java | 572 +-------------- .../lookupfield/LookupFieldFilter.java | 17 + .../lookupfield/LookupFieldFilterAction.java | 14 + .../lookupfield/MultiSelectLookupField.java | 201 ++++++ pom.xml | 2 +- 13 files changed, 1071 insertions(+), 541 deletions(-) create mode 100644 lookup-field-flow-demo/src/main/java/com/vaadin/componentfactory/lookupfield/CreateItemView.java create mode 100644 lookup-field-flow-demo/src/main/java/com/vaadin/componentfactory/lookupfield/CustomFilterView.java create mode 100644 lookup-field-flow-demo/src/main/java/com/vaadin/componentfactory/lookupfield/MultipleView.java create mode 100644 lookup-field-flow-demo/src/main/java/com/vaadin/componentfactory/lookupfield/filter/CustomFilterString.java create mode 100644 lookup-field-flow/src/main/java/com/vaadin/componentfactory/lookupfield/AbstractLookupField.java create mode 100644 lookup-field-flow/src/main/java/com/vaadin/componentfactory/lookupfield/LookupFieldFilter.java create mode 100644 lookup-field-flow/src/main/java/com/vaadin/componentfactory/lookupfield/LookupFieldFilterAction.java create mode 100644 lookup-field-flow/src/main/java/com/vaadin/componentfactory/lookupfield/MultiSelectLookupField.java diff --git a/lookup-field-flow-demo/pom.xml b/lookup-field-flow-demo/pom.xml index 86ee9b7..8133969 100644 --- a/lookup-field-flow-demo/pom.xml +++ b/lookup-field-flow-demo/pom.xml @@ -18,7 +18,7 @@ - 14.5.3 + 14.7.1 1.8 1.8 UTF-8 diff --git a/lookup-field-flow-demo/src/main/java/com/vaadin/componentfactory/lookupfield/CreateItemView.java b/lookup-field-flow-demo/src/main/java/com/vaadin/componentfactory/lookupfield/CreateItemView.java new file mode 100644 index 0000000..fd223a4 --- /dev/null +++ b/lookup-field-flow-demo/src/main/java/com/vaadin/componentfactory/lookupfield/CreateItemView.java @@ -0,0 +1,30 @@ +package com.vaadin.componentfactory.lookupfield; + +import com.vaadin.componentfactory.theme.EnhancedDialogVariant; +import com.vaadin.flow.component.html.Div; +import com.vaadin.flow.component.notification.Notification; +import com.vaadin.flow.data.provider.DataProvider; +import com.vaadin.flow.router.Route; + +import java.util.Arrays; +import java.util.List; + +/** + * Basic example with setItems + */ +@Route(value = "create", layout = MainLayout.class) +public class CreateItemView extends Div { + + + public CreateItemView() { + LookupField lookupField = new LookupField<>(); + List items = Arrays.asList("item1","item2", "item3"); + lookupField.setDataProvider(DataProvider.ofCollection(items)); + lookupField.getGrid().addColumn(s -> s).setHeader("item"); + lookupField.setLabel("Item selector"); + lookupField.addThemeVariants(EnhancedDialogVariant.SIZE_MEDIUM); + lookupField.addCreateItemListener(e -> Notification.show("Create item clicked")); + add(lookupField); + } + +} diff --git a/lookup-field-flow-demo/src/main/java/com/vaadin/componentfactory/lookupfield/CustomFilterView.java b/lookup-field-flow-demo/src/main/java/com/vaadin/componentfactory/lookupfield/CustomFilterView.java new file mode 100644 index 0000000..a45aebc --- /dev/null +++ b/lookup-field-flow-demo/src/main/java/com/vaadin/componentfactory/lookupfield/CustomFilterView.java @@ -0,0 +1,34 @@ +package com.vaadin.componentfactory.lookupfield; + +import com.vaadin.componentfactory.lookupfield.filter.CustomFilterString; +import com.vaadin.componentfactory.theme.EnhancedDialogVariant; +import com.vaadin.flow.component.button.Button; +import com.vaadin.flow.component.html.Div; +import com.vaadin.flow.component.notification.Notification; +import com.vaadin.flow.component.orderedlayout.HorizontalLayout; +import com.vaadin.flow.component.textfield.TextField; +import com.vaadin.flow.data.provider.DataProvider; +import com.vaadin.flow.router.Route; + +import java.util.Arrays; +import java.util.List; + +/** + * Basic example with setItems + */ +@Route(value = "custom-filter", layout = MainLayout.class) +public class CustomFilterView extends Div { + + + public CustomFilterView() { + LookupField lookupField = new LookupField<>(); + List items = Arrays.asList("item1","item2", "item3"); + lookupField.setDataProvider(DataProvider.ofCollection(items)); + lookupField.getGrid().addColumn(s -> s).setHeader("item"); + lookupField.setLabel("Item selector"); + lookupField.addThemeVariants(EnhancedDialogVariant.SIZE_MEDIUM); + lookupField.setFilter(new CustomFilterString()); + add(lookupField); + } + +} diff --git a/lookup-field-flow-demo/src/main/java/com/vaadin/componentfactory/lookupfield/MainLayout.java b/lookup-field-flow-demo/src/main/java/com/vaadin/componentfactory/lookupfield/MainLayout.java index bc75965..d56296e 100644 --- a/lookup-field-flow-demo/src/main/java/com/vaadin/componentfactory/lookupfield/MainLayout.java +++ b/lookup-field-flow-demo/src/main/java/com/vaadin/componentfactory/lookupfield/MainLayout.java @@ -16,8 +16,11 @@ public MainLayout() { final RouterLink binderView = new RouterLink("Binder example", BinderView.class); final RouterLink customHeader = new RouterLink("Custom Header", CustomHeaderView.class); final RouterLink enableSelectButtonView = new RouterLink("Selection Button enabled", EnableSelectButtonView.class); + final RouterLink enableMultipleButtonView = new RouterLink("Multiselect", MultipleView.class); + final RouterLink createView = new RouterLink("Create", CreateItemView.class); + final RouterLink customFilterView = new RouterLink("CustomFilter", CustomFilterView.class); final VerticalLayout menuLayout = new VerticalLayout(personLookupField, simple, personLabelLookupField, i18nView, - binderView, customHeader, enableSelectButtonView); + binderView, customHeader, enableSelectButtonView, enableMultipleButtonView, createView, customFilterView); addToDrawer(menuLayout); addToNavbar(drawerToggle); } diff --git a/lookup-field-flow-demo/src/main/java/com/vaadin/componentfactory/lookupfield/MultipleView.java b/lookup-field-flow-demo/src/main/java/com/vaadin/componentfactory/lookupfield/MultipleView.java new file mode 100644 index 0000000..54c7eec --- /dev/null +++ b/lookup-field-flow-demo/src/main/java/com/vaadin/componentfactory/lookupfield/MultipleView.java @@ -0,0 +1,28 @@ +package com.vaadin.componentfactory.lookupfield; + +import com.vaadin.componentfactory.theme.EnhancedDialogVariant; +import com.vaadin.flow.component.html.Div; +import com.vaadin.flow.data.provider.DataProvider; +import com.vaadin.flow.router.Route; + +import java.util.Arrays; +import java.util.List; + +/** + * Basic example with setItems + */ +@Route(value = "multiple", layout = MainLayout.class) +public class MultipleView extends Div { + + + public MultipleView() { + MultiSelectLookupField lookupField = new MultiSelectLookupField<>(); + List items = Arrays.asList("item1","item2", "item3"); + lookupField.setDataProvider(DataProvider.ofCollection(items)); + lookupField.getGrid().addColumn(s -> s).setHeader("item"); + lookupField.setLabel("Item selector"); + lookupField.addThemeVariants(EnhancedDialogVariant.SIZE_MEDIUM); + add(lookupField); + } + +} diff --git a/lookup-field-flow-demo/src/main/java/com/vaadin/componentfactory/lookupfield/filter/CustomFilterString.java b/lookup-field-flow-demo/src/main/java/com/vaadin/componentfactory/lookupfield/filter/CustomFilterString.java new file mode 100644 index 0000000..27b2959 --- /dev/null +++ b/lookup-field-flow-demo/src/main/java/com/vaadin/componentfactory/lookupfield/filter/CustomFilterString.java @@ -0,0 +1,44 @@ +package com.vaadin.componentfactory.lookupfield.filter; + +import com.vaadin.componentfactory.lookupfield.LookupFieldFilter; +import com.vaadin.componentfactory.lookupfield.LookupFieldFilterAction; +import com.vaadin.flow.component.Component; +import com.vaadin.flow.component.button.Button; +import com.vaadin.flow.component.orderedlayout.FlexComponent; +import com.vaadin.flow.component.orderedlayout.HorizontalLayout; +import com.vaadin.flow.component.textfield.TextField; + +/** + * @author jcgueriaud + */ +public class CustomFilterString implements LookupFieldFilter { + + private final HorizontalLayout layout = new HorizontalLayout(); + private final TextField filterField; + + private LookupFieldFilterAction fieldFilterAction; + + public CustomFilterString() { + layout.setDefaultVerticalComponentAlignment(FlexComponent.Alignment.BASELINE); + filterField = new TextField("filter"); + layout.addAndExpand(filterField); + Button filter = new Button("Filter"); + filter.addClickListener(e -> { + if (fieldFilterAction != null) { + fieldFilterAction.filter(filterField.getValue()); + } + }); + layout.add(filter); + } + + @Override + public Component getComponent() { + return layout; + } + + @Override + public void setFilterAction(LookupFieldFilterAction fieldFilterAction) { + this.fieldFilterAction = fieldFilterAction; + } + +} diff --git a/lookup-field-flow/pom.xml b/lookup-field-flow/pom.xml index bed2f9a..a78eaf0 100644 --- a/lookup-field-flow/pom.xml +++ b/lookup-field-flow/pom.xml @@ -19,7 +19,7 @@ - 14.5.3 + 14.7.1 1.8 1.8 UTF-8 @@ -106,6 +106,11 @@ enhanced-dialog 1.0.4 + + org.vaadin.gatanaso + multiselect-combo-box-flow + 3.0.2 + diff --git a/lookup-field-flow/src/main/java/com/vaadin/componentfactory/lookupfield/AbstractLookupField.java b/lookup-field-flow/src/main/java/com/vaadin/componentfactory/lookupfield/AbstractLookupField.java new file mode 100644 index 0000000..0432b33 --- /dev/null +++ b/lookup-field-flow/src/main/java/com/vaadin/componentfactory/lookupfield/AbstractLookupField.java @@ -0,0 +1,656 @@ +package com.vaadin.componentfactory.lookupfield; + +import com.vaadin.componentfactory.EnhancedDialog; +import com.vaadin.componentfactory.theme.EnhancedDialogVariant; +import com.vaadin.flow.component.*; +import com.vaadin.flow.component.button.Button; +import com.vaadin.flow.component.combobox.ComboBox; +import com.vaadin.flow.component.dependency.JsModule; +import com.vaadin.flow.component.dependency.NpmPackage; +import com.vaadin.flow.component.dependency.Uses; +import com.vaadin.flow.component.dialog.Dialog; +import com.vaadin.flow.component.grid.Grid; +import com.vaadin.flow.component.html.Div; +import com.vaadin.flow.component.icon.Icon; +import com.vaadin.flow.component.notification.Notification; +import com.vaadin.flow.component.textfield.TextField; +import com.vaadin.flow.data.binder.HasFilterableDataProvider; +import com.vaadin.flow.data.provider.ConfigurableFilterDataProvider; +import com.vaadin.flow.data.provider.DataProvider; +import com.vaadin.flow.data.provider.ListDataProvider; +import com.vaadin.flow.function.SerializableConsumer; +import com.vaadin.flow.function.SerializableFunction; +import com.vaadin.flow.internal.JsonSerializer; +import com.vaadin.flow.shared.Registration; +import elemental.json.JsonObject; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Objects; +import java.util.function.Consumer; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * Server-side component for the {@code vcf-lookup-field} webcomponent. + * + * The LookupField is a combination of a combobox and a dialog for advanced search. + * + * + * @param the type of the items to be inserted in the combo box + */ +@Uses(value = Icon.class) +@Uses(value = TextField.class) +@Uses(value = Button.class) +@Uses(value = EnhancedDialog.class) +@Tag("vcf-lookup-field") +@JsModule("@vaadin-component-factory/vcf-lookup-field") +@NpmPackage(value = "@vaadin-component-factory/vcf-lookup-field", version = "1.1.2") +public abstract class AbstractLookupField & HasValue, + ComponentT extends AbstractLookupField> extends Div + implements HasFilterableDataProvider, HasValueAndElement, SelectT>, HasValidation, HasSize, HasTheme { + protected static final String FIELD_SLOT_NAME = "field"; + private static final String GRID_SLOT_NAME = "grid"; + private static final String FILTER_SLOT_NAME = "filter"; + private static final String HEADER_SLOT_NAME = "dialog-header"; + private static final String FOOTER_SLOT_NAME = "dialog-footer"; + protected static final String SLOT_KEY = "slot"; + private LookupFieldI18n i18n; + private Grid grid; + protected ComboboxT comboBox; + private ConfigurableFilterDataProvider gridDataProvider; + private LookupFieldFilter filter; + private Component header; + private Component footer; + private Runnable notificationWhenEmptySelection; + + public AbstractLookupField() { + super(); + } + + /** + * Set the grid + * + * @param grid the grid + */ + public void setGrid(Grid grid) { + Objects.requireNonNull(grid, "Grid cannot be null"); + + if (this.grid != null && this.grid.getElement().getParent() == getElement()) { + this.grid.getElement().removeFromParent(); + } + + this.grid = grid; + grid.getElement().setAttribute(SLOT_KEY, GRID_SLOT_NAME); + + // It might already have a parent e.g when injected from a template + if (grid.getElement().getParent() == null) { + getElement().appendChild(grid.getElement()); + } + } + + /** + * Set the comboBox + * + * @param comboBox the comboBox + */ + + public abstract void setComboBox(ComboboxT comboBox); + + /** + * @return the internal field + */ + public abstract ComboboxT getComboBox(); + + /** + *

+ * Filtering will use a case insensitive match to show all items where the + * filter text is a substring of the label displayed for that item, which + * you can configure with + * {@link #setItemLabelGenerator(ItemLabelGenerator)}. + *

+ * + * @param items the data items to display + */ + @Override + public void setItems(Collection items) { + setDataProvider(DataProvider.ofCollection(items)); + } + + /** + * @param itemFilter filter to check if an item is shown when user typed some text + * into the ComboBox + * @param items the data items to display + */ + public void setItems(ComboBox.ItemFilter itemFilter, Collection items) { + ListDataProvider listDataProvider = DataProvider.ofCollection(items); + + setDataProvider(itemFilter, listDataProvider); + } + + public abstract void setDataProvider(ListDataProvider listDataProvider); + + /** + * Sets a list data provider with an item filter as the data provider. + * + * @param itemFilter filter to check if an item is shown when user typed some text + * into the ComboBox + * @param listDataProvider the list data provider to use, not null + */ + public abstract void setDataProvider(ComboBox.ItemFilter itemFilter, + ListDataProvider listDataProvider); + + @Override + public void setDataProvider(DataProvider dataProvider, + SerializableFunction filterConverter) { + Objects.requireNonNull(dataProvider, "data provider cannot be null"); + comboBox.setDataProvider(dataProvider, filterConverter); + gridDataProvider = dataProvider.withConvertedFilter(filterConverter).withConfigurableFilter(); + grid.setDataProvider(gridDataProvider); + } + + /** + * @return the internal grid + */ + public Grid getGrid() { + return grid; + } + + + /** + * Copy the selected value of the grid into the field + */ + protected abstract void copyFieldValueFromGrid(); + + /** + * Copy the selected value of the field into the grid + */ + protected abstract void copyFieldValueToGrid(); + + /** + * Filter the grid + * + * @param filter filter text + */ + @ClientCallable + private void filterGrid(String filter) { + // don't filter the grid if the filter is custom + if (filter != null && this.filter == null) { + gridDataProvider.setFilter(filter); + } + } + + /** + * Sets the item label generator that is used to produce the strings shown + * in the combo box for each item. By default, + * {@link String#valueOf(Object)} is used. + *

+ * + * @param itemLabelGenerator the item label provider to use, not null + */ + public abstract void setItemLabelGenerator(ItemLabelGenerator itemLabelGenerator); + + /** + * Set the width of the grid + * Also set a max width to 100% + * + * @param width the width to set, may be {@code null} + */ + public void setGridWidth(String width) { + grid.setWidth(width); + grid.setMaxWidth("100%"); + } + + /** + * Set the header of the dialog + * + * @param header text for the header of the dialog + */ + public void setHeader(String header) { + getElement().setAttribute("header", header); + } + + /** + * Set the label of the field + * + * @param label label of the field + */ + public abstract void setLabel(String label); + + /** + * Sets whether component will open modal or modeless dialog. + *

+ * Note: When dialog is set to be modeless, then it's up to you to provide + * means for it to be closed (eg. a button that calls {@link Dialog#close()}). + * The reason being that a modeless dialog allows user to interact with the + * interface under it and won't be closed by clicking outside or the ESC key. + * + * @param modal {@code false} to enable dialog to open as modeless modal, + * {@code true} otherwise. + */ + public void setModal(boolean modal) { + getElement().setProperty("modeless", !modal); + } + + /** + * Gets whether component is set as modal or modeless dialog. + * + * @return {@code true} if modal dialog (default), + * {@code false} otherwise. + */ + public boolean isModal() { + return !getElement().getProperty("modeless", false); + } + + /** + * Sets whether dialog is enabled to be dragged by the user or not. + *

+ * To allow an element inside the dialog to be dragged by the user + * (for instance, a header inside the dialog), a class {@code "draggable"} + * can be added to it (see {@link HasStyle#addClassName(String)}). + *

+ * Note: If draggable is enabled and dialog is opened without first + * being explicitly attached to a parent, then it won't restore its + * last position in the case the user closes and opens it again. + * Reason being that a self attached dialog is removed from the DOM + * when it's closed and position is not synched. + * + * @param draggable {@code true} to enable dragging of the dialog, + * {@code false} otherwise + */ + public void setDraggable(boolean draggable) { + getElement().setProperty("draggable", draggable); + } + + /** + * Gets whether dialog is enabled to be dragged or not. + * + * @return {@code true} if dragging is enabled, + * {@code false} otherwise (default). + */ + public boolean isDraggable() { + return getElement().getProperty("draggable", false); + } + + /** + * Sets whether dialog can be resized by user or not. + * + * @param resizable {@code true} to enabled resizing of the dialog, + * {@code false} otherwise. + */ + public void setResizable(boolean resizable) { + getElement().setProperty("resizable", resizable); + } + + /** + * Gets whether dialog is enabled to be resized or not. + * + * @return {@code true} if resizing is enabled, + * {@code false} otherwiser (default). + */ + public boolean isResizable() { + return getElement().getProperty("resizable", false); + } + + /** + * Sets whether the select button is disabled or send an error when the selection is empty or not. + * + * @param defaultselectdisabled {@code true} to disabled the button if no item is disabled, + * {@code false} otherwise. + */ + public void setSelectionDisabledIfEmpty(boolean defaultselectdisabled) { + getElement().setProperty("defaultselectdisabled", defaultselectdisabled); + } + + /** + * Gets whether the select button is disabled or send an error when the selection is empty or not. + * + * @return {@code true} if resizing is enabled, + * {@code false} otherwiser (default). + */ + public boolean getSelectionDisabledIfEmpty() { + return getElement().getProperty("defaultselectdisabled", true); + } + + /** + * Gets the internationalization object previously set for this component. + *

+ * Note: updating the object content that is gotten from this method will + * not update the lang on the component if not set back using + * {@link LookupField#setI18n(LookupFieldI18n)} + * + * @return the i18n object. It will be null, If the i18n + * properties weren't set. + */ + public LookupFieldI18n getI18n() { + return i18n; + } + + /** + * Sets the internationalization properties for this component. + * + * @param i18n the internationalized properties, not null + */ + public void setI18n(LookupFieldI18n i18n) { + Objects.requireNonNull(i18n, + "The I18N properties object should not be null"); + this.i18n = i18n; + setI18nWithJS(); + } + + private void setI18nWithJS() { + runBeforeClientResponse(ui -> { + JsonObject i18nObject = (JsonObject) JsonSerializer.toJson(i18n); + for (String key : i18nObject.keys()) { + getElement().executeJs("this.set('i18n." + key + "', $0)", + i18nObject.get(key)); + } + }); + } + + private void runBeforeClientResponse(SerializableConsumer command) { + getElement().getNode().runWhenAttached(ui -> ui + .beforeClientResponse(this, context -> command.accept(ui))); + } + + + + @Override + public boolean isInvalid() { + return comboBox.isInvalid(); + } + + @Override + public void setInvalid(boolean invalid) { + comboBox.setInvalid(invalid); + } + + @Override + public void setErrorMessage(String errorMessage) { + comboBox.setErrorMessage(errorMessage); + } + + @Override + public String getErrorMessage() { + return comboBox.getErrorMessage(); + } + + /** + * Sets the theme variants of this component. This method overwrites any + * previous set theme variants. + * + * @param variants theme variant + */ + public void setThemeVariants(EnhancedDialogVariant... variants) { + getElement().getThemeList().clear(); + addThemeVariants(variants); + } + + /** + * Adds the theme variants of this component. + * + * @param variants theme variant + */ + public void addThemeVariants(EnhancedDialogVariant... variants) { + getElement().getThemeList().addAll(Stream.of(variants).map(EnhancedDialogVariant::getVariantName).collect(Collectors.toList())); + } + + /** + * Set the header with a custom component + * + * @param header custom header + */ + public void setHeaderComponent(Component header) { + Objects.requireNonNull(header, "Header cannot be null"); + + if (this.header != null && this.header.getElement().getParent() == getElement()) { + this.header.getElement().removeFromParent(); + } + + this.header = header; + header.getElement().setAttribute(SLOT_KEY, HEADER_SLOT_NAME); + + // It might already have a parent e.g when injected from a template + if (header.getElement().getParent() == null) { + getElement().appendChild(header.getElement()); + } + } + + private Registration filterRegistration; + /** + * Set the filter with a custom component + * + * @param filter custom filter + */ + public void setFilter(LookupFieldFilter filter) { + Objects.requireNonNull(filter, "Filter cannot be null"); + Objects.requireNonNull(filter.getComponent(), "Filter component cannot be null"); + + if (this.filter != null && this.filter.getComponent() != null && this.filter.getComponent().getElement().getParent() == getElement()) { + this.filter.getComponent().getElement().removeFromParent(); + } + + this.filter = filter; + filter.getComponent().getElement().setAttribute(SLOT_KEY, FILTER_SLOT_NAME); + filter.setFilterAction(value -> { + ComponentUtil.fireEvent(this, new AbstractLookupField.FilterEvent(this, false, value)); + }); + + if (filterRegistration != null) { + filterRegistration.remove(); + } + + filterRegistration = addFilterListener(e -> { + filterServerGrid(e.getFilterValue()); + }); + + // It might already have a parent e.g when injected from a template + if (filter.getComponent().getElement().getParent() == null) { + getElement().appendChild(filter.getComponent().getElement()); + } + } + + private void filterServerGrid(String filter) { + gridDataProvider.setFilter(filter); + } + + /** + * Set the footer with a custom component + * WARNING: You have to implement your own buttons to select and close the dialog + * + * @param footer Custom footer + */ + public void setFooterComponent(Component footer) { + Objects.requireNonNull(grid, "Footer cannot be null"); + + if (this.footer != null && this.footer.getElement().getParent() == getElement()) { + this.footer.getElement().removeFromParent(); + } + + this.footer = footer; + footer.getElement().setAttribute(SLOT_KEY, FOOTER_SLOT_NAME); + + // It might already have a parent e.g when injected from a template + if (footer.getElement().getParent() == null) { + getElement().appendChild(footer.getElement()); + } + } + + /** + * Select and close the dialog + */ + public void footerSelectAction() { + copyFieldValueFromGrid(); + footerCloseAction(); + } + + /** + * Close the dialog + */ + public void footerCloseAction() { + getElement().executeJs("$0.__close()", getElement()); + } + + /** + * Copy the selected value of the field into the grid + */ + @ClientCallable + private void openErrorNotification() { + getNotificationWhenEmptySelection().run(); + } + + private Runnable getNotificationWhenEmptySelection() { + if (notificationWhenEmptySelection == null) { + return () -> { + String emptySelection = (getI18n() == null) ? "Please select an item." : getI18n().getEmptyselection(); + new Notification(emptySelection, 2000, Notification.Position.TOP_CENTER).open(); + }; + } + return notificationWhenEmptySelection; + } + + /** + * Replace the default notification to an action + * + * @param notificationWhenEmptySelection action to run when the selection is empty and the select button is clicked + */ + public void addEmptySelectionListener(Runnable notificationWhenEmptySelection) { + this.notificationWhenEmptySelection = notificationWhenEmptySelection; + } + + + /** + * Add an action when the user filters + * + * @param listener the listener to add, not null + * @return a handle that can be used for removing the listener + */ + @SuppressWarnings("unchecked") + public Registration addFilterListener(ComponentEventListener> listener) { + return addListener(FilterEvent.class, (ComponentEventListener) listener); + } + + @DomEvent("vcf-lookup-field-filter-event") + public static class FilterEvent extends ComponentEvent { + private final FILTERTYPE filterValue; + public FilterEvent(AbstractLookupField source, boolean fromClient, @EventData("event.detail.value") FILTERTYPE filterValue) { + super(source, fromClient); + this.filterValue = filterValue; + } + + public FILTERTYPE getFilterValue() { + return filterValue; + } + } + + /** + * Add an action when the user click on create item + * + * @param listener the listener to add, not null + * @return a handle that can be used for removing the listener + */ + @SuppressWarnings("unchecked") + public Registration addCreateItemListener(ComponentEventListener listener) { + // show edit button + setCreateVisible(true); + return addListener(CreateItemEvent.class, listener); + } + + public void setCreateVisible(boolean createVisible) { + getElement().setProperty("createhidden", !createVisible); + } + + @DomEvent("vcf-lookup-field-create-item-event") + public static class CreateItemEvent extends ComponentEvent { + public CreateItemEvent(AbstractLookupField source, boolean fromClient) { + super(source, fromClient); + } + } + + /** + * The internationalization properties for {@link LookupField}. + */ + public static class LookupFieldI18n implements Serializable { + private String select; + private String cancel; + private String searcharialabel; + private String headerprefix; + private String headerpostfix; + private String search; + private String emptyselection; + private String create; + + public String getSearch() { + return search; + } + + public LookupFieldI18n setSearch(String search) { + this.search = search; + return this; + } + + public String getSelect() { + return select; + } + + public LookupFieldI18n setSelect(String select) { + this.select = select; + return this; + } + + public String getCancel() { + return cancel; + } + + public LookupFieldI18n setCancel(String cancel) { + this.cancel = cancel; + return this; + } + + public String getSearcharialabel() { + return searcharialabel; + } + + public LookupFieldI18n setSearcharialabel(String searcharialabel) { + this.searcharialabel = searcharialabel; + return this; + } + + public String getHeaderprefix() { + return headerprefix; + } + + public LookupFieldI18n setHeaderprefix(String headerprefix) { + this.headerprefix = headerprefix; + return this; + } + + public String getHeaderpostfix() { + return headerpostfix; + } + + public LookupFieldI18n setHeaderpostfix(String headerpostfix) { + this.headerpostfix = headerpostfix; + return this; + } + + public String getEmptyselection() { + return emptyselection; + } + + public LookupFieldI18n setEmptyselection(String emptyselection) { + this.emptyselection = emptyselection; + return this; + } + + public String getCreate() { + return create; + } + + public LookupFieldI18n setCreate(String create) { + this.create = create; + return this; + } + } +} diff --git a/lookup-field-flow/src/main/java/com/vaadin/componentfactory/lookupfield/LookupField.java b/lookup-field-flow/src/main/java/com/vaadin/componentfactory/lookupfield/LookupField.java index 1f2a82a..182e4b1 100644 --- a/lookup-field-flow/src/main/java/com/vaadin/componentfactory/lookupfield/LookupField.java +++ b/lookup-field-flow/src/main/java/com/vaadin/componentfactory/lookupfield/LookupField.java @@ -20,78 +20,15 @@ * #L% */ -import com.vaadin.componentfactory.EnhancedDialog; -import com.vaadin.componentfactory.theme.EnhancedDialogVariant; -import com.vaadin.flow.component.AbstractField; -import com.vaadin.flow.component.ClientCallable; -import com.vaadin.flow.component.Component; -import com.vaadin.flow.component.HasHelper; -import com.vaadin.flow.component.HasSize; -import com.vaadin.flow.component.HasStyle; -import com.vaadin.flow.component.HasTheme; -import com.vaadin.flow.component.HasValidation; -import com.vaadin.flow.component.HasValueAndElement; -import com.vaadin.flow.component.ItemLabelGenerator; -import com.vaadin.flow.component.Tag; -import com.vaadin.flow.component.UI; -import com.vaadin.flow.component.button.Button; +import com.vaadin.flow.component.*; import com.vaadin.flow.component.combobox.ComboBox; -import com.vaadin.flow.component.dependency.JsModule; -import com.vaadin.flow.component.dependency.NpmPackage; -import com.vaadin.flow.component.dependency.Uses; -import com.vaadin.flow.component.dialog.Dialog; import com.vaadin.flow.component.grid.Grid; -import com.vaadin.flow.component.html.Div; -import com.vaadin.flow.component.icon.Icon; -import com.vaadin.flow.component.notification.Notification; -import com.vaadin.flow.component.textfield.TextField; -import com.vaadin.flow.data.binder.HasFilterableDataProvider; -import com.vaadin.flow.data.provider.ConfigurableFilterDataProvider; -import com.vaadin.flow.data.provider.DataProvider; import com.vaadin.flow.data.provider.ListDataProvider; -import com.vaadin.flow.function.SerializableConsumer; -import com.vaadin.flow.function.SerializableFunction; -import com.vaadin.flow.internal.JsonSerializer; import com.vaadin.flow.shared.Registration; -import elemental.json.JsonObject; -import java.io.Serializable; -import java.util.Collection; import java.util.Objects; -import java.util.stream.Collectors; -import java.util.stream.Stream; -/** - * Server-side component for the {@code vcf-lookup-field} webcomponent. - * - * The LookupField is a combination of a combobox and a dialog for advanced search. - * - * - * @param the type of the items to be inserted in the combo box - */ -@Uses(value = Icon.class) -@Uses(value = TextField.class) -@Uses(value = Button.class) -@Uses(value = EnhancedDialog.class) -@Tag("vcf-lookup-field") -@JsModule("@vaadin-component-factory/vcf-lookup-field") -@NpmPackage(value = "@vaadin-component-factory/vcf-lookup-field", version = "1.1.2") -public class LookupField extends Div implements HasFilterableDataProvider, - HasValueAndElement, T>, T>, HasValidation, HasHelper, HasSize, HasTheme { - - private static final String FIELD_SLOT_NAME = "field"; - private static final String GRID_SLOT_NAME = "grid"; - private static final String HEADER_SLOT_NAME = "dialog-header"; - private static final String FOOTER_SLOT_NAME = "dialog-footer"; - private static final String SLOT_KEY = "slot"; - - private LookupFieldI18n i18n; - private Grid grid; - private ComboBox comboBox; - private ConfigurableFilterDataProvider gridDataProvider; - private Component header; - private Component footer; - private Runnable notificationWhenEmptySelection; +public class LookupField extends AbstractLookupField, LookupField> implements HasHelper { public LookupField() { this(new Grid<>(), new ComboBox<>()); @@ -107,27 +44,23 @@ public LookupField(Grid grid, ComboBox comboBox) { setComboBox(comboBox); } - /** - * Set the grid - * - * @param grid the grid - */ - public void setGrid(Grid grid) { - Objects.requireNonNull(grid, "Grid cannot be null"); - if (this.grid != null && this.grid.getElement().getParent() == getElement()) { - this.grid.getElement().removeFromParent(); - } + @Override + public void setValue(T value) { + comboBox.setValue(value); + } - this.grid = grid; - grid.getElement().setAttribute(SLOT_KEY, GRID_SLOT_NAME); + @Override + public T getValue() { + return comboBox.getValue(); + } - // It might already have a parent e.g when injected from a template - if (grid.getElement().getParent() == null) { - getElement().appendChild(grid.getElement()); - } + @Override + public Registration addValueChangeListener(ValueChangeListener, T>> listener) { + return comboBox.addValueChangeListener((ValueChangeListener) listener); } + /** * Set the comboBox * @@ -152,37 +85,18 @@ public void setComboBox(ComboBox comboBox) { } /** - *

- * Filtering will use a case insensitive match to show all items where the - * filter text is a substring of the label displayed for that item, which - * you can configure with - * {@link #setItemLabelGenerator(ItemLabelGenerator)}. - *

- * @param items the data items to display + * @return the internal field */ - @Override - public void setItems(Collection items) { - setDataProvider(DataProvider.ofCollection(items)); + public ComboBox getComboBox() { + return comboBox; } - /** - * - * @param itemFilter - * filter to check if an item is shown when user typed some text - * into the ComboBox - * @param items - * the data items to display - */ - public void setItems(ComboBox.ItemFilter itemFilter, Collection items) { - ListDataProvider listDataProvider = DataProvider.ofCollection(items); - - setDataProvider(itemFilter, listDataProvider); - } + @Override public void setDataProvider(ListDataProvider listDataProvider) { ComboBox.ItemFilter defaultItemFilter = (item, filterText) -> - comboBox.getItemLabelGenerator().apply(item).toLowerCase(getLocale()) - .contains(filterText.toLowerCase(getLocale())); + comboBox.getItemLabelGenerator().apply(item).toLowerCase(getLocale()) + .contains(filterText.toLowerCase(getLocale())); setDataProvider(defaultItemFilter, listDataProvider); } @@ -190,107 +104,30 @@ public void setDataProvider(ListDataProvider listDataProvider) { /** * Sets a list data provider with an item filter as the data provider. * - * @param itemFilter - * filter to check if an item is shown when user typed some text - * into the ComboBox - * @param listDataProvider - * the list data provider to use, not null + * @param itemFilter filter to check if an item is shown when user typed some text + * into the ComboBox + * @param listDataProvider the list data provider to use, not null */ + @Override public void setDataProvider(ComboBox.ItemFilter itemFilter, ListDataProvider listDataProvider) { Objects.requireNonNull(listDataProvider, - "List data provider cannot be null"); + "List data provider cannot be null"); setDataProvider(listDataProvider, - filterText -> item -> itemFilter.test(item, filterText)); - } - - @Override - public void setDataProvider(DataProvider dataProvider, - SerializableFunction filterConverter) { - Objects.requireNonNull(dataProvider, "data provider cannot be null"); - comboBox.setDataProvider(dataProvider, filterConverter); - gridDataProvider = dataProvider.withConvertedFilter(filterConverter).withConfigurableFilter(); - grid.setDataProvider(gridDataProvider); - } - - /** - * - * @return the internal grid - */ - public Grid getGrid() { - return grid; + filterText -> item -> itemFilter.test(item, filterText)); } - - /** - * - * @return the internal field - */ - public ComboBox getComboBox() { - return comboBox; - } - - /** - * Copy the selected value of the grid into the field - */ - @ClientCallable - private void copyFieldValueFromGrid() { - grid.getSelectedItems().stream().findFirst().ifPresent(comboBox::setValue); - } - - /** - * Copy the selected value of the field into the grid - */ - @ClientCallable - private void copyFieldValueToGrid() { - grid.select(comboBox.getValue()); - } - - /** - * Filter the grid - * - * @param filter filter text - */ - @ClientCallable - private void filterGrid(String filter) { - if (filter != null) { - gridDataProvider.setFilter(filter); - } - } - /** * Sets the item label generator that is used to produce the strings shown * in the combo box for each item. By default, * {@link String#valueOf(Object)} is used. *

* - * @param itemLabelGenerator - * the item label provider to use, not null + * @param itemLabelGenerator the item label provider to use, not null */ public void setItemLabelGenerator(ItemLabelGenerator itemLabelGenerator) { comboBox.setItemLabelGenerator(itemLabelGenerator); } - - /** - * Set the width of the grid - * Also set a max width to 100% - * - * @param width the width to set, may be {@code null} - */ - public void setGridWidth(String width) { - grid.setWidth(width); - grid.setMaxWidth("100%"); - } - - /** - * Set the header of the dialog - * - * @param header text for the header of the dialog - */ - public void setHeader(String header) { - getElement().setAttribute("header", header); - } - /** * Set the label of the field * @@ -300,190 +137,17 @@ public void setLabel(String label) { comboBox.setLabel(label); } - /** - * Sets whether component will open modal or modeless dialog. - *

- * Note: When dialog is set to be modeless, then it's up to you to provide - * means for it to be closed (eg. a button that calls {@link Dialog#close()}). - * The reason being that a modeless dialog allows user to interact with the - * interface under it and won't be closed by clicking outside or the ESC key. - * - * @param modal - * {@code false} to enable dialog to open as modeless modal, - * {@code true} otherwise. - */ - public void setModal(boolean modal) { - getElement().setProperty("modeless", !modal); - } - - /** - * Gets whether component is set as modal or modeless dialog. - * - * @return {@code true} if modal dialog (default), - * {@code false} otherwise. - */ - public boolean isModal() { - return !getElement().getProperty("modeless", false); - } - - - /** - * Sets whether dialog is enabled to be dragged by the user or not. - *

- * To allow an element inside the dialog to be dragged by the user - * (for instance, a header inside the dialog), a class {@code "draggable"} - * can be added to it (see {@link HasStyle#addClassName(String)}). - *

- * Note: If draggable is enabled and dialog is opened without first - * being explicitly attached to a parent, then it won't restore its - * last position in the case the user closes and opens it again. - * Reason being that a self attached dialog is removed from the DOM - * when it's closed and position is not synched. - * - * @param draggable - * {@code true} to enable dragging of the dialog, - * {@code false} otherwise - */ - public void setDraggable(boolean draggable) { - getElement().setProperty("draggable", draggable); - } - - /** - * Gets whether dialog is enabled to be dragged or not. - * - * @return - * {@code true} if dragging is enabled, - * {@code false} otherwise (default). - */ - public boolean isDraggable() { - return getElement().getProperty("draggable", false); - } - - /** - * Sets whether dialog can be resized by user or not. - * - * @param resizable - * {@code true} to enabled resizing of the dialog, - * {@code false} otherwise. - */ - public void setResizable(boolean resizable) { - getElement().setProperty("resizable", resizable); - } - - /** - * Gets whether dialog is enabled to be resized or not. - * - * @return - * {@code true} if resizing is enabled, - * {@code false} otherwiser (default). - */ - public boolean isResizable() { - return getElement().getProperty("resizable", false); - } - - /** - * Sets whether the select button is disabled or send an error when the selection is empty or not. - * - * @param defaultselectdisabled - * {@code true} to disabled the button if no item is disabled, - * {@code false} otherwise. - */ - public void setSelectionDisabledIfEmpty(boolean defaultselectdisabled) { - getElement().setProperty("defaultselectdisabled", defaultselectdisabled); - } - - /** - * Gets whether the select button is disabled or send an error when the selection is empty or not. - * - * @return - * {@code true} if resizing is enabled, - * {@code false} otherwiser (default). - */ - public boolean getSelectionDisabledIfEmpty() { - return getElement().getProperty("defaultselectdisabled", true); - } - - /** - * Gets the internationalization object previously set for this component. - *

- * Note: updating the object content that is gotten from this method will - * not update the lang on the component if not set back using - * {@link LookupField#setI18n(LookupFieldI18n)} - * - * @return the i18n object. It will be null, If the i18n - * properties weren't set. - */ - public LookupFieldI18n getI18n() { - return i18n; - } - - /** - * Sets the internationalization properties for this component. - * - * @param i18n - * the internationalized properties, not null - */ - public void setI18n(LookupFieldI18n i18n) { - Objects.requireNonNull(i18n, - "The I18N properties object should not be null"); - this.i18n = i18n; - setI18nWithJS(); - } - - private void setI18nWithJS() { - runBeforeClientResponse(ui -> { - JsonObject i18nObject = (JsonObject) JsonSerializer.toJson(i18n); - for (String key : i18nObject.keys()) { - getElement().executeJs("this.set('i18n." + key + "', $0)", - i18nObject.get(key)); - } - }); - } - - private void runBeforeClientResponse(SerializableConsumer command) { - getElement().getNode().runWhenAttached(ui -> ui - .beforeClientResponse(this, context -> command.accept(ui))); - } - - @Override - public void setValue(T value) { - comboBox.setValue(value); - } - - @Override - public T getValue() { - return comboBox.getValue(); - } - - @Override - public Registration addValueChangeListener(ValueChangeListener, T>> listener) { - return comboBox.addValueChangeListener((ValueChangeListener) listener); - } - @Override - public boolean isInvalid() { - return comboBox.isInvalid(); - } - @Override - public void setInvalid(boolean invalid) { - comboBox.setInvalid(invalid); - } - @Override - public void setErrorMessage(String errorMessage) { - comboBox.setErrorMessage(errorMessage); - } - @Override - public String getErrorMessage() { - return comboBox.getErrorMessage(); - } @Override public String getHelperText() { return comboBox.getHelperText(); } + @Override public void setHelperText(String helperText) { comboBox.setHelperText(helperText); } + @Override public void setHelperComponent(Component component) { comboBox.setHelperComponent(component); @@ -493,185 +157,19 @@ public void setHelperComponent(Component component) { public Component getHelperComponent() { return comboBox.getHelperComponent(); } - /** - * Sets the theme variants of this component. This method overwrites any - * previous set theme variants. - * - * @param variants theme variant - */ - public void setThemeVariants(EnhancedDialogVariant... variants) { - getElement().getThemeList().clear(); - addThemeVariants(variants); - } - - /** - * Adds the theme variants of this component. - * - * @param variants theme variant - */ - public void addThemeVariants(EnhancedDialogVariant... variants) { - getElement().getThemeList().addAll(Stream.of(variants).map(EnhancedDialogVariant::getVariantName).collect(Collectors.toList())); - } - - /** - * Set the header with a custom component - * - * @param header custom header - */ - public void setHeaderComponent(Component header) { - Objects.requireNonNull(grid, "Header cannot be null"); - - if (this.header != null && this.header.getElement().getParent() == getElement()) { - this.header.getElement().removeFromParent(); - } - - this.header = header; - header.getElement().setAttribute(SLOT_KEY, HEADER_SLOT_NAME); - - // It might already have a parent e.g when injected from a template - if (header.getElement().getParent() == null) { - getElement().appendChild(header.getElement()); - } - } - - /** - * Set the footer with a custom component - * WARNING: You have to implement your own buttons to select and close the dialog - * - * @param footer Custom footer - */ - public void setFooterComponent(Component footer) { - Objects.requireNonNull(grid, "Footer cannot be null"); - - if (this.footer != null && this.footer.getElement().getParent() == getElement()) { - this.footer.getElement().removeFromParent(); - } - - this.footer = footer; - footer.getElement().setAttribute(SLOT_KEY, FOOTER_SLOT_NAME); - - // It might already have a parent e.g when injected from a template - if (footer.getElement().getParent() == null) { - getElement().appendChild(footer.getElement()); - } - } - - /** - * Select and close the dialog - */ - public void footerSelectAction() { - copyFieldValueFromGrid(); - footerCloseAction(); - } - - /** - * Close the dialog + * Copy the selected value of the grid into the field */ - public void footerCloseAction() { - getElement().executeJs("$0.__close()", getElement()); + @ClientCallable + protected void copyFieldValueFromGrid() { + getGrid().getSelectedItems().stream().findFirst().ifPresent(comboBox::setValue); } /** * Copy the selected value of the field into the grid */ @ClientCallable - private void openErrorNotification() { - getNotificationWhenEmptySelection().run(); - } - - private Runnable getNotificationWhenEmptySelection() { - if (notificationWhenEmptySelection == null) { - return () -> { - String emptySelection = (getI18n() == null)? "Please select an item.":getI18n().getEmptyselection(); - new Notification(emptySelection, 2000, Notification.Position.TOP_CENTER).open(); - }; - } - return notificationWhenEmptySelection; - } - - /** - * Replace the default notification to an action - * - * @param notificationWhenEmptySelection action to run when the selection is empty and the select button is clicked - */ - public void addEmptySelectionListener(Runnable notificationWhenEmptySelection) { - this.notificationWhenEmptySelection = notificationWhenEmptySelection; - } - - /** - * The internationalization properties for {@link LookupField}. - */ - public static class LookupFieldI18n implements Serializable { - private String select; - private String cancel; - private String searcharialabel; - private String headerprefix; - private String headerpostfix; - private String search; - private String emptyselection; - - public String getSearch() { - return search; - } - - public LookupFieldI18n setSearch(String search) { - this.search = search; - return this; - } - - public String getSelect() { - return select; - } - - public LookupFieldI18n setSelect(String select) { - this.select = select; - return this; - } - - public String getCancel() { - return cancel; - } - - public LookupFieldI18n setCancel(String cancel) { - this.cancel = cancel; - return this; - } - - public String getSearcharialabel() { - return searcharialabel; - } - - public LookupFieldI18n setSearcharialabel(String searcharialabel) { - this.searcharialabel = searcharialabel; - return this; - } - - public String getHeaderprefix() { - return headerprefix; - } - - public LookupFieldI18n setHeaderprefix(String headerprefix) { - this.headerprefix = headerprefix; - return this; - } - - public String getHeaderpostfix() { - return headerpostfix; - } - - public LookupFieldI18n setHeaderpostfix(String headerpostfix) { - this.headerpostfix = headerpostfix; - return this; - } - - public String getEmptyselection() { - return emptyselection; - } - - public LookupFieldI18n setEmptyselection(String emptyselection) { - this.emptyselection = emptyselection; - return this; - } + protected void copyFieldValueToGrid() { + getGrid().select(comboBox.getValue()); } } diff --git a/lookup-field-flow/src/main/java/com/vaadin/componentfactory/lookupfield/LookupFieldFilter.java b/lookup-field-flow/src/main/java/com/vaadin/componentfactory/lookupfield/LookupFieldFilter.java new file mode 100644 index 0000000..7d95d2f --- /dev/null +++ b/lookup-field-flow/src/main/java/com/vaadin/componentfactory/lookupfield/LookupFieldFilter.java @@ -0,0 +1,17 @@ +package com.vaadin.componentfactory.lookupfield; + +import com.vaadin.flow.component.Component; +import com.vaadin.flow.component.ComponentEventListener; + +import java.util.function.Consumer; +import java.util.function.Supplier; + +/** + * @author jcgueriaud + */ +public interface LookupFieldFilter { + + Component getComponent(); + + void setFilterAction(LookupFieldFilterAction filterAction); +} diff --git a/lookup-field-flow/src/main/java/com/vaadin/componentfactory/lookupfield/LookupFieldFilterAction.java b/lookup-field-flow/src/main/java/com/vaadin/componentfactory/lookupfield/LookupFieldFilterAction.java new file mode 100644 index 0000000..ce39824 --- /dev/null +++ b/lookup-field-flow/src/main/java/com/vaadin/componentfactory/lookupfield/LookupFieldFilterAction.java @@ -0,0 +1,14 @@ +package com.vaadin.componentfactory.lookupfield; + +/** + * @author jcgueriaud + */ +@FunctionalInterface +public interface LookupFieldFilterAction { + /** + * Apply the filter + * + * @param t the input argument + */ + void filter(T t); +} \ No newline at end of file diff --git a/lookup-field-flow/src/main/java/com/vaadin/componentfactory/lookupfield/MultiSelectLookupField.java b/lookup-field-flow/src/main/java/com/vaadin/componentfactory/lookupfield/MultiSelectLookupField.java new file mode 100644 index 0000000..4b5403c --- /dev/null +++ b/lookup-field-flow/src/main/java/com/vaadin/componentfactory/lookupfield/MultiSelectLookupField.java @@ -0,0 +1,201 @@ +package com.vaadin.componentfactory.lookupfield; + +/* + * #%L + * lookup-field-flow + * %% + * Copyright (C) 2020 Vaadin Ltd + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + +import com.vaadin.componentfactory.EnhancedDialog; +import com.vaadin.componentfactory.theme.EnhancedDialogVariant; +import com.vaadin.flow.component.*; +import com.vaadin.flow.component.button.Button; +import com.vaadin.flow.component.combobox.ComboBox; +import com.vaadin.flow.component.dependency.JsModule; +import com.vaadin.flow.component.dependency.NpmPackage; +import com.vaadin.flow.component.dependency.Uses; +import com.vaadin.flow.component.dialog.Dialog; +import com.vaadin.flow.component.grid.Grid; +import com.vaadin.flow.component.html.Div; +import com.vaadin.flow.component.icon.Icon; +import com.vaadin.flow.component.notification.Notification; +import com.vaadin.flow.component.textfield.TextField; +import com.vaadin.flow.data.binder.HasFilterableDataProvider; +import com.vaadin.flow.data.provider.ConfigurableFilterDataProvider; +import com.vaadin.flow.data.provider.DataProvider; +import com.vaadin.flow.data.provider.ListDataProvider; +import com.vaadin.flow.function.SerializableConsumer; +import com.vaadin.flow.function.SerializableFunction; +import com.vaadin.flow.internal.JsonSerializer; +import com.vaadin.flow.shared.Registration; +import elemental.json.JsonObject; +import org.vaadin.gatanaso.MultiselectComboBox; + +import java.io.Serializable; +import java.util.Collection; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * Server-side component for the {@code vcf-lookup-field} webcomponent. + * + * The LookupField is a combination of a combobox and a dialog for advanced search. + * + * + * @param the type of the items to be inserted in the combo box + */ + +public class MultiSelectLookupField extends AbstractLookupField, MultiselectComboBox, MultiSelectLookupField> { + + public MultiSelectLookupField() { + this(new Grid<>(), new MultiselectComboBox<>()); + } + + public MultiSelectLookupField(Class beanType) { + this(new Grid<>(beanType), new MultiselectComboBox<>()); + } + + public MultiSelectLookupField(Grid grid, MultiselectComboBox comboBox) { + super(); + setGrid(grid); + setComboBox(comboBox); + } + + @Override + public void setGrid(Grid grid) { + super.setGrid(grid); + grid.setSelectionMode(Grid.SelectionMode.MULTI); + } + + @Override + public void setValue(Set value) { + comboBox.setValue(value); + } + + @Override + public Set getValue() { + return comboBox.getValue(); + } + + @Override + public Registration addValueChangeListener(ValueChangeListener, Set>> listener) { + return comboBox.addValueChangeListener((ValueChangeListener) listener); + } + + + /** + * Set the comboBox + * + * @param comboBox the comboBox + */ + public void setComboBox(MultiselectComboBox comboBox) { + Objects.requireNonNull(comboBox, "ComboBox cannot be null"); + + if (this.comboBox != null && this.comboBox.getElement().getParent() == getElement()) { + this.comboBox.getElement().removeFromParent(); + } + comboBox.setClearButtonVisible(true); + comboBox.setClearButtonVisible(true); + comboBox.setAllowCustomValues(true); + comboBox.setCompactMode(true); + //comboBox.setAllowCustomValue(true); + + this.comboBox = comboBox; + comboBox.getElement().setAttribute(SLOT_KEY, FIELD_SLOT_NAME); + + // It might already have a parent e.g when injected from a template + if (comboBox.getElement().getParent() == null) { + getElement().appendChild(comboBox.getElement()); + } + } + + /** + * @return the internal field + */ + public MultiselectComboBox getComboBox() { + return comboBox; + } + + + @Override + public void setDataProvider(ListDataProvider listDataProvider) { + ComboBox.ItemFilter defaultItemFilter = (item, filterText) -> + comboBox.getItemLabelGenerator().apply(item).toLowerCase(getLocale()) + .contains(filterText.toLowerCase(getLocale())); + + setDataProvider(defaultItemFilter, listDataProvider); + } + + /** + * Sets a list data provider with an item filter as the data provider. + * + * @param itemFilter filter to check if an item is shown when user typed some text + * into the ComboBox + * @param listDataProvider the list data provider to use, not null + */ + @Override + public void setDataProvider(ComboBox.ItemFilter itemFilter, + ListDataProvider listDataProvider) { + Objects.requireNonNull(listDataProvider, + "List data provider cannot be null"); + + setDataProvider(listDataProvider, + filterText -> item -> itemFilter.test(item, filterText)); + } + /** + * Sets the item label generator that is used to produce the strings shown + * in the combo box for each item. By default, + * {@link String#valueOf(Object)} is used. + *

+ * + * @param itemLabelGenerator the item label provider to use, not null + */ + public void setItemLabelGenerator(ItemLabelGenerator itemLabelGenerator) { + comboBox.setItemLabelGenerator(itemLabelGenerator); + } + /** + * Set the label of the field + * + * @param label label of the field + */ + @Override + public void setLabel(String label) { + comboBox.setLabel(label); + } + /** + * Copy the selected value of the grid into the field + */ + @Override + @ClientCallable + protected void copyFieldValueFromGrid() { + comboBox.setValue(getGrid().getSelectedItems()); + } + + /** + * Copy the selected value of the field into the grid + */ + @Override + @ClientCallable + protected void copyFieldValueToGrid() { + for (T value : comboBox.getValue()) { + getGrid().select(value); + } + } +} diff --git a/pom.xml b/pom.xml index c6447b0..eb4e549 100755 --- a/pom.xml +++ b/pom.xml @@ -13,7 +13,7 @@ lookup-field-flow - 14.5.3 + 14.7.1 1.8 1.8 UTF-8 From a8175892b1338f88812ffff308ef3c9018c7e447 Mon Sep 17 00:00:00 2001 From: Jean-Christophe Gueriaud Date: Wed, 6 Oct 2021 17:02:30 +0300 Subject: [PATCH 2/4] Add custom filter type --- .../lookupfield/CustomFilterTypeView.java | 51 +++++ .../lookupfield/MainLayout.java | 4 +- .../lookupfield/bean/PersonFilter.java | 38 ++++ .../filter/CustomFilterPerson.java | 48 +++++ .../service/FilteredPersonService.java | 45 +++++ .../lookupfield/AbstractLookupField.java | 30 +-- .../lookupfield/CustomFilterLookupField.java | 181 ++++++++++++++++++ .../lookupfield/LookupField.java | 136 +------------ .../lookupfield/MultiSelectLookupField.java | 4 +- 9 files changed, 389 insertions(+), 148 deletions(-) create mode 100644 lookup-field-flow-demo/src/main/java/com/vaadin/componentfactory/lookupfield/CustomFilterTypeView.java create mode 100644 lookup-field-flow-demo/src/main/java/com/vaadin/componentfactory/lookupfield/bean/PersonFilter.java create mode 100644 lookup-field-flow-demo/src/main/java/com/vaadin/componentfactory/lookupfield/filter/CustomFilterPerson.java create mode 100644 lookup-field-flow-demo/src/main/java/com/vaadin/componentfactory/lookupfield/service/FilteredPersonService.java create mode 100644 lookup-field-flow/src/main/java/com/vaadin/componentfactory/lookupfield/CustomFilterLookupField.java diff --git a/lookup-field-flow-demo/src/main/java/com/vaadin/componentfactory/lookupfield/CustomFilterTypeView.java b/lookup-field-flow-demo/src/main/java/com/vaadin/componentfactory/lookupfield/CustomFilterTypeView.java new file mode 100644 index 0000000..802a73e --- /dev/null +++ b/lookup-field-flow-demo/src/main/java/com/vaadin/componentfactory/lookupfield/CustomFilterTypeView.java @@ -0,0 +1,51 @@ +package com.vaadin.componentfactory.lookupfield; + +import com.vaadin.componentfactory.lookupfield.bean.Address; +import com.vaadin.componentfactory.lookupfield.bean.Person; +import com.vaadin.componentfactory.lookupfield.bean.PersonFilter; +import com.vaadin.componentfactory.lookupfield.filter.CustomFilterPerson; +import com.vaadin.componentfactory.lookupfield.service.FilteredPersonService; +import com.vaadin.componentfactory.lookupfield.service.PersonService; +import com.vaadin.flow.component.html.Div; +import com.vaadin.flow.component.notification.Notification; +import com.vaadin.flow.data.binder.Binder; +import com.vaadin.flow.data.provider.DataProvider; +import com.vaadin.flow.router.Route; + +import java.util.Arrays; +import java.util.List; + +/** + * Basic example with a binder and field validation + */ +@Route(value = "custom-filter-type", layout = MainLayout.class) +public class CustomFilterTypeView extends Div { + + private FilteredPersonService personService = new FilteredPersonService(); + + public CustomFilterTypeView() { + CustomFilterLookupField lookupField = new CustomFilterLookupField<>( person -> new PersonFilter(person, ""), p -> p.getLastName()); + DataProvider dataProvider = + DataProvider.fromFilteringCallbacks( + // First callback fetches items based on a query + query -> { + // The index of the first item to load + int offset = query.getOffset(); + + // The number of items to load + int limit = query.getLimit(); + + List persons = personService + .fetch(offset, limit, query.getFilter().orElse(null)); + + return persons.stream(); + }, + // Second callback fetches the total number of items currently in the Grid. + // The grid can then use it to properly adjust the scrollbars. + query -> personService.count(query.getFilter().orElse(null))); + + lookupField.setDataProvider(dataProvider); + lookupField.setFilter(new CustomFilterPerson()); + add(lookupField); + } +} diff --git a/lookup-field-flow-demo/src/main/java/com/vaadin/componentfactory/lookupfield/MainLayout.java b/lookup-field-flow-demo/src/main/java/com/vaadin/componentfactory/lookupfield/MainLayout.java index d56296e..9e1133e 100644 --- a/lookup-field-flow-demo/src/main/java/com/vaadin/componentfactory/lookupfield/MainLayout.java +++ b/lookup-field-flow-demo/src/main/java/com/vaadin/componentfactory/lookupfield/MainLayout.java @@ -19,8 +19,10 @@ public MainLayout() { final RouterLink enableMultipleButtonView = new RouterLink("Multiselect", MultipleView.class); final RouterLink createView = new RouterLink("Create", CreateItemView.class); final RouterLink customFilterView = new RouterLink("CustomFilter", CustomFilterView.class); + final RouterLink customFilterTypeView = new RouterLink("Filter Person", CustomFilterTypeView.class); final VerticalLayout menuLayout = new VerticalLayout(personLookupField, simple, personLabelLookupField, i18nView, - binderView, customHeader, enableSelectButtonView, enableMultipleButtonView, createView, customFilterView); + binderView, customHeader, enableSelectButtonView, enableMultipleButtonView, createView, customFilterView, + customFilterTypeView); addToDrawer(menuLayout); addToNavbar(drawerToggle); } diff --git a/lookup-field-flow-demo/src/main/java/com/vaadin/componentfactory/lookupfield/bean/PersonFilter.java b/lookup-field-flow-demo/src/main/java/com/vaadin/componentfactory/lookupfield/bean/PersonFilter.java new file mode 100644 index 0000000..c8cb421 --- /dev/null +++ b/lookup-field-flow-demo/src/main/java/com/vaadin/componentfactory/lookupfield/bean/PersonFilter.java @@ -0,0 +1,38 @@ +package com.vaadin.componentfactory.lookupfield.bean; + +/** + * @author jcgueriaud + */ +public class PersonFilter { + + private String firstName; + private String lastName; + + public PersonFilter(String firstName, String lastName) { + this.firstName = firstName; + this.lastName = lastName; + } + + public String getFirstName() { + return firstName; + } + + public PersonFilter setFirstName(String firstName) { + this.firstName = firstName; + return this; + } + + public String getLastName() { + return lastName; + } + + public PersonFilter setLastName(String lastName) { + this.lastName = lastName; + return this; + } + + @Override + public String toString() { + return firstName + " " + lastName; + } +} diff --git a/lookup-field-flow-demo/src/main/java/com/vaadin/componentfactory/lookupfield/filter/CustomFilterPerson.java b/lookup-field-flow-demo/src/main/java/com/vaadin/componentfactory/lookupfield/filter/CustomFilterPerson.java new file mode 100644 index 0000000..af3c404 --- /dev/null +++ b/lookup-field-flow-demo/src/main/java/com/vaadin/componentfactory/lookupfield/filter/CustomFilterPerson.java @@ -0,0 +1,48 @@ +package com.vaadin.componentfactory.lookupfield.filter; + +import com.vaadin.componentfactory.lookupfield.LookupFieldFilter; +import com.vaadin.componentfactory.lookupfield.LookupFieldFilterAction; +import com.vaadin.componentfactory.lookupfield.bean.PersonFilter; +import com.vaadin.flow.component.Component; +import com.vaadin.flow.component.button.Button; +import com.vaadin.flow.component.orderedlayout.FlexComponent; +import com.vaadin.flow.component.orderedlayout.HorizontalLayout; +import com.vaadin.flow.component.textfield.TextField; + +/** + * @author jcgueriaud + */ +public class CustomFilterPerson implements LookupFieldFilter { + + private final HorizontalLayout layout = new HorizontalLayout(); + private final TextField firstNameField; + private final TextField lastNameField; + + private LookupFieldFilterAction fieldFilterAction; + + public CustomFilterPerson() { + layout.setDefaultVerticalComponentAlignment(FlexComponent.Alignment.BASELINE); + firstNameField = new TextField("First name"); + lastNameField = new TextField("Last name"); + layout.addAndExpand(firstNameField); + layout.addAndExpand(lastNameField); + Button filter = new Button("Filter"); + filter.addClickListener(e -> { + if (fieldFilterAction != null) { + fieldFilterAction.filter(new PersonFilter(firstNameField.getValue(), lastNameField.getValue())); + } + }); + layout.add(filter); + } + + @Override + public Component getComponent() { + return layout; + } + + @Override + public void setFilterAction(LookupFieldFilterAction fieldFilterAction) { + this.fieldFilterAction = fieldFilterAction; + } + +} diff --git a/lookup-field-flow-demo/src/main/java/com/vaadin/componentfactory/lookupfield/service/FilteredPersonService.java b/lookup-field-flow-demo/src/main/java/com/vaadin/componentfactory/lookupfield/service/FilteredPersonService.java new file mode 100644 index 0000000..3b11417 --- /dev/null +++ b/lookup-field-flow-demo/src/main/java/com/vaadin/componentfactory/lookupfield/service/FilteredPersonService.java @@ -0,0 +1,45 @@ +package com.vaadin.componentfactory.lookupfield.service; + +import com.vaadin.componentfactory.lookupfield.bean.Person; +import com.vaadin.componentfactory.lookupfield.bean.PersonFilter; + +import java.util.List; +import java.util.stream.Collectors; + +public class FilteredPersonService { + private PersonData personData = new PersonData(); + + public List fetch(int offset, int limit, PersonFilter filter) { + int end = offset + limit; + List collect = personData.getPersons().stream().filter(person -> filter(person, filter)).collect(Collectors.toList()); + int size = collect.size(); + if (size <= end) { + end = size; + } + return collect.subList(offset, end); + } + + private boolean filter(Person person, PersonFilter filter) { + boolean result = true; + if (filter == null || (filter.getLastName() == null && filter.getFirstName() == null)) { + return result; + } + if (filter.getLastName() != null) { + if (person.getLastName().contains(filter.getLastName())) { + return true; + } + } + + if (filter.getFirstName() != null) { + if (person.getFirstName().contains(filter.getLastName())) { + return true; + } + } + return false; + } + + public int count(PersonFilter filter) { + return (int) personData.getPersons().stream().filter(person -> filter(person, filter)).count(); + } + +} \ No newline at end of file diff --git a/lookup-field-flow/src/main/java/com/vaadin/componentfactory/lookupfield/AbstractLookupField.java b/lookup-field-flow/src/main/java/com/vaadin/componentfactory/lookupfield/AbstractLookupField.java index 0432b33..155f320 100644 --- a/lookup-field-flow/src/main/java/com/vaadin/componentfactory/lookupfield/AbstractLookupField.java +++ b/lookup-field-flow/src/main/java/com/vaadin/componentfactory/lookupfield/AbstractLookupField.java @@ -48,8 +48,9 @@ @JsModule("@vaadin-component-factory/vcf-lookup-field") @NpmPackage(value = "@vaadin-component-factory/vcf-lookup-field", version = "1.1.2") public abstract class AbstractLookupField & HasValue, - ComponentT extends AbstractLookupField> extends Div - implements HasFilterableDataProvider, HasValueAndElement, SelectT>, HasValidation, HasSize, HasTheme { + ComponentT extends AbstractLookupField, FilterType> extends Div + implements HasFilterableDataProvider, + HasValueAndElement, SelectT>, HasValidation, HasSize, HasTheme { protected static final String FIELD_SLOT_NAME = "field"; private static final String GRID_SLOT_NAME = "grid"; private static final String FILTER_SLOT_NAME = "filter"; @@ -59,14 +60,19 @@ public abstract class AbstractLookupField grid; protected ComboboxT comboBox; - private ConfigurableFilterDataProvider gridDataProvider; - private LookupFieldFilter filter; + private ConfigurableFilterDataProvider gridDataProvider; + private LookupFieldFilter filter; private Component header; private Component footer; private Runnable notificationWhenEmptySelection; + protected final SerializableFunction filterConverter; + protected final SerializableFunction invertedFilterConverter; - public AbstractLookupField() { + public AbstractLookupField(SerializableFunction filterConverter + ,SerializableFunction invertedFilterConverter) { super(); + this.filterConverter = filterConverter; + this.invertedFilterConverter = invertedFilterConverter; } /** @@ -143,9 +149,9 @@ public abstract void setDataProvider(ComboBox.ItemFilter itemFilter, @Override public void setDataProvider(DataProvider dataProvider, - SerializableFunction filterConverter) { + SerializableFunction filterConverter) { Objects.requireNonNull(dataProvider, "data provider cannot be null"); - comboBox.setDataProvider(dataProvider, filterConverter); + comboBox.setDataProvider(dataProvider, str -> filterConverter.apply(this.filterConverter.apply(str))); gridDataProvider = dataProvider.withConvertedFilter(filterConverter).withConfigurableFilter(); grid.setDataProvider(gridDataProvider); } @@ -177,7 +183,7 @@ public Grid getGrid() { private void filterGrid(String filter) { // don't filter the grid if the filter is custom if (filter != null && this.filter == null) { - gridDataProvider.setFilter(filter); + filterServerGrid(filterConverter.apply(filter)); } } @@ -423,7 +429,7 @@ public void setHeaderComponent(Component header) { * * @param filter custom filter */ - public void setFilter(LookupFieldFilter filter) { + public void setFilter(LookupFieldFilter filter) { Objects.requireNonNull(filter, "Filter cannot be null"); Objects.requireNonNull(filter.getComponent(), "Filter component cannot be null"); @@ -434,7 +440,7 @@ public void setFilter(LookupFieldFilter filter) { this.filter = filter; filter.getComponent().getElement().setAttribute(SLOT_KEY, FILTER_SLOT_NAME); filter.setFilterAction(value -> { - ComponentUtil.fireEvent(this, new AbstractLookupField.FilterEvent(this, false, value)); + ComponentUtil.fireEvent(this, new AbstractLookupField.FilterEvent<>(this, false, value)); }); if (filterRegistration != null) { @@ -451,7 +457,7 @@ public void setFilter(LookupFieldFilter filter) { } } - private void filterServerGrid(String filter) { + private void filterServerGrid(FilterType filter) { gridDataProvider.setFilter(filter); } @@ -527,7 +533,7 @@ public void addEmptySelectionListener(Runnable notificationWhenEmptySelection) { * @return a handle that can be used for removing the listener */ @SuppressWarnings("unchecked") - public Registration addFilterListener(ComponentEventListener> listener) { + public Registration addFilterListener(ComponentEventListener> listener) { return addListener(FilterEvent.class, (ComponentEventListener) listener); } diff --git a/lookup-field-flow/src/main/java/com/vaadin/componentfactory/lookupfield/CustomFilterLookupField.java b/lookup-field-flow/src/main/java/com/vaadin/componentfactory/lookupfield/CustomFilterLookupField.java new file mode 100644 index 0000000..cb9b8d4 --- /dev/null +++ b/lookup-field-flow/src/main/java/com/vaadin/componentfactory/lookupfield/CustomFilterLookupField.java @@ -0,0 +1,181 @@ +package com.vaadin.componentfactory.lookupfield; + +/* + * #%L + * lookup-field-flow + * %% + * Copyright (C) 2020 Vaadin Ltd + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + +import com.vaadin.flow.component.*; +import com.vaadin.flow.component.combobox.ComboBox; +import com.vaadin.flow.component.grid.Grid; +import com.vaadin.flow.data.provider.ListDataProvider; +import com.vaadin.flow.function.SerializableFunction; +import com.vaadin.flow.shared.Registration; + +import java.util.Objects; + +public class CustomFilterLookupField extends AbstractLookupField, CustomFilterLookupField, FilterType> implements HasHelper { + + public CustomFilterLookupField( + SerializableFunction filterConverter + ,SerializableFunction invertedFilterConverter) { + this(new Grid<>(), new ComboBox<>(), filterConverter, invertedFilterConverter); + } + + public CustomFilterLookupField(Class beanType, + SerializableFunction filterConverter + ,SerializableFunction invertedFilterConverter) { + this(new Grid<>(beanType), new ComboBox<>(), filterConverter, invertedFilterConverter); + } + + public CustomFilterLookupField(Grid grid, ComboBox comboBox, + SerializableFunction filterConverter + ,SerializableFunction invertedFilterConverter) { + super(filterConverter, invertedFilterConverter); + setGrid(grid); + setComboBox(comboBox); + } + + + @Override + public void setValue(T value) { + comboBox.setValue(value); + } + + @Override + public T getValue() { + return comboBox.getValue(); + } + + @Override + public Registration addValueChangeListener(ValueChangeListener, T>> listener) { + return comboBox.addValueChangeListener((ValueChangeListener)listener); + } + + /** + * Set the comboBox + * + * @param comboBox the comboBox + */ + public void setComboBox(ComboBox comboBox) { + Objects.requireNonNull(comboBox, "ComboBox cannot be null"); + + if (this.comboBox != null && this.comboBox.getElement().getParent() == getElement()) { + this.comboBox.getElement().removeFromParent(); + } + comboBox.setClearButtonVisible(true); + comboBox.setAllowCustomValue(true); + + this.comboBox = comboBox; + comboBox.getElement().setAttribute(SLOT_KEY, FIELD_SLOT_NAME); + + // It might already have a parent e.g when injected from a template + if (comboBox.getElement().getParent() == null) { + getElement().appendChild(comboBox.getElement()); + } + } + + /** + * @return the internal field + */ + public ComboBox getComboBox() { + return comboBox; + } + + + @Override + public void setDataProvider(ListDataProvider listDataProvider) { + ComboBox.ItemFilter defaultItemFilter = (item, filterText) -> + comboBox.getItemLabelGenerator().apply(item).toLowerCase(getLocale()) + .contains(filterText.toLowerCase(getLocale())); + + setDataProvider(defaultItemFilter, listDataProvider); + } + + /** + * Sets a list data provider with an item filter as the data provider. + * + * @param itemFilter filter to check if an item is shown when user typed some text + * into the ComboBox + * @param listDataProvider the list data provider to use, not null + */ + @Override + public void setDataProvider(ComboBox.ItemFilter itemFilter, + ListDataProvider listDataProvider) { + Objects.requireNonNull(listDataProvider, + "List data provider cannot be null"); + + setDataProvider(listDataProvider, + filterText -> item -> itemFilter.test(item, invertedFilterConverter.apply(filterText))); + } + /** + * Sets the item label generator that is used to produce the strings shown + * in the combo box for each item. By default, + * {@link String#valueOf(Object)} is used. + *

+ * + * @param itemLabelGenerator the item label provider to use, not null + */ + public void setItemLabelGenerator(ItemLabelGenerator itemLabelGenerator) { + comboBox.setItemLabelGenerator(itemLabelGenerator); + } + /** + * Set the label of the field + * + * @param label label of the field + */ + public void setLabel(String label) { + comboBox.setLabel(label); + } + + + @Override + public String getHelperText() { + return comboBox.getHelperText(); + } + + @Override + public void setHelperText(String helperText) { + comboBox.setHelperText(helperText); + } + + @Override + public void setHelperComponent(Component component) { + comboBox.setHelperComponent(component); + } + + @Override + public Component getHelperComponent() { + return comboBox.getHelperComponent(); + } + /** + * Copy the selected value of the grid into the field + */ + @ClientCallable + protected void copyFieldValueFromGrid() { + getGrid().getSelectedItems().stream().findFirst().ifPresent(comboBox::setValue); + } + + /** + * Copy the selected value of the field into the grid + */ + @ClientCallable + protected void copyFieldValueToGrid() { + getGrid().select(comboBox.getValue()); + } +} diff --git a/lookup-field-flow/src/main/java/com/vaadin/componentfactory/lookupfield/LookupField.java b/lookup-field-flow/src/main/java/com/vaadin/componentfactory/lookupfield/LookupField.java index 182e4b1..b3efdf8 100644 --- a/lookup-field-flow/src/main/java/com/vaadin/componentfactory/lookupfield/LookupField.java +++ b/lookup-field-flow/src/main/java/com/vaadin/componentfactory/lookupfield/LookupField.java @@ -24,11 +24,12 @@ import com.vaadin.flow.component.combobox.ComboBox; import com.vaadin.flow.component.grid.Grid; import com.vaadin.flow.data.provider.ListDataProvider; +import com.vaadin.flow.function.SerializableFunction; import com.vaadin.flow.shared.Registration; import java.util.Objects; -public class LookupField extends AbstractLookupField, LookupField> implements HasHelper { +public class LookupField extends CustomFilterLookupField implements HasHelper { public LookupField() { this(new Grid<>(), new ComboBox<>()); @@ -39,137 +40,6 @@ public LookupField(Class beanType) { } public LookupField(Grid grid, ComboBox comboBox) { - super(); - setGrid(grid); - setComboBox(comboBox); - } - - - @Override - public void setValue(T value) { - comboBox.setValue(value); - } - - @Override - public T getValue() { - return comboBox.getValue(); - } - - @Override - public Registration addValueChangeListener(ValueChangeListener, T>> listener) { - return comboBox.addValueChangeListener((ValueChangeListener) listener); - } - - - /** - * Set the comboBox - * - * @param comboBox the comboBox - */ - public void setComboBox(ComboBox comboBox) { - Objects.requireNonNull(comboBox, "ComboBox cannot be null"); - - if (this.comboBox != null && this.comboBox.getElement().getParent() == getElement()) { - this.comboBox.getElement().removeFromParent(); - } - comboBox.setClearButtonVisible(true); - comboBox.setAllowCustomValue(true); - - this.comboBox = comboBox; - comboBox.getElement().setAttribute(SLOT_KEY, FIELD_SLOT_NAME); - - // It might already have a parent e.g when injected from a template - if (comboBox.getElement().getParent() == null) { - getElement().appendChild(comboBox.getElement()); - } - } - - /** - * @return the internal field - */ - public ComboBox getComboBox() { - return comboBox; - } - - - @Override - public void setDataProvider(ListDataProvider listDataProvider) { - ComboBox.ItemFilter defaultItemFilter = (item, filterText) -> - comboBox.getItemLabelGenerator().apply(item).toLowerCase(getLocale()) - .contains(filterText.toLowerCase(getLocale())); - - setDataProvider(defaultItemFilter, listDataProvider); - } - - /** - * Sets a list data provider with an item filter as the data provider. - * - * @param itemFilter filter to check if an item is shown when user typed some text - * into the ComboBox - * @param listDataProvider the list data provider to use, not null - */ - @Override - public void setDataProvider(ComboBox.ItemFilter itemFilter, - ListDataProvider listDataProvider) { - Objects.requireNonNull(listDataProvider, - "List data provider cannot be null"); - - setDataProvider(listDataProvider, - filterText -> item -> itemFilter.test(item, filterText)); - } - /** - * Sets the item label generator that is used to produce the strings shown - * in the combo box for each item. By default, - * {@link String#valueOf(Object)} is used. - *

- * - * @param itemLabelGenerator the item label provider to use, not null - */ - public void setItemLabelGenerator(ItemLabelGenerator itemLabelGenerator) { - comboBox.setItemLabelGenerator(itemLabelGenerator); - } - /** - * Set the label of the field - * - * @param label label of the field - */ - public void setLabel(String label) { - comboBox.setLabel(label); - } - - - @Override - public String getHelperText() { - return comboBox.getHelperText(); - } - - @Override - public void setHelperText(String helperText) { - comboBox.setHelperText(helperText); - } - - @Override - public void setHelperComponent(Component component) { - comboBox.setHelperComponent(component); - } - - @Override - public Component getHelperComponent() { - return comboBox.getHelperComponent(); - } - /** - * Copy the selected value of the grid into the field - */ - @ClientCallable - protected void copyFieldValueFromGrid() { - getGrid().getSelectedItems().stream().findFirst().ifPresent(comboBox::setValue); - } - - /** - * Copy the selected value of the field into the grid - */ - @ClientCallable - protected void copyFieldValueToGrid() { - getGrid().select(comboBox.getValue()); + super(grid, comboBox,SerializableFunction.identity(), SerializableFunction.identity()); } } diff --git a/lookup-field-flow/src/main/java/com/vaadin/componentfactory/lookupfield/MultiSelectLookupField.java b/lookup-field-flow/src/main/java/com/vaadin/componentfactory/lookupfield/MultiSelectLookupField.java index 4b5403c..f95f233 100644 --- a/lookup-field-flow/src/main/java/com/vaadin/componentfactory/lookupfield/MultiSelectLookupField.java +++ b/lookup-field-flow/src/main/java/com/vaadin/componentfactory/lookupfield/MultiSelectLookupField.java @@ -62,7 +62,7 @@ * @param the type of the items to be inserted in the combo box */ -public class MultiSelectLookupField extends AbstractLookupField, MultiselectComboBox, MultiSelectLookupField> { +public class MultiSelectLookupField extends AbstractLookupField, MultiselectComboBox, MultiSelectLookupField, String> { public MultiSelectLookupField() { this(new Grid<>(), new MultiselectComboBox<>()); @@ -73,7 +73,7 @@ public MultiSelectLookupField(Class beanType) { } public MultiSelectLookupField(Grid grid, MultiselectComboBox comboBox) { - super(); + super(SerializableFunction.identity(), SerializableFunction.identity()); setGrid(grid); setComboBox(comboBox); } From 845d495e101aeeb99617c79cad9bbc44875b88be Mon Sep 17 00:00:00 2001 From: Jean-Christophe Gueriaud Date: Thu, 7 Oct 2021 13:21:58 +0300 Subject: [PATCH 3/4] Add custom filter type + example --- .../lookupfield/CustomFilterTypeView.java | 20 +- .../lookupfield/bean/PersonFilter.java | 17 +- .../service/FilteredPersonService.java | 20 +- .../lookupfield/AbstractLookupField.java | 10 - .../lookupfield/CustomFilterLookupField.java | 26 ++- .../CustomFilterMultiSelectLookupField.java | 198 ++++++++++++++++++ .../lookupfield/LookupField.java | 14 +- .../lookupfield/LookupFieldFilter.java | 20 +- .../lookupfield/LookupFieldFilterAction.java | 2 +- .../lookupfield/MultiSelectLookupField.java | 155 +------------- 10 files changed, 288 insertions(+), 194 deletions(-) create mode 100644 lookup-field-flow/src/main/java/com/vaadin/componentfactory/lookupfield/CustomFilterMultiSelectLookupField.java diff --git a/lookup-field-flow-demo/src/main/java/com/vaadin/componentfactory/lookupfield/CustomFilterTypeView.java b/lookup-field-flow-demo/src/main/java/com/vaadin/componentfactory/lookupfield/CustomFilterTypeView.java index 802a73e..0128262 100644 --- a/lookup-field-flow-demo/src/main/java/com/vaadin/componentfactory/lookupfield/CustomFilterTypeView.java +++ b/lookup-field-flow-demo/src/main/java/com/vaadin/componentfactory/lookupfield/CustomFilterTypeView.java @@ -6,8 +6,10 @@ import com.vaadin.componentfactory.lookupfield.filter.CustomFilterPerson; import com.vaadin.componentfactory.lookupfield.service.FilteredPersonService; import com.vaadin.componentfactory.lookupfield.service.PersonService; +import com.vaadin.componentfactory.theme.EnhancedDialogVariant; import com.vaadin.flow.component.html.Div; import com.vaadin.flow.component.notification.Notification; +import com.vaadin.flow.component.orderedlayout.VerticalLayout; import com.vaadin.flow.data.binder.Binder; import com.vaadin.flow.data.provider.DataProvider; import com.vaadin.flow.router.Route; @@ -19,12 +21,12 @@ * Basic example with a binder and field validation */ @Route(value = "custom-filter-type", layout = MainLayout.class) -public class CustomFilterTypeView extends Div { +public class CustomFilterTypeView extends VerticalLayout { private FilteredPersonService personService = new FilteredPersonService(); public CustomFilterTypeView() { - CustomFilterLookupField lookupField = new CustomFilterLookupField<>( person -> new PersonFilter(person, ""), p -> p.getLastName()); + CustomFilterLookupField lookupField = new CustomFilterLookupField<>( fullName -> new PersonFilter(fullName), p -> p.getLastName()); DataProvider dataProvider = DataProvider.fromFilteringCallbacks( // First callback fetches items based on a query @@ -46,6 +48,20 @@ public CustomFilterTypeView() { lookupField.setDataProvider(dataProvider); lookupField.setFilter(new CustomFilterPerson()); + lookupField.getGrid().addColumn(s -> s).setHeader("item"); + lookupField.setLabel("Select one item"); + lookupField.addThemeVariants(EnhancedDialogVariant.SIZE_MEDIUM); add(lookupField); + + CustomFilterMultiSelectLookupField multipleLookupField = + new CustomFilterMultiSelectLookupField<>( fullName -> new PersonFilter(fullName), p -> p.getLastName()); + + + multipleLookupField.setDataProvider(dataProvider); + multipleLookupField.setFilter(new CustomFilterPerson()); + multipleLookupField.getGrid().addColumn(s -> s).setHeader("item"); + multipleLookupField.setLabel("Select Multiple items"); + multipleLookupField.addThemeVariants(EnhancedDialogVariant.SIZE_MEDIUM); + add(multipleLookupField); } } diff --git a/lookup-field-flow-demo/src/main/java/com/vaadin/componentfactory/lookupfield/bean/PersonFilter.java b/lookup-field-flow-demo/src/main/java/com/vaadin/componentfactory/lookupfield/bean/PersonFilter.java index c8cb421..2dd3556 100644 --- a/lookup-field-flow-demo/src/main/java/com/vaadin/componentfactory/lookupfield/bean/PersonFilter.java +++ b/lookup-field-flow-demo/src/main/java/com/vaadin/componentfactory/lookupfield/bean/PersonFilter.java @@ -1,12 +1,19 @@ package com.vaadin.componentfactory.lookupfield.bean; +import org.apache.commons.lang3.StringUtils; + /** * @author jcgueriaud */ public class PersonFilter { - private String firstName; - private String lastName; + private String firstName = ""; + private String fullName = ""; + private String lastName = ""; + + public PersonFilter(String fullName) { + this.fullName = fullName; + } public PersonFilter(String firstName, String lastName) { this.firstName = firstName; @@ -31,8 +38,12 @@ public PersonFilter setLastName(String lastName) { return this; } + public String getFullName() { + return fullName; + } + @Override public String toString() { - return firstName + " " + lastName; + return StringUtils.trim(firstName + " " + lastName); } } diff --git a/lookup-field-flow-demo/src/main/java/com/vaadin/componentfactory/lookupfield/service/FilteredPersonService.java b/lookup-field-flow-demo/src/main/java/com/vaadin/componentfactory/lookupfield/service/FilteredPersonService.java index 3b11417..eae9c4a 100644 --- a/lookup-field-flow-demo/src/main/java/com/vaadin/componentfactory/lookupfield/service/FilteredPersonService.java +++ b/lookup-field-flow-demo/src/main/java/com/vaadin/componentfactory/lookupfield/service/FilteredPersonService.java @@ -2,6 +2,7 @@ import com.vaadin.componentfactory.lookupfield.bean.Person; import com.vaadin.componentfactory.lookupfield.bean.PersonFilter; +import org.apache.commons.lang3.StringUtils; import java.util.List; import java.util.stream.Collectors; @@ -21,21 +22,14 @@ public List fetch(int offset, int limit, PersonFilter filter) { private boolean filter(Person person, PersonFilter filter) { boolean result = true; - if (filter == null || (filter.getLastName() == null && filter.getFirstName() == null)) { - return result; + if (filter == null || (StringUtils.isEmpty(filter.getLastName()) && StringUtils.isEmpty(filter.getFirstName()))) { + return true; } - if (filter.getLastName() != null) { - if (person.getLastName().contains(filter.getLastName())) { - return true; - } + if ((StringUtils.isEmpty(filter.getLastName()) && StringUtils.isEmpty(filter.getFirstName()))) { + return person.toString().contains(filter.getFullName()); + } else { + return person.getLastName().contains(filter.getLastName()) && person.getFirstName().contains(filter.getFirstName()); } - - if (filter.getFirstName() != null) { - if (person.getFirstName().contains(filter.getLastName())) { - return true; - } - } - return false; } public int count(PersonFilter filter) { diff --git a/lookup-field-flow/src/main/java/com/vaadin/componentfactory/lookupfield/AbstractLookupField.java b/lookup-field-flow/src/main/java/com/vaadin/componentfactory/lookupfield/AbstractLookupField.java index 155f320..4dfbf4b 100644 --- a/lookup-field-flow/src/main/java/com/vaadin/componentfactory/lookupfield/AbstractLookupField.java +++ b/lookup-field-flow/src/main/java/com/vaadin/componentfactory/lookupfield/AbstractLookupField.java @@ -25,21 +25,11 @@ import elemental.json.JsonObject; import java.io.Serializable; -import java.util.ArrayList; import java.util.Collection; import java.util.Objects; -import java.util.function.Consumer; import java.util.stream.Collectors; import java.util.stream.Stream; -/** - * Server-side component for the {@code vcf-lookup-field} webcomponent. - * - * The LookupField is a combination of a combobox and a dialog for advanced search. - * - * - * @param the type of the items to be inserted in the combo box - */ @Uses(value = Icon.class) @Uses(value = TextField.class) @Uses(value = Button.class) diff --git a/lookup-field-flow/src/main/java/com/vaadin/componentfactory/lookupfield/CustomFilterLookupField.java b/lookup-field-flow/src/main/java/com/vaadin/componentfactory/lookupfield/CustomFilterLookupField.java index cb9b8d4..a5e00dd 100644 --- a/lookup-field-flow/src/main/java/com/vaadin/componentfactory/lookupfield/CustomFilterLookupField.java +++ b/lookup-field-flow/src/main/java/com/vaadin/componentfactory/lookupfield/CustomFilterLookupField.java @@ -29,8 +29,24 @@ import java.util.Objects; +/** + * Server-side component for the {@code vcf-lookup-field} webcomponent. + * + * The CustomFilterLookupField is a combination of a combobox and a dialog for advanced search. + * + * @param the type of the items to be inserted in the combo box and grid + * @param Type of the filter + */ public class CustomFilterLookupField extends AbstractLookupField, CustomFilterLookupField, FilterType> implements HasHelper { + /** + * Constructor + * The converters are used to convert the backend filter to the combobox filter (String) + * or if you are using setItems + * + * @param filterConverter Convert a string to FilterType + * @param invertedFilterConverter Convert a FilterType to String + */ public CustomFilterLookupField( SerializableFunction filterConverter ,SerializableFunction invertedFilterConverter) { @@ -51,7 +67,6 @@ public CustomFilterLookupField(Grid grid, ComboBox comboBox, setComboBox(comboBox); } - @Override public void setValue(T value) { comboBox.setValue(value); @@ -80,7 +95,14 @@ public void setComboBox(ComboBox comboBox) { } comboBox.setClearButtonVisible(true); comboBox.setAllowCustomValue(true); - + comboBox.addCustomValueSetListener(e -> { + getElement().setProperty("_filterdata", e.getDetail()); + }); + + comboBox.addValueChangeListener(e -> { + String value = (e.getValue() == null)? "":comboBox.getItemLabelGenerator().apply(e.getValue()); + getElement().setProperty("_filterdata", value); + }); this.comboBox = comboBox; comboBox.getElement().setAttribute(SLOT_KEY, FIELD_SLOT_NAME); diff --git a/lookup-field-flow/src/main/java/com/vaadin/componentfactory/lookupfield/CustomFilterMultiSelectLookupField.java b/lookup-field-flow/src/main/java/com/vaadin/componentfactory/lookupfield/CustomFilterMultiSelectLookupField.java new file mode 100644 index 0000000..0860789 --- /dev/null +++ b/lookup-field-flow/src/main/java/com/vaadin/componentfactory/lookupfield/CustomFilterMultiSelectLookupField.java @@ -0,0 +1,198 @@ +package com.vaadin.componentfactory.lookupfield; + +/* + * #%L + * lookup-field-flow + * %% + * Copyright (C) 2020 Vaadin Ltd + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + +import com.vaadin.flow.component.AbstractField; +import com.vaadin.flow.component.ClientCallable; +import com.vaadin.flow.component.ItemLabelGenerator; +import com.vaadin.flow.component.combobox.ComboBox; +import com.vaadin.flow.component.grid.Grid; +import com.vaadin.flow.data.provider.ListDataProvider; +import com.vaadin.flow.function.SerializableFunction; +import com.vaadin.flow.shared.Registration; +import org.vaadin.gatanaso.MultiselectComboBox; + +import java.util.Objects; +import java.util.Set; + +/** + * Server-side component for the {@code vcf-lookup-field} webcomponent. + * + * The LookupField is a combination of a combobox and a dialog for advanced search. + * + * + * @param the type of the items to be inserted in the combo box + */ + +public class CustomFilterMultiSelectLookupField extends AbstractLookupField, MultiselectComboBox, CustomFilterMultiSelectLookupField, FilterType> { + + /** + * Constructor + * The converters are used to convert the backend filter to the combobox filter (String) + * or if you are using setItems + * + * @param filterConverter Convert a string to FilterType + * @param invertedFilterConverter Convert a FilterType to String + */ + public CustomFilterMultiSelectLookupField( + SerializableFunction filterConverter + ,SerializableFunction invertedFilterConverter) { + this(new Grid<>(), new MultiselectComboBox<>(), filterConverter, invertedFilterConverter); + } + + public CustomFilterMultiSelectLookupField(Class beanType, + SerializableFunction filterConverter + ,SerializableFunction invertedFilterConverter) { + this(new Grid<>(beanType), new MultiselectComboBox<>(), filterConverter, invertedFilterConverter); + } + + public CustomFilterMultiSelectLookupField(Grid grid, MultiselectComboBox comboBox, + SerializableFunction filterConverter + ,SerializableFunction invertedFilterConverter) { + super(filterConverter, invertedFilterConverter); + setGrid(grid); + setComboBox(comboBox); + } + + @Override + public void setGrid(Grid grid) { + super.setGrid(grid); + grid.setSelectionMode(Grid.SelectionMode.MULTI); + } + + @Override + public void setValue(Set value) { + comboBox.setValue(value); + } + + @Override + public Set getValue() { + return comboBox.getValue(); + } + + @Override + public Registration addValueChangeListener(ValueChangeListener, Set>> listener) { + return comboBox.addValueChangeListener((ValueChangeListener) listener); + } + + + /** + * Set the comboBox + * + * @param comboBox the comboBox + */ + public void setComboBox(MultiselectComboBox comboBox) { + Objects.requireNonNull(comboBox, "ComboBox cannot be null"); + + if (this.comboBox != null && this.comboBox.getElement().getParent() == getElement()) { + this.comboBox.getElement().removeFromParent(); + } + comboBox.setClearButtonVisible(true); + comboBox.setAllowCustomValues(true); + comboBox.addCustomValuesSetListener(e -> { + getElement().setProperty("_filterdata", e.getDetail()); + }); + comboBox.addValueChangeListener(e -> { + getElement().setProperty("_filterdata", ""); + }); + + this.comboBox = comboBox; + comboBox.getElement().setAttribute(SLOT_KEY, FIELD_SLOT_NAME); + + // It might already have a parent e.g when injected from a template + if (comboBox.getElement().getParent() == null) { + getElement().appendChild(comboBox.getElement()); + } + } + + /** + * @return the internal field + */ + public MultiselectComboBox getComboBox() { + return comboBox; + } + + + @Override + public void setDataProvider(ListDataProvider listDataProvider) { + ComboBox.ItemFilter defaultItemFilter = (item, filterText) -> + comboBox.getItemLabelGenerator().apply(item).toLowerCase(getLocale()) + .contains(filterText.toLowerCase(getLocale())); + + setDataProvider(defaultItemFilter, listDataProvider); + } + + /** + * Sets a list data provider with an item filter as the data provider. + * + * @param itemFilter filter to check if an item is shown when user typed some text + * into the ComboBox + * @param listDataProvider the list data provider to use, not null + */ + @Override + public void setDataProvider(ComboBox.ItemFilter itemFilter, + ListDataProvider listDataProvider) { + Objects.requireNonNull(listDataProvider, + "List data provider cannot be null"); + + setDataProvider(listDataProvider, + filterText -> item -> itemFilter.test(item, invertedFilterConverter.apply(filterText))); + } + /** + * Sets the item label generator that is used to produce the strings shown + * in the combo box for each item. By default, + * {@link String#valueOf(Object)} is used. + *

+ * + * @param itemLabelGenerator the item label provider to use, not null + */ + public void setItemLabelGenerator(ItemLabelGenerator itemLabelGenerator) { + comboBox.setItemLabelGenerator(itemLabelGenerator); + } + /** + * Set the label of the field + * + * @param label label of the field + */ + @Override + public void setLabel(String label) { + comboBox.setLabel(label); + } + /** + * Copy the selected value of the grid into the field + */ + @Override + @ClientCallable + protected void copyFieldValueFromGrid() { + comboBox.setValue(getGrid().getSelectedItems()); + } + + /** + * Copy the selected value of the field into the grid + */ + @Override + @ClientCallable + protected void copyFieldValueToGrid() { + for (T value : comboBox.getValue()) { + getGrid().select(value); + } + } +} diff --git a/lookup-field-flow/src/main/java/com/vaadin/componentfactory/lookupfield/LookupField.java b/lookup-field-flow/src/main/java/com/vaadin/componentfactory/lookupfield/LookupField.java index b3efdf8..328c1dc 100644 --- a/lookup-field-flow/src/main/java/com/vaadin/componentfactory/lookupfield/LookupField.java +++ b/lookup-field-flow/src/main/java/com/vaadin/componentfactory/lookupfield/LookupField.java @@ -20,15 +20,19 @@ * #L% */ -import com.vaadin.flow.component.*; +import com.vaadin.flow.component.HasHelper; import com.vaadin.flow.component.combobox.ComboBox; import com.vaadin.flow.component.grid.Grid; -import com.vaadin.flow.data.provider.ListDataProvider; import com.vaadin.flow.function.SerializableFunction; -import com.vaadin.flow.shared.Registration; - -import java.util.Objects; +/** + * Server-side component for the {@code vcf-lookup-field} webcomponent. + * + * The LookupField is a combination of a combobox and a dialog for advanced search. + * + * + * @param the type of the items to be inserted in the combo box + */ public class LookupField extends CustomFilterLookupField implements HasHelper { public LookupField() { diff --git a/lookup-field-flow/src/main/java/com/vaadin/componentfactory/lookupfield/LookupFieldFilter.java b/lookup-field-flow/src/main/java/com/vaadin/componentfactory/lookupfield/LookupFieldFilter.java index 7d95d2f..0ebdb86 100644 --- a/lookup-field-flow/src/main/java/com/vaadin/componentfactory/lookupfield/LookupFieldFilter.java +++ b/lookup-field-flow/src/main/java/com/vaadin/componentfactory/lookupfield/LookupFieldFilter.java @@ -1,17 +1,27 @@ package com.vaadin.componentfactory.lookupfield; import com.vaadin.flow.component.Component; -import com.vaadin.flow.component.ComponentEventListener; - -import java.util.function.Consumer; -import java.util.function.Supplier; /** - * @author jcgueriaud + * Class for the custom filter component + * The component need to call manually the filter action. For example + * filterButton.addClickListener(e -> { + * if (fieldFilterAction != null) { + * fieldFilterAction.filter(new FILTERTYPE); + * } + * }); */ public interface LookupFieldFilter { + /** + * + * @return Filter component + */ Component getComponent(); + /** + * + * @param filterAction action to call when you want to filter the grid + */ void setFilterAction(LookupFieldFilterAction filterAction); } diff --git a/lookup-field-flow/src/main/java/com/vaadin/componentfactory/lookupfield/LookupFieldFilterAction.java b/lookup-field-flow/src/main/java/com/vaadin/componentfactory/lookupfield/LookupFieldFilterAction.java index ce39824..9865782 100644 --- a/lookup-field-flow/src/main/java/com/vaadin/componentfactory/lookupfield/LookupFieldFilterAction.java +++ b/lookup-field-flow/src/main/java/com/vaadin/componentfactory/lookupfield/LookupFieldFilterAction.java @@ -1,7 +1,7 @@ package com.vaadin.componentfactory.lookupfield; /** - * @author jcgueriaud + * Filter action */ @FunctionalInterface public interface LookupFieldFilterAction { diff --git a/lookup-field-flow/src/main/java/com/vaadin/componentfactory/lookupfield/MultiSelectLookupField.java b/lookup-field-flow/src/main/java/com/vaadin/componentfactory/lookupfield/MultiSelectLookupField.java index f95f233..1092d4e 100644 --- a/lookup-field-flow/src/main/java/com/vaadin/componentfactory/lookupfield/MultiSelectLookupField.java +++ b/lookup-field-flow/src/main/java/com/vaadin/componentfactory/lookupfield/MultiSelectLookupField.java @@ -20,39 +20,10 @@ * #L% */ -import com.vaadin.componentfactory.EnhancedDialog; -import com.vaadin.componentfactory.theme.EnhancedDialogVariant; -import com.vaadin.flow.component.*; -import com.vaadin.flow.component.button.Button; -import com.vaadin.flow.component.combobox.ComboBox; -import com.vaadin.flow.component.dependency.JsModule; -import com.vaadin.flow.component.dependency.NpmPackage; -import com.vaadin.flow.component.dependency.Uses; -import com.vaadin.flow.component.dialog.Dialog; import com.vaadin.flow.component.grid.Grid; -import com.vaadin.flow.component.html.Div; -import com.vaadin.flow.component.icon.Icon; -import com.vaadin.flow.component.notification.Notification; -import com.vaadin.flow.component.textfield.TextField; -import com.vaadin.flow.data.binder.HasFilterableDataProvider; -import com.vaadin.flow.data.provider.ConfigurableFilterDataProvider; -import com.vaadin.flow.data.provider.DataProvider; -import com.vaadin.flow.data.provider.ListDataProvider; -import com.vaadin.flow.function.SerializableConsumer; import com.vaadin.flow.function.SerializableFunction; -import com.vaadin.flow.internal.JsonSerializer; -import com.vaadin.flow.shared.Registration; -import elemental.json.JsonObject; import org.vaadin.gatanaso.MultiselectComboBox; -import java.io.Serializable; -import java.util.Collection; -import java.util.HashSet; -import java.util.Objects; -import java.util.Set; -import java.util.stream.Collectors; -import java.util.stream.Stream; - /** * Server-side component for the {@code vcf-lookup-field} webcomponent. * @@ -62,7 +33,7 @@ * @param the type of the items to be inserted in the combo box */ -public class MultiSelectLookupField extends AbstractLookupField, MultiselectComboBox, MultiSelectLookupField, String> { +public class MultiSelectLookupField extends CustomFilterMultiSelectLookupField { public MultiSelectLookupField() { this(new Grid<>(), new MultiselectComboBox<>()); @@ -73,129 +44,7 @@ public MultiSelectLookupField(Class beanType) { } public MultiSelectLookupField(Grid grid, MultiselectComboBox comboBox) { - super(SerializableFunction.identity(), SerializableFunction.identity()); - setGrid(grid); - setComboBox(comboBox); - } - - @Override - public void setGrid(Grid grid) { - super.setGrid(grid); - grid.setSelectionMode(Grid.SelectionMode.MULTI); - } - - @Override - public void setValue(Set value) { - comboBox.setValue(value); - } - - @Override - public Set getValue() { - return comboBox.getValue(); - } - - @Override - public Registration addValueChangeListener(ValueChangeListener, Set>> listener) { - return comboBox.addValueChangeListener((ValueChangeListener) listener); - } - - - /** - * Set the comboBox - * - * @param comboBox the comboBox - */ - public void setComboBox(MultiselectComboBox comboBox) { - Objects.requireNonNull(comboBox, "ComboBox cannot be null"); - - if (this.comboBox != null && this.comboBox.getElement().getParent() == getElement()) { - this.comboBox.getElement().removeFromParent(); - } - comboBox.setClearButtonVisible(true); - comboBox.setClearButtonVisible(true); - comboBox.setAllowCustomValues(true); - comboBox.setCompactMode(true); - //comboBox.setAllowCustomValue(true); - - this.comboBox = comboBox; - comboBox.getElement().setAttribute(SLOT_KEY, FIELD_SLOT_NAME); - - // It might already have a parent e.g when injected from a template - if (comboBox.getElement().getParent() == null) { - getElement().appendChild(comboBox.getElement()); - } - } - - /** - * @return the internal field - */ - public MultiselectComboBox getComboBox() { - return comboBox; - } - - - @Override - public void setDataProvider(ListDataProvider listDataProvider) { - ComboBox.ItemFilter defaultItemFilter = (item, filterText) -> - comboBox.getItemLabelGenerator().apply(item).toLowerCase(getLocale()) - .contains(filterText.toLowerCase(getLocale())); - - setDataProvider(defaultItemFilter, listDataProvider); + super(grid, comboBox, SerializableFunction.identity(), SerializableFunction.identity()); } - /** - * Sets a list data provider with an item filter as the data provider. - * - * @param itemFilter filter to check if an item is shown when user typed some text - * into the ComboBox - * @param listDataProvider the list data provider to use, not null - */ - @Override - public void setDataProvider(ComboBox.ItemFilter itemFilter, - ListDataProvider listDataProvider) { - Objects.requireNonNull(listDataProvider, - "List data provider cannot be null"); - - setDataProvider(listDataProvider, - filterText -> item -> itemFilter.test(item, filterText)); - } - /** - * Sets the item label generator that is used to produce the strings shown - * in the combo box for each item. By default, - * {@link String#valueOf(Object)} is used. - *

- * - * @param itemLabelGenerator the item label provider to use, not null - */ - public void setItemLabelGenerator(ItemLabelGenerator itemLabelGenerator) { - comboBox.setItemLabelGenerator(itemLabelGenerator); - } - /** - * Set the label of the field - * - * @param label label of the field - */ - @Override - public void setLabel(String label) { - comboBox.setLabel(label); - } - /** - * Copy the selected value of the grid into the field - */ - @Override - @ClientCallable - protected void copyFieldValueFromGrid() { - comboBox.setValue(getGrid().getSelectedItems()); - } - - /** - * Copy the selected value of the field into the grid - */ - @Override - @ClientCallable - protected void copyFieldValueToGrid() { - for (T value : comboBox.getValue()) { - getGrid().select(value); - } - } } From 02dc1cf7cdfff92bc4ee4ed6f6a2fce4b0121a5a Mon Sep 17 00:00:00 2001 From: Jean-Christophe Gueriaud Date: Thu, 7 Oct 2021 13:30:14 +0300 Subject: [PATCH 4/4] Bump the version --- lookup-field-flow-demo/pom.xml | 4 ++-- lookup-field-flow/pom.xml | 2 +- .../componentfactory/lookupfield/AbstractLookupField.java | 2 +- pom.xml | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lookup-field-flow-demo/pom.xml b/lookup-field-flow-demo/pom.xml index 8133969..be69276 100644 --- a/lookup-field-flow-demo/pom.xml +++ b/lookup-field-flow-demo/pom.xml @@ -6,7 +6,7 @@ com.vaadin.componentfactory lookup-field-flow-demo - 1.1.1 + 1.2.0 Lookup Field Demo war @@ -96,7 +96,7 @@ com.vaadin.componentfactory lookup-field-flow - 1.1.1 + 1.2.0 diff --git a/lookup-field-flow/pom.xml b/lookup-field-flow/pom.xml index a78eaf0..d455896 100644 --- a/lookup-field-flow/pom.xml +++ b/lookup-field-flow/pom.xml @@ -6,7 +6,7 @@ com.vaadin.componentfactory lookup-field-flow - 1.1.1 + 1.2.0 jar Lookup Field diff --git a/lookup-field-flow/src/main/java/com/vaadin/componentfactory/lookupfield/AbstractLookupField.java b/lookup-field-flow/src/main/java/com/vaadin/componentfactory/lookupfield/AbstractLookupField.java index 4dfbf4b..c53ebbd 100644 --- a/lookup-field-flow/src/main/java/com/vaadin/componentfactory/lookupfield/AbstractLookupField.java +++ b/lookup-field-flow/src/main/java/com/vaadin/componentfactory/lookupfield/AbstractLookupField.java @@ -36,7 +36,7 @@ @Uses(value = EnhancedDialog.class) @Tag("vcf-lookup-field") @JsModule("@vaadin-component-factory/vcf-lookup-field") -@NpmPackage(value = "@vaadin-component-factory/vcf-lookup-field", version = "1.1.2") +@NpmPackage(value = "@vaadin-component-factory/vcf-lookup-field", version = "1.2.0") public abstract class AbstractLookupField & HasValue, ComponentT extends AbstractLookupField, FilterType> extends Div implements HasFilterableDataProvider, diff --git a/pom.xml b/pom.xml index eb4e549..cb9850d 100755 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ com.vaadin.componentfactory lookup-field-flow-root - 1.1.1 + 1.2.0 pom lookup-field-flow @@ -18,7 +18,7 @@ 1.8 UTF-8 UTF-8 - 1.1.1 + 1.2.0 2020