From 535f259607fa3ee1b4f3da09f4df0b4d40449965 Mon Sep 17 00:00:00 2001 From: Pekka Hyvönen Date: Fri, 17 Nov 2017 10:46:14 +0200 Subject: Rename GridDragger to GridRowDragger (#10333) The old name GridDragger gave an impression that you're dragging the whole grid instead of rows. --- .../ui/components/grid/DropIndexCalculator.java | 2 +- .../vaadin/ui/components/grid/GridDragSource.java | 2 +- .../com/vaadin/ui/components/grid/GridDragger.java | 519 --------------------- .../vaadin/ui/components/grid/GridDropTarget.java | 2 +- .../vaadin/ui/components/grid/GridRowDragger.java | 519 +++++++++++++++++++++ .../components/grid/SourceDataProviderUpdater.java | 2 +- .../components/grid/TargetDataProviderUpdater.java | 2 +- .../component/grid/GridDraggerOneGridTest.java | 226 --------- .../component/grid/GridDraggerTwoGridsTest.java | 270 ----------- .../component/grid/GridRowDraggerOneGridTest.java | 226 +++++++++ .../component/grid/GridRowDraggerTwoGridsTest.java | 270 +++++++++++ 11 files changed, 1020 insertions(+), 1020 deletions(-) delete mode 100644 server/src/main/java/com/vaadin/ui/components/grid/GridDragger.java create mode 100644 server/src/main/java/com/vaadin/ui/components/grid/GridRowDragger.java delete mode 100644 server/src/test/java/com/vaadin/tests/server/component/grid/GridDraggerOneGridTest.java delete mode 100644 server/src/test/java/com/vaadin/tests/server/component/grid/GridDraggerTwoGridsTest.java create mode 100644 server/src/test/java/com/vaadin/tests/server/component/grid/GridRowDraggerOneGridTest.java create mode 100644 server/src/test/java/com/vaadin/tests/server/component/grid/GridRowDraggerTwoGridsTest.java (limited to 'server') diff --git a/server/src/main/java/com/vaadin/ui/components/grid/DropIndexCalculator.java b/server/src/main/java/com/vaadin/ui/components/grid/DropIndexCalculator.java index e61b2df8e6..5b2e4ead0d 100644 --- a/server/src/main/java/com/vaadin/ui/components/grid/DropIndexCalculator.java +++ b/server/src/main/java/com/vaadin/ui/components/grid/DropIndexCalculator.java @@ -23,7 +23,7 @@ import java.io.Serializable; * * @author Vaadin Ltd * @since 8.2 - * @see GridDragger + * @see GridRowDragger * @param * the bean type */ diff --git a/server/src/main/java/com/vaadin/ui/components/grid/GridDragSource.java b/server/src/main/java/com/vaadin/ui/components/grid/GridDragSource.java index e3482a04f2..cb0973d016 100644 --- a/server/src/main/java/com/vaadin/ui/components/grid/GridDragSource.java +++ b/server/src/main/java/com/vaadin/ui/components/grid/GridDragSource.java @@ -43,7 +43,7 @@ import elemental.json.JsonObject; * The Grid bean type. * @author Vaadin Ltd. * @since 8.1 - * @see GridDragger + * @see GridRowDragger */ public class GridDragSource extends DragSourceExtension> { diff --git a/server/src/main/java/com/vaadin/ui/components/grid/GridDragger.java b/server/src/main/java/com/vaadin/ui/components/grid/GridDragger.java deleted file mode 100644 index a181b792ad..0000000000 --- a/server/src/main/java/com/vaadin/ui/components/grid/GridDragger.java +++ /dev/null @@ -1,519 +0,0 @@ -/* - * Copyright 2000-2016 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.ui.components.grid; - -import java.io.Serializable; -import java.util.Collection; -import java.util.List; -import java.util.Optional; - -import com.vaadin.data.provider.DataProvider; -import com.vaadin.data.provider.ListDataProvider; -import com.vaadin.shared.ui.dnd.DropEffect; -import com.vaadin.shared.ui.grid.DropLocation; -import com.vaadin.shared.ui.grid.DropMode; -import com.vaadin.ui.Grid; -import com.vaadin.ui.Grid.Column; - -/** - * Allows dragging rows for reordering within a Grid and between two separate - * Grids when the item type is the same. - *

- * When dragging a selected row, all the visible selected rows are dragged. Note - * that ONLY currently visible rows are taken into account. The drop mode for - * the target grid is by default {@link DropMode#BETWEEN}. - *

- * To customize the settings for either the source or the target grid, use - * {@link #getGridDragSource()} and {@link #getGridDropTarget()}.The drop target - * grid has been set to not allow drops for a target row when the grid has been - * sorted, since the visual drop target location would not match where the item - * would actually be dropped into. - *

- * NOTE: this helper works only with {@link ListDataProvider} on both grids. - * If you have another data provider, you should customize data provider - * updating on drop with - * {@link #setSourceDataProviderUpdater(SourceDataProviderUpdater)} & - * {@link #setTargetDataProviderUpdater(TargetDataProviderUpdater)} and add a - * custom drop index calculator with - * {@link #setDropIndexCalculator(DropIndexCalculator)}. - *

- * In case you are not using a {@link ListDataProvider} and don't have custom - * handlers, {@link UnsupportedOperationException} is thrown on drop event. - * - * @param - * The Grid bean type. - * @author Vaadin Ltd - * @since 8.2 - */ -public class GridDragger implements Serializable { - - private final GridDropTarget gridDropTarget; - private final GridDragSource gridDragSource; - - private DropIndexCalculator dropTargetIndexCalculator = null; - private SourceDataProviderUpdater sourceDataProviderUpdater = null; - private TargetDataProviderUpdater targetDataProviderUpdater = null; - - /** - * Set of items currently being dragged. - */ - private List draggedItems; - private int shiftedDropIndex; - - /** - * Enables DnD reordering for the rows in the given grid. - *

- * {@link DropMode#BETWEEN} is used. - *

- * NOTE: this only works when the grid has a - * {@link ListDataProvider}. Use the custom handlers - * {@link #setSourceDataProviderUpdater(SourceDataProviderUpdater)} and - * {@link #setTargetDataProviderUpdater(TargetDataProviderUpdater)} for - * other data providers. - *

- * NOTE: When allowing the user to DnD reorder a grid's rows, you - * should not allow the user to sort the grid since when the grid is sorted, - * as the reordering doens't make any sense since the drop target cannot be - * shown for the correct place due to the sorting. Sorting columns is - * enabled by default for in-memory data provider grids. Sorting can be - * disabled for columns with {@link Grid#getColumns()} and - * {@link Column#setSortable(boolean)}. - * - * @param grid - * Grid to be extended. - */ - public GridDragger(Grid grid) { - this(grid, DropMode.BETWEEN); - } - - /** - * Enables DnD reordering the rows in the given grid with the given drop - * mode. - *

- * NOTE: this only works when the grid has a - * {@link ListDataProvider}. Use the custom handlers - * {@link #setSourceDataProviderUpdater(SourceDataProviderUpdater)} and - * {@link #setTargetDataProviderUpdater(TargetDataProviderUpdater)} for - * other data providers. - *

- * NOTE: When allowing the user to DnD reorder a grid's rows, you - * should not allow the user to sort the grid since when the grid is sorted, - * as the reordering doens't make any sense since the drop target cannot be - * shown for the correct place due to the sorting. Sorting columns is - * enabled by default for in-memory data provider grids. Sorting can be - * disabled for columns with {@link Grid#getColumns()} and - * {@link Column#setSortable(boolean)}. - * - * @param grid - * the grid to enable row DnD reordering on - * @param dropMode - * DropMode to be used. - */ - public GridDragger(Grid grid, DropMode dropMode) { - this(grid, grid, dropMode); - } - - /** - * Enables DnD moving of rows from the source grid to the target grid. - *

- * {@link DropMode#BETWEEN} is used. - *

- * NOTE: this only works when the grids have a - * {@link ListDataProvider}. Use the custom handlers - * {@link #setSourceDataProviderUpdater(SourceDataProviderUpdater)} and - * {@link #setTargetDataProviderUpdater(TargetDataProviderUpdater)} for - * other data providers. - * - * @param source - * the source grid dragged from. - * @param target - * the target grid dropped to. - */ - public GridDragger(Grid source, Grid target) { - this(source, target, DropMode.BETWEEN); - } - - /** - * Enables DnD moving of rows from the source grid to the target grid with - * the custom data provider updaters. - *

- * {@link DropMode#BETWEEN} is used. - * - * @param source - * grid dragged from - * @param target - * grid dragged to - * @param targetDataProviderUpdater - * handler for updating target grid data provider - * @param sourceDataProviderUpdater - * handler for updating source grid data provider - */ - public GridDragger(Grid source, Grid target, - TargetDataProviderUpdater targetDataProviderUpdater, - SourceDataProviderUpdater sourceDataProviderUpdater) { - this(source, target, DropMode.BETWEEN); - this.targetDataProviderUpdater = targetDataProviderUpdater; - this.sourceDataProviderUpdater = sourceDataProviderUpdater; - } - - /** - * Enables DnD moving of rows from the source grid to the target grid with - * the given drop mode. - *

- * NOTE: this only works when the grids have a - * {@link ListDataProvider}. Use the other constructors or custom - * handlers {@link #setSourceDataProviderUpdater(SourceDataProviderUpdater)} - * and {@link #setTargetDataProviderUpdater(TargetDataProviderUpdater)} for - * other data providers. - * - * @param source - * the drag source grid - * @param target - * the drop target grid - * @param dropMode - * the drop mode to use - */ - public GridDragger(Grid source, Grid target, DropMode dropMode) { - gridDragSource = new GridDragSource<>(source); - - gridDropTarget = new GridDropTarget<>(target, dropMode); - gridDropTarget.setDropAllowedOnSortedGridRows(false); - - gridDragSource.addGridDragStartListener(event -> { - draggedItems = event.getDraggedItems(); - }); - - gridDropTarget.addGridDropListener(this::handleDrop); - } - - /** - * Sets the target data provider updater, which handles adding the dropped - * items to the target grid. - *

- * By default, items are added to the index where they were dropped on for - * any {@link ListDataProvider}. If another type of data provider is used, - * this updater should be set to handle updating instead. - * - * @param targetDataProviderUpdater - * the target drop handler to set, or {@code null} to remove - */ - public void setTargetDataProviderUpdater( - TargetDataProviderUpdater targetDataProviderUpdater) { - this.targetDataProviderUpdater = targetDataProviderUpdater; - } - - /** - * Returns the target grid data provider updater. - * - * @return target grid drop handler - */ - public TargetDataProviderUpdater getTargetDataProviderUpdater() { - return targetDataProviderUpdater; - } - - /** - * Sets the source data provider updater, which handles removing items from - * the drag source grid. - *

- * By default the items are removed from any {@link ListDataProvider}. If - * another type of data provider is used, this updater should be set to - * handle updating instead. - *

- * If you want to skip removing items from the source, you can use - * {@link SourceDataProviderUpdater#NOOP}. - * - * @param sourceDataProviderUpdater - * the drag source data provider updater to set, or {@code null} - * to remove - */ - public void setSourceDataProviderUpdater( - SourceDataProviderUpdater sourceDataProviderUpdater) { - this.sourceDataProviderUpdater = sourceDataProviderUpdater; - } - - /** - * Returns the source grid data provider updater. - *

- * Default is {@code null} and the items are just removed from the source - * grid, which only works for {@link ListDataProvider}. - * - * @return the source grid drop handler - */ - public SourceDataProviderUpdater getSourceDataProviderUpdater() { - return sourceDataProviderUpdater; - } - - /** - * Sets the drop index calculator for the target grid. With this callback - * you can have a custom drop location instead of the actual one. - *

- * By default, items are placed on the index they are dropped into in the - * target grid. - *

- * If you want to always drop items to the end of the target grid, you can - * use {@link DropIndexCalculator#ALWAYS_DROP_TO_END}. - * - * @param dropIndexCalculator - * the drop index calculator - */ - public void setDropIndexCalculator( - DropIndexCalculator dropIndexCalculator) { - this.dropTargetIndexCalculator = dropIndexCalculator; - } - - /** - * Gets the drop index calculator. - *

- * Default is {@code null} and the dropped items are placed on the drop - * location. - * - * @return the drop index calculator - */ - public DropIndexCalculator getDropIndexCalculator() { - return dropTargetIndexCalculator; - } - - /** - * Returns the drop target grid to allow performing customizations such as - * altering {@link DropEffect}. - * - * @return the drop target grid - */ - public GridDropTarget getGridDropTarget() { - return gridDropTarget; - } - - /** - * Returns the drag source grid, exposing it for customizations. - * - * @return the drag source grid - */ - public GridDragSource getGridDragSource() { - return gridDragSource; - } - - /** - * Returns the currently dragged items captured from the source grid no drag - * start event, or {@code null} if no drag active. - * - * @return the currently dragged items or {@code null} - */ - protected List getDraggedItems() { - return draggedItems; - } - - /** - * This method is triggered when there has been a drop on the target grid. - *

- * This method is protected only for testing reasons, you should not - * override this but instead use - * {@link #setSourceDataProviderUpdater(SourceDataProviderUpdater)}, - * {@link #setTargetDataProviderUpdater(TargetDataProviderUpdater)} and - * {@link #setDropIndexCalculator(DropIndexCalculator)} to customize how to - * handle the drops. - * - * @param event - * the drop event on the target grid - */ - protected void handleDrop(GridDropEvent event) { - // there is a case that the drop happened from some other grid than the - // source one - if (getDraggedItems() == null) { - return; - } - - // don't do anything if not supported data providers used without custom - // handlers - verifySupportedDataProviders(); - - shiftedDropIndex = -1; - handleSourceGridDrop(event, getDraggedItems()); - - int index = calculateDropIndex(event); - - handleTargetGridDrop(event, index, getDraggedItems()); - - draggedItems = null; - } - - private void handleSourceGridDrop(GridDropEvent event, - final Collection droppedItems) { - Grid source = getGridDragSource().getGrid(); - - if (getSourceDataProviderUpdater() != null) { - getSourceDataProviderUpdater().removeItems(event.getDropEffect(), - source.getDataProvider(), droppedItems); - return; - } - - ListDataProvider listDataProvider = (ListDataProvider) source - .getDataProvider(); - - // use the existing data source to keep filters and sort orders etc. in - // place. - Collection sourceItems = listDataProvider.getItems(); - - // if reordering the same grid and dropping on top of one of the dragged - // rows, need to calculate the new drop index before removing the items - if (getGridDragSource().getGrid() == getGridDropTarget().getGrid() - && event.getDropTargetRow().isPresent() - && getDraggedItems().contains(event.getDropTargetRow().get())) { - List sourceItemsList = (List) sourceItems; - shiftedDropIndex = sourceItemsList - .indexOf(event.getDropTargetRow().get()); - shiftedDropIndex -= getDraggedItems().stream().filter( - item -> sourceItemsList.indexOf(item) < shiftedDropIndex) - .count(); - } - - sourceItems.removeAll(droppedItems); - listDataProvider.refreshAll(); - } - - private void handleTargetGridDrop(GridDropEvent event, final int index, - Collection droppedItems) { - Grid target = getGridDropTarget().getGrid(); - - if (getTargetDataProviderUpdater() != null) { - getTargetDataProviderUpdater().onDrop(event.getDropEffect(), - target.getDataProvider(), index, droppedItems); - return; - } - - ListDataProvider listDataProvider = (ListDataProvider) target - .getDataProvider(); - // update the existing to keep filters etc. - List targetItems = (List) listDataProvider.getItems(); - - if (index != Integer.MAX_VALUE) { - targetItems.addAll(index, droppedItems); - } else { - targetItems.addAll(droppedItems); - } - // instead of using setItems or creating a new data provider, - // refresh the existing one to keep filters etc. in place - listDataProvider.refreshAll(); - - // if dropped to the end of the grid, the grid should scroll there so - // that the dropped row is visible, but that is just recommended in - // documentation and left for the users to take into use - } - - private int calculateDropIndex(GridDropEvent event) { - // use custom calculator if present - if (getDropIndexCalculator() != null) { - return getDropIndexCalculator().calculateDropIndex(event); - } - - // if the source and target grids are the same, then the index has been - // calculated before removing the items. In this case the drop location - // is always above, since the items will be starting from that point on - if (shiftedDropIndex != -1) { - return shiftedDropIndex; - } - - ListDataProvider targetDataProvider = (ListDataProvider) getGridDropTarget() - .getGrid().getDataProvider(); - List items = (List) targetDataProvider.getItems(); - int index = items.size(); - - Optional dropTargetRow = event.getDropTargetRow(); - if (dropTargetRow.isPresent()) { - index = items.indexOf(dropTargetRow.get()) - + (event.getDropLocation() == DropLocation.BELOW ? 1 : 0); - } - - return index; - } - - private void verifySupportedDataProviders() { - verifySourceDataProvider(); - verifyTargetDataProvider(); - } - - @SuppressWarnings("unchecked") - private void verifySourceDataProvider() { - if (getSourceDataProviderUpdater() != null) { - return; // custom updater is always fine - } - - if (!(getSourceDataProvider() instanceof ListDataProvider)) { - throwUnsupportedOperationExceptionForUnsupportedDataProvider(true); - } - - if (!(((ListDataProvider) getSourceDataProvider()) - .getItems() instanceof List)) { - throwUnsupportedOperationExceptionForUnsupportedCollectionInListDataProvider( - true); - } - } - - @SuppressWarnings("unchecked") - private void verifyTargetDataProvider() { - if (getTargetDataProviderUpdater() != null - && getDropIndexCalculator() != null) { - return; // custom updater and calculator is always fine - } - - if (!(getTargetDataProvider() instanceof ListDataProvider)) { - throwUnsupportedOperationExceptionForUnsupportedDataProvider(false); - } - - if (!(((ListDataProvider) getTargetDataProvider()) - .getItems() instanceof List)) { - throwUnsupportedOperationExceptionForUnsupportedCollectionInListDataProvider( - false); - } - } - - private DataProvider getSourceDataProvider() { - return getGridDragSource().getGrid().getDataProvider(); - } - - private DataProvider getTargetDataProvider() { - return getGridDropTarget().getGrid().getDataProvider(); - } - - private static void throwUnsupportedOperationExceptionForUnsupportedDataProvider( - boolean sourceGrid) { - throw new UnsupportedOperationException( - new StringBuilder().append(sourceGrid ? "Source " : "Target ") - .append("grid does not have a ListDataProvider, cannot automatically ") - .append(sourceGrid ? "remove " : "add ") - .append("items. Use GridDragger.set") - .append(sourceGrid ? "Source" : "Target") - .append("DataProviderUpdater(...) ") - .append(sourceGrid ? "" - : "and setDropIndexCalculator(...) " - + "to customize how to handle updating the data provider.") - .toString()); - } - - private static void throwUnsupportedOperationExceptionForUnsupportedCollectionInListDataProvider( - boolean sourceGrid) { - throw new UnsupportedOperationException(new StringBuilder() - .append(sourceGrid ? "Source " : "Target ") - .append("grid's ListDataProvider is not backed by a List-collection, cannot ") - .append(sourceGrid ? "remove " : "add ") - .append("items. Use a ListDataProvider backed by a List, or use GridDragger.set") - .append(sourceGrid ? "Source" : "Target") - .append("DataProviderUpdater(...) ") - .append(sourceGrid ? "" : "and setDropIndexCalculator(...) ") - .append(" to customize how to handle updating the data provider to customize how to handle updating the data provider.") - .toString()); - } - -} diff --git a/server/src/main/java/com/vaadin/ui/components/grid/GridDropTarget.java b/server/src/main/java/com/vaadin/ui/components/grid/GridDropTarget.java index 5073287c52..632a787107 100644 --- a/server/src/main/java/com/vaadin/ui/components/grid/GridDropTarget.java +++ b/server/src/main/java/com/vaadin/ui/components/grid/GridDropTarget.java @@ -35,7 +35,7 @@ import com.vaadin.ui.dnd.DropTargetExtension; * Type of the Grid bean. * @author Vaadin Ltd * @since 8.1 - * @see GridDragger + * @see GridRowDragger */ public class GridDropTarget extends DropTargetExtension> { diff --git a/server/src/main/java/com/vaadin/ui/components/grid/GridRowDragger.java b/server/src/main/java/com/vaadin/ui/components/grid/GridRowDragger.java new file mode 100644 index 0000000000..960fe3ec39 --- /dev/null +++ b/server/src/main/java/com/vaadin/ui/components/grid/GridRowDragger.java @@ -0,0 +1,519 @@ +/* + * Copyright 2000-2016 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.ui.components.grid; + +import java.io.Serializable; +import java.util.Collection; +import java.util.List; +import java.util.Optional; + +import com.vaadin.data.provider.DataProvider; +import com.vaadin.data.provider.ListDataProvider; +import com.vaadin.shared.ui.dnd.DropEffect; +import com.vaadin.shared.ui.grid.DropLocation; +import com.vaadin.shared.ui.grid.DropMode; +import com.vaadin.ui.Grid; +import com.vaadin.ui.Grid.Column; + +/** + * Allows dragging rows for reordering within a Grid and between two separate + * Grids when the item type is the same. + *

+ * When dragging a selected row, all the visible selected rows are dragged. Note + * that ONLY currently visible rows are taken into account. The drop mode for + * the target grid is by default {@link DropMode#BETWEEN}. + *

+ * To customize the settings for either the source or the target grid, use + * {@link #getGridDragSource()} and {@link #getGridDropTarget()}.The drop target + * grid has been set to not allow drops for a target row when the grid has been + * sorted, since the visual drop target location would not match where the item + * would actually be dropped into. + *

+ * NOTE: this helper works only with {@link ListDataProvider} on both grids. + * If you have another data provider, you should customize data provider + * updating on drop with + * {@link #setSourceDataProviderUpdater(SourceDataProviderUpdater)} & + * {@link #setTargetDataProviderUpdater(TargetDataProviderUpdater)} and add a + * custom drop index calculator with + * {@link #setDropIndexCalculator(DropIndexCalculator)}. + *

+ * In case you are not using a {@link ListDataProvider} and don't have custom + * handlers, {@link UnsupportedOperationException} is thrown on drop event. + * + * @param + * The Grid bean type. + * @author Vaadin Ltd + * @since 8.2 + */ +public class GridRowDragger implements Serializable { + + private final GridDropTarget gridDropTarget; + private final GridDragSource gridDragSource; + + private DropIndexCalculator dropTargetIndexCalculator = null; + private SourceDataProviderUpdater sourceDataProviderUpdater = null; + private TargetDataProviderUpdater targetDataProviderUpdater = null; + + /** + * Set of items currently being dragged. + */ + private List draggedItems; + private int shiftedDropIndex; + + /** + * Enables DnD reordering for the rows in the given grid. + *

+ * {@link DropMode#BETWEEN} is used. + *

+ * NOTE: this only works when the grid has a + * {@link ListDataProvider}. Use the custom handlers + * {@link #setSourceDataProviderUpdater(SourceDataProviderUpdater)} and + * {@link #setTargetDataProviderUpdater(TargetDataProviderUpdater)} for + * other data providers. + *

+ * NOTE: When allowing the user to DnD reorder a grid's rows, you + * should not allow the user to sort the grid since when the grid is sorted, + * as the reordering doens't make any sense since the drop target cannot be + * shown for the correct place due to the sorting. Sorting columns is + * enabled by default for in-memory data provider grids. Sorting can be + * disabled for columns with {@link Grid#getColumns()} and + * {@link Column#setSortable(boolean)}. + * + * @param grid + * Grid to be extended. + */ + public GridRowDragger(Grid grid) { + this(grid, DropMode.BETWEEN); + } + + /** + * Enables DnD reordering the rows in the given grid with the given drop + * mode. + *

+ * NOTE: this only works when the grid has a + * {@link ListDataProvider}. Use the custom handlers + * {@link #setSourceDataProviderUpdater(SourceDataProviderUpdater)} and + * {@link #setTargetDataProviderUpdater(TargetDataProviderUpdater)} for + * other data providers. + *

+ * NOTE: When allowing the user to DnD reorder a grid's rows, you + * should not allow the user to sort the grid since when the grid is sorted, + * as the reordering doens't make any sense since the drop target cannot be + * shown for the correct place due to the sorting. Sorting columns is + * enabled by default for in-memory data provider grids. Sorting can be + * disabled for columns with {@link Grid#getColumns()} and + * {@link Column#setSortable(boolean)}. + * + * @param grid + * the grid to enable row DnD reordering on + * @param dropMode + * DropMode to be used. + */ + public GridRowDragger(Grid grid, DropMode dropMode) { + this(grid, grid, dropMode); + } + + /** + * Enables DnD moving of rows from the source grid to the target grid. + *

+ * {@link DropMode#BETWEEN} is used. + *

+ * NOTE: this only works when the grids have a + * {@link ListDataProvider}. Use the custom handlers + * {@link #setSourceDataProviderUpdater(SourceDataProviderUpdater)} and + * {@link #setTargetDataProviderUpdater(TargetDataProviderUpdater)} for + * other data providers. + * + * @param source + * the source grid dragged from. + * @param target + * the target grid dropped to. + */ + public GridRowDragger(Grid source, Grid target) { + this(source, target, DropMode.BETWEEN); + } + + /** + * Enables DnD moving of rows from the source grid to the target grid with + * the custom data provider updaters. + *

+ * {@link DropMode#BETWEEN} is used. + * + * @param source + * grid dragged from + * @param target + * grid dragged to + * @param targetDataProviderUpdater + * handler for updating target grid data provider + * @param sourceDataProviderUpdater + * handler for updating source grid data provider + */ + public GridRowDragger(Grid source, Grid target, + TargetDataProviderUpdater targetDataProviderUpdater, + SourceDataProviderUpdater sourceDataProviderUpdater) { + this(source, target, DropMode.BETWEEN); + this.targetDataProviderUpdater = targetDataProviderUpdater; + this.sourceDataProviderUpdater = sourceDataProviderUpdater; + } + + /** + * Enables DnD moving of rows from the source grid to the target grid with + * the given drop mode. + *

+ * NOTE: this only works when the grids have a + * {@link ListDataProvider}. Use the other constructors or custom + * handlers {@link #setSourceDataProviderUpdater(SourceDataProviderUpdater)} + * and {@link #setTargetDataProviderUpdater(TargetDataProviderUpdater)} for + * other data providers. + * + * @param source + * the drag source grid + * @param target + * the drop target grid + * @param dropMode + * the drop mode to use + */ + public GridRowDragger(Grid source, Grid target, DropMode dropMode) { + gridDragSource = new GridDragSource<>(source); + + gridDropTarget = new GridDropTarget<>(target, dropMode); + gridDropTarget.setDropAllowedOnSortedGridRows(false); + + gridDragSource.addGridDragStartListener(event -> { + draggedItems = event.getDraggedItems(); + }); + + gridDropTarget.addGridDropListener(this::handleDrop); + } + + /** + * Sets the target data provider updater, which handles adding the dropped + * items to the target grid. + *

+ * By default, items are added to the index where they were dropped on for + * any {@link ListDataProvider}. If another type of data provider is used, + * this updater should be set to handle updating instead. + * + * @param targetDataProviderUpdater + * the target drop handler to set, or {@code null} to remove + */ + public void setTargetDataProviderUpdater( + TargetDataProviderUpdater targetDataProviderUpdater) { + this.targetDataProviderUpdater = targetDataProviderUpdater; + } + + /** + * Returns the target grid data provider updater. + * + * @return target grid drop handler + */ + public TargetDataProviderUpdater getTargetDataProviderUpdater() { + return targetDataProviderUpdater; + } + + /** + * Sets the source data provider updater, which handles removing items from + * the drag source grid. + *

+ * By default the items are removed from any {@link ListDataProvider}. If + * another type of data provider is used, this updater should be set to + * handle updating instead. + *

+ * If you want to skip removing items from the source, you can use + * {@link SourceDataProviderUpdater#NOOP}. + * + * @param sourceDataProviderUpdater + * the drag source data provider updater to set, or {@code null} + * to remove + */ + public void setSourceDataProviderUpdater( + SourceDataProviderUpdater sourceDataProviderUpdater) { + this.sourceDataProviderUpdater = sourceDataProviderUpdater; + } + + /** + * Returns the source grid data provider updater. + *

+ * Default is {@code null} and the items are just removed from the source + * grid, which only works for {@link ListDataProvider}. + * + * @return the source grid drop handler + */ + public SourceDataProviderUpdater getSourceDataProviderUpdater() { + return sourceDataProviderUpdater; + } + + /** + * Sets the drop index calculator for the target grid. With this callback + * you can have a custom drop location instead of the actual one. + *

+ * By default, items are placed on the index they are dropped into in the + * target grid. + *

+ * If you want to always drop items to the end of the target grid, you can + * use {@link DropIndexCalculator#ALWAYS_DROP_TO_END}. + * + * @param dropIndexCalculator + * the drop index calculator + */ + public void setDropIndexCalculator( + DropIndexCalculator dropIndexCalculator) { + this.dropTargetIndexCalculator = dropIndexCalculator; + } + + /** + * Gets the drop index calculator. + *

+ * Default is {@code null} and the dropped items are placed on the drop + * location. + * + * @return the drop index calculator + */ + public DropIndexCalculator getDropIndexCalculator() { + return dropTargetIndexCalculator; + } + + /** + * Returns the drop target grid to allow performing customizations such as + * altering {@link DropEffect}. + * + * @return the drop target grid + */ + public GridDropTarget getGridDropTarget() { + return gridDropTarget; + } + + /** + * Returns the drag source grid, exposing it for customizations. + * + * @return the drag source grid + */ + public GridDragSource getGridDragSource() { + return gridDragSource; + } + + /** + * Returns the currently dragged items captured from the source grid no drag + * start event, or {@code null} if no drag active. + * + * @return the currently dragged items or {@code null} + */ + protected List getDraggedItems() { + return draggedItems; + } + + /** + * This method is triggered when there has been a drop on the target grid. + *

+ * This method is protected only for testing reasons, you should not + * override this but instead use + * {@link #setSourceDataProviderUpdater(SourceDataProviderUpdater)}, + * {@link #setTargetDataProviderUpdater(TargetDataProviderUpdater)} and + * {@link #setDropIndexCalculator(DropIndexCalculator)} to customize how to + * handle the drops. + * + * @param event + * the drop event on the target grid + */ + protected void handleDrop(GridDropEvent event) { + // there is a case that the drop happened from some other grid than the + // source one + if (getDraggedItems() == null) { + return; + } + + // don't do anything if not supported data providers used without custom + // handlers + verifySupportedDataProviders(); + + shiftedDropIndex = -1; + handleSourceGridDrop(event, getDraggedItems()); + + int index = calculateDropIndex(event); + + handleTargetGridDrop(event, index, getDraggedItems()); + + draggedItems = null; + } + + private void handleSourceGridDrop(GridDropEvent event, + final Collection droppedItems) { + Grid source = getGridDragSource().getGrid(); + + if (getSourceDataProviderUpdater() != null) { + getSourceDataProviderUpdater().removeItems(event.getDropEffect(), + source.getDataProvider(), droppedItems); + return; + } + + ListDataProvider listDataProvider = (ListDataProvider) source + .getDataProvider(); + + // use the existing data source to keep filters and sort orders etc. in + // place. + Collection sourceItems = listDataProvider.getItems(); + + // if reordering the same grid and dropping on top of one of the dragged + // rows, need to calculate the new drop index before removing the items + if (getGridDragSource().getGrid() == getGridDropTarget().getGrid() + && event.getDropTargetRow().isPresent() + && getDraggedItems().contains(event.getDropTargetRow().get())) { + List sourceItemsList = (List) sourceItems; + shiftedDropIndex = sourceItemsList + .indexOf(event.getDropTargetRow().get()); + shiftedDropIndex -= getDraggedItems().stream().filter( + item -> sourceItemsList.indexOf(item) < shiftedDropIndex) + .count(); + } + + sourceItems.removeAll(droppedItems); + listDataProvider.refreshAll(); + } + + private void handleTargetGridDrop(GridDropEvent event, final int index, + Collection droppedItems) { + Grid target = getGridDropTarget().getGrid(); + + if (getTargetDataProviderUpdater() != null) { + getTargetDataProviderUpdater().onDrop(event.getDropEffect(), + target.getDataProvider(), index, droppedItems); + return; + } + + ListDataProvider listDataProvider = (ListDataProvider) target + .getDataProvider(); + // update the existing to keep filters etc. + List targetItems = (List) listDataProvider.getItems(); + + if (index != Integer.MAX_VALUE) { + targetItems.addAll(index, droppedItems); + } else { + targetItems.addAll(droppedItems); + } + // instead of using setItems or creating a new data provider, + // refresh the existing one to keep filters etc. in place + listDataProvider.refreshAll(); + + // if dropped to the end of the grid, the grid should scroll there so + // that the dropped row is visible, but that is just recommended in + // documentation and left for the users to take into use + } + + private int calculateDropIndex(GridDropEvent event) { + // use custom calculator if present + if (getDropIndexCalculator() != null) { + return getDropIndexCalculator().calculateDropIndex(event); + } + + // if the source and target grids are the same, then the index has been + // calculated before removing the items. In this case the drop location + // is always above, since the items will be starting from that point on + if (shiftedDropIndex != -1) { + return shiftedDropIndex; + } + + ListDataProvider targetDataProvider = (ListDataProvider) getGridDropTarget() + .getGrid().getDataProvider(); + List items = (List) targetDataProvider.getItems(); + int index = items.size(); + + Optional dropTargetRow = event.getDropTargetRow(); + if (dropTargetRow.isPresent()) { + index = items.indexOf(dropTargetRow.get()) + + (event.getDropLocation() == DropLocation.BELOW ? 1 : 0); + } + + return index; + } + + private void verifySupportedDataProviders() { + verifySourceDataProvider(); + verifyTargetDataProvider(); + } + + @SuppressWarnings("unchecked") + private void verifySourceDataProvider() { + if (getSourceDataProviderUpdater() != null) { + return; // custom updater is always fine + } + + if (!(getSourceDataProvider() instanceof ListDataProvider)) { + throwUnsupportedOperationExceptionForUnsupportedDataProvider(true); + } + + if (!(((ListDataProvider) getSourceDataProvider()) + .getItems() instanceof List)) { + throwUnsupportedOperationExceptionForUnsupportedCollectionInListDataProvider( + true); + } + } + + @SuppressWarnings("unchecked") + private void verifyTargetDataProvider() { + if (getTargetDataProviderUpdater() != null + && getDropIndexCalculator() != null) { + return; // custom updater and calculator is always fine + } + + if (!(getTargetDataProvider() instanceof ListDataProvider)) { + throwUnsupportedOperationExceptionForUnsupportedDataProvider(false); + } + + if (!(((ListDataProvider) getTargetDataProvider()) + .getItems() instanceof List)) { + throwUnsupportedOperationExceptionForUnsupportedCollectionInListDataProvider( + false); + } + } + + private DataProvider getSourceDataProvider() { + return getGridDragSource().getGrid().getDataProvider(); + } + + private DataProvider getTargetDataProvider() { + return getGridDropTarget().getGrid().getDataProvider(); + } + + private static void throwUnsupportedOperationExceptionForUnsupportedDataProvider( + boolean sourceGrid) { + throw new UnsupportedOperationException( + new StringBuilder().append(sourceGrid ? "Source " : "Target ") + .append("grid does not have a ListDataProvider, cannot automatically ") + .append(sourceGrid ? "remove " : "add ") + .append("items. Use GridRowDragger.set") + .append(sourceGrid ? "Source" : "Target") + .append("DataProviderUpdater(...) ") + .append(sourceGrid ? "" + : "and setDropIndexCalculator(...) " + + "to customize how to handle updating the data provider.") + .toString()); + } + + private static void throwUnsupportedOperationExceptionForUnsupportedCollectionInListDataProvider( + boolean sourceGrid) { + throw new UnsupportedOperationException(new StringBuilder() + .append(sourceGrid ? "Source " : "Target ") + .append("grid's ListDataProvider is not backed by a List-collection, cannot ") + .append(sourceGrid ? "remove " : "add ") + .append("items. Use a ListDataProvider backed by a List, or use GridRowDragger.set") + .append(sourceGrid ? "Source" : "Target") + .append("DataProviderUpdater(...) ") + .append(sourceGrid ? "" : "and setDropIndexCalculator(...) ") + .append(" to customize how to handle updating the data provider to customize how to handle updating the data provider.") + .toString()); + } + +} diff --git a/server/src/main/java/com/vaadin/ui/components/grid/SourceDataProviderUpdater.java b/server/src/main/java/com/vaadin/ui/components/grid/SourceDataProviderUpdater.java index f84c2bd5db..8fc1cf2bcc 100644 --- a/server/src/main/java/com/vaadin/ui/components/grid/SourceDataProviderUpdater.java +++ b/server/src/main/java/com/vaadin/ui/components/grid/SourceDataProviderUpdater.java @@ -22,7 +22,7 @@ import com.vaadin.data.provider.DataProvider; import com.vaadin.shared.ui.dnd.DropEffect; /** - * A handler for source grid data provider updater for {@link GridDragger}. + * A handler for source grid data provider updater for {@link GridRowDragger}. * * Used to handle updates to the source grid's {@link DataProvider} after a * drop. diff --git a/server/src/main/java/com/vaadin/ui/components/grid/TargetDataProviderUpdater.java b/server/src/main/java/com/vaadin/ui/components/grid/TargetDataProviderUpdater.java index 9596eb7a03..dd02b86c17 100644 --- a/server/src/main/java/com/vaadin/ui/components/grid/TargetDataProviderUpdater.java +++ b/server/src/main/java/com/vaadin/ui/components/grid/TargetDataProviderUpdater.java @@ -22,7 +22,7 @@ import com.vaadin.data.provider.DataProvider; import com.vaadin.shared.ui.dnd.DropEffect; /** - * A handler for target grid data provider updater for {@link GridDragger}. + * A handler for target grid data provider updater for {@link GridRowDragger}. * * Used to handle updates to the target grid's {@link DataProvider} after a * drop. diff --git a/server/src/test/java/com/vaadin/tests/server/component/grid/GridDraggerOneGridTest.java b/server/src/test/java/com/vaadin/tests/server/component/grid/GridDraggerOneGridTest.java deleted file mode 100644 index 24e72566f4..0000000000 --- a/server/src/test/java/com/vaadin/tests/server/component/grid/GridDraggerOneGridTest.java +++ /dev/null @@ -1,226 +0,0 @@ -package com.vaadin.tests.server.component.grid; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.List; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.stream.Stream; - -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; - -import com.vaadin.data.provider.ListDataProvider; -import com.vaadin.shared.ui.grid.DropLocation; -import com.vaadin.ui.Grid; -import com.vaadin.ui.components.grid.DropIndexCalculator; -import com.vaadin.ui.components.grid.GridDragger; -import com.vaadin.ui.components.grid.GridDropEvent; -import com.vaadin.ui.components.grid.SourceDataProviderUpdater; - -public class GridDraggerOneGridTest { - - public class TestGridDragger extends GridDragger { - - public TestGridDragger(Grid grid) { - super(grid); - } - - @Override - public void handleDrop(GridDropEvent event) { - super.handleDrop(event); - } - - @Override - public List getDraggedItems() { - return draggedItems; - } - } - - private Grid source; - private TestGridDragger dragger; - private List draggedItems; - - @Before - public void setupListCase() { - source = new Grid<>(); - source.addColumn(s -> s).setId("1"); - dragger = new TestGridDragger(source); - } - - private void drop(String dropIndex, DropLocation dropLocation, - String... items) { - draggedItems = new ArrayList<>(Arrays.asList(items)); - dragger.handleDrop(new GridDropEvent<>(source, null, null, null, - dropIndex, dropLocation, null)); - } - - private void verifyDataProvider(String... items) { - Collection list = ((ListDataProvider) source - .getDataProvider()).getItems(); - Assert.assertArrayEquals("Invalid items in target data provider", items, - list.toArray()); - } - - private static void setCustomDataProvider(Grid grid) { - grid.setDataProvider((so, i, l) -> null, null); - } - - private static void setCustomDataProvider(Grid grid, - String... items) { - grid.setDataProvider((so, i, l) -> Stream.of(items), null); - } - - @Test - public void listDataProviders_basicOperation() { - source.setItems("0", "1", "2"); - - drop(null, null, "0"); - - verifyDataProvider("1", "2", "0"); - - drop("0", DropLocation.BELOW, "1"); - - verifyDataProvider("2", "0", "1"); - - drop("1", DropLocation.ABOVE, "2"); - - verifyDataProvider("0", "2", "1"); - } - - @Test - public void listDataProvider_dropAboveFirst() { - source.setItems("0", "1"); - - drop("0", DropLocation.ABOVE, "1"); - verifyDataProvider("1", "0"); - } - - @Test - public void listDataProvider_customCalculator() { - source.setItems("0", "1"); - - AtomicInteger trigger = new AtomicInteger(); - dragger.setDropIndexCalculator(event -> { - trigger.incrementAndGet(); - return 0; - }); - - drop("1", DropLocation.BELOW, "0"); - - Assert.assertEquals("Custom calculator should be invoked", 1, - trigger.get()); - verifyDataProvider("0", "1"); - } - - @Test - public void listDataProvider_customCalculatorReturnsMax_droppedToEnd() { - source.setItems("0", "1", "2"); - - dragger.setDropIndexCalculator(event -> { - return Integer.MAX_VALUE; - }); - - drop("1", DropLocation.ABOVE, "0"); - - verifyDataProvider("1", "2", "0"); - } - - @Test - public void noopSourceUpdater() { - source.setItems("0", "1", "2"); - - dragger.setSourceDataProviderUpdater(SourceDataProviderUpdater.NOOP); - - drop("2", DropLocation.ABOVE, "0", "1"); - - verifyDataProvider("0", "1", "0", "1", "2"); - - } - - @Test - public void alwaysDropToEndCalculator() { - source.setItems("0", "1", "2"); - - dragger.setDropIndexCalculator(DropIndexCalculator.ALWAYS_DROP_TO_END); - - drop("1", DropLocation.ABOVE, "0"); - - verifyDataProvider("1", "2", "0"); - } - - @Test - public void dropTwoFromEnd_beginning() { - source.setItems("0", "1", "2", "3"); - - drop("0", DropLocation.ABOVE, "2", "3"); - - verifyDataProvider("2", "3", "0", "1"); - } - - @Test - public void dropTwoFromEnd_middle() { - source.setItems("0", "1", "2", "3"); - - drop("1", DropLocation.ABOVE, "2", "3"); - - verifyDataProvider("0", "2", "3", "1"); - } - - @Test - public void dropTwoFromEnd_aboveOneThatIsDragged_doesntExplode() { - source.setItems("0", "1", "2", "3"); - - drop("2", DropLocation.ABOVE, "2", "3"); - - verifyDataProvider("0", "1", "2", "3"); - } - - @Test - public void dragAndAboveFirst_thatIsAlsoDragged_doesntExplode() { - source.setItems("0", "1", "2", "3"); - - drop("2", DropLocation.ABOVE, "2", "3"); - - verifyDataProvider("0", "1", "2", "3"); - } - - @Test - public void dropFromBeginning_afterOneDragged_doesntExplode() { - source.setItems("0", "1", "2", "3", "4"); - - drop("3", DropLocation.BELOW, "0", "1", "3"); - - verifyDataProvider("2", "0", "1", "3", "4"); - } - - @Test - public void dropMixedSet_onOneOfTheDragged_doesntExplode() { - source.setItems("0", "1", "2", "3", "4"); - - drop("2", DropLocation.BELOW, "0", "2", "4"); - - verifyDataProvider("1", "0", "2", "4", "3"); - } - - @Test - public void dropOnSortedGrid_byDefault_dropsToTheEnd() { - Assert.assertFalse( - "Default drops on sorted grid rows should not be allowed", - dragger.getGridDropTarget().isDropAllowedOnSortedGridRows()); - - source.setItems("0", "1", "2", "3", "4"); - - drop("3", DropLocation.BELOW, "1"); - - verifyDataProvider("0", "2", "3", "1", "4"); - - source.sort("1"); - - drop(null, DropLocation.EMPTY, "0"); - - verifyDataProvider("2", "3", "1", "4", "0"); - } - -} diff --git a/server/src/test/java/com/vaadin/tests/server/component/grid/GridDraggerTwoGridsTest.java b/server/src/test/java/com/vaadin/tests/server/component/grid/GridDraggerTwoGridsTest.java deleted file mode 100644 index c73641097d..0000000000 --- a/server/src/test/java/com/vaadin/tests/server/component/grid/GridDraggerTwoGridsTest.java +++ /dev/null @@ -1,270 +0,0 @@ -package com.vaadin.tests.server.component.grid; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.List; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.stream.Stream; - -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; - -import com.vaadin.data.provider.ListDataProvider; -import com.vaadin.shared.ui.grid.DropLocation; -import com.vaadin.ui.Grid; -import com.vaadin.ui.components.grid.DropIndexCalculator; -import com.vaadin.ui.components.grid.GridDragger; -import com.vaadin.ui.components.grid.GridDropEvent; -import com.vaadin.ui.components.grid.SourceDataProviderUpdater; - -public class GridDraggerTwoGridsTest { - - public class TestGridDragger extends GridDragger { - - public TestGridDragger(Grid source, Grid target) { - super(source, target); - } - - @Override - public void handleDrop(GridDropEvent event) { - super.handleDrop(event); - } - - @Override - public List getDraggedItems() { - return draggedItems; - } - } - - private Grid source; - private Grid target; - private TestGridDragger dragger; - private List draggedItems; - - @Before - public void setupListCase() { - source = new Grid<>(); - target = new Grid<>(); - target.addColumn(s -> s).setId("1"); - dragger = new TestGridDragger(source, target); - - target.setItems(); // setup to use list data provider - } - - private void drop(String dropIndex, DropLocation dropLocation, - String... items) { - draggedItems = new ArrayList<>(Arrays.asList(items)); - dragger.handleDrop(new GridDropEvent<>(target, null, null, null, - dropIndex, dropLocation, null)); - } - - private void verifySourceDataProvider(String... items) { - Collection list = ((ListDataProvider) source - .getDataProvider()).getItems(); - Assert.assertArrayEquals("Invalid items in source data provider", items, - list.toArray()); - } - - private void verifyTargetDataProvider(String... items) { - Collection list = ((ListDataProvider) target - .getDataProvider()).getItems(); - Assert.assertArrayEquals("Invalid items in target data provider", items, - list.toArray()); - } - - private static void setCustomDataProvider(Grid grid) { - grid.setDataProvider((so, i, l) -> null, null); - } - - private static void setCustomDataProvider(Grid grid, - String... items) { - grid.setDataProvider((so, i, l) -> Stream.of(items), null); - } - - @Test - public void listDataProviders_basicOperation() { - source.setItems("0", "1", "2"); - - drop(null, null, "0"); - - verifySourceDataProvider("1", "2"); - verifyTargetDataProvider("0"); - - drop("0", DropLocation.BELOW, "1"); - - verifySourceDataProvider("2"); - verifyTargetDataProvider("0", "1"); - - drop("1", DropLocation.ABOVE, "2"); - - verifySourceDataProvider(); - verifyTargetDataProvider("0", "2", "1"); - } - - @Test - public void listDataProvider_dropAboveFirst() { - source.setItems("0"); - target.setItems("1"); - - drop("1", DropLocation.ABOVE, "0"); - verifySourceDataProvider(); - verifyTargetDataProvider("0", "1"); - } - - @Test - public void listDataProvider_customCalculator() { - source.setItems("0"); - target.setItems("1"); - - AtomicInteger trigger = new AtomicInteger(); - dragger.setDropIndexCalculator(event -> { - trigger.incrementAndGet(); - return 0; - }); - - drop("1", DropLocation.BELOW, "0"); - - Assert.assertEquals("Custom calculator should be invoked", 1, - trigger.get()); - verifySourceDataProvider(); - verifyTargetDataProvider("0", "1"); - } - - @Test - public void listDataProvider_customCalculatorReturnsMax_droppedToEnd() { - source.setItems("0"); - target.setItems("1", "2"); - - dragger.setDropIndexCalculator(event -> { - return Integer.MAX_VALUE; - }); - - drop("1", DropLocation.ABOVE, "0"); - - verifySourceDataProvider(); - verifyTargetDataProvider("1", "2", "0"); - } - - @Test - public void customSourceDataProvider_isInvoked() { - setCustomDataProvider(source, "0", "1"); - target.setItems("2"); - - AtomicInteger updaterTrigger = new AtomicInteger(); - List droppedItems = new ArrayList<>(); - dragger.setSourceDataProviderUpdater((event, dp, items) -> { - updaterTrigger.incrementAndGet(); - droppedItems.addAll(items); - }); - - drop("2", DropLocation.BELOW, "0", "1"); - - Assert.assertEquals("source updater not triggered", 1, - updaterTrigger.get()); - Assert.assertArrayEquals(droppedItems.toArray(), - new Object[] { "0", "1" }); - verifyTargetDataProvider("2", "0", "1"); - } - - @Test - public void noopSourceUpdater() { - source.setItems("0", "1"); - target.setItems("2"); - - dragger.setSourceDataProviderUpdater(SourceDataProviderUpdater.NOOP); - - drop("2", DropLocation.ABOVE, "0", "1"); - - verifySourceDataProvider("0", "1"); - verifyTargetDataProvider("0", "1", "2"); - } - - @Test - public void alwaysDropToEndCalculator() { - source.setItems("0"); - target.setItems("1", "2"); - - dragger.setDropIndexCalculator(DropIndexCalculator.ALWAYS_DROP_TO_END); - - drop("1", DropLocation.ABOVE, "0"); - - verifySourceDataProvider(); - verifyTargetDataProvider("1", "2", "0"); - } - - @Test(expected = UnsupportedOperationException.class) - public void customSourceDataProvider_noCustomSourceUpdater_unsupportedOperationExceptionThrown() { - setCustomDataProvider(source); - - drop(null, DropLocation.BELOW, "0"); - } - - @Test(expected = UnsupportedOperationException.class) - public void customTargetDataProvider_noCustomCalculatorAndNoCustomTargetUpdater_unsupportedOperationExceptionThrown() { - setCustomDataProvider(target); - - drop(null, DropLocation.BELOW, "0"); - } - - @Test(expected = UnsupportedOperationException.class) - public void customTargetDataProvider_customCalculatorAndNoCustomTargetUpdater_unsupportedOperationExceptionThrown() { - setCustomDataProvider(target); - dragger.setDropIndexCalculator(event -> 0); - - drop(null, DropLocation.BELOW, "0"); - } - - @Test(expected = UnsupportedOperationException.class) - public void customTargetDataProvider_noCustomCalculatorAndCustomTargetUpdater_unsupportedOperationExceptionThrown() { - source.setItems("0"); - - setCustomDataProvider(target); - dragger.setTargetDataProviderUpdater((event, dp, index, items) -> { - }); - - drop(null, DropLocation.BELOW, "0"); - } - - @Test - public void customTargetDataProvider_customCalculatorAndCustomTargetUpdater_triggeredWithMaxIndex() { - source.setItems("0"); - setCustomDataProvider(target, "1", "2", "3"); - - AtomicInteger updaterTrigger = new AtomicInteger(-1); - dragger.setTargetDataProviderUpdater( - (event, dp, index, items) -> updaterTrigger.set(index)); - - AtomicInteger calculatorTrigger = new AtomicInteger(); - dragger.setDropIndexCalculator(event -> { - calculatorTrigger.incrementAndGet(); - return 2; - }); - - drop("1", DropLocation.ABOVE, "2"); - - Assert.assertEquals("custom calculator not triggered", 1, - calculatorTrigger.get()); - // getting value from custom calculator - Assert.assertEquals("given drop index to target updater is wrong", 2, - updaterTrigger.get()); - } - - @Test - public void dropOnSortedGrid_byDefault_dropsToTheEnd() { - Assert.assertFalse( - "Default drops on sorted grid rows should not be allowed", - dragger.getGridDropTarget().isDropAllowedOnSortedGridRows()); - - source.setItems("0", "1", "2"); - target.setItems("4", "5"); - - target.sort("1"); - - drop(null, DropLocation.EMPTY, "0"); - - verifySourceDataProvider("1", "2"); - verifyTargetDataProvider("4", "5", "0"); - } -} diff --git a/server/src/test/java/com/vaadin/tests/server/component/grid/GridRowDraggerOneGridTest.java b/server/src/test/java/com/vaadin/tests/server/component/grid/GridRowDraggerOneGridTest.java new file mode 100644 index 0000000000..9888f42807 --- /dev/null +++ b/server/src/test/java/com/vaadin/tests/server/component/grid/GridRowDraggerOneGridTest.java @@ -0,0 +1,226 @@ +package com.vaadin.tests.server.component.grid; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Stream; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import com.vaadin.data.provider.ListDataProvider; +import com.vaadin.shared.ui.grid.DropLocation; +import com.vaadin.ui.Grid; +import com.vaadin.ui.components.grid.DropIndexCalculator; +import com.vaadin.ui.components.grid.GridRowDragger; +import com.vaadin.ui.components.grid.GridDropEvent; +import com.vaadin.ui.components.grid.SourceDataProviderUpdater; + +public class GridRowDraggerOneGridTest { + + public class TestGridRowDragger extends GridRowDragger { + + public TestGridRowDragger(Grid grid) { + super(grid); + } + + @Override + public void handleDrop(GridDropEvent event) { + super.handleDrop(event); + } + + @Override + public List getDraggedItems() { + return draggedItems; + } + } + + private Grid source; + private TestGridRowDragger dragger; + private List draggedItems; + + @Before + public void setupListCase() { + source = new Grid<>(); + source.addColumn(s -> s).setId("1"); + dragger = new TestGridRowDragger(source); + } + + private void drop(String dropIndex, DropLocation dropLocation, + String... items) { + draggedItems = new ArrayList<>(Arrays.asList(items)); + dragger.handleDrop(new GridDropEvent<>(source, null, null, null, + dropIndex, dropLocation, null)); + } + + private void verifyDataProvider(String... items) { + Collection list = ((ListDataProvider) source + .getDataProvider()).getItems(); + Assert.assertArrayEquals("Invalid items in target data provider", items, + list.toArray()); + } + + private static void setCustomDataProvider(Grid grid) { + grid.setDataProvider((so, i, l) -> null, null); + } + + private static void setCustomDataProvider(Grid grid, + String... items) { + grid.setDataProvider((so, i, l) -> Stream.of(items), null); + } + + @Test + public void listDataProviders_basicOperation() { + source.setItems("0", "1", "2"); + + drop(null, null, "0"); + + verifyDataProvider("1", "2", "0"); + + drop("0", DropLocation.BELOW, "1"); + + verifyDataProvider("2", "0", "1"); + + drop("1", DropLocation.ABOVE, "2"); + + verifyDataProvider("0", "2", "1"); + } + + @Test + public void listDataProvider_dropAboveFirst() { + source.setItems("0", "1"); + + drop("0", DropLocation.ABOVE, "1"); + verifyDataProvider("1", "0"); + } + + @Test + public void listDataProvider_customCalculator() { + source.setItems("0", "1"); + + AtomicInteger trigger = new AtomicInteger(); + dragger.setDropIndexCalculator(event -> { + trigger.incrementAndGet(); + return 0; + }); + + drop("1", DropLocation.BELOW, "0"); + + Assert.assertEquals("Custom calculator should be invoked", 1, + trigger.get()); + verifyDataProvider("0", "1"); + } + + @Test + public void listDataProvider_customCalculatorReturnsMax_droppedToEnd() { + source.setItems("0", "1", "2"); + + dragger.setDropIndexCalculator(event -> { + return Integer.MAX_VALUE; + }); + + drop("1", DropLocation.ABOVE, "0"); + + verifyDataProvider("1", "2", "0"); + } + + @Test + public void noopSourceUpdater() { + source.setItems("0", "1", "2"); + + dragger.setSourceDataProviderUpdater(SourceDataProviderUpdater.NOOP); + + drop("2", DropLocation.ABOVE, "0", "1"); + + verifyDataProvider("0", "1", "0", "1", "2"); + + } + + @Test + public void alwaysDropToEndCalculator() { + source.setItems("0", "1", "2"); + + dragger.setDropIndexCalculator(DropIndexCalculator.ALWAYS_DROP_TO_END); + + drop("1", DropLocation.ABOVE, "0"); + + verifyDataProvider("1", "2", "0"); + } + + @Test + public void dropTwoFromEnd_beginning() { + source.setItems("0", "1", "2", "3"); + + drop("0", DropLocation.ABOVE, "2", "3"); + + verifyDataProvider("2", "3", "0", "1"); + } + + @Test + public void dropTwoFromEnd_middle() { + source.setItems("0", "1", "2", "3"); + + drop("1", DropLocation.ABOVE, "2", "3"); + + verifyDataProvider("0", "2", "3", "1"); + } + + @Test + public void dropTwoFromEnd_aboveOneThatIsDragged_doesntExplode() { + source.setItems("0", "1", "2", "3"); + + drop("2", DropLocation.ABOVE, "2", "3"); + + verifyDataProvider("0", "1", "2", "3"); + } + + @Test + public void dragAndAboveFirst_thatIsAlsoDragged_doesntExplode() { + source.setItems("0", "1", "2", "3"); + + drop("2", DropLocation.ABOVE, "2", "3"); + + verifyDataProvider("0", "1", "2", "3"); + } + + @Test + public void dropFromBeginning_afterOneDragged_doesntExplode() { + source.setItems("0", "1", "2", "3", "4"); + + drop("3", DropLocation.BELOW, "0", "1", "3"); + + verifyDataProvider("2", "0", "1", "3", "4"); + } + + @Test + public void dropMixedSet_onOneOfTheDragged_doesntExplode() { + source.setItems("0", "1", "2", "3", "4"); + + drop("2", DropLocation.BELOW, "0", "2", "4"); + + verifyDataProvider("1", "0", "2", "4", "3"); + } + + @Test + public void dropOnSortedGrid_byDefault_dropsToTheEnd() { + Assert.assertFalse( + "Default drops on sorted grid rows should not be allowed", + dragger.getGridDropTarget().isDropAllowedOnSortedGridRows()); + + source.setItems("0", "1", "2", "3", "4"); + + drop("3", DropLocation.BELOW, "1"); + + verifyDataProvider("0", "2", "3", "1", "4"); + + source.sort("1"); + + drop(null, DropLocation.EMPTY, "0"); + + verifyDataProvider("2", "3", "1", "4", "0"); + } + +} diff --git a/server/src/test/java/com/vaadin/tests/server/component/grid/GridRowDraggerTwoGridsTest.java b/server/src/test/java/com/vaadin/tests/server/component/grid/GridRowDraggerTwoGridsTest.java new file mode 100644 index 0000000000..127365be36 --- /dev/null +++ b/server/src/test/java/com/vaadin/tests/server/component/grid/GridRowDraggerTwoGridsTest.java @@ -0,0 +1,270 @@ +package com.vaadin.tests.server.component.grid; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Stream; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import com.vaadin.data.provider.ListDataProvider; +import com.vaadin.shared.ui.grid.DropLocation; +import com.vaadin.ui.Grid; +import com.vaadin.ui.components.grid.DropIndexCalculator; +import com.vaadin.ui.components.grid.GridRowDragger; +import com.vaadin.ui.components.grid.GridDropEvent; +import com.vaadin.ui.components.grid.SourceDataProviderUpdater; + +public class GridRowDraggerTwoGridsTest { + + public class TestGridRowDragger extends GridRowDragger { + + public TestGridRowDragger(Grid source, Grid target) { + super(source, target); + } + + @Override + public void handleDrop(GridDropEvent event) { + super.handleDrop(event); + } + + @Override + public List getDraggedItems() { + return draggedItems; + } + } + + private Grid source; + private Grid target; + private TestGridRowDragger dragger; + private List draggedItems; + + @Before + public void setupListCase() { + source = new Grid<>(); + target = new Grid<>(); + target.addColumn(s -> s).setId("1"); + dragger = new TestGridRowDragger(source, target); + + target.setItems(); // setup to use list data provider + } + + private void drop(String dropIndex, DropLocation dropLocation, + String... items) { + draggedItems = new ArrayList<>(Arrays.asList(items)); + dragger.handleDrop(new GridDropEvent<>(target, null, null, null, + dropIndex, dropLocation, null)); + } + + private void verifySourceDataProvider(String... items) { + Collection list = ((ListDataProvider) source + .getDataProvider()).getItems(); + Assert.assertArrayEquals("Invalid items in source data provider", items, + list.toArray()); + } + + private void verifyTargetDataProvider(String... items) { + Collection list = ((ListDataProvider) target + .getDataProvider()).getItems(); + Assert.assertArrayEquals("Invalid items in target data provider", items, + list.toArray()); + } + + private static void setCustomDataProvider(Grid grid) { + grid.setDataProvider((so, i, l) -> null, null); + } + + private static void setCustomDataProvider(Grid grid, + String... items) { + grid.setDataProvider((so, i, l) -> Stream.of(items), null); + } + + @Test + public void listDataProviders_basicOperation() { + source.setItems("0", "1", "2"); + + drop(null, null, "0"); + + verifySourceDataProvider("1", "2"); + verifyTargetDataProvider("0"); + + drop("0", DropLocation.BELOW, "1"); + + verifySourceDataProvider("2"); + verifyTargetDataProvider("0", "1"); + + drop("1", DropLocation.ABOVE, "2"); + + verifySourceDataProvider(); + verifyTargetDataProvider("0", "2", "1"); + } + + @Test + public void listDataProvider_dropAboveFirst() { + source.setItems("0"); + target.setItems("1"); + + drop("1", DropLocation.ABOVE, "0"); + verifySourceDataProvider(); + verifyTargetDataProvider("0", "1"); + } + + @Test + public void listDataProvider_customCalculator() { + source.setItems("0"); + target.setItems("1"); + + AtomicInteger trigger = new AtomicInteger(); + dragger.setDropIndexCalculator(event -> { + trigger.incrementAndGet(); + return 0; + }); + + drop("1", DropLocation.BELOW, "0"); + + Assert.assertEquals("Custom calculator should be invoked", 1, + trigger.get()); + verifySourceDataProvider(); + verifyTargetDataProvider("0", "1"); + } + + @Test + public void listDataProvider_customCalculatorReturnsMax_droppedToEnd() { + source.setItems("0"); + target.setItems("1", "2"); + + dragger.setDropIndexCalculator(event -> { + return Integer.MAX_VALUE; + }); + + drop("1", DropLocation.ABOVE, "0"); + + verifySourceDataProvider(); + verifyTargetDataProvider("1", "2", "0"); + } + + @Test + public void customSourceDataProvider_isInvoked() { + setCustomDataProvider(source, "0", "1"); + target.setItems("2"); + + AtomicInteger updaterTrigger = new AtomicInteger(); + List droppedItems = new ArrayList<>(); + dragger.setSourceDataProviderUpdater((event, dp, items) -> { + updaterTrigger.incrementAndGet(); + droppedItems.addAll(items); + }); + + drop("2", DropLocation.BELOW, "0", "1"); + + Assert.assertEquals("source updater not triggered", 1, + updaterTrigger.get()); + Assert.assertArrayEquals(droppedItems.toArray(), + new Object[] { "0", "1" }); + verifyTargetDataProvider("2", "0", "1"); + } + + @Test + public void noopSourceUpdater() { + source.setItems("0", "1"); + target.setItems("2"); + + dragger.setSourceDataProviderUpdater(SourceDataProviderUpdater.NOOP); + + drop("2", DropLocation.ABOVE, "0", "1"); + + verifySourceDataProvider("0", "1"); + verifyTargetDataProvider("0", "1", "2"); + } + + @Test + public void alwaysDropToEndCalculator() { + source.setItems("0"); + target.setItems("1", "2"); + + dragger.setDropIndexCalculator(DropIndexCalculator.ALWAYS_DROP_TO_END); + + drop("1", DropLocation.ABOVE, "0"); + + verifySourceDataProvider(); + verifyTargetDataProvider("1", "2", "0"); + } + + @Test(expected = UnsupportedOperationException.class) + public void customSourceDataProvider_noCustomSourceUpdater_unsupportedOperationExceptionThrown() { + setCustomDataProvider(source); + + drop(null, DropLocation.BELOW, "0"); + } + + @Test(expected = UnsupportedOperationException.class) + public void customTargetDataProvider_noCustomCalculatorAndNoCustomTargetUpdater_unsupportedOperationExceptionThrown() { + setCustomDataProvider(target); + + drop(null, DropLocation.BELOW, "0"); + } + + @Test(expected = UnsupportedOperationException.class) + public void customTargetDataProvider_customCalculatorAndNoCustomTargetUpdater_unsupportedOperationExceptionThrown() { + setCustomDataProvider(target); + dragger.setDropIndexCalculator(event -> 0); + + drop(null, DropLocation.BELOW, "0"); + } + + @Test(expected = UnsupportedOperationException.class) + public void customTargetDataProvider_noCustomCalculatorAndCustomTargetUpdater_unsupportedOperationExceptionThrown() { + source.setItems("0"); + + setCustomDataProvider(target); + dragger.setTargetDataProviderUpdater((event, dp, index, items) -> { + }); + + drop(null, DropLocation.BELOW, "0"); + } + + @Test + public void customTargetDataProvider_customCalculatorAndCustomTargetUpdater_triggeredWithMaxIndex() { + source.setItems("0"); + setCustomDataProvider(target, "1", "2", "3"); + + AtomicInteger updaterTrigger = new AtomicInteger(-1); + dragger.setTargetDataProviderUpdater( + (event, dp, index, items) -> updaterTrigger.set(index)); + + AtomicInteger calculatorTrigger = new AtomicInteger(); + dragger.setDropIndexCalculator(event -> { + calculatorTrigger.incrementAndGet(); + return 2; + }); + + drop("1", DropLocation.ABOVE, "2"); + + Assert.assertEquals("custom calculator not triggered", 1, + calculatorTrigger.get()); + // getting value from custom calculator + Assert.assertEquals("given drop index to target updater is wrong", 2, + updaterTrigger.get()); + } + + @Test + public void dropOnSortedGrid_byDefault_dropsToTheEnd() { + Assert.assertFalse( + "Default drops on sorted grid rows should not be allowed", + dragger.getGridDropTarget().isDropAllowedOnSortedGridRows()); + + source.setItems("0", "1", "2"); + target.setItems("4", "5"); + + target.sort("1"); + + drop(null, DropLocation.EMPTY, "0"); + + verifySourceDataProvider("1", "2"); + verifyTargetDataProvider("4", "5", "0"); + } +} -- cgit v1.2.3