diff --git a/pom.xml b/pom.xml index 9a7b4bd..7ee5d11 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 com.flowingcode.vaadin.addons twincolgrid - 2.9.3-SNAPSHOT + 3.0.0-SNAPSHOT TwinColGrid add-on 14.8.1 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/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/TwinColGrid.java b/src/main/java/com/flowingcode/vaadin/addons/twincolgrid/TwinColGrid.java index 2045bdc..0605ba8 100644 --- a/src/main/java/com/flowingcode/vaadin/addons/twincolgrid/TwinColGrid.java +++ b/src/main/java/com/flowingcode/vaadin/addons/twincolgrid/TwinColGrid.java @@ -20,6 +20,7 @@ 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; @@ -27,7 +28,6 @@ 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; @@ -35,28 +35,21 @@ 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.selection.SelectionListener; -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.Comparator; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; @@ -70,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") @@ -80,61 +72,14 @@ 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, - VERTICAL, - HORIZONTAL_REVERSE, - VERTICAL_REVERSE; + HORIZONTAL, VERTICAL, HORIZONTAL_REVERSE, VERTICAL_REVERSE; } - private final TwinColModel available; - - private final TwinColModel selection; - - /** @deprecated Use getAvailableGrid() */ - @Deprecated protected final Grid leftGrid; - - /** @deprecated Use getSelectionGrid() */ - @Deprecated protected final Grid rightGrid; - - /** @deprecated Use getAvailableGrid().getDataProvider() */ - @Deprecated protected ListDataProvider leftGridDataProvider; + private final TwinColModel available; - /** @deprecated Use getSelectionGrid().getDataProvider() */ - @Deprecated protected ListDataProvider rightGridDataProvider; + private final EagerTwinColModel selection; private Label captionLabel; @@ -148,8 +93,6 @@ public enum Orientation { private Component buttonContainer; - private Grid draggedGrid; - private Label fakeButtonContainerLabel = new Label(); private Orientation orientation = Orientation.HORIZONTAL; @@ -158,10 +101,6 @@ public enum Orientation { private boolean isFromClient = false; - private static ListDataProvider emptyDataProvider() { - return DataProvider.ofCollection(new LinkedHashSet<>()); - } - /** Constructs a new TwinColGrid with an empty {@link ListDataProvider}. */ public TwinColGrid() { this(Grid::new); @@ -171,44 +110,6 @@ public TwinColGrid() { * Constructs a new empty TwinColGrid with caption * * @param caption the component caption - * @deprecated Use {@link TwinColGrid#TwinColGrid()} and {{@link #setCaption(String)} - */ - @Deprecated - public TwinColGrid(String caption) { - this(Grid::new); - setCaption(caption); - } - - /** - * Constructs a new TwinColGrid with data provider for options. - * - * @param dataProvider the data provider, not {@code null} - * @param caption the component caption - * @deprecated Use {@link #TwinColGrid()} and {@link #setDataProvider(ListDataProvider)}, - * {@link #setCaption(String)} - */ - @Deprecated - public TwinColGrid(final ListDataProvider dataProvider, String caption) { - this(Grid::new); - setDataProvider(dataProvider); - setCaption(caption); - } - - /** - * Constructs a new empty TwinColGrid, using the specified supplier for instantiating both grids. - * - * @param caption the component caption - * @param gridSupplier a supplier for instantiating both grids - * @deprecated Use {@link TwinColGrid#TwinColGrid(Supplier)} and {@link #setCaption(String)} - */ - @Deprecated - public TwinColGrid(String caption, Supplier> gridSupplier) { - this(gridSupplier.get(), gridSupplier.get()); - setCaption(caption); - } - - /** - * Constructs a new empty TwinColGrid, using the specified supplier for instantiating both grids. * * @param gridSupplier a supplier for instantiating both grids */ @@ -216,22 +117,6 @@ public TwinColGrid(Supplier> gridSupplier) { this(gridSupplier.get(), gridSupplier.get()); } - /** - * Constructs a new empty TwinColGrid, using the specified grids for each side. - * - * @param caption the component caption - * @param availableGrid the grid that contains the available items - * @param selectionGrid the grid that contains the selected items - * - * @deprecated Use {@link TwinColGrid#TwinColGrid(Grid, Grid)} and {@link #setCaption(String)} - */ - @Deprecated - public TwinColGrid(String caption, @NonNull Grid availableGrid, - @NonNull Grid selectionGrid) { - this(availableGrid, selectionGrid); - setCaption(caption); - } - /** * Constructs a new empty TwinColGrid, using the specified grids for each side. * @@ -244,24 +129,23 @@ 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"); + } - leftGrid = available.grid; - rightGrid = selection.grid; + available = createModel(availableGrid, ""); + selection = new EagerTwinColModel<>(selectionGrid, "twincol-grid-selection"); setClassName("twincol-grid"); setMargin(false); setPadding(false); - setDataProvider(emptyDataProvider()); - 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 = available.getDataProvider().withConfigurableFilter() @@ -270,8 +154,7 @@ public TwinColGrid(@NonNull Grid availableGrid, @NonNull Grid selectionGri }); addButton.addClickListener( - e -> - updateSelection( + e -> updateSelection( new LinkedHashSet<>(getAvailableGrid().getSelectedItems()), new HashSet<>(), true)); removeButton.addClickListener( @@ -279,26 +162,33 @@ public TwinColGrid(@NonNull Grid availableGrid, @NonNull Grid selectionGri removeAllButton.addClickListener( e -> { - List filteredItems= selection.getDataProvider().withConfigurableFilter().fetch(new Query<>()).collect(Collectors.toList()); + List filteredItems = selection.getDataProvider().withConfigurableFilter() + .fetch(new Query<>()).collect(Collectors.toList()); updateSelection(new HashSet<>(), new HashSet<>(filteredItems), true); }); 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); add(createContainerLayout()); setSizeUndefined(); } + /** + * 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))); + } + + private TwinColModel createModel(@NonNull Grid grid, String className) { + return grid.getDataProvider().isInMemory() ? new EagerTwinColModel<>(grid, className) + : new LazyTwinColModel<>(grid, className); + } /** * Sets the component caption. @@ -339,8 +229,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; } @@ -350,7 +240,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); } @@ -389,9 +279,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"); @@ -404,9 +294,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"); @@ -440,127 +330,31 @@ private HorizontalLayout getHorizontalButtonContainer() { /** Return the grid that contains the available items. */ public Grid getAvailableGrid() { - return available.grid; + return available.getGrid(); } /** Return the grid that contains the selected items. */ public Grid getSelectionGrid() { - return selection.grid; + return selection.getGrid(); } - /** - * Return the left grid component. - * - * @deprecated Use {@link #getAvailableGrid()}. Depending on the orientation, the "left grid" may - * not be located at the left side. - */ - @Deprecated - public Grid getLeftGrid() { - return leftGrid; - } - - /** - * Return the right grid component. - * - * @deprecated Use {@link #getSelectionGrid()}. Depending on the orientation, the "right grid" may - * not be located at the right side. - */ - @Deprecated - public Grid getRightGrid() { - return rightGrid; - } - - private void forEachSide(Consumer> consumer) { + private void forEachSide(Consumer> consumer) { consumer.accept(available); consumer.accept(selection); } public final void forEachGrid(Consumer> consumer) { - consumer.accept(available.grid); - consumer.accept(selection.grid); - } - - public void setItems(Collection items) { - setDataProvider(DataProvider.ofCollection(items)); - } - - public void setItems(Stream items) { - setDataProvider(DataProvider.fromStream(items)); - } - - /** @deprecated Use {@code getAvailableGrid().setClassName(classname)} */ - @Deprecated - public void setLeftGridClassName(String classname) { - getAvailableGrid().setClassName(classname); - } - - /** @deprecated Use {@code getAvailableGrid().addClassName(classname)} */ - @Deprecated - public void addLeftGridClassName(String classname) { - getAvailableGrid().addClassName(classname); - } - - /** @deprecated Use {@code getAvailableGrid().removeClassName(classname)} */ - @Deprecated - public void removeLeftGridClassName(String classname) { - getAvailableGrid().removeClassName(classname); - } - - /** @deprecated Use {@code getSelectionGrid().setClassName(classname)} */ - @Deprecated - public void setRightGridClassName(String classname) { - getSelectionGrid().setClassName(classname); - } - - /** @deprecated Use {@code getSelectionGrid().addClassName(classname)} */ - @Deprecated - public void addRightGridClassName(String classname) { - getSelectionGrid().addClassName(classname); - } - - /** @deprecated Use {@code getSelectionGrid().removeClassName(classname)} */ - @Deprecated - public void removeRightGridClassName(String classname) { - getSelectionGrid().removeClassName(classname); - } - - public void clearAll() { - updateSelection(new HashSet<>(), new HashSet<>(selection.getItems()), false); + consumer.accept(available.getGrid()); + consumer.accept(selection.getGrid()); } private void setDataProvider(ListDataProvider dataProvider) { - leftGridDataProvider = dataProvider; getAvailableGrid().setDataProvider(dataProvider); if (selection.getDataProvider() != null) { - selection.getItems().clear(); - selection.getDataProvider().refreshAll(); + selection.clear(); } } - /** - * 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 TwinColGrid with caption and the given options. - * - * @param caption the caption to set, can be {@code null} - * @param options the options, cannot be {@code null} - * - * @deprecated Use {@link #TwinColGrid(Collection)} and {{@link #setCaption(String)} - */ - @Deprecated - public TwinColGrid(final Collection options, final String caption) { - this(options); - setCaption(caption); - } - /** * Sets the text shown above the grid with the available items. {@code null} clears the caption. * @@ -568,8 +362,8 @@ public TwinColGrid(final Collection options, final String caption) { * @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; } @@ -581,97 +375,25 @@ 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; } /** - * Sets the text shown above the grid with the available items. {@code null} clears the caption. - * - * @param caption The text to show, {@code null} to clear - * @return this instance - * @deprecated Use {@link #withAvailableGridCaption(String)} - */ - @Deprecated - public TwinColGrid withRightColumnCaption(final String caption) { - return withSelectionGridCaption(caption); - } - - /** - * Sets the text shown above the grid with the available items. {@code null} clears the caption. - * - * @param caption The text to show, {@code null} to clear - * @return this instance - * @deprecated Use {@link #withSelectionGridCaption(String)} - */ - @Deprecated - public TwinColGrid withLeftColumnCaption(final String caption) { - return withAvailableGridCaption(caption); - } - - /** - * Adds a new text column to this {@link Grid} with a value provider. The column will use a {@link - * TextRenderer}. The value is converted to a String using the provided {@code - * itemLabelGenerator}. - * - * @param itemLabelGenerator the value provider - * @param header the column header - * @return this instance - */ - public TwinColGrid addColumn( - final ItemLabelGenerator itemLabelGenerator, final String header) { - getAvailableGrid().addColumn(new TextRenderer<>(itemLabelGenerator)).setHeader(header); - getSelectionGrid().addColumn(new TextRenderer<>(itemLabelGenerator)).setHeader(header); - return this; - } - - /** - * Adds a new sortable text column to this {@link Grid} with a value provider. The column will use - * a {@link TextRenderer}. The value is converted to a String 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 comparator the in-memory comparator - * @param header the column header - * @return this instance + * @param valueProvider the value provider + * @return the pair of columns */ - public TwinColGrid addSortableColumn( - final ItemLabelGenerator itemLabelGenerator, - Comparator comparator, - final String header) { - forEachGrid(grid -> grid - .addColumn(new TextRenderer<>(itemLabelGenerator)) - .setHeader(header) - .setComparator(comparator) - .setSortable(true)); - return this; - } - - /** - * Adds a new sortable text column to this {@link Grid} with a value provider. The column will use - * a {@link TextRenderer}. The value is converted to a String using the provided {@code - * itemLabelGenerator}. - * - * @param itemLabelGenerator the value provider - * @param comparator the in-memory comparator - * @param header the column header - * @param header the column key - * @return this instance - */ - public TwinColGrid addSortableColumn( - final ItemLabelGenerator itemLabelGenerator, - Comparator comparator, - final String header, - final String key) { - forEachGrid(grid -> grid - .addColumn(new TextRenderer<>(itemLabelGenerator)) - .setHeader(header) - .setComparator(comparator) - .setSortable(true) - .setKey(key)); - return this; + public TwinColumn addColumn(ValueProvider valueProvider) { + Column availableColumn = + getAvailableGrid().addColumn(valueProvider); + Column selectionColumn = + getSelectionGrid().addColumn(valueProvider); + return new TwinColumn<>(availableColumn, selectionColumn); } public TwinColGrid withoutAddAllButton() { @@ -730,7 +452,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(); } /** @@ -739,7 +461,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(); } /** @@ -800,7 +522,7 @@ public Set getValue() { */ C collectValue(Collector collector) { Stream stream = selection.getItems().stream(); - SerializableComparator comparator = createSortingComparator(selection.grid); + SerializableComparator comparator = createSortingComparator(selection.getGrid()); if (comparator != null) { return stream.sorted(comparator).collect(collector); } else { @@ -830,7 +552,8 @@ public Registration addValueChangeListener( .addDataProviderListener( e -> { ComponentValueChangeEvent, Set> e2 = - new ComponentValueChangeEvent<>(TwinColGrid.this, TwinColGrid.this, null, isFromClient); + new ComponentValueChangeEvent<>(TwinColGrid.this, TwinColGrid.this, null, + isFromClient); listener.valueChanged(e2); }); } @@ -860,105 +583,84 @@ public void setRequiredIndicatorVisible(boolean requiredIndicatorVisible) { getElement().setAttribute("required", requiredIndicatorVisible); } - private void updateSelection(final Set addedItems, final Set removedItems, boolean isFromClient) { + 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(); - }); + }); } - private void configDragAndDrop( - final TwinColModel sourceModel, final TwinColModel targetModel) { + private void configDragAndDrop(TwinColModel sourceModel, TwinColModel targetModel) { + Set draggedItems = new LinkedHashSet<>(); - final Set draggedItems = new LinkedHashSet<>(); + Grid draggedGrid = sourceModel.getGrid(); - sourceModel.grid.setRowsDraggable(true); - sourceModel.grid.addDragStartListener( + sourceModel.getGrid().setRowsDraggable(true); + sourceModel.getGrid().addDragStartListener( event -> { - draggedGrid = sourceModel.grid; - - if (!(sourceModel.grid.getSelectionModel() instanceof GridNoneSelectionModel)) { + if (!(sourceModel.getGrid().getSelectionModel() instanceof GridNoneSelectionModel)) { draggedItems.addAll(event.getDraggedItems()); } - sourceModel.grid + sourceModel.getGrid() .setDropMode(sourceModel.isReorderingEnabled() ? GridDropMode.BETWEEN : null); - targetModel.grid.setDropMode( + targetModel.getGrid().setDropMode( targetModel.isReorderingEnabled() ? GridDropMode.BETWEEN : GridDropMode.ON_GRID); }); - sourceModel.grid.addDragEndListener( + sourceModel.getGrid().addDragEndListener( event -> { - if (targetModel.droppedInsideGrid - && sourceModel.grid == draggedGrid + if (targetModel.isDroppedInsideGrid() + && sourceModel.getGrid() == 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(); }); - targetModel.grid.addDropListener( + targetModel.getGrid().addDropListener( 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()); } }); - sourceModel.grid.addDropListener(event -> { + sourceModel.getGrid().addDropListener(event -> { event.getDropTargetItem().ifPresent(dropOverItem -> { if (sourceModel.isReorderingEnabled() && event.getSource() == draggedGrid && !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); @@ -967,83 +669,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; - } - - /** @deprecated Use {@code getAvailableGrid().addSelectionListener(listener);} */ - @Deprecated - public void addLeftGridSelectionListener(SelectionListener, T> listener) { - getAvailableGrid().addSelectionListener(listener); - } - - /** @deprecated Use {@code getSelectionGrid().addSelectionListener(listener);} */ - @Deprecated - public void addRightGridSelectionListener(SelectionListener, T> listener) { - getSelectionGrid().addSelectionListener(listener); - } - - public TwinColGrid addFilterableColumn( - final ItemLabelGenerator itemLabelGenerator, - SerializableFunction filterableValue, - final String header, - String filterPlaceholder, - boolean enableClearButton, String key) { - forEachSide( - side -> { - Column column = - side.grid.addColumn(new TextRenderer<>(itemLabelGenerator)).setHeader(header); - - Optional.ofNullable(key).ifPresent(column::setKey); - - TextField filterTF = new TextField(); - filterTF.setClearButtonVisible(enableClearButton); - - 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(); - filterTF.setPlaceholder(filterPlaceholder); - }); - - return this; - } - - public TwinColGrid addFilterableColumn( - final ItemLabelGenerator itemLabelGenerator, - final String header, - String filterPlaceholder, - boolean enableClearButton) { - return addFilterableColumn(itemLabelGenerator, itemLabelGenerator, header, filterPlaceholder, - enableClearButton, null); - } - - public TwinColGrid addFilterableColumn(ItemLabelGenerator itemLabelGenerator, - SerializableFunction filterableValue, String header, String filterPlaceholder, - boolean enableClearButton) { - return addFilterableColumn(itemLabelGenerator, filterableValue, header, filterPlaceholder, - enableClearButton, null); - } - - public TwinColGrid addFilterableColumn(ItemLabelGenerator itemLabelGenerator, String header, - String filterPlaceholder, boolean enableClearButton, String key) { - return addFilterableColumn(itemLabelGenerator, itemLabelGenerator, header, filterPlaceholder, - enableClearButton, key); + return selection.isAllowReordering(); } public TwinColGrid selectRowOnClick() { @@ -1051,14 +682,14 @@ public TwinColGrid selectRowOnClick() { grid.addClassName("hide-selector-col"); grid.addItemClickListener( - c -> { + c -> { if (grid.getSelectedItems().contains(c.getItem())) { grid.deselect(c.getItem()); - } else { + } else { grid.select(c.getItem()); - } - }); - }); + } + }); + }); return this; } @@ -1107,8 +738,8 @@ public void setAutoResize(boolean autoResize) { */ public void setMoveItemsByDoubleClick(boolean value) { forEachSide(side -> { - if (value && side.moveItemsByDoubleClick == null) { - side.moveItemsByDoubleClick = side.grid.addItemDoubleClickListener(ev -> { + if (value && side.getMoveItemsByDoubleClick() == null) { + side.addItemDoubleClickListener(ev -> { Set item = Collections.singleton(ev.getItem()); if (side == available) { updateSelection(item, Collections.emptySet(), true); @@ -1118,9 +749,8 @@ public void setMoveItemsByDoubleClick(boolean value) { } }); } - if (!value && side.moveItemsByDoubleClick != null) { - side.moveItemsByDoubleClick.remove(); - side.moveItemsByDoubleClick = null; + if (!value) { + side.removeItemDoubleClickListener(); } }); } @@ -1134,4 +764,57 @@ private void updateOrientationOnResize(int width, int height) { } } + @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") + private 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/main/java/com/flowingcode/vaadin/addons/twincolgrid/TwinColumn.java b/src/main/java/com/flowingcode/vaadin/addons/twincolgrid/TwinColumn.java new file mode 100644 index 0000000..8064d7a --- /dev/null +++ b/src/main/java/com/flowingcode/vaadin/addons/twincolgrid/TwinColumn.java @@ -0,0 +1,238 @@ +package com.flowingcode.vaadin.addons.twincolgrid; + +import com.vaadin.flow.component.Component; +import com.vaadin.flow.component.grid.Grid.Column; +import com.vaadin.flow.component.grid.SortOrderProvider; +import com.vaadin.flow.data.provider.QuerySortOrder; +import com.vaadin.flow.function.SerializableFunction; +import com.vaadin.flow.function.ValueProvider; +import java.util.Comparator; +import java.util.function.Supplier; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +/** Fluent helper object that delegates setters on both columns. */ +@RequiredArgsConstructor +public class TwinColumn { + + /** + * Returns the column in the grid with the available items. + * + * @return The column in the grid with the available items. + */ + @Getter + private final Column availableColumn; + + /** + * Returns the column in the grid with the selected items. + * + * @return The column in the grid with the selected items. + */ + @Getter + private final Column selectionColumn; + + /** + * Sets the width of the columns as a CSS-string. + * + * @see Column#setWidth(String) + * + * @param width the width to set both columns to, as a CSS-string, not {@code null} + * @return this instance, for method chaining + */ + public TwinColumn setWidth(String width) { + availableColumn.setWidth(width); + selectionColumn.setWidth(width); + return this; + } + + /** + * Sets the flex grow ratio for the columns. When set to 0, column width is fixed. + * + * @see Column#setFlexGrow(int) + * + * @param flexGrow the flex grow ratio + * @return this instance, for method chaining + */ + public TwinColumn setFlexGrow(int flexGrow) { + availableColumn.setFlexGrow(flexGrow); + selectionColumn.setFlexGrow(flexGrow); + return this; + } + + /** + * Enables or disables automatic width for the columns. + * + * @see Column#setAutoWidth(boolean) + * + * @param autoWidth whether to enable or disable automatic width on both columns + * @return this instance, for method chaining + */ + public TwinColumn setAutoWidth(boolean autoWidth) { + availableColumn.setAutoWidth(autoWidth); + selectionColumn.setAutoWidth(autoWidth); + return this; + } + + /** + * Sets the user-defined identifier to map the columns. + * + * @see Column#setKey(String) + * + * @param key the identifier key, can't be {@code null} + * @return this instance, for method chaining + */ + public TwinColumn setKey(String key) { + availableColumn.setKey(key); + selectionColumn.setKey(key); + return this; + } + + /** + * Sets a comparator to use with in-memory sorting with both columns. + * + * @see Column#setComparator(Comparator) + * + * @param comparator the comparator to use when sorting data in both columns + * @return this instance, for method chaining + */ + public TwinColumn setComparator(Comparator comparator) { + availableColumn.setComparator(comparator); + selectionColumn.setComparator(comparator); + return this; + } + + /** + * Sets a comparator to use with in-memory sorting with both columns based on the return type of + * the given {@link ValueProvider}. + * + * @see Column#setComparator(ValueProvider) + * + * @param the value of the column + * @param keyExtractor the value provider used to extract the {@link Comparable} sort key + * @return this instance, for method chaining + * @see Comparator#comparing(java.util.function.Function) + */ + public > TwinColumn setComparator( + ValueProvider keyExtractor) { + availableColumn.setComparator(keyExtractor); + selectionColumn.setComparator(keyExtractor); + return this; + } + + /** + * Sets strings describing back end properties to be used when sorting the columns. + * + * @see Column#setSortProperty(String...) + * + * @param properties the array of strings describing backend properties + * @return this instance, for method chaining + */ + public TwinColumn setSortProperty(String... properties) { + availableColumn.setSortProperty(properties); + selectionColumn.setSortProperty(properties); + return this; + } + + /** + * Sets the sort orders when sorting the columns. The sort order provider is a function which + * provides {@link QuerySortOrder} objects to describe how to sort by the columns. + * + * @see Column#setSortOrderProvider(SortOrderProvider) + * + * @param provider the function to use when generating sort orders with the given direction + * @return this instance, for method chaining + */ + public TwinColumn setSortOrderProvider(SortOrderProvider provider) { + availableColumn.setSortOrderProvider(provider); + selectionColumn.setSortOrderProvider(provider); + return this; + } + + /** + * Sets whether the user can sort the columns or not. + * + * @see Column#setSortable(boolean) + * + * @param sortable {@code true} if the columns can be sorted by the user; {@code false} if not + * @return this instance, for method chaining + */ + public TwinColumn setSortable(boolean sortable) { + availableColumn.setSortable(sortable); + selectionColumn.setSortable(sortable); + return this; + } + + /** + * Sets a header text to both columns. + * + * @see Column#setHeader(String) + * + * @param labelText the text to be shown at the columns headers + * @return this instance, for method chaining + */ + public TwinColumn setHeader(String labelText) { + availableColumn.setHeader(labelText); + selectionColumn.setHeader(labelText); + return this; + } + + /** + * Sets a footer text to both columns. + * + * @see Column#setFooter(String) + * + * @param labelText the text to be shown at the columns footers + * @return this instance, for method chaining + */ + public TwinColumn setFooter(String labelText) { + availableColumn.setFooter(labelText); + selectionColumn.setFooter(labelText); + return this; + } + + /** + * Sets a header component to both columns. + * + * @see Column#setHeader(String) + * + * @param headerComponentSupplier a supplier that instantiates the component to be used in the + * header of each column + * @return this instance, for method chaining + */ + public TwinColumn setHeader(Supplier footerComponentSupplier) { + availableColumn.setHeader(footerComponentSupplier.get()); + selectionColumn.setHeader(footerComponentSupplier.get()); + return this; + } + + /** + * Sets a footer component to both columns. + * + * @see Column#setFooter(String) + * + * @param footerComponentSuppleir a supplier that instantiates the component to be used in the + * footer of each column + * @return this instance, for method chaining + */ + public TwinColumn setFooter(Supplier footerComponentSupplier) { + availableColumn.setFooter(footerComponentSupplier.get()); + selectionColumn.setFooter(footerComponentSupplier.get()); + return this; + } + + /** + * Sets the function that is used for generating CSS class names for cells in both columns. + * + * @see Column#setClassNameGenerator(SerializableFunction) + * + * @param classNameGenerator the class name generator to set, not {@code null} + * @return this instance, for method chaining + * @throws NullPointerException if {@code classNameGenerator} is {@code null} + */ + public TwinColumn setClassNameGenerator(SerializableFunction classNameGenerator) { + availableColumn.setClassNameGenerator(classNameGenerator); + selectionColumn.setClassNameGenerator(classNameGenerator); + return this; + } + +} 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 6f6fd09..ff6b8a9 100644 --- a/src/test/java/com/flowingcode/vaadin/addons/twincolgrid/BoundDemo.java +++ b/src/test/java/com/flowingcode/vaadin/addons/twincolgrid/BoundDemo.java @@ -29,7 +29,6 @@ import com.vaadin.flow.router.PageTitle; import com.vaadin.flow.router.Route; import java.util.ArrayList; -import java.util.Comparator; import java.util.List; @SuppressWarnings("serial") @@ -47,16 +46,19 @@ public BoundDemo() { // Binded final TwinColGrid twinColGrid = - new TwinColGrid<>( - availableBooks, "TwinColGrid demo with Binder and row select without checkbox") - .addSortableColumn(Book::getIsbn, Comparator.comparing(Book::getIsbn), "ISBN") - .addSortableColumn(Book::getTitle, Comparator.comparing(Book::getTitle), "Title") + new TwinColGrid<>(availableBooks) + // .addSortableColumn(Book::getIsbn, Comparator.comparing(Book::getIsbn), "ISBN") + // .addSortableColumn(Book::getTitle, Comparator.comparing(Book::getTitle), "Title") .withAvailableGridCaption("Available books") .withSelectionGridCaption("Added books") .withoutRemoveAllButton() .withSizeFull() .selectRowOnClick(); + 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<>(); binder.forField(twinColGrid.asList()).asRequired().bind(Library::getBooks, Library::setBooks); binder.setBean(library); @@ -75,18 +77,20 @@ public BoundDemo() { } 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("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")); - 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")); + .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 e6bc74f..4782394 100644 --- a/src/test/java/com/flowingcode/vaadin/addons/twincolgrid/DoubleClickDemo.java +++ b/src/test/java/com/flowingcode/vaadin/addons/twincolgrid/DoubleClickDemo.java @@ -25,7 +25,6 @@ import com.vaadin.flow.router.PageTitle; import com.vaadin.flow.router.Route; import java.util.ArrayList; -import java.util.Comparator; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -43,13 +42,14 @@ public DoubleClickDemo() { initializeData(); final TwinColGrid twinColGrid = - new TwinColGrid<>(availableBooks, null) - .addSortableColumn(Book::getIsbn, Comparator.comparing(Book::getIsbn), "ISBN") - .addSortableColumn(Book::getTitle, Comparator.comparing(Book::getTitle), "Title") + new TwinColGrid<>(availableBooks) .withAvailableGridCaption("Available books") .withSelectionGridCaption("Added books") .withSizeFull() .selectRowOnClick(); + + twinColGrid.addColumn(Book::getIsbn).setHeader("ISBN"); + twinColGrid.addColumn(Book::getTitle).setHeader("Title"); twinColGrid.setValue(selectedBooks); twinColGrid.setMoveItemsByDoubleClick(true); @@ -58,16 +58,20 @@ public DoubleClickDemo() { } 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 ff6205c..6da1c3d 100644 --- a/src/test/java/com/flowingcode/vaadin/addons/twincolgrid/DragAndDropDemo.java +++ b/src/test/java/com/flowingcode/vaadin/addons/twincolgrid/DragAndDropDemo.java @@ -30,7 +30,6 @@ import com.vaadin.flow.router.PageTitle; import com.vaadin.flow.router.Route; import java.util.ArrayList; -import java.util.Comparator; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -50,16 +49,16 @@ public DragAndDropDemo() { initializeData(); twinColGrid = new TwinColGrid<>(availableBooks) - .addSortableColumn(Book::getIsbn, Comparator.comparing(Book::getIsbn), "ISBN") - .addSortableColumn(Book::getTitle, Comparator.comparing(Book::getTitle), "Title") - .withAvailableGridCaption("Available books") - .withSelectionGridCaption("Added books") - .withoutAddAllButton() - .withSizeFull() - .withDragAndDropSupport() - .withSelectionGridReordering() - .selectRowOnClick(); - + .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); @@ -75,19 +74,20 @@ public DragAndDropDemo() { } 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("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")); - 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")); + .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() { 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 779dae3..ee7c8fb 100644 --- a/src/test/java/com/flowingcode/vaadin/addons/twincolgrid/FilterableDemo.java +++ b/src/test/java/com/flowingcode/vaadin/addons/twincolgrid/FilterableDemo.java @@ -20,7 +20,6 @@ package com.flowingcode.vaadin.addons.twincolgrid; -import com.flowingcode.vaadin.addons.demo.DemoSource; import com.vaadin.flow.component.orderedlayout.VerticalLayout; import com.vaadin.flow.router.PageTitle; import com.vaadin.flow.router.Route; @@ -28,10 +27,10 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +import org.apache.commons.lang3.StringUtils; @SuppressWarnings("serial") @PageTitle("Filterable") -@DemoSource @Route(value = "twincolgrid/filterable", layout = TwincolDemoView.class) public class FilterableDemo extends VerticalLayout { @@ -42,13 +41,33 @@ public FilterableDemo() { initializeData(); final TwinColGrid twinColGrid = - new TwinColGrid<>(availableBooks, "TwinColGrid demo with filtering support") - .addFilterableColumn(Book::getIsbn, Book::getIsbn, "ISBN", "ISBN Filter", true) - .addFilterableColumn(Book::getTitle, "Title", "Title filter", false) + new TwinColGrid<>(availableBooks) .withAvailableGridCaption("Available books") .withSelectionGridCaption("Added books") .withoutAddAllButton() .withSizeFull(); + + 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); add(twinColGrid); @@ -56,19 +75,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("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")); - 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")); + .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..0bbeb0d --- /dev/null +++ b/src/test/java/com/flowingcode/vaadin/addons/twincolgrid/LazyFilterableDemo.java @@ -0,0 +1,118 @@ +/*- + * #%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.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") +@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 c8b4a01..9ac2eb2 100644 --- a/src/test/java/com/flowingcode/vaadin/addons/twincolgrid/OrientationDemo.java +++ b/src/test/java/com/flowingcode/vaadin/addons/twincolgrid/OrientationDemo.java @@ -27,7 +27,6 @@ import com.vaadin.flow.router.PageTitle; import com.vaadin.flow.router.Route; import java.util.ArrayList; -import java.util.Comparator; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -45,9 +44,7 @@ public OrientationDemo() { initializeData(); final TwinColGrid twinColGrid = - new TwinColGrid<>(availableBooks, null) - .addSortableColumn(Book::getIsbn, Comparator.comparing(Book::getIsbn), "ISBN") - .addSortableColumn(Book::getTitle, Comparator.comparing(Book::getTitle), "Title") + new TwinColGrid<>(availableBooks) .withAvailableGridCaption("Available books") .withSelectionGridCaption("Added books") .withSizeFull() @@ -55,6 +52,9 @@ public OrientationDemo() { .withOrientation(Orientation.VERTICAL); 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<>(Orientation.values()); orientationField.addValueChangeListener(ev -> twinColGrid.withOrientation(ev.getValue())); @@ -67,16 +67,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);