From 199e613e62031a56b838f7a41ea0560ad34e4cdc Mon Sep 17 00:00:00 2001 From: Felipe Lang Date: Fri, 7 Jul 2023 12:34:56 -0300 Subject: [PATCH] feat: add lazy support to available items grid Close #130 --- .../addons/twincolgrid/BaseLazyFilter.java | 19 + .../twincolgrid/EagerFilterConfiguration.java | 26 ++ .../twincolgrid/EagerFilterableColumn.java | 25 ++ .../addons/twincolgrid/EagerTwinColModel.java | 95 +++++ .../twincolgrid/FilterConfiguration.java | 36 ++ .../addons/twincolgrid/FilterableColumn.java | 14 + .../twincolgrid/FilterableTwinColumn.java | 8 +- .../vaadin/addons/twincolgrid/LazyFilter.java | 23 ++ .../twincolgrid/LazyFilterConfiguration.java | 33 ++ .../twincolgrid/LazyFilterableColumn.java | 38 ++ .../addons/twincolgrid/LazyTwinColModel.java | 101 +++++ .../addons/twincolgrid/LegacyTwinColGrid.java | 105 +++++ .../addons/twincolgrid/TwinColGrid.java | 389 +++++++----------- .../addons/twincolgrid/TwinColModel.java | 121 ++++++ .../vaadin/addons/twincolgrid/Book.java | 32 +- .../vaadin/addons/twincolgrid/BookFilter.java | 14 + .../addons/twincolgrid/BookService.java | 95 +++++ .../vaadin/addons/twincolgrid/BoundDemo.java | 51 ++- .../addons/twincolgrid/DoubleClickDemo.java | 31 +- .../addons/twincolgrid/DragAndDropDemo.java | 80 ++-- .../addons/twincolgrid/FilterableDemo.java | 48 ++- .../twincolgrid/LazyFilterableDemo.java | 120 ++++++ .../addons/twincolgrid/OrientationDemo.java | 32 +- .../addons/twincolgrid/TwincolDemoView.java | 1 + 24 files changed, 1149 insertions(+), 388 deletions(-) create mode 100644 src/main/java/com/flowingcode/vaadin/addons/twincolgrid/BaseLazyFilter.java create mode 100644 src/main/java/com/flowingcode/vaadin/addons/twincolgrid/EagerFilterConfiguration.java create mode 100644 src/main/java/com/flowingcode/vaadin/addons/twincolgrid/EagerFilterableColumn.java create mode 100644 src/main/java/com/flowingcode/vaadin/addons/twincolgrid/EagerTwinColModel.java create mode 100644 src/main/java/com/flowingcode/vaadin/addons/twincolgrid/FilterConfiguration.java create mode 100644 src/main/java/com/flowingcode/vaadin/addons/twincolgrid/FilterableColumn.java create mode 100644 src/main/java/com/flowingcode/vaadin/addons/twincolgrid/LazyFilter.java create mode 100644 src/main/java/com/flowingcode/vaadin/addons/twincolgrid/LazyFilterConfiguration.java create mode 100644 src/main/java/com/flowingcode/vaadin/addons/twincolgrid/LazyFilterableColumn.java create mode 100644 src/main/java/com/flowingcode/vaadin/addons/twincolgrid/LazyTwinColModel.java create mode 100644 src/main/java/com/flowingcode/vaadin/addons/twincolgrid/TwinColModel.java create mode 100644 src/test/java/com/flowingcode/vaadin/addons/twincolgrid/BookFilter.java create mode 100644 src/test/java/com/flowingcode/vaadin/addons/twincolgrid/BookService.java create mode 100644 src/test/java/com/flowingcode/vaadin/addons/twincolgrid/LazyFilterableDemo.java diff --git a/src/main/java/com/flowingcode/vaadin/addons/twincolgrid/BaseLazyFilter.java b/src/main/java/com/flowingcode/vaadin/addons/twincolgrid/BaseLazyFilter.java new file mode 100644 index 0000000..bafd820 --- /dev/null +++ b/src/main/java/com/flowingcode/vaadin/addons/twincolgrid/BaseLazyFilter.java @@ -0,0 +1,19 @@ +package com.flowingcode.vaadin.addons.twincolgrid; + +import com.vaadin.flow.data.provider.SortOrder; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class BaseLazyFilter implements LazyFilter { + + private Collection selectedItems = new HashSet<>(); + + private List> sorting = new ArrayList<>(); + +} diff --git a/src/main/java/com/flowingcode/vaadin/addons/twincolgrid/EagerFilterConfiguration.java b/src/main/java/com/flowingcode/vaadin/addons/twincolgrid/EagerFilterConfiguration.java new file mode 100644 index 0000000..f23911a --- /dev/null +++ b/src/main/java/com/flowingcode/vaadin/addons/twincolgrid/EagerFilterConfiguration.java @@ -0,0 +1,26 @@ +package com.flowingcode.vaadin.addons.twincolgrid; + +import com.flowingcode.vaadin.addons.twincolgrid.TwinColModel.TwinColModelMode; + +/** + * Eager filter configuration for {@link TwinColGrid}. + * + * @param + */ +public class EagerFilterConfiguration extends FilterConfiguration> { + + @SafeVarargs + public EagerFilterConfiguration(EagerFilterableColumn... columns) { + super(columns); + } + + void apply(TwinColGrid grid) { + filteredColumns.forEach(grid::addFilterableColumn); + } + + @Override + boolean supports(TwinColModelMode mode) { + return TwinColModelMode.EAGER.equals(mode); + } + +} diff --git a/src/main/java/com/flowingcode/vaadin/addons/twincolgrid/EagerFilterableColumn.java b/src/main/java/com/flowingcode/vaadin/addons/twincolgrid/EagerFilterableColumn.java new file mode 100644 index 0000000..005c556 --- /dev/null +++ b/src/main/java/com/flowingcode/vaadin/addons/twincolgrid/EagerFilterableColumn.java @@ -0,0 +1,25 @@ +package com.flowingcode.vaadin.addons.twincolgrid; + +import com.vaadin.flow.function.SerializableBiPredicate; +import lombok.Getter; + +/** + * Enables in memory filtering support to column. + * + * @param + */ +@Getter +public class EagerFilterableColumn extends FilterableColumn { + + /** + * filter condition to apply to column values. + */ + private final SerializableBiPredicate filterCondition; + + public EagerFilterableColumn(TwinColumn column, String filterPlaceholder, + boolean enableClearButton, SerializableBiPredicate filterCondition) { + super(column, filterPlaceholder, enableClearButton); + this.filterCondition = filterCondition; + } + +} diff --git a/src/main/java/com/flowingcode/vaadin/addons/twincolgrid/EagerTwinColModel.java b/src/main/java/com/flowingcode/vaadin/addons/twincolgrid/EagerTwinColModel.java new file mode 100644 index 0000000..f2fec2d --- /dev/null +++ b/src/main/java/com/flowingcode/vaadin/addons/twincolgrid/EagerTwinColModel.java @@ -0,0 +1,95 @@ +package com.flowingcode.vaadin.addons.twincolgrid; + +import com.vaadin.flow.component.grid.Grid; +import com.vaadin.flow.component.grid.Grid.Column; +import com.vaadin.flow.component.grid.dnd.GridDropLocation; +import com.vaadin.flow.component.textfield.TextField; +import com.vaadin.flow.data.provider.DataProvider; +import com.vaadin.flow.data.provider.InMemoryDataProvider; +import com.vaadin.flow.data.provider.ListDataProvider; +import com.vaadin.flow.data.value.ValueChangeMode; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import lombok.NonNull; + +/** + * Model that supports {@link InMemoryDataProvider} for {@link TwinColGrid} available and selection + * grids. + * + * @param + */ +class EagerTwinColModel extends TwinColModel> { + + EagerTwinColModel(@NonNull Grid grid, String className) { + super(grid, className); + this.grid.setDataProvider(DataProvider.ofCollection(new ArrayList<>())); + } + + @Override + @SuppressWarnings("unchecked") + ListDataProvider getDataProvider() { + return (ListDataProvider) grid.getDataProvider(); + } + + @Override + void addAll(Collection items) { + getDataProvider().getItems().addAll(items); + } + + @Override + void removeAll(Collection items) { + getDataProvider().getItems().removeAll(items); + } + + @Override + void addFilterableColumn(Column column, EagerFilterableColumn filter) { + TextField filterField = new TextField(); + filterField.setClearButtonVisible(filter.isEnableClearButton()); + filterField.setValueChangeMode(ValueChangeMode.EAGER); + filterField.setSizeFull(); + filterField.setPlaceholder(filter.getFilterPlaceholder()); + filterField.addValueChangeListener( + event -> getDataProvider() + .addFilter(item -> filter.getFilterCondition().test(item, filterField.getValue()))); + + if (headerRow == null) { + setHeaderRow(grid.appendHeaderRow()); + } + + headerRow.getCell(column).setComponent(filterField); + } + + void clear() { + getDataProvider().getItems().clear(); + } + + void addItems(Collection draggedItems, T dropOverItem, GridDropLocation dropLocation) { + if (dropOverItem != null) { + Collection collection = getDataProvider().getItems(); + List list = new ArrayList<>(collection); + int dropIndex = list.indexOf(dropOverItem) + (dropLocation == GridDropLocation.BELOW ? 1 : 0); + list.addAll(dropIndex, draggedItems); + getDataProvider().getItems().clear(); + addAll(list); + } else { + addAll(draggedItems); + } + getDataProvider().refreshAll(); + } + + Collection getItems() { + return getDataProvider().getItems(); + } + + @Override + TwinColModelMode getMode() { + return TwinColModelMode.EAGER; + } + + @Override + boolean supportsAddAll() { + return true; + } + +} diff --git a/src/main/java/com/flowingcode/vaadin/addons/twincolgrid/FilterConfiguration.java b/src/main/java/com/flowingcode/vaadin/addons/twincolgrid/FilterConfiguration.java new file mode 100644 index 0000000..554e823 --- /dev/null +++ b/src/main/java/com/flowingcode/vaadin/addons/twincolgrid/FilterConfiguration.java @@ -0,0 +1,36 @@ +package com.flowingcode.vaadin.addons.twincolgrid; + +import com.flowingcode.vaadin.addons.twincolgrid.TwinColModel.TwinColModelMode; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; + +public abstract class FilterConfiguration> { + + protected final Collection filteredColumns = new ArrayList<>(); + + @SafeVarargs + public FilterConfiguration(C... columns) { + filteredColumns.addAll(Arrays.asList(columns)); + } + + public void addFilteredColumn(C column) { + filteredColumns.add(column); + } + + /** + * Applies this {@link FilterConfiguration} to grid. + * + * @param grid + */ + abstract void apply(TwinColGrid grid); + + /** + * Checks if {@link FilterConfiguration} supports the given {@link TwinColModelMode} + * + * @param mode mode to check. + * @return true mode is supported. + */ + abstract boolean supports(TwinColModelMode mode); + +} diff --git a/src/main/java/com/flowingcode/vaadin/addons/twincolgrid/FilterableColumn.java b/src/main/java/com/flowingcode/vaadin/addons/twincolgrid/FilterableColumn.java new file mode 100644 index 0000000..dbce165 --- /dev/null +++ b/src/main/java/com/flowingcode/vaadin/addons/twincolgrid/FilterableColumn.java @@ -0,0 +1,14 @@ +package com.flowingcode.vaadin.addons.twincolgrid; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +@Getter +public abstract class FilterableColumn { + + private final TwinColumn column; + private final String filterPlaceholder; + private final boolean enableClearButton; + +} diff --git a/src/main/java/com/flowingcode/vaadin/addons/twincolgrid/FilterableTwinColumn.java b/src/main/java/com/flowingcode/vaadin/addons/twincolgrid/FilterableTwinColumn.java index 0e002cf..81690eb 100644 --- a/src/main/java/com/flowingcode/vaadin/addons/twincolgrid/FilterableTwinColumn.java +++ b/src/main/java/com/flowingcode/vaadin/addons/twincolgrid/FilterableTwinColumn.java @@ -22,8 +22,8 @@ public FilterableTwinColumn(Column availableColumn, Column selectionColumn * @see TextField#setPlaceholder(String) */ public FilterableTwinColumn setFilterPlaceholder(String filterPlaceholder) { - TwinColGrid.getFilterTextField(getAvailableColumn()).setPlaceholder(filterPlaceholder); - TwinColGrid.getFilterTextField(getSelectionColumn()).setPlaceholder(filterPlaceholder); + LegacyTwinColGrid.getFilterTextField(getAvailableColumn()).setPlaceholder(filterPlaceholder); + LegacyTwinColGrid.getFilterTextField(getSelectionColumn()).setPlaceholder(filterPlaceholder); return this; } @@ -33,8 +33,8 @@ public FilterableTwinColumn setFilterPlaceholder(String filterPlaceholder) { * @see TextField#setClearButtonVisible(boolean) */ public FilterableTwinColumn setClearButtonVisible(boolean clearButtonVisible) { - TwinColGrid.getFilterTextField(getAvailableColumn()).setClearButtonVisible(clearButtonVisible); - TwinColGrid.getFilterTextField(getSelectionColumn()).setClearButtonVisible(clearButtonVisible); + LegacyTwinColGrid.getFilterTextField(getAvailableColumn()).setClearButtonVisible(clearButtonVisible); + LegacyTwinColGrid.getFilterTextField(getSelectionColumn()).setClearButtonVisible(clearButtonVisible); return this; } diff --git a/src/main/java/com/flowingcode/vaadin/addons/twincolgrid/LazyFilter.java b/src/main/java/com/flowingcode/vaadin/addons/twincolgrid/LazyFilter.java new file mode 100644 index 0000000..cfbaee4 --- /dev/null +++ b/src/main/java/com/flowingcode/vaadin/addons/twincolgrid/LazyFilter.java @@ -0,0 +1,23 @@ +package com.flowingcode.vaadin.addons.twincolgrid; + +import com.vaadin.flow.data.provider.SortOrder; +import java.util.Collection; +import java.util.List; + +public interface LazyFilter { + + /** + * Items already selected. + * + * @return + */ + Collection getSelectedItems(); + + /** + * Sorting criterias. + * + * @return + */ + List> getSorting(); + +} diff --git a/src/main/java/com/flowingcode/vaadin/addons/twincolgrid/LazyFilterConfiguration.java b/src/main/java/com/flowingcode/vaadin/addons/twincolgrid/LazyFilterConfiguration.java new file mode 100644 index 0000000..d87a9ff --- /dev/null +++ b/src/main/java/com/flowingcode/vaadin/addons/twincolgrid/LazyFilterConfiguration.java @@ -0,0 +1,33 @@ +package com.flowingcode.vaadin.addons.twincolgrid; + +import com.flowingcode.vaadin.addons.twincolgrid.TwinColModel.TwinColModelMode; +import lombok.RequiredArgsConstructor; + +/** + * Lazy filter configuration for {@link TwinColGrid}. + * + * @param + */ +@RequiredArgsConstructor +public class LazyFilterConfiguration extends FilterConfiguration> { + + private final LazyFilter lazyFilter; + + @SafeVarargs + public LazyFilterConfiguration(LazyFilter lazyFilter, LazyFilterableColumn... columns) { + super(columns); + this.lazyFilter = lazyFilter; + } + + @Override + void apply(TwinColGrid grid) { + filteredColumns.forEach(grid::addFilterableColumn); + grid.setLazyFilter(lazyFilter); + } + + @Override + boolean supports(TwinColModelMode mode) { + return TwinColModelMode.LAZY.equals(mode); + } + +} diff --git a/src/main/java/com/flowingcode/vaadin/addons/twincolgrid/LazyFilterableColumn.java b/src/main/java/com/flowingcode/vaadin/addons/twincolgrid/LazyFilterableColumn.java new file mode 100644 index 0000000..3b91990 --- /dev/null +++ b/src/main/java/com/flowingcode/vaadin/addons/twincolgrid/LazyFilterableColumn.java @@ -0,0 +1,38 @@ +package com.flowingcode.vaadin.addons.twincolgrid; + +import com.vaadin.flow.function.SerializableBiPredicate; +import com.vaadin.flow.function.SerializableConsumer; +import lombok.Getter; + +/** + * Enables lazy filtering support to column. + * + * @param + */ +@Getter +public class LazyFilterableColumn extends FilterableColumn { + + /** + * filter bean field to store the query string. + */ + private final SerializableConsumer lazyFilterField; + + /** + * filter condition to apply to column values in selection grid. + */ + private final SerializableBiPredicate eagerFilterCondition; + + public LazyFilterableColumn(TwinColumn column, String filterPlaceholder, + boolean enableClearButton, SerializableConsumer lazyFilterField, + SerializableBiPredicate eagerFilterCondition) { + super(column, filterPlaceholder, enableClearButton); + this.lazyFilterField = lazyFilterField; + this.eagerFilterCondition = eagerFilterCondition; + } + + public EagerFilterableColumn asEager() { + return new EagerFilterableColumn<>(super.getColumn(), super.getFilterPlaceholder(), + super.isEnableClearButton(), eagerFilterCondition); + } + +} diff --git a/src/main/java/com/flowingcode/vaadin/addons/twincolgrid/LazyTwinColModel.java b/src/main/java/com/flowingcode/vaadin/addons/twincolgrid/LazyTwinColModel.java new file mode 100644 index 0000000..d19fd62 --- /dev/null +++ b/src/main/java/com/flowingcode/vaadin/addons/twincolgrid/LazyTwinColModel.java @@ -0,0 +1,101 @@ +package com.flowingcode.vaadin.addons.twincolgrid; + +import com.vaadin.flow.component.grid.Grid; +import com.vaadin.flow.component.grid.Grid.Column; +import com.vaadin.flow.component.grid.dnd.GridDropLocation; +import com.vaadin.flow.component.textfield.TextField; +import com.vaadin.flow.data.provider.BackEndDataProvider; +import com.vaadin.flow.data.provider.ConfigurableFilterDataProvider; +import com.vaadin.flow.data.provider.DataProvider; +import com.vaadin.flow.data.value.ValueChangeMode; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import lombok.NonNull; + +/** + * Model that supports {@link BackEndDataProvider} for {@link TwinColGrid} available grid. + * + * @param + */ +class LazyTwinColModel extends TwinColModel> { + + private final ConfigurableFilterDataProvider> dataProviderWrapper; + private LazyFilter lazyFilter; + + LazyTwinColModel(@NonNull Grid grid, String className) { + super(grid, className); + + lazyFilter = new BaseLazyFilter<>(); + dataProviderWrapper = getDataProvider().withConfigurableFilter(); + dataProviderWrapper.setFilter(lazyFilter); + grid.setDataProvider(dataProviderWrapper); + } + + @Override + @SuppressWarnings("unchecked") + DataProvider> getDataProvider() { + return (DataProvider>) grid.getDataProvider(); + } + + @Override + void addAll(Collection items) { + lazyFilter.getSelectedItems().removeAll(items); + } + + @Override + void removeAll(Collection items) { + lazyFilter.getSelectedItems().addAll(items); + } + + @Override + void addFilterableColumn(Column column, LazyFilterableColumn filter) { + TextField filterField = new TextField(); + filterField.setClearButtonVisible(filter.isEnableClearButton()); + filterField.setValueChangeMode(ValueChangeMode.EAGER); + filterField.setSizeFull(); + filterField.setPlaceholder(filter.getFilterPlaceholder()); + filterField.addValueChangeListener( + event -> { + filter.getLazyFilterField().accept(filterField.getValue()); + getDataProvider().refreshAll(); + }); + + if (headerRow == null) { + setHeaderRow(grid.appendHeaderRow()); + } + + headerRow.getCell(column).setComponent(filterField); + } + + void addItems(Collection draggedItems, + T dropOverItem, GridDropLocation dropLocation) { + if (dropOverItem != null) { + Collection collection = lazyFilter.getSelectedItems(); + List list = new ArrayList<>(collection); + int dropIndex = list.indexOf(dropOverItem) + (dropLocation == GridDropLocation.BELOW ? 1 : 0); + list.addAll(dropIndex, draggedItems); + lazyFilter.getSelectedItems().clear(); + addAll(list); + } else { + addAll(draggedItems); + } + grid.getDataProvider().refreshAll(); + } + + > void setFilter(C filter) { + lazyFilter = filter; + dataProviderWrapper.setFilter(filter); + } + + @Override + TwinColModelMode getMode() { + return TwinColModelMode.LAZY; + } + + @Override + boolean supportsAddAll() { + return false; + } + +} diff --git a/src/main/java/com/flowingcode/vaadin/addons/twincolgrid/LegacyTwinColGrid.java b/src/main/java/com/flowingcode/vaadin/addons/twincolgrid/LegacyTwinColGrid.java index 31c5d4d..2f9ef28 100644 --- a/src/main/java/com/flowingcode/vaadin/addons/twincolgrid/LegacyTwinColGrid.java +++ b/src/main/java/com/flowingcode/vaadin/addons/twincolgrid/LegacyTwinColGrid.java @@ -1,21 +1,28 @@ package com.flowingcode.vaadin.addons.twincolgrid; +import com.vaadin.flow.component.ComponentUtil; import com.vaadin.flow.component.ItemLabelGenerator; import com.vaadin.flow.component.grid.Grid; +import com.vaadin.flow.component.grid.Grid.Column; +import com.vaadin.flow.component.textfield.TextField; import com.vaadin.flow.data.provider.ListDataProvider; import com.vaadin.flow.data.renderer.TextRenderer; import com.vaadin.flow.data.selection.SelectionListener; +import com.vaadin.flow.data.value.ValueChangeMode; import com.vaadin.flow.function.SerializableFunction; import java.util.Collection; import java.util.Comparator; import java.util.function.Supplier; import lombok.NonNull; +import org.apache.commons.lang3.StringUtils; /** Implementation of {@code TwinColGrid} with deprecated methods from version 2.9.0. */ @SuppressWarnings("serial") @Deprecated public class LegacyTwinColGrid extends TwinColGrid { + private static final String COMPONENT_DATA_FILTER = "TwinColGrid#filterTF"; + /** @deprecated Use getAvailableGrid() */ @Deprecated protected final Grid leftGrid; @@ -28,6 +35,8 @@ public class LegacyTwinColGrid extends TwinColGrid { /** @deprecated Use getSelectionGrid().getDataProvider() */ @Deprecated protected ListDataProvider rightGridDataProvider; + private boolean explicitHeaderRow = true; + /** Constructs a new TwinColGrid with an empty {@link ListDataProvider}. */ @SuppressWarnings("unchecked") public LegacyTwinColGrid() { @@ -283,6 +292,22 @@ protected void setDataProvider(ListDataProvider dataProvider) { leftGridDataProvider = dataProvider; super.setDataProvider(dataProvider); } + + /** + * Adds a column to each grids. Both columns will use a {@link TextRenderer} and the value + * will be converted to a String by using the provided {@code itemLabelGenerator}. + * + * @param itemLabelGenerator the value provider + * @return the pair of columns + */ + public TwinColumn addColumn(ItemLabelGenerator itemLabelGenerator) { + createFirstHeaderRowIfNeeded(); + Column availableColumn = + getAvailableGrid().addColumn(new TextRenderer<>(itemLabelGenerator)); + Column selectionColumn = + getSelectionGrid().addColumn(new TextRenderer<>(itemLabelGenerator)); + return new TwinColumn<>(availableColumn, selectionColumn); + } /** * Adds a new text column to this {@link Grid} with a value provider. The column will use a @@ -421,4 +446,84 @@ public LegacyTwinColGrid addFilterableColumn(ItemLabelGenerator itemLabelG return addFilterableColumn(itemLabelGenerator, itemLabelGenerator, header, filterPlaceholder, enableClearButton, key); } + + public FilterableTwinColumn addFilterableColumn(ItemLabelGenerator itemLabelGenerator) { + return addFilterableColumn(itemLabelGenerator, itemLabelGenerator); + } + + private void createFirstHeaderRowIfNeeded() { + if (explicitHeaderRow) { + forEachGrid(grid -> { + if (grid.getColumns().isEmpty() && grid.getHeaderRows().isEmpty()) { + grid.appendHeaderRow(); + } + }); + } + } + + /** + * Configure this component to create the first header row (for column header labels). If no + * column will have a header, this property must be set to {@code false}. + * + *

+ * When this property is {@code true} (default), the first column added through this component + * will {@linkplain Grid#appendHeaderRow() append} a header row, which will be the "default header + * row" (used by {@code Column.setHeader}). If no headers are set, then the default header row + * will be empty. + * + *

+ * When this property is {@code false}, then {@code Column.setHeader} will allocate a header row + * when called (which prevents an empty row if no headers are set, but also replaces the filter + * componentes). + * + * @param value whether the first header row will be created when a column is added. + * @return this instance + */ + public TwinColGrid createFirstHeaderRow(boolean value) { + explicitHeaderRow = value; + return this; + } + + public FilterableTwinColumn addFilterableColumn(ItemLabelGenerator itemLabelGenerator, + SerializableFunction filterableValue) { + + createFirstHeaderRowIfNeeded(); + + Column availableColumn = + createFilterableColumn(availableAsEager(), itemLabelGenerator, filterableValue); + Column selectionColumn = + createFilterableColumn(selection, itemLabelGenerator, filterableValue); + + return new FilterableTwinColumn<>(availableColumn, selectionColumn); + } + + private Column createFilterableColumn(EagerTwinColModel side, + ItemLabelGenerator itemLabelGenerator, + SerializableFunction filterableValue) { + Column column = side.grid.addColumn(new TextRenderer<>(itemLabelGenerator)); + TextField filterTF = new TextField(); + + filterTF.addValueChangeListener( + event -> side.getDataProvider() + .addFilter( + filterableEntity -> StringUtils.containsIgnoreCase( + filterableValue.apply(filterableEntity), filterTF.getValue()))); + + if (side.headerRow == null) { + side.headerRow = side.grid.appendHeaderRow(); + } + + side.headerRow.getCell(column).setComponent(filterTF); + + filterTF.setValueChangeMode(ValueChangeMode.EAGER); + filterTF.setSizeFull(); + + ComponentUtil.setData(column, COMPONENT_DATA_FILTER, filterTF); + return column; + } + + static TextField getFilterTextField(Column column) { + return (TextField) ComponentUtil.getData(column, COMPONENT_DATA_FILTER); + } + } diff --git a/src/main/java/com/flowingcode/vaadin/addons/twincolgrid/TwinColGrid.java b/src/main/java/com/flowingcode/vaadin/addons/twincolgrid/TwinColGrid.java index dcf65ca..5773c07 100644 --- a/src/main/java/com/flowingcode/vaadin/addons/twincolgrid/TwinColGrid.java +++ b/src/main/java/com/flowingcode/vaadin/addons/twincolgrid/TwinColGrid.java @@ -20,15 +20,14 @@ package com.flowingcode.vaadin.addons.twincolgrid; +import com.flowingcode.vaadin.addons.twincolgrid.TwinColModel.TwinColModelMode; import com.vaadin.flow.component.AbstractField.ComponentValueChangeEvent; import com.vaadin.flow.component.ClientCallable; import com.vaadin.flow.component.Component; -import com.vaadin.flow.component.ComponentUtil; import com.vaadin.flow.component.HasComponents; import com.vaadin.flow.component.HasSize; import com.vaadin.flow.component.HasValue; import com.vaadin.flow.component.HasValue.ValueChangeEvent; -import com.vaadin.flow.component.ItemLabelGenerator; import com.vaadin.flow.component.button.Button; import com.vaadin.flow.component.dependency.CssImport; import com.vaadin.flow.component.dependency.JsModule; @@ -36,24 +35,19 @@ import com.vaadin.flow.component.grid.Grid.Column; import com.vaadin.flow.component.grid.Grid.SelectionMode; import com.vaadin.flow.component.grid.GridNoneSelectionModel; -import com.vaadin.flow.component.grid.HeaderRow; -import com.vaadin.flow.component.grid.dnd.GridDropLocation; import com.vaadin.flow.component.grid.dnd.GridDropMode; import com.vaadin.flow.component.html.Label; import com.vaadin.flow.component.icon.VaadinIcon; import com.vaadin.flow.component.orderedlayout.HorizontalLayout; import com.vaadin.flow.component.orderedlayout.VerticalLayout; -import com.vaadin.flow.component.textfield.TextField; import com.vaadin.flow.data.provider.DataProvider; +import com.vaadin.flow.data.provider.InMemoryDataProvider; import com.vaadin.flow.data.provider.ListDataProvider; import com.vaadin.flow.data.provider.Query; import com.vaadin.flow.data.renderer.TextRenderer; -import com.vaadin.flow.data.value.ValueChangeMode; import com.vaadin.flow.function.SerializableComparator; -import com.vaadin.flow.function.SerializableFunction; +import com.vaadin.flow.function.ValueProvider; import com.vaadin.flow.shared.Registration; -import java.io.Serializable; -import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashSet; @@ -69,7 +63,6 @@ import java.util.stream.Collectors; import java.util.stream.Stream; import lombok.NonNull; -import org.apache.commons.lang3.StringUtils; @SuppressWarnings("serial") @JsModule(value = "./src/fc-twin-col-grid-auto-resize.js") @@ -79,38 +72,6 @@ public class TwinColGrid extends VerticalLayout implements HasValue>, Set>, HasComponents, HasSize { - private static final class TwinColModel implements Serializable { - final Grid grid; - final Label columnLabel = new Label(); - final VerticalLayout layout; - HeaderRow headerRow; - boolean droppedInsideGrid = false; - boolean allowReordering = false; - Registration moveItemsByDoubleClick; - - TwinColModel(@NonNull Grid grid, String className) { - this.grid = grid; - layout = new VerticalLayout(columnLabel, grid); - - layout.setClassName(className); - grid.setClassName("twincol-grid-items"); - columnLabel.setClassName("twincol-grid-label"); - } - - @SuppressWarnings("unchecked") - ListDataProvider getDataProvider() { - return (ListDataProvider) grid.getDataProvider(); - } - - Collection getItems() { - return getDataProvider().getItems(); - } - - boolean isReorderingEnabled() { - return allowReordering && grid.getSortOrder().isEmpty(); - } - } - /** enumeration of all available orientation for TwinGolGrid component */ public enum Orientation { HORIZONTAL, @@ -119,9 +80,9 @@ public enum Orientation { VERTICAL_REVERSE; } - private final TwinColModel available; + private final TwinColModel available; - private final TwinColModel selection; + protected final EagerTwinColModel selection; private Label captionLabel; @@ -135,8 +96,6 @@ public enum Orientation { private Component buttonContainer; - private Grid draggedGrid; - private Label fakeButtonContainerLabel = new Label(); private Orientation orientation = Orientation.HORIZONTAL; @@ -145,12 +104,6 @@ public enum Orientation { private boolean isFromClient = false; - private boolean explicitHeaderRow = true; - - private static ListDataProvider emptyDataProvider() { - return DataProvider.ofCollection(new LinkedHashSet<>()); - } - /** Constructs a new TwinColGrid with an empty {@link ListDataProvider}. */ public TwinColGrid() { this(Grid::new); @@ -165,6 +118,16 @@ public TwinColGrid(Supplier> gridSupplier) { this(gridSupplier.get(), gridSupplier.get()); } + /** + * Constructs a new TwinColGrid with the given options. + * + * @param options the options, cannot be {@code null} + */ + public TwinColGrid(final Collection options) { + this(); + setDataProvider(DataProvider.ofCollection(new LinkedHashSet<>(options))); + } + /** * Constructs a new empty TwinColGrid, using the specified grids for each side. * @@ -177,21 +140,22 @@ public TwinColGrid(@NonNull Grid availableGrid, @NonNull Grid selectionGri throw new IllegalArgumentException("Grids must be different"); } - available = new TwinColModel<>(availableGrid, "twincol-grid-available"); - selection = new TwinColModel<>(selectionGrid, "twincol-grid-selection"); + if (!(selectionGrid.getDataProvider() instanceof InMemoryDataProvider)) { + throw new IllegalArgumentException("Selection Grid only supports InMemoryDataProvider"); + } + + available = createModel(availableGrid, ""); + selection = new EagerTwinColModel<>(selectionGrid, "twincol-grid-selection"); setClassName("twincol-grid"); setMargin(false); setPadding(false); - setDataProvider(emptyDataProvider()); - ListDataProvider rightGridDataProvider = DataProvider.ofCollection(new LinkedHashSet<>()); - getSelectionGrid().setDataProvider(rightGridDataProvider); - getAvailableGrid().setWidth("100%"); getSelectionGrid().setWidth("100%"); - + + addAllButton.setVisible(addAllButton.isVisible() && available.supportsAddAll()); addAllButton.addClickListener( e -> { List filteredItems = @@ -224,16 +188,8 @@ public TwinColGrid(@NonNull Grid availableGrid, @NonNull Grid selectionGri getElement().getStyle().set("display", "flex"); - forEachSide( - side -> { - side.grid.setSelectionMode(SelectionMode.MULTI); - side.columnLabel.setVisible(false); - side.layout.setSizeFull(); - side.layout.setMargin(false); - side.layout.setPadding(false); - side.layout.setSpacing(false); - }); - + forEachSide(TwinColModel::init); + initMoveItemsByDoubleClick(); add(createContainerLayout()); setSizeUndefined(); @@ -243,6 +199,11 @@ public TwinColGrid(@NonNull Grid availableGrid, @NonNull Grid selectionGri private void initMoveItemsByDoubleClick() { setMoveItemsByDoubleClick(!(this instanceof LegacyTwinColGrid)); } + + private TwinColModel createModel(@NonNull Grid grid, String className) { + return grid.getDataProvider().isInMemory() ? new EagerTwinColModel<>(grid, className) + : new LazyTwinColModel<>(grid, className); + } /** * Sets the component caption. @@ -283,8 +244,8 @@ public TwinColGrid withOrientation(Orientation orientation) { if (this.orientation != orientation) { this.orientation = orientation; updateContainerLayout(); - available.grid.getDataProvider().refreshAll(); - selection.grid.getDataProvider().refreshAll(); + available.getGrid().getDataProvider().refreshAll(); + selection.getGrid().getDataProvider().refreshAll(); } return this; } @@ -294,7 +255,7 @@ public Orientation getOrientation() { } private void updateContainerLayout() { - Component oldContainerComponent = available.layout.getParent().get(); + Component oldContainerComponent = available.getLayout().getParent().get(); Component newContainerComponent = createContainerLayout(); replace(oldContainerComponent, newContainerComponent); } @@ -333,9 +294,9 @@ private HorizontalLayout createHorizontalContainer(boolean reverse) { buttonContainer = getVerticalButtonContainer(); HorizontalLayout hl; if (reverse) { - hl = new HorizontalLayout(selection.layout, buttonContainer, available.layout); + hl = new HorizontalLayout(selection.getLayout(), buttonContainer, available.getLayout()); } else { - hl = new HorizontalLayout(available.layout, buttonContainer, selection.layout); + hl = new HorizontalLayout(available.getLayout(), buttonContainer, selection.getLayout()); } hl.getElement().getStyle().set("min-height", "0px"); hl.getElement().getStyle().set("flex", "1 1 0px"); @@ -348,9 +309,9 @@ private VerticalLayout createVerticalContainer(boolean reverse) { buttonContainer = getHorizontalButtonContainer(); VerticalLayout vl; if (reverse) { - vl = new VerticalLayout(selection.layout, buttonContainer, available.layout); + vl = new VerticalLayout(selection.getLayout(), buttonContainer, available.getLayout()); } else { - vl = new VerticalLayout(available.layout, buttonContainer, selection.layout); + vl = new VerticalLayout(available.getLayout(), buttonContainer, selection.getLayout()); } vl.getElement().getStyle().set("min-width", "0px"); vl.getElement().getStyle().set("flex", "1 1 0px"); @@ -391,7 +352,7 @@ public Grid getSelectionGrid() { return selection.grid; } - private void forEachSide(Consumer> consumer) { + private void forEachSide(Consumer> consumer) { consumer.accept(available); consumer.accept(selection); } @@ -417,20 +378,10 @@ protected void setDataProvider(ListDataProvider dataProvider) { getAvailableGrid().setDataProvider(dataProvider); if (selection.getDataProvider() != null) { selection.getItems().clear(); - selection.getDataProvider().refreshAll(); +// selection.getDataProvider().refreshAll(); } } - /** - * Constructs a new TwinColGrid with the given options. - * - * @param options the options, cannot be {@code null} - */ - public TwinColGrid(final Collection options) { - this(); - setDataProvider(DataProvider.ofCollection(new LinkedHashSet<>(options))); - } - /** * Sets the text shown above the grid with the available items. {@code null} clears the caption. * @@ -438,8 +389,8 @@ public TwinColGrid(final Collection options) { * @return this instance */ public TwinColGrid withAvailableGridCaption(final String caption) { - available.columnLabel.setText(caption); - available.columnLabel.setVisible(true); + available.getColumnLabel().setText(caption); + available.getColumnLabel().setVisible(true); fakeButtonContainerLabel.setVisible(true); return this; } @@ -451,58 +402,24 @@ public TwinColGrid withAvailableGridCaption(final String caption) { * @return this instance */ public TwinColGrid withSelectionGridCaption(final String caption) { - selection.columnLabel.setText(caption); - selection.columnLabel.setVisible(true); + selection.getColumnLabel().setText(caption); + selection.getColumnLabel().setVisible(true); fakeButtonContainerLabel.setVisible(true); return this; } /** - * Configure this component to create the first header row (for column header labels). If no - * column will have a header, this property must be set to {@code false}. - * - *

- * When this property is {@code true} (default), the first column added through this component - * will {@linkplain Grid#appendHeaderRow() append} a header row, which will be the "default header - * row" (used by {@code Column.setHeader}). If no headers are set, then the default header row - * will be empty. - * - *

- * When this property is {@code false}, then {@code Column.setHeader} will allocate a header row - * when called (which prevents an empty row if no headers are set, but also replaces the filter - * componentes). - * - * @param value whether the first header row will be created when a column is added. - * @return this instance - */ - public TwinColGrid createFirstHeaderRow(boolean value) { - explicitHeaderRow = value; - return this; - } - - private void createFirstHeaderRowIfNeeded() { - if (explicitHeaderRow) { - forEachGrid(grid -> { - if (grid.getColumns().isEmpty() && grid.getHeaderRows().isEmpty()) { - grid.appendHeaderRow(); - } - }); - } - } - - /** - * Adds a column to each grids. Both columns will use a {@link TextRenderer} and the value - * will be converted to a String by using the provided {@code itemLabelGenerator}. + * Adds a column to each grids. Both columns will use a {@link TextRenderer} and the value will be + * converted to a String by using the provided {@code itemLabelGenerator}. * - * @param itemLabelGenerator the value provider + * @param valueProvider the value provider * @return the pair of columns */ - public TwinColumn addColumn(ItemLabelGenerator itemLabelGenerator) { - createFirstHeaderRowIfNeeded(); + public TwinColumn addColumn(ValueProvider valueProvider) { Column availableColumn = - getAvailableGrid().addColumn(new TextRenderer<>(itemLabelGenerator)); + getAvailableGrid().addColumn(valueProvider); Column selectionColumn = - getSelectionGrid().addColumn(new TextRenderer<>(itemLabelGenerator)); + getSelectionGrid().addColumn(valueProvider); return new TwinColumn<>(availableColumn, selectionColumn); } @@ -562,7 +479,7 @@ public TwinColGrid withDragAndDropSupport() { * @return The text shown or {@code null} if not set. */ public String getAvailableGridCaption() { - return available.columnLabel.getText(); + return available.getColumnLabel().getText(); } /** @@ -571,7 +488,7 @@ public String getAvailableGridCaption() { * @return The text shown or {@code null} if not set. */ public String getSelectionGridCaption() { - return selection.columnLabel.getText(); + return selection.getColumnLabel().getText(); } /** @@ -676,29 +593,28 @@ public void setRequiredIndicatorVisible(boolean requiredIndicatorVisible) { private void updateSelection( final Set addedItems, final Set removedItems, boolean isFromClient) { this.isFromClient = isFromClient; - available.getItems().addAll(removedItems); - available.getItems().removeAll(addedItems); + available.addAll(removedItems); + available.removeAll(addedItems); - selection.getItems().addAll(addedItems); - selection.getItems().removeAll(removedItems); + selection.addAll(addedItems); + selection.removeAll(removedItems); - forEachGrid( - grid -> { - grid.getDataProvider().refreshAll(); - grid.getSelectionModel().deselectAll(); - }); + forEachGrid(grid -> { + grid.getDataProvider().refreshAll(); + grid.getSelectionModel().deselectAll(); + }); } private void configDragAndDrop( - final TwinColModel sourceModel, final TwinColModel targetModel) { + final TwinColModel sourceModel, final TwinColModel targetModel) { final Set draggedItems = new LinkedHashSet<>(); + Grid draggedGrid = sourceModel.getGrid(); + sourceModel.grid.setRowsDraggable(true); sourceModel.grid.addDragStartListener( event -> { - draggedGrid = sourceModel.grid; - if (!(sourceModel.grid.getSelectionModel() instanceof GridNoneSelectionModel)) { draggedItems.addAll(event.getDraggedItems()); } @@ -711,22 +627,19 @@ private void configDragAndDrop( sourceModel.grid.addDragEndListener( event -> { - if (targetModel.droppedInsideGrid + if (targetModel.isDroppedInsideGrid() && sourceModel.grid == draggedGrid && !draggedItems.isEmpty()) { - final ListDataProvider dragGridSourceDataProvider = sourceModel.getDataProvider(); + sourceModel.removeAll(draggedItems); - dragGridSourceDataProvider.getItems().removeAll(draggedItems); - dragGridSourceDataProvider.refreshAll(); - - targetModel.droppedInsideGrid = false; + targetModel.setDroppedInsideGrid(false); draggedItems.clear(); - sourceModel.grid.deselectAll(); - - sourceModel.grid.setDropMode(null); - targetModel.grid.setDropMode(null); + sourceModel.getGrid().deselectAll(); + sourceModel.getDataProvider().refreshAll(); + sourceModel.getGrid().setDropMode(null); + targetModel.getGrid().setDropMode(null); } draggedItems.clear(); }); @@ -735,9 +648,9 @@ private void configDragAndDrop( event -> { if (!draggedItems.isEmpty()) { isFromClient = true; - targetModel.droppedInsideGrid = true; + targetModel.setDroppedInsideGrid(true); T dropOverItem = event.getDropTargetItem().orElse(null); - addItems(targetModel, draggedItems, dropOverItem, event.getDropLocation()); + targetModel.addItems(draggedItems, dropOverItem, event.getDropLocation()); } }); @@ -752,34 +665,14 @@ private void configDragAndDrop( && !draggedItems.contains(dropOverItem) && !draggedItems.isEmpty()) { isFromClient = true; - sourceModel.getItems().removeAll(draggedItems); - addItems(sourceModel, draggedItems, dropOverItem, event.getDropLocation()); + sourceModel.removeAll(draggedItems); + sourceModel.addItems(draggedItems, dropOverItem, event.getDropLocation()); draggedItems.clear(); - draggedGrid = null; } }); }); } - private void addItems( - TwinColModel model, - Collection draggedItems, - T dropOverItem, - GridDropLocation dropLocation) { - if (dropOverItem != null) { - Collection collection = model.getItems(); - List list = new ArrayList<>(collection); - int dropIndex = list.indexOf(dropOverItem) + (dropLocation == GridDropLocation.BELOW ? 1 : 0); - list.addAll(dropIndex, draggedItems); - model.getItems().clear(); - model.getItems().addAll(list); - model.getDataProvider().refreshAll(); - } else { - model.getItems().addAll(draggedItems); - model.getDataProvider().refreshAll(); - } - } - /** Allow drag-and-drop within the selection grid. */ public TwinColGrid withSelectionGridReordering() { setSelectionGridReorderingAllowed(true); @@ -788,60 +681,12 @@ public TwinColGrid withSelectionGridReordering() { /** Configure whether drag-and-drop within the selection grid is allowed. */ public void setSelectionGridReorderingAllowed(boolean value) { - selection.allowReordering = value; + selection.setAllowReordering(value); } /** Return whether drag-and-drop within the selection grid is allowed. */ public boolean isSelectionGridReorderingAllowed() { - return selection.allowReordering; - } - - private static final String COMPONENT_DATA_FILTER = "TwinColGrid#filterTF"; - - private Column createFilterableColumn(TwinColModel side, - ItemLabelGenerator itemLabelGenerator, - SerializableFunction filterableValue) { - Column column = side.grid.addColumn(new TextRenderer<>(itemLabelGenerator)); - TextField filterTF = new TextField(); - - filterTF.addValueChangeListener( - event -> - side.getDataProvider() - .addFilter( - filterableEntity -> - StringUtils.containsIgnoreCase( - filterableValue.apply(filterableEntity), filterTF.getValue()))); - - if (side.headerRow == null) { - side.headerRow = side.grid.appendHeaderRow(); - } - - side.headerRow.getCell(column).setComponent(filterTF); - - filterTF.setValueChangeMode(ValueChangeMode.EAGER); - filterTF.setSizeFull(); - - ComponentUtil.setData(column, COMPONENT_DATA_FILTER, filterTF); - return column; - } - - static TextField getFilterTextField(Column column) { - return (TextField) ComponentUtil.getData(column, COMPONENT_DATA_FILTER); - } - - public FilterableTwinColumn addFilterableColumn(ItemLabelGenerator itemLabelGenerator) { - return addFilterableColumn(itemLabelGenerator, itemLabelGenerator); - } - - public FilterableTwinColumn addFilterableColumn(ItemLabelGenerator itemLabelGenerator, - SerializableFunction filterableValue) { - - createFirstHeaderRowIfNeeded(); - - Column availableColumn = createFilterableColumn(available, itemLabelGenerator, filterableValue); - Column selectionColumn = createFilterableColumn(selection, itemLabelGenerator, filterableValue); - - return new FilterableTwinColumn<>(availableColumn, selectionColumn); + return selection.isAllowReordering(); } public TwinColGrid selectRowOnClick() { @@ -903,26 +748,22 @@ public void setAutoResize(boolean autoResize) { * @param value if true, a a doubleclick event will immediately move an item to the other grid */ public void setMoveItemsByDoubleClick(boolean value) { - forEachSide( - side -> { - if (value && side.moveItemsByDoubleClick == null) { - side.moveItemsByDoubleClick = - side.grid.addItemDoubleClickListener( - ev -> { - Set item = Collections.singleton(ev.getItem()); - if (side == available) { - updateSelection(item, Collections.emptySet(), true); - } - if (side == selection) { - updateSelection(Collections.emptySet(), item, true); - } - }); + forEachSide(side -> { + if (value && side.getMoveItemsByDoubleClick() == null) { + side.addItemDoubleClickListener(ev -> { + Set item = Collections.singleton(ev.getItem()); + if (side == available) { + updateSelection(item, Collections.emptySet(), true); } - if (!value && side.moveItemsByDoubleClick != null) { - side.moveItemsByDoubleClick.remove(); - side.moveItemsByDoubleClick = null; + if (side == selection) { + updateSelection(Collections.emptySet(), item, true); } }); + } + if (!value) { + side.removeItemDoubleClickListener(); + } + }); } @ClientCallable @@ -933,4 +774,58 @@ private void updateOrientationOnResize(int width, int height) { this.withOrientation(Orientation.HORIZONTAL); } } + + @SuppressWarnings("unchecked") + private LazyTwinColModel availableAsLazy() { + if (!TwinColModelMode.LAZY.equals(available.getMode())) { + throw new IllegalStateException("Available model is not in lazy mode"); + } + return (LazyTwinColModel) available; + } + + @SuppressWarnings("unchecked") + final EagerTwinColModel availableAsEager() { + if (!TwinColModelMode.EAGER.equals(available.getMode())) { + throw new IllegalStateException("Available model is not in eager mode"); + } + return (EagerTwinColModel) available; + } + + /** + * Apply a filter configuration. + * + * @param filter + * @return + */ + public TwinColGrid withFilter(FilterConfiguration filter) { + if (!filter.supports(available.getMode())) { + throw new IllegalArgumentException("TwinColGrid " + available.getMode().toString() + + " mode does not support this type of filter configuration."); + } + filter.apply(this); + return this; + } + + TwinColGrid addFilterableColumn(LazyFilterableColumn filter) { + if (filter.getLazyFilterField() != null) { + availableAsLazy().addFilterableColumn(filter.getColumn().getAvailableColumn(), filter); + } + if (filter.getEagerFilterCondition() != null) { + selection.addFilterableColumn(filter.getColumn().getSelectionColumn(), filter.asEager()); + } + return this; + } + + TwinColGrid addFilterableColumn(EagerFilterableColumn filter) { + if (filter.getFilterCondition() != null) { + availableAsEager().addFilterableColumn(filter.getColumn().getAvailableColumn(), filter); + selection.addFilterableColumn(filter.getColumn().getSelectionColumn(), filter); + } + return this; + } + + > void setLazyFilter(F filter) { + availableAsLazy().setFilter(filter); + } + } diff --git a/src/main/java/com/flowingcode/vaadin/addons/twincolgrid/TwinColModel.java b/src/main/java/com/flowingcode/vaadin/addons/twincolgrid/TwinColModel.java new file mode 100644 index 0000000..db5822f --- /dev/null +++ b/src/main/java/com/flowingcode/vaadin/addons/twincolgrid/TwinColModel.java @@ -0,0 +1,121 @@ +package com.flowingcode.vaadin.addons.twincolgrid; + +import com.vaadin.flow.component.ComponentEventListener; +import com.vaadin.flow.component.grid.Grid; +import com.vaadin.flow.component.grid.Grid.Column; +import com.vaadin.flow.component.grid.Grid.SelectionMode; +import com.vaadin.flow.component.grid.HeaderRow; +import com.vaadin.flow.component.grid.ItemDoubleClickEvent; +import com.vaadin.flow.component.grid.dnd.GridDropLocation; +import com.vaadin.flow.component.html.Label; +import com.vaadin.flow.component.orderedlayout.VerticalLayout; +import com.vaadin.flow.data.provider.DataProvider; +import com.vaadin.flow.shared.Registration; +import java.io.Serializable; +import java.util.Collection; +import lombok.NonNull; + +abstract class TwinColModel> implements Serializable { + + enum TwinColModelMode { + EAGER, LAZY; + } + + protected final Grid grid; + private final Label columnLabel = new Label(); + private final VerticalLayout layout; + protected HeaderRow headerRow; + private boolean droppedInsideGrid = false; + private boolean allowReordering = false; + private Registration moveItemsByDoubleClick; + + TwinColModel(@NonNull Grid grid, String className) { + this.grid = grid; + + layout = new VerticalLayout(columnLabel, grid); + layout.setClassName(className); + grid.setClassName("twincol-grid-items"); + columnLabel.setClassName("twincol-grid-label"); + } + + void init() { + getGrid().setSelectionMode(SelectionMode.MULTI); + getColumnLabel().setVisible(false); + getLayout().setSizeFull(); + getLayout().setMargin(false); + getLayout().setPadding(false); + getLayout().setSpacing(false); + } + + boolean isReorderingEnabled() { + return allowReordering && grid.getSortOrder().isEmpty(); + } + + Grid getGrid() { + return grid; + } + + Label getColumnLabel() { + return columnLabel; + } + + boolean isDroppedInsideGrid() { + return droppedInsideGrid; + } + + void setDroppedInsideGrid(boolean droppedInsideGrid) { + this.droppedInsideGrid = droppedInsideGrid; + } + + void setAllowReordering(boolean allowReordering) { + this.allowReordering = allowReordering; + } + + boolean isAllowReordering() { + return allowReordering; + } + + HeaderRow getHeaderRow() { + return headerRow; + } + + void setHeaderRow(HeaderRow headerRow) { + this.headerRow = headerRow; + } + + Registration getMoveItemsByDoubleClick() { + return moveItemsByDoubleClick; + } + + Registration addItemDoubleClickListener( + ComponentEventListener> listener) { + moveItemsByDoubleClick = grid.addItemDoubleClickListener(listener); + return moveItemsByDoubleClick; + } + + void removeItemDoubleClickListener() { + if (moveItemsByDoubleClick != null) { + moveItemsByDoubleClick.remove(); + moveItemsByDoubleClick = null; + } + } + + VerticalLayout getLayout() { + return layout; + } + + abstract > D getDataProvider(); + + abstract void addAll(Collection items); + + abstract void removeAll(Collection items); + + abstract void addFilterableColumn(Column column, F filter); + + abstract void addItems(Collection draggedItems, T dropOverItem, GridDropLocation dropLocation); + + abstract TwinColModelMode getMode(); + + abstract boolean supportsAddAll(); + +} diff --git a/src/test/java/com/flowingcode/vaadin/addons/twincolgrid/Book.java b/src/test/java/com/flowingcode/vaadin/addons/twincolgrid/Book.java index 66719b7..49ac39a 100644 --- a/src/test/java/com/flowingcode/vaadin/addons/twincolgrid/Book.java +++ b/src/test/java/com/flowingcode/vaadin/addons/twincolgrid/Book.java @@ -19,39 +19,23 @@ */ package com.flowingcode.vaadin.addons.twincolgrid; -import java.util.Objects; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +@RequiredArgsConstructor +@Getter +@EqualsAndHashCode public class Book { private final String isbn; private final String title; - public Book(final String isbn, final String title) { - this.isbn = isbn; - this.title = title; - } - - public String getIsbn() { - return isbn; - } - - public String getTitle() { - return title; - } - - @Override - public int hashCode() { - return Objects.hash(isbn, title); - } - - @Override - public boolean equals(final Object obj) { - return ObjectUtils.equals(this, (Book) obj, Book::getIsbn, Book::getTitle); - } + private final int price; @Override public String toString() { - return "[Book " + isbn + " - " + title + "]"; + return "[Book " + isbn + " - " + title + " - " + price + "]"; } } diff --git a/src/test/java/com/flowingcode/vaadin/addons/twincolgrid/BookFilter.java b/src/test/java/com/flowingcode/vaadin/addons/twincolgrid/BookFilter.java new file mode 100644 index 0000000..bd8b610 --- /dev/null +++ b/src/test/java/com/flowingcode/vaadin/addons/twincolgrid/BookFilter.java @@ -0,0 +1,14 @@ +package com.flowingcode.vaadin.addons.twincolgrid; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class BookFilter extends BaseLazyFilter { + + private String isbn; + + private String title; + +} diff --git a/src/test/java/com/flowingcode/vaadin/addons/twincolgrid/BookService.java b/src/test/java/com/flowingcode/vaadin/addons/twincolgrid/BookService.java new file mode 100644 index 0000000..cc8c439 --- /dev/null +++ b/src/test/java/com/flowingcode/vaadin/addons/twincolgrid/BookService.java @@ -0,0 +1,95 @@ +package com.flowingcode.vaadin.addons.twincolgrid; + +import com.vaadin.flow.data.provider.SortDirection; +import com.vaadin.flow.function.SerializableBiPredicate; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.apache.commons.lang3.RandomStringUtils; +import org.apache.commons.lang3.StringUtils; + +public class BookService { + + public static final int BOOKS_COUNT = 500; + + private final List availableBooks = new ArrayList<>(); + + private final SerializableBiPredicate isbnFilterPredicate = + (book, filter) -> filter == null ? true + : StringUtils.containsIgnoreCase(book.getIsbn(), filter); + + private final SerializableBiPredicate titleFilterPredicate = + (book, filter) -> filter == null ? true + : StringUtils.containsIgnoreCase(book.getTitle(), filter); + + + public BookService() { + initializeData(); + } + + private void initializeData() { + for (int i = 0; i < BOOKS_COUNT; i++) { + availableBooks.add(new Book(RandomStringUtils.randomNumeric(8), "Vaadin Recipes " + i, + (int) (Math.random() * 1000))); + } + } + + @SuppressWarnings("unchecked") + public Stream fetch(int offset, int limit, BookFilter bookFilter) { + List filtered = availableBooks.stream() + .filter(book -> !bookFilter.getSelectedItems().contains(book)) + .filter(book -> isbnFilterPredicate.test(book, bookFilter.getIsbn())) + .filter(book -> titleFilterPredicate.test(book, bookFilter.getTitle())) + .collect(Collectors.toList()); + + Comparator combinedComparator = bookFilter.getSorting().stream() + .map(sorting -> { + Comparator comparator = Comparator + .comparing(item -> toComparable(item, sorting.getSorted())); + return SortDirection.ASCENDING.equals(sorting.getDirection()) ? comparator + : comparator.reversed(); + }) + .reduce(Comparator::thenComparing) + .orElse((a, b) -> 0); + + return filtered.subList(offset, Math.min(filtered.size(), offset + limit)).stream() + .sorted(combinedComparator); + } + + private Comparable toComparable(Book book, String fieldName) { + Field sortByField; + try { + + sortByField = Book.class.getDeclaredField(fieldName); + sortByField.setAccessible(true); + Object fieldValue = sortByField.get(book); + + // This check still passes if the type of fieldValue implements Comparable, + // where U is an unrelated type from the type of fieldValue, but this is the + // best we can do here, since we don't know the type of field at compile time + if (!(fieldValue instanceof Comparable) && fieldValue != null) { + return null; + } + return (Comparable) fieldValue; + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + public int count(BookFilter bookFilter) { + return (int) availableBooks.stream() + .filter(book -> !bookFilter.getSelectedItems().contains(book)) + .filter(book -> isbnFilterPredicate.test(book, bookFilter.getIsbn())) + .filter(book -> titleFilterPredicate.test(book, bookFilter.getTitle())) + .count(); + } + + public Book getAny() { + int index = (int) (Math.random() * (BookService.BOOKS_COUNT - 1)); + return availableBooks.get(index); + } +} diff --git a/src/test/java/com/flowingcode/vaadin/addons/twincolgrid/BoundDemo.java b/src/test/java/com/flowingcode/vaadin/addons/twincolgrid/BoundDemo.java index 56c99d1..25855d8 100644 --- a/src/test/java/com/flowingcode/vaadin/addons/twincolgrid/BoundDemo.java +++ b/src/test/java/com/flowingcode/vaadin/addons/twincolgrid/BoundDemo.java @@ -53,8 +53,8 @@ public BoundDemo() { .withSizeFull() .selectRowOnClick(); - twinColGrid.addColumn(Book::getIsbn).setComparator(Book::getIsbn).setHeader("ISBN"); - twinColGrid.addColumn(Book::getTitle).setComparator(Book::getTitle).setHeader("Title"); + twinColGrid.addColumn(Book::getIsbn).setHeader("ISBN").setSortable(true); + twinColGrid.addColumn(Book::getTitle).setHeader("Title").setSortable(true); twinColGrid.setCaption("TwinColGrid demo with Binder and row select without checkbox"); final Binder binder = new Binder<>(); @@ -62,38 +62,33 @@ public BoundDemo() { binder.setBean(library); add(twinColGrid); - add( - new Button( - "Get values", - ev -> { - binder - .getBean() - .getBooks() - .forEach(book -> Notification.show(book.getTitle(), 3000, Position.BOTTOM_START)); - })); + add(new Button("Get values", ev -> { + binder.getBean().getBooks() + .forEach(book -> Notification.show(book.getTitle(), 3000, Position.BOTTOM_START)); + })); - add( - new Button( - "Clear TwinColGrid", - ev -> { - twinColGrid.clear(); - })); + add(new Button("Clear TwinColGrid", ev -> { + twinColGrid.clear(); + })); setSizeFull(); } private void initializeData() { - selectedBooks.add(new Book("1478375108", "Vaadin Recipes")); - selectedBooks.add(new Book("9789526800677", "Book of Vaadin: Volume 2 ")); + selectedBooks.add(new Book("1478375108", "Vaadin Recipes", 222)); + selectedBooks.add(new Book("9789526800677", "Book of Vaadin: Volume 2 ", 121)); - availableBooks.add(new Book("1478375108", "Vaadin Recipes")); - availableBooks.add(new Book("9781849515221", "Learning Vaadin")); - availableBooks.add( - new Book("9781782162261", "Vaadin 7 UI Design By Example: Beginner\u2019s Guide")); - availableBooks.add(new Book("9781849518802", "Vaadin 7 Cookbook")); - availableBooks.add(new Book("9526800605", "Book of Vaadin: 7th Edition, 1st Revision")); - availableBooks.add(new Book("9789526800677", "Book of Vaadin: Volume 2 ")); - availableBooks.add(new Book("9529267533", "Book of Vaadin")); - availableBooks.add(new Book("1782169776", "Learning Vaadin 7, Second Edition")); + + availableBooks.add(new Book("1478375108", "Vaadin Recipes", 232)); + availableBooks.add(new Book("9781849515221", "Learning Vaadin", 333)); + availableBooks + .add( + new Book("9781782162261", "Vaadin 7 UI Design By Example: Beginner\u2019s Guide", 991)); + availableBooks.add(new Book("9781849518802", "Vaadin 7 Cookbook", 121)); + availableBooks.add(new Book("9526800605", "Book of Vaadin: 7th Edition, 1st Revision", 244)); + availableBooks.add(new Book("9789526800677", "Book of Vaadin: Volume 2 ", 555)); + availableBooks.add(new Book("9529267533", "Book of Vaadin", 666)); + availableBooks.add(new Book("1782169776", "Learning Vaadin 7, Second Edition", 423)); } + } diff --git a/src/test/java/com/flowingcode/vaadin/addons/twincolgrid/DoubleClickDemo.java b/src/test/java/com/flowingcode/vaadin/addons/twincolgrid/DoubleClickDemo.java index 4e2aeeb..4782394 100644 --- a/src/test/java/com/flowingcode/vaadin/addons/twincolgrid/DoubleClickDemo.java +++ b/src/test/java/com/flowingcode/vaadin/addons/twincolgrid/DoubleClickDemo.java @@ -48,25 +48,30 @@ public DoubleClickDemo() { .withSizeFull() .selectRowOnClick(); - twinColGrid.addColumn(Book::getIsbn).setComparator(Book::getIsbn).setHeader("ISBN"); - twinColGrid.addColumn(Book::getTitle).setComparator(Book::getTitle).setHeader("Title"); + twinColGrid.addColumn(Book::getIsbn).setHeader("ISBN"); + twinColGrid.addColumn(Book::getTitle).setHeader("Title"); twinColGrid.setValue(selectedBooks); + twinColGrid.setMoveItemsByDoubleClick(true); add(new Span("Move items by double click"), twinColGrid); setSizeFull(); } private void initializeData() { - selectedBooks.add(new Book("1478375108", "Vaadin Recipes")); - selectedBooks.add(new Book("9789526800677", "Book of Vaadin: Volume 2 ")); - availableBooks.add(new Book("1478375108", "Vaadin Recipes")); - availableBooks.add(new Book("9781849515221", "Learning Vaadin")); - availableBooks.add( - new Book("9781782162261", "Vaadin 7 UI Design By Example: Beginner\u2019s Guide")); - availableBooks.add(new Book("9781849518802", "Vaadin 7 Cookbook")); - availableBooks.add(new Book("9526800605", "Book of Vaadin: 7th Edition, 1st Revision")); - availableBooks.add(new Book("9789526800677", "Book of Vaadin: Volume 2 ")); - availableBooks.add(new Book("9529267533", "Book of Vaadin")); - availableBooks.add(new Book("1782169776", "Learning Vaadin 7, Second Edition")); + selectedBooks.add(new Book("1478375108", "Vaadin Recipes", 222)); + selectedBooks.add(new Book("9789526800677", "Book of Vaadin: Volume 2 ", 121)); + + + availableBooks.add(new Book("1478375108", "Vaadin Recipes", 232)); + availableBooks.add(new Book("9781849515221", "Learning Vaadin", 333)); + availableBooks + .add( + new Book("9781782162261", "Vaadin 7 UI Design By Example: Beginner\u2019s Guide", 991)); + availableBooks.add(new Book("9781849518802", "Vaadin 7 Cookbook", 121)); + availableBooks.add(new Book("9526800605", "Book of Vaadin: 7th Edition, 1st Revision", 244)); + availableBooks.add(new Book("9789526800677", "Book of Vaadin: Volume 2 ", 555)); + availableBooks.add(new Book("9529267533", "Book of Vaadin", 666)); + availableBooks.add(new Book("1782169776", "Learning Vaadin 7, Second Edition", 423)); } + } diff --git a/src/test/java/com/flowingcode/vaadin/addons/twincolgrid/DragAndDropDemo.java b/src/test/java/com/flowingcode/vaadin/addons/twincolgrid/DragAndDropDemo.java index 9658eff..6da1c3d 100644 --- a/src/test/java/com/flowingcode/vaadin/addons/twincolgrid/DragAndDropDemo.java +++ b/src/test/java/com/flowingcode/vaadin/addons/twincolgrid/DragAndDropDemo.java @@ -48,29 +48,23 @@ public class DragAndDropDemo extends VerticalLayout { public DragAndDropDemo() { initializeData(); - twinColGrid = - new TwinColGrid<>(availableBooks) - .withAvailableGridCaption("Available books") - .withSelectionGridCaption("Added books") - .withoutAddAllButton() - .withSizeFull() - .withDragAndDropSupport() - .withSelectionGridReordering() - .selectRowOnClick(); - - twinColGrid.addColumn(Book::getIsbn).setComparator(Book::getIsbn).setHeader("ISBN"); - twinColGrid.addColumn(Book::getTitle).setComparator(Book::getTitle).setHeader("Title"); - + twinColGrid = new TwinColGrid<>(availableBooks) + .withAvailableGridCaption("Available books") + .withSelectionGridCaption("Added books") + .withoutAddAllButton() + .withSizeFull() + .withDragAndDropSupport() + .withSelectionGridReordering() + .selectRowOnClick(); + + twinColGrid.addColumn(Book::getIsbn).setHeader("ISBN").setSortable(true); + twinColGrid.addColumn(Book::getTitle).setHeader("Title").setSortable(true); twinColGrid.setCaption("TwinColGrid demo with drag and drop support"); twinColGrid.setValue(selectedBooks); final Label countLabel = new Label("Selected items in left grid: 0"); - twinColGrid - .getAvailableGrid() - .addSelectionListener( - e -> - countLabel.setText( - "Selected items in left grid: " + e.getAllSelectedItems().size())); + twinColGrid.getAvailableGrid().addSelectionListener( + e -> countLabel.setText("Selected items in left grid: " + e.getAllSelectedItems().size())); twinColGrid.addValueChangeListener(e -> countLabel.setText("Selected items in left grid: 0")); add(twinColGrid, countLabel); @@ -80,18 +74,20 @@ public DragAndDropDemo() { } private void initializeData() { - selectedBooks.add(new Book("1478375108", "Vaadin Recipes")); - selectedBooks.add(new Book("9789526800677", "Book of Vaadin: Volume 2 ")); - - availableBooks.add(new Book("1478375108", "Vaadin Recipes")); - availableBooks.add(new Book("9781849515221", "Learning Vaadin")); - availableBooks.add( - new Book("9781782162261", "Vaadin 7 UI Design By Example: Beginner\u2019s Guide")); - availableBooks.add(new Book("9781849518802", "Vaadin 7 Cookbook")); - availableBooks.add(new Book("9526800605", "Book of Vaadin: 7th Edition, 1st Revision")); - availableBooks.add(new Book("9789526800677", "Book of Vaadin: Volume 2 ")); - availableBooks.add(new Book("9529267533", "Book of Vaadin")); - availableBooks.add(new Book("1782169776", "Learning Vaadin 7, Second Edition")); + selectedBooks.add(new Book("1478375108", "Vaadin Recipes", 222)); + selectedBooks.add(new Book("9789526800677", "Book of Vaadin: Volume 2 ", 121)); + + + availableBooks.add(new Book("1478375108", "Vaadin Recipes", 232)); + availableBooks.add(new Book("9781849515221", "Learning Vaadin", 333)); + availableBooks + .add( + new Book("9781782162261", "Vaadin 7 UI Design By Example: Beginner\u2019s Guide", 991)); + availableBooks.add(new Book("9781849518802", "Vaadin 7 Cookbook", 121)); + availableBooks.add(new Book("9526800605", "Book of Vaadin: 7th Edition, 1st Revision", 244)); + availableBooks.add(new Book("9789526800677", "Book of Vaadin: Volume 2 ", 555)); + availableBooks.add(new Book("9529267533", "Book of Vaadin", 666)); + availableBooks.add(new Book("1782169776", "Learning Vaadin 7, Second Edition", 423)); } private void addReorderingToggle() { @@ -99,18 +95,16 @@ private void addReorderingToggle() { Span description = new Span("(Reordering is disabled while the grid is sorted)"); description.setVisible(false); - SerializableRunnable refresh = - () -> { - boolean sorted = !twinColGrid.getSelectionGrid().getSortOrder().isEmpty(); - boolean allowed = twinColGrid.isSelectionGridReorderingAllowed(); - description.setVisible(sorted && allowed); - }; - - checkbox.addValueChangeListener( - ev -> { - twinColGrid.setSelectionGridReorderingAllowed(ev.getValue()); - refresh.run(); - }); + SerializableRunnable refresh = () -> { + boolean sorted = !twinColGrid.getSelectionGrid().getSortOrder().isEmpty(); + boolean allowed = twinColGrid.isSelectionGridReorderingAllowed(); + description.setVisible(sorted && allowed); + }; + + checkbox.addValueChangeListener(ev -> { + twinColGrid.setSelectionGridReorderingAllowed(ev.getValue()); + refresh.run(); + }); twinColGrid.getSelectionGrid().addSortListener(ev -> refresh.run()); add(new Div(checkbox, description)); diff --git a/src/test/java/com/flowingcode/vaadin/addons/twincolgrid/FilterableDemo.java b/src/test/java/com/flowingcode/vaadin/addons/twincolgrid/FilterableDemo.java index 9e4bb61..6b62728 100644 --- a/src/test/java/com/flowingcode/vaadin/addons/twincolgrid/FilterableDemo.java +++ b/src/test/java/com/flowingcode/vaadin/addons/twincolgrid/FilterableDemo.java @@ -28,6 +28,7 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +import org.apache.commons.lang3.StringUtils; @SuppressWarnings("serial") @PageTitle("Filterable") @@ -48,10 +49,25 @@ public FilterableDemo() { .withoutAddAllButton() .withSizeFull(); - twinColGrid.addFilterableColumn(Book::getIsbn).setHeader("ISBN") - .setFilterPlaceholder("ISBN Filter").setClearButtonVisible(true); - twinColGrid.addFilterableColumn(Book::getTitle).setHeader("Title") - .setFilterPlaceholder("Title filter").setClearButtonVisible(false); + TwinColumn isbnColumn = + twinColGrid.addColumn(Book::getIsbn).setHeader("ISBN").setSortable(true); + EagerFilterableColumn isbnFilterableColumn = + new EagerFilterableColumn<>(isbnColumn, "ISBN Filter", true, + (item, filter) -> StringUtils.isBlank(filter) + || StringUtils.containsIgnoreCase(item.getIsbn(), filter)); + + TwinColumn titleColumn = twinColGrid.addColumn(Book::getTitle).setHeader("Title"); + EagerFilterableColumn titleFilterableColumn = + new EagerFilterableColumn<>(titleColumn, "Title Filter", true, + (item, filter) -> StringUtils.isBlank(filter) + || StringUtils.containsIgnoreCase(item.getTitle(), filter)); + + twinColGrid.addColumn(Book::getPrice).setHeader("Price").setSortable(true); + + EagerFilterConfiguration filterConfig = new EagerFilterConfiguration<>(); + filterConfig.addFilteredColumn(isbnFilterableColumn); + filterConfig.addFilteredColumn(titleFilterableColumn); + twinColGrid.withFilter(filterConfig); twinColGrid.setCaption("TwinColGrid demo with filtering support"); twinColGrid.setValue(selectedBooks); @@ -61,17 +77,19 @@ public FilterableDemo() { } private void initializeData() { - selectedBooks.add(new Book("1478375108", "Vaadin Recipes")); - selectedBooks.add(new Book("9789526800677", "Book of Vaadin: Volume 2 ")); + selectedBooks.add(new Book("1478375108", "Vaadin Recipes", 222)); + selectedBooks.add(new Book("9789526800677", "Book of Vaadin: Volume 2 ", 121)); - availableBooks.add(new Book("1478375108", "Vaadin Recipes")); - availableBooks.add(new Book("9781849515221", "Learning Vaadin")); - availableBooks.add( - new Book("9781782162261", "Vaadin 7 UI Design By Example: Beginner\u2019s Guide")); - availableBooks.add(new Book("9781849518802", "Vaadin 7 Cookbook")); - availableBooks.add(new Book("9526800605", "Book of Vaadin: 7th Edition, 1st Revision")); - availableBooks.add(new Book("9789526800677", "Book of Vaadin: Volume 2 ")); - availableBooks.add(new Book("9529267533", "Book of Vaadin")); - availableBooks.add(new Book("1782169776", "Learning Vaadin 7, Second Edition")); + availableBooks.add(new Book("1478375108", "Vaadin Recipes", 232)); + availableBooks.add(new Book("9781849515221", "Learning Vaadin", 333)); + availableBooks + .add( + new Book("9781782162261", "Vaadin 7 UI Design By Example: Beginner\u2019s Guide", 991)); + availableBooks.add(new Book("9781849518802", "Vaadin 7 Cookbook", 121)); + availableBooks.add(new Book("9526800605", "Book of Vaadin: 7th Edition, 1st Revision", 244)); + availableBooks.add(new Book("9789526800677", "Book of Vaadin: Volume 2 ", 555)); + availableBooks.add(new Book("9529267533", "Book of Vaadin", 666)); + availableBooks.add(new Book("1782169776", "Learning Vaadin 7, Second Edition", 423)); } + } diff --git a/src/test/java/com/flowingcode/vaadin/addons/twincolgrid/LazyFilterableDemo.java b/src/test/java/com/flowingcode/vaadin/addons/twincolgrid/LazyFilterableDemo.java new file mode 100644 index 0000000..d68ba2c --- /dev/null +++ b/src/test/java/com/flowingcode/vaadin/addons/twincolgrid/LazyFilterableDemo.java @@ -0,0 +1,120 @@ +/*- + * #%L + * TwinColGrid add-on + * %% + * Copyright (C) 2017 - 2022 Flowing Code + * %% + * 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% + */ + +package com.flowingcode.vaadin.addons.twincolgrid; + +import com.flowingcode.vaadin.addons.demo.DemoSource; +import com.vaadin.flow.component.button.Button; +import com.vaadin.flow.component.grid.Grid; +import com.vaadin.flow.component.notification.Notification; +import com.vaadin.flow.component.notification.Notification.Position; +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.data.provider.SortOrder; +import com.vaadin.flow.router.PageTitle; +import com.vaadin.flow.router.Route; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Set; +import java.util.stream.Collectors; +import org.apache.commons.lang3.StringUtils; + +@SuppressWarnings("serial") +@PageTitle("Lazy Filterable") +@DemoSource +@Route(value = "twincolgrid/lazyfilterable", layout = TwincolDemoView.class) +public class LazyFilterableDemo extends VerticalLayout { + + public LazyFilterableDemo() { + BookService bookService = new BookService(); + + DataProvider availableDataProvider = + DataProvider.fromFilteringCallbacks( + query -> { + BookFilter filter = query.getFilter().orElseGet(BookFilter::new); + filter.setSorting(query.getSortOrders().stream() + .map(q -> new SortOrder<>(q.getSorted(), q.getDirection())) + .collect(Collectors.toList())); + return bookService.fetch(query.getOffset(), query.getLimit(), filter); + }, + query -> bookService.count(query.getFilter().orElseGet(BookFilter::new))); + + Grid availableGrid = new Grid<>(); + availableGrid.setDataProvider(availableDataProvider); + + Grid selectionGrid = new Grid<>(); + BookFilter bookFilter = new BookFilter(); + + final TwinColGrid twinColGrid = + new TwinColGrid<>(availableGrid, selectionGrid) + .withAvailableGridCaption("Available books") + .withSelectionGridCaption("Added books") + .withDragAndDropSupport() + .withSelectionGridReordering() + .withSizeFull(); + + TwinColumn isbnColumn = twinColGrid.addColumn(Book::getIsbn).setHeader("ISBN") + .setSortable(true).setSortProperty("isbn"); + LazyFilterableColumn isbnFilterableColumn = + new LazyFilterableColumn<>(isbnColumn, "ISBN Filter", true, bookFilter::setIsbn, + (item, filter) -> StringUtils.isBlank(filter) + || StringUtils.containsIgnoreCase(item.getIsbn(), filter)); + + TwinColumn titleColumn = + twinColGrid.addColumn(Book::getTitle).setHeader("Title").setSortable(true) + .setSortProperty("title"); + LazyFilterableColumn titleFilterableColumn = + new LazyFilterableColumn<>(titleColumn, "Title Filter", true, bookFilter::setTitle, + (item, filter) -> StringUtils.isBlank(filter) + || StringUtils.containsIgnoreCase(item.getTitle(), filter)); + + twinColGrid.addColumn(Book::getPrice).setHeader("Price").setSortProperty("price"); + + LazyFilterConfiguration filterConfig = new LazyFilterConfiguration<>(bookFilter); + filterConfig.addFilteredColumn(isbnFilterableColumn); + filterConfig.addFilteredColumn(titleFilterableColumn); + twinColGrid.withFilter(filterConfig); + + twinColGrid.setCaption("TwinColGrid demo with lazy loading, binder and drag and drop support"); + twinColGrid.setMoveItemsByDoubleClick(true); + + Set selectedBooks = new HashSet<>(); + selectedBooks.add(bookService.getAny()); + selectedBooks.add(bookService.getAny()); + selectedBooks.add(bookService.getAny()); + + Binder binder = new Binder<>(); + binder.forField(twinColGrid.asList()).asRequired().bind(Library::getBooks, Library::setBooks); + Library library = new Library("Public Library", new ArrayList<>(selectedBooks)); + binder.setBean(library); + + add(twinColGrid); + add(new Button("Get values", ev -> { + binder.getBean().getBooks() + .forEach(book -> Notification.show(book.getTitle(), 3000, Position.BOTTOM_START)); + })); + + add(new Button("Clear TwinColGrid", ev -> twinColGrid.clear())); + + setSizeFull(); + } + +} diff --git a/src/test/java/com/flowingcode/vaadin/addons/twincolgrid/OrientationDemo.java b/src/test/java/com/flowingcode/vaadin/addons/twincolgrid/OrientationDemo.java index 93f4628..876ef74 100644 --- a/src/test/java/com/flowingcode/vaadin/addons/twincolgrid/OrientationDemo.java +++ b/src/test/java/com/flowingcode/vaadin/addons/twincolgrid/OrientationDemo.java @@ -52,11 +52,11 @@ public OrientationDemo() { .withSizeFull() .selectRowOnClick() .withOrientation(Orientation.VERTICAL); - - twinColGrid.addColumn(Book::getIsbn).setComparator(Book::getIsbn).setHeader("ISBN"); - twinColGrid.addColumn(Book::getTitle).setComparator(Book::getTitle).setHeader("Title"); twinColGrid.setValue(selectedBooks); + twinColGrid.addColumn(Book::getIsbn).setHeader("ISBN").setSortable(true); + twinColGrid.addColumn(Book::getTitle).setHeader("Title").setSortable(true); + FormLayout formLayout = new FormLayout(); Select orientationField = new Select<>(); orientationField.setItems(Orientation.values()); @@ -70,16 +70,20 @@ public OrientationDemo() { } private void initializeData() { - selectedBooks.add(new Book("1478375108", "Vaadin Recipes")); - selectedBooks.add(new Book("9789526800677", "Book of Vaadin: Volume 2 ")); - availableBooks.add(new Book("1478375108", "Vaadin Recipes")); - availableBooks.add(new Book("9781849515221", "Learning Vaadin")); - availableBooks.add( - new Book("9781782162261", "Vaadin 7 UI Design By Example: Beginner\u2019s Guide")); - availableBooks.add(new Book("9781849518802", "Vaadin 7 Cookbook")); - availableBooks.add(new Book("9526800605", "Book of Vaadin: 7th Edition, 1st Revision")); - availableBooks.add(new Book("9789526800677", "Book of Vaadin: Volume 2 ")); - availableBooks.add(new Book("9529267533", "Book of Vaadin")); - availableBooks.add(new Book("1782169776", "Learning Vaadin 7, Second Edition")); + selectedBooks.add(new Book("1478375108", "Vaadin Recipes", 222)); + selectedBooks.add(new Book("9789526800677", "Book of Vaadin: Volume 2 ", 121)); + + + availableBooks.add(new Book("1478375108", "Vaadin Recipes", 232)); + availableBooks.add(new Book("9781849515221", "Learning Vaadin", 333)); + availableBooks + .add( + new Book("9781782162261", "Vaadin 7 UI Design By Example: Beginner\u2019s Guide", 991)); + availableBooks.add(new Book("9781849518802", "Vaadin 7 Cookbook", 121)); + availableBooks.add(new Book("9526800605", "Book of Vaadin: 7th Edition, 1st Revision", 244)); + availableBooks.add(new Book("9789526800677", "Book of Vaadin: Volume 2 ", 555)); + availableBooks.add(new Book("9529267533", "Book of Vaadin", 666)); + availableBooks.add(new Book("1782169776", "Learning Vaadin 7, Second Edition", 423)); } + } diff --git a/src/test/java/com/flowingcode/vaadin/addons/twincolgrid/TwincolDemoView.java b/src/test/java/com/flowingcode/vaadin/addons/twincolgrid/TwincolDemoView.java index b882e5d..e166e59 100644 --- a/src/test/java/com/flowingcode/vaadin/addons/twincolgrid/TwincolDemoView.java +++ b/src/test/java/com/flowingcode/vaadin/addons/twincolgrid/TwincolDemoView.java @@ -37,6 +37,7 @@ public class TwincolDemoView extends TabbedDemo { public TwincolDemoView() { addDemo(DragAndDropDemo.class); addDemo(FilterableDemo.class); + addDemo(LazyFilterableDemo.class); addDemo(BoundDemo.class); addDemo(OrientationDemo.class); addDemo(DoubleClickDemo.class);