summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--all/src/main/templates/release-notes.html2
-rw-r--r--server/src/main/java/com/vaadin/ui/components/grid/DropIndexCalculator.java49
-rw-r--r--server/src/main/java/com/vaadin/ui/components/grid/GridDragEndEvent.java10
-rw-r--r--server/src/main/java/com/vaadin/ui/components/grid/GridDragSource.java16
-rw-r--r--server/src/main/java/com/vaadin/ui/components/grid/GridDragStartEvent.java16
-rw-r--r--server/src/main/java/com/vaadin/ui/components/grid/GridDragger.java483
-rw-r--r--server/src/main/java/com/vaadin/ui/components/grid/GridDropTarget.java10
-rw-r--r--server/src/main/java/com/vaadin/ui/components/grid/SourceDataProviderUpdater.java57
-rw-r--r--server/src/main/java/com/vaadin/ui/components/grid/TargetDataProviderUpdater.java55
-rw-r--r--server/src/test/java/com/vaadin/tests/server/component/grid/GridDraggerOneGridTest.java206
-rw-r--r--server/src/test/java/com/vaadin/tests/server/component/grid/GridDraggerTwoGridsTest.java252
-rw-r--r--uitest/src/main/java/com/vaadin/tests/components/grid/AbstractGridDnD.java120
-rw-r--r--uitest/src/main/java/com/vaadin/tests/components/grid/GridDragAndDrop.java98
-rw-r--r--uitest/src/main/java/com/vaadin/tests/components/grid/GridDraggerOneGrid.java40
-rw-r--r--uitest/src/main/java/com/vaadin/tests/components/grid/GridDraggerTwoGrids.java59
15 files changed, 1364 insertions, 109 deletions
diff --git a/all/src/main/templates/release-notes.html b/all/src/main/templates/release-notes.html
index 84fea86ec2..ba6a6a16f5 100644
--- a/all/src/main/templates/release-notes.html
+++ b/all/src/main/templates/release-notes.html
@@ -126,6 +126,8 @@
<li><tt>AbstractDateField</tt> is not a <tt>LegacyComponent</tt> anymore.</li>
<li><tt>AbstractDateField</tt>.<tt>formatDate</tt> is now abstract.</li>
<li><tt>VAbstractTextualDate</tt>.<tt>updateDateVariables()</tt> is now <tt>updateBufferedResolutions()</tt> and <tt>updateAndSendBufferedValues()</tt>.</li>
+ <li><tt></tt></li>
+ <li>For <tt>GridDragStartEvent</tt> and <tt>GridDragEndEvent</tt> classes, the method <tt>getDraggedItems</tt> now returns a <tt>List</tt> instead of a <tt>Set</tt>, with the first-to-last item ordering from client side.</li>
<h2>For incompatible or behavior-altering changes in 8.1, please see <a href="https://vaadin.com/download/release/8.1/8.1.0/release-notes.html#incompatible">8.1 release notes</a></h2>
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
new file mode 100644
index 0000000000..1598937e62
--- /dev/null
+++ b/server/src/main/java/com/vaadin/ui/components/grid/DropIndexCalculator.java
@@ -0,0 +1,49 @@
+/*
+ * 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;
+
+/**
+ * A handler for calculating the index of the dropped items on the drop target
+ * grid.
+ *
+ * @author Vaadin Ltd
+ * @since
+ * @see GridDragger
+ * @param <T>
+ * the bean type
+ */
+@FunctionalInterface
+public interface DropIndexCalculator<T> extends Serializable {
+
+ /**
+ * Calculator for always dropping items to the end of the target grid,
+ * regardless of drop position.
+ */
+ @SuppressWarnings("rawtypes")
+ static DropIndexCalculator ALWAYS_DROP_TO_END = (event -> Integer.MAX_VALUE);
+
+ /**
+ * Called when Items are dropped onto a target grid.
+ *
+ * @param event
+ * the GridDropEvent.
+ * @return index the target index, use {@link Integer#MAX_VALUE} for always
+ * dropping to end
+ */
+ public int calculateDropIndex(GridDropEvent<T> event);
+}
diff --git a/server/src/main/java/com/vaadin/ui/components/grid/GridDragEndEvent.java b/server/src/main/java/com/vaadin/ui/components/grid/GridDragEndEvent.java
index 27fcf222fa..76de717978 100644
--- a/server/src/main/java/com/vaadin/ui/components/grid/GridDragEndEvent.java
+++ b/server/src/main/java/com/vaadin/ui/components/grid/GridDragEndEvent.java
@@ -16,7 +16,7 @@
package com.vaadin.ui.components.grid;
import java.util.Collections;
-import java.util.Set;
+import java.util.List;
import com.vaadin.shared.ui.dnd.DropEffect;
import com.vaadin.ui.Grid;
@@ -33,7 +33,7 @@ import com.vaadin.ui.dnd.event.DragEndEvent;
*/
public class GridDragEndEvent<T> extends DragEndEvent<Grid<T>> {
- private final Set<T> draggedItems;
+ private final List<T> draggedItems;
/**
* Creates a drag end event.
@@ -46,7 +46,7 @@ public class GridDragEndEvent<T> extends DragEndEvent<Grid<T>> {
* Set of items having been dragged.
*/
public GridDragEndEvent(Grid<T> source, DropEffect dropEffect,
- Set<T> draggedItems) {
+ List<T> draggedItems) {
super(source, dropEffect);
this.draggedItems = draggedItems;
@@ -57,7 +57,7 @@ public class GridDragEndEvent<T> extends DragEndEvent<Grid<T>> {
*
* @return an unmodifiable set of items that were being dragged.
*/
- public Set<T> getDraggedItems() {
- return Collections.unmodifiableSet(draggedItems);
+ public List<T> getDraggedItems() {
+ return Collections.unmodifiableList(draggedItems);
}
}
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 80b8daf898..150df40aec 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
@@ -18,7 +18,6 @@ package com.vaadin.ui.components.grid;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import java.util.Set;
import java.util.stream.Collectors;
import com.vaadin.data.provider.DataGenerator;
@@ -97,6 +96,16 @@ public class GridDragSource<T> extends DragSourceExtension<Grid<T>> {
defaultGridGenerator);
}
+ /**
+ * Gets the grid this extension has been attached to.
+ *
+ * @return the grid for this extension
+ * @since
+ */
+ public Grid<T> getGrid() {
+ return getParent();
+ }
+
@Override
protected void registerDragSourceRpc() {
registerRpc(new GridDragSourceRpc() {
@@ -126,7 +135,8 @@ public class GridDragSource<T> extends DragSourceExtension<Grid<T>> {
/**
* Collects the dragged items of a Grid given the list of item keys.
*/
- private Set<T> getDraggedItems(Grid<T> grid, List<String> draggedItemKeys) {
+ private List<T> getDraggedItems(Grid<T> grid,
+ List<String> draggedItemKeys) {
if (draggedItemKeys == null || draggedItemKeys.isEmpty()) {
throw new IllegalStateException(
"The drag event does not contain dragged items");
@@ -134,7 +144,7 @@ public class GridDragSource<T> extends DragSourceExtension<Grid<T>> {
return draggedItemKeys.stream()
.map(key -> grid.getDataCommunicator().getKeyMapper().get(key))
- .collect(Collectors.toSet());
+ .collect(Collectors.toList());
}
/**
diff --git a/server/src/main/java/com/vaadin/ui/components/grid/GridDragStartEvent.java b/server/src/main/java/com/vaadin/ui/components/grid/GridDragStartEvent.java
index 576dca4ef5..0372235111 100644
--- a/server/src/main/java/com/vaadin/ui/components/grid/GridDragStartEvent.java
+++ b/server/src/main/java/com/vaadin/ui/components/grid/GridDragStartEvent.java
@@ -16,7 +16,7 @@
package com.vaadin.ui.components.grid;
import java.util.Collections;
-import java.util.Set;
+import java.util.List;
import com.vaadin.shared.ui.dnd.EffectAllowed;
import com.vaadin.ui.Grid;
@@ -33,7 +33,7 @@ import com.vaadin.ui.dnd.event.DragStartEvent;
*/
public class GridDragStartEvent<T> extends DragStartEvent<Grid<T>> {
- private final Set<T> draggedItems;
+ private final List<T> draggedItems;
/**
* Creates a drag start event.
@@ -46,7 +46,7 @@ public class GridDragStartEvent<T> extends DragStartEvent<Grid<T>> {
* Set of items being dragged.
*/
public GridDragStartEvent(Grid<T> source, EffectAllowed effectAllowed,
- Set<T> draggedItems) {
+ List<T> draggedItems) {
super(source, effectAllowed);
this.draggedItems = draggedItems;
@@ -54,10 +54,14 @@ public class GridDragStartEvent<T> extends DragStartEvent<Grid<T>> {
/**
* Get the dragged row items.
+ * <p>
+ * The ordering of the list is the following: first the item that the drag
+ * started from, optionally followed by all the other selected rows in
+ * first-to-last order on the client side.
*
- * @return an unmodifiable set of items that are being dragged.
+ * @return an unmodifiable list of items that are being dragged.
*/
- public Set<T> getDraggedItems() {
- return Collections.unmodifiableSet(draggedItems);
+ public List<T> getDraggedItems() {
+ return Collections.unmodifiableList(draggedItems);
}
}
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
new file mode 100644
index 0000000000..5258000c46
--- /dev/null
+++ b/server/src/main/java/com/vaadin/ui/components/grid/GridDragger.java
@@ -0,0 +1,483 @@
+/*
+ * 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;
+
+/**
+ * Allows dragging rows for reordering within a Grid and between separate Grids.
+ * <p>
+ * When dragging a selected row, all the visible selected rows are dragged. Note
+ * that ONLY currently visible rows are taken into account.
+ * <p>
+ * <em>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)}.</em>
+ * <p>
+ * In case you are not using a {@link ListDataProvider} and don't have custom
+ * handlers, {@link UnsupportedOperationException} is thrown on drop event.
+ *
+ * @param <T>
+ * The Grid bean type.
+ * @author Vaadin Ltd
+ * @since
+ */
+public class GridDragger<T> implements Serializable {
+
+ private final GridDropTarget<T> gridDropTarget;
+ private final GridDragSource<T> gridDragSource;
+
+ private DropIndexCalculator<T> dropTargetIndexCalculator = null;
+ private SourceDataProviderUpdater<T> sourceDataProviderUpdater = null;
+ private TargetDataProviderUpdater<T> targetDataProviderUpdater = null;
+
+ /**
+ * Set of items currently being dragged.
+ */
+ private List<T> draggedItems;
+ private int shiftedDropIndex;
+
+ /**
+ * Enables DnD reordering for the rows in the given grid.
+ * <p>
+ * {@link DropMode#BETWEEN} is used.
+ *
+ * @param grid
+ * Grid to be extended.
+ */
+ public GridDragger(Grid<T> grid) {
+ this(grid, DropMode.BETWEEN);
+ }
+
+ /**
+ * Enables DnD reordering the rows in the given grid with the given drop
+ * mode.
+ * <p>
+ * <em>NOTE: this only works when the grid has a
+ * {@link ListDataProvider}.</em> Use the custom handlers
+ * {@link #setSourceDataProviderUpdater(SourceDataProviderUpdater)} and
+ * {@link #setTargetDataProviderUpdater(TargetDataProviderUpdater)} for
+ * other data providers.
+ *
+ * @param grid
+ * the grid to enable row DnD reordering on
+ * @param dropMode
+ * DropMode to be used.
+ */
+ public GridDragger(Grid<T> grid, DropMode dropMode) {
+ this(grid, grid, dropMode);
+ }
+
+ /**
+ * Enables DnD moving of rows from the source grid to the target grid.
+ * <p>
+ * {@link DropMode#BETWEEN} is used.
+ * <p>
+ * <em>NOTE: this only works when the grids have a
+ * {@link ListDataProvider}.</em> 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<T> source, Grid<T> 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.
+ * <p>
+ * {@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<T> source, Grid<T> target,
+ TargetDataProviderUpdater<T> targetDataProviderUpdater,
+ SourceDataProviderUpdater<T> 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.
+ * <p>
+ * <em>NOTE: this only works when the grids have a
+ * {@link ListDataProvider}.</em> 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<T> source, Grid<T> target, DropMode dropMode) {
+ gridDragSource = new GridDragSource<>(source);
+
+ gridDropTarget = new GridDropTarget<>(target, dropMode);
+
+ 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.
+ * <p>
+ * 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<T> targetDataProviderUpdater) {
+ this.targetDataProviderUpdater = targetDataProviderUpdater;
+ }
+
+ /**
+ * Returns the target grid data provider updater.
+ *
+ * @return target grid drop handler
+ */
+ public TargetDataProviderUpdater<T> getTargetDataProviderUpdater() {
+ return targetDataProviderUpdater;
+ }
+
+ /**
+ * Sets the source data provider updater, which handles removing items from
+ * the drag source grid.
+ * <p>
+ * 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.
+ * <p>
+ * 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<T> sourceDataProviderUpdater) {
+ this.sourceDataProviderUpdater = sourceDataProviderUpdater;
+ }
+
+ /**
+ * Returns the source grid data provider updater.
+ * <p>
+ * 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<T> 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.
+ * <p>
+ * By default, items are placed on the index they are dropped into in the
+ * target grid.
+ * <p>
+ * 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<T> dropIndexCalculator) {
+ this.dropTargetIndexCalculator = dropIndexCalculator;
+ }
+
+ /**
+ * Gets the drop index calculator.
+ * <p>
+ * Default is {@code null} and the dropped items are placed on the drop
+ * location.
+ *
+ * @return the drop index calculator
+ */
+ public DropIndexCalculator<T> getDropIndexCalculator() {
+ return dropTargetIndexCalculator;
+ }
+
+ /**
+ * Returns the drop target grid to allow performing customizations such as
+ * altering {@link DropEffect}.
+ *
+ * @return the drop target grid
+ */
+ public GridDropTarget<T> getGridDropTarget() {
+ return gridDropTarget;
+ }
+
+ /**
+ * Returns the drag source grid, exposing it for customizations.
+ *
+ * @return the drag source grid
+ */
+ public GridDragSource<T> 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 currenytly dragged items or {@code null}
+ */
+ protected List<T> getDraggedItems() {
+ return draggedItems;
+ }
+
+ /**
+ * This method is triggered when there has been a drop on the target grid.
+ * <p>
+ * <em>This method is protected only for testing reasons, you should not
+ * override this</em> 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<T> 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<T> event,
+ final Collection<T> droppedItems) {
+ Grid<T> source = getGridDragSource().getGrid();
+
+ if (getSourceDataProviderUpdater() != null) {
+ getSourceDataProviderUpdater().removeItems(event.getDropEffect(),
+ source.getDataProvider(), droppedItems);
+ return;
+ }
+
+ ListDataProvider<T> listDataProvider = (ListDataProvider<T>) source
+ .getDataProvider();
+
+ // use the existing data source to keep filters and sort orders etc. in
+ // place.
+ Collection<T> 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<T> sourceItemsList = (List<T>) 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<T> event, final int index,
+ Collection<T> droppedItems) {
+ Grid<T> target = getGridDropTarget().getGrid();
+
+ if (getTargetDataProviderUpdater() != null) {
+ getTargetDataProviderUpdater().onDrop(event.getDropEffect(),
+ target.getDataProvider(), index, droppedItems);
+ return;
+ }
+
+ ListDataProvider<T> listDataProvider = (ListDataProvider<T>) target
+ .getDataProvider();
+ // update the existing to keep filters etc.
+ List<T> targetItems = (List<T>) 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();
+ }
+
+ private int calculateDropIndex(GridDropEvent<T> 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<T> targetDataProvider = (ListDataProvider<T>) getGridDropTarget()
+ .getGrid().getDataProvider();
+ List<T> items = (List<T>) targetDataProvider.getItems();
+ int index = items.size();
+
+ Optional<T> 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<T>) 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<T>) getTargetDataProvider())
+ .getItems() instanceof List)) {
+ throwUnsupportedOperationExceptionForUnsupportedCollectionInListDataProvider(
+ false);
+ }
+ }
+
+ private DataProvider<T, ?> getSourceDataProvider() {
+ return getGridDragSource().getGrid().getDataProvider();
+ }
+
+ private DataProvider<T, ?> 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 3f7ee6128e..a9e9a02264 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
@@ -59,6 +59,16 @@ public class GridDropTarget<T> extends DropTargetExtension<Grid<T>> {
}
/**
+ * Gets the grid this extension has been attached to.
+ *
+ * @return the grid for this extension
+ * @since
+ */
+ public Grid<T> getGrid() {
+ return getParent();
+ }
+
+ /**
* Sets the drop mode of this drop target.
* <p>
* When using {@link DropMode#ON_TOP}, and the grid is either empty or has
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
new file mode 100644
index 0000000000..2a56776077
--- /dev/null
+++ b/server/src/main/java/com/vaadin/ui/components/grid/SourceDataProviderUpdater.java
@@ -0,0 +1,57 @@
+/*
+ * 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 com.vaadin.data.provider.DataProvider;
+import com.vaadin.shared.ui.dnd.DropEffect;
+
+/**
+ * A handler for source grid data provider updater for {@link GridDragger}.
+ *
+ * Used to handle updates to the source grid's {@link DataProvider} after a
+ * drop.
+ *
+ * @author Vaadin Ltd
+ * @since
+ *
+ * @param <T>
+ * the bean type
+ */
+@FunctionalInterface
+public interface SourceDataProviderUpdater<T> extends Serializable {
+
+ /**
+ * A NOOP updater that does not do anything for the source data provider.
+ */
+ static SourceDataProviderUpdater NOOP = (e, dp, i) -> {
+ };
+
+ /**
+ * Called when Items have been dragged.
+ *
+ * @param dropEffect
+ * the reported drop effect from the drop event
+ * @param dataProvider
+ * the data provider for the source grid
+ * @param items
+ * dragged items.
+ */
+ public void removeItems(DropEffect dropEffect,
+ DataProvider<T, ?> dataProvider, Collection<T> items);
+}
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
new file mode 100644
index 0000000000..17e7848d58
--- /dev/null
+++ b/server/src/main/java/com/vaadin/ui/components/grid/TargetDataProviderUpdater.java
@@ -0,0 +1,55 @@
+/*
+ * 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 com.vaadin.data.provider.DataProvider;
+import com.vaadin.shared.ui.dnd.DropEffect;
+
+/**
+ * A handler for target grid data provider updater for {@link GridDragger}.
+ *
+ * Used to handle updates to the target grid's {@link DataProvider} after a
+ * drop.
+ *
+ * @author Vaadin Ltd
+ * @since
+ *
+ * @param <T>
+ * the bean type
+ */
+@FunctionalInterface
+public interface TargetDataProviderUpdater<T> extends Serializable {
+
+ /**
+ * Called when items have been dropped on the target Grid.
+ *
+ * @param dropEffect
+ * the reported drop effect from the drop event
+ * @param dataProvider
+ * the target grid data provider
+ * @param index
+ * the target index, {@link Integer#MAX_VALUE} is used for
+ * dropping things always to the end of the grid without having
+ * to fetch the size of the data provider
+ * @param items
+ * items to be added.
+ */
+ public void onDrop(DropEffect dropEffect, DataProvider<T, ?> dataProvider,
+ int index, Collection<T> items);
+}
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
new file mode 100644
index 0000000000..6d24020e45
--- /dev/null
+++ b/server/src/test/java/com/vaadin/tests/server/component/grid/GridDraggerOneGridTest.java
@@ -0,0 +1,206 @@
+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<String> {
+
+ public TestGridDragger(Grid<String> grid) {
+ super(grid);
+ }
+
+ @Override
+ public void handleDrop(GridDropEvent<String> event) {
+ super.handleDrop(event);
+ }
+
+ @Override
+ public List<String> getDraggedItems() {
+ return draggedItems;
+ }
+ }
+
+ private Grid<String> source;
+ private TestGridDragger dragger;
+ private List<String> draggedItems;
+
+ @Before
+ public void setupListCase() {
+ source = new Grid<>();
+ 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<String> list = ((ListDataProvider<String>) source
+ .getDataProvider()).getItems();
+ Assert.assertArrayEquals("Invalid items in target data provider", items,
+ list.toArray());
+ }
+
+ private static void setCustomDataProvider(Grid<String> grid) {
+ grid.setDataProvider((so, i, l) -> null, null);
+ }
+
+ private static void setCustomDataProvider(Grid<String> 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");
+ }
+
+}
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
new file mode 100644
index 0000000000..0711b711df
--- /dev/null
+++ b/server/src/test/java/com/vaadin/tests/server/component/grid/GridDraggerTwoGridsTest.java
@@ -0,0 +1,252 @@
+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<String> {
+
+ public TestGridDragger(Grid<String> source, Grid<String> target) {
+ super(source, target);
+ }
+
+ @Override
+ public void handleDrop(GridDropEvent<String> event) {
+ super.handleDrop(event);
+ }
+
+ @Override
+ public List<String> getDraggedItems() {
+ return draggedItems;
+ }
+ }
+
+ private Grid<String> source;
+ private Grid<String> target;
+ private TestGridDragger dragger;
+ private List<String> draggedItems;
+
+ @Before
+ public void setupListCase() {
+ source = new Grid<>();
+ target = new Grid<>();
+ 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<String> list = ((ListDataProvider<String>) source
+ .getDataProvider()).getItems();
+ Assert.assertArrayEquals("Invalid items in source data provider", items,
+ list.toArray());
+ }
+
+ private void verifyTargetDataProvider(String... items) {
+ Collection<String> list = ((ListDataProvider<String>) target
+ .getDataProvider()).getItems();
+ Assert.assertArrayEquals("Invalid items in target data provider", items,
+ list.toArray());
+ }
+
+ private static void setCustomDataProvider(Grid<String> grid) {
+ grid.setDataProvider((so, i, l) -> null, null);
+ }
+
+ private static void setCustomDataProvider(Grid<String> 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<String> 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());
+ }
+}
diff --git a/uitest/src/main/java/com/vaadin/tests/components/grid/AbstractGridDnD.java b/uitest/src/main/java/com/vaadin/tests/components/grid/AbstractGridDnD.java
new file mode 100644
index 0000000000..1e1c790898
--- /dev/null
+++ b/uitest/src/main/java/com/vaadin/tests/components/grid/AbstractGridDnD.java
@@ -0,0 +1,120 @@
+package com.vaadin.tests.components.grid;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Random;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import com.vaadin.shared.ui.grid.DropMode;
+import com.vaadin.tests.components.AbstractTestUIWithLog;
+import com.vaadin.tests.util.Person;
+import com.vaadin.tests.util.TestDataGenerator;
+import com.vaadin.ui.CheckBox;
+import com.vaadin.ui.Grid;
+import com.vaadin.ui.HorizontalLayout;
+import com.vaadin.ui.Layout;
+import com.vaadin.ui.RadioButtonGroup;
+import com.vaadin.ui.components.grid.GridDragSource;
+import com.vaadin.ui.components.grid.GridDragger;
+import com.vaadin.ui.components.grid.GridDropTarget;
+
+public abstract class AbstractGridDnD extends AbstractTestUIWithLog {
+
+ protected final Layout controls = new HorizontalLayout();
+
+ protected void initializeTestFor(GridDragger<Person> gridDragger) {
+ initializeTestFor(gridDragger.getGridDragSource().getGrid(),
+ gridDragger.getGridDropTarget().getGrid(),
+ gridDragger.getGridDragSource(),
+ gridDragger.getGridDropTarget());
+ }
+
+ protected void initializeTestFor(Grid<Person> source, Grid<Person> target,
+ GridDragSource<Person> dragSource,
+ GridDropTarget<Person> dropTarget) {
+ // Layout the two grids
+ Layout layout = new HorizontalLayout();
+
+ layout.addComponent(source);
+ layout.addComponent(target); // noop if source == target
+ layout.setWidth("100%");
+
+ // Selection modes
+ List<Grid.SelectionMode> selectionModes = Arrays
+ .asList(Grid.SelectionMode.SINGLE, Grid.SelectionMode.MULTI);
+ RadioButtonGroup<Grid.SelectionMode> selectionModeSelect = new RadioButtonGroup<>(
+ "Selection mode", selectionModes);
+ selectionModeSelect.setSelectedItem(Grid.SelectionMode.SINGLE);
+ selectionModeSelect.addValueChangeListener(
+ event -> source.setSelectionMode(event.getValue()));
+
+ // Drop locations
+ List<DropMode> dropLocations = Arrays.asList(DropMode.values());
+ RadioButtonGroup<DropMode> dropLocationSelect = new RadioButtonGroup<>(
+ "Allowed drop location", dropLocations);
+ dropLocationSelect.setSelectedItem(DropMode.BETWEEN);
+ dropLocationSelect.addValueChangeListener(
+ event -> dropTarget.setDropMode(event.getValue()));
+
+ CheckBox transitionCheckBox = new CheckBox("Transition layout", false);
+ transitionCheckBox.addValueChangeListener(event -> {
+ if (event.getValue()) {
+ layout.addStyleName("transitioned");
+ } else {
+ layout.removeStyleName("transitioned");
+ }
+ });
+ CheckBox dropOnSortedGridRows = new CheckBox("Drop on Sorted Grid Rows",
+ dropTarget.isDropAllowedOnSortedGridRows());
+ dropOnSortedGridRows.addValueChangeListener(event -> {
+ dropTarget.setDropAllowedOnSortedGridRows(event.getValue());
+ });
+
+ RadioButtonGroup<Integer> frozenColumnSelect = new RadioButtonGroup<>(
+ "Frozen columns", Arrays.asList(new Integer[] { -1, 0, 1 }));
+ frozenColumnSelect.setValue(source.getFrozenColumnCount());
+ frozenColumnSelect.addValueChangeListener(event -> {
+ source.setFrozenColumnCount(event.getValue());
+ target.setFrozenColumnCount(event.getValue());
+ });
+
+ controls.addComponents(selectionModeSelect, dropLocationSelect,
+ transitionCheckBox, dropOnSortedGridRows, frozenColumnSelect);
+
+ addComponents(controls, layout);
+
+ getPage().getStyles()
+ .add(".transitioned { transform: translate(-30px, 30px);}");
+ }
+
+ protected Grid<Person> createGridAndFillWithData(int numberOfItems) {
+ Grid<Person> grid = new Grid<>();
+ grid.setWidth("100%");
+
+ grid.setItems(generateItems(numberOfItems));
+ grid.addColumn(
+ person -> person.getFirstName() + " " + person.getLastName())
+ .setCaption("Name");
+ grid.addColumn(person -> person.getAddress().getStreetAddress())
+ .setCaption("Street Address");
+ grid.addColumn(person -> person.getAddress().getCity())
+ .setCaption("City");
+
+ return grid;
+ }
+
+ private List<Person> generateItems(int num) {
+ return Stream.generate(() -> generateRandomPerson(new Random()))
+ .limit(num).collect(Collectors.toList());
+ }
+
+ private Person generateRandomPerson(Random r) {
+ return new Person(TestDataGenerator.getFirstName(r),
+ TestDataGenerator.getLastName(r), "foo@bar.com",
+ TestDataGenerator.getPhoneNumber(r),
+ TestDataGenerator.getStreetAddress(r),
+ TestDataGenerator.getPostalCode(r),
+ TestDataGenerator.getCity(r));
+ }
+}
diff --git a/uitest/src/main/java/com/vaadin/tests/components/grid/GridDragAndDrop.java b/uitest/src/main/java/com/vaadin/tests/components/grid/GridDragAndDrop.java
index 209bb77a69..b6e2dc2af7 100644
--- a/uitest/src/main/java/com/vaadin/tests/components/grid/GridDragAndDrop.java
+++ b/uitest/src/main/java/com/vaadin/tests/components/grid/GridDragAndDrop.java
@@ -15,12 +15,8 @@
*/
package com.vaadin.tests.components.grid;
-import java.util.Arrays;
import java.util.List;
-import java.util.Random;
-import java.util.Set;
import java.util.stream.Collectors;
-import java.util.stream.Stream;
import com.vaadin.annotations.Theme;
import com.vaadin.annotations.Widgetset;
@@ -30,22 +26,16 @@ import com.vaadin.shared.ui.dnd.DropEffect;
import com.vaadin.shared.ui.dnd.EffectAllowed;
import com.vaadin.shared.ui.grid.DropLocation;
import com.vaadin.shared.ui.grid.DropMode;
-import com.vaadin.tests.components.AbstractTestUIWithLog;
import com.vaadin.tests.util.Person;
-import com.vaadin.tests.util.TestDataGenerator;
-import com.vaadin.ui.CheckBox;
import com.vaadin.ui.Grid;
-import com.vaadin.ui.HorizontalLayout;
-import com.vaadin.ui.Layout;
-import com.vaadin.ui.RadioButtonGroup;
import com.vaadin.ui.components.grid.GridDragSource;
import com.vaadin.ui.components.grid.GridDropTarget;
@Theme("valo")
@Widgetset("com.vaadin.DefaultWidgetSet")
-public class GridDragAndDrop extends AbstractTestUIWithLog {
+public class GridDragAndDrop extends AbstractGridDnD {
- private Set<Person> draggedItems;
+ private List<Person> draggedItems;
@Override
protected void setup(VaadinRequest request) {
@@ -59,76 +49,7 @@ public class GridDragAndDrop extends AbstractTestUIWithLog {
Grid<Person> right = createGridAndFillWithData(0);
GridDropTarget<Person> dropTarget = applyDropTarget(right);
- // Layout the two grids
- Layout grids = new HorizontalLayout();
-
- grids.addComponents(left, right);
- grids.setWidth("100%");
-
- // Selection modes
- List<Grid.SelectionMode> selectionModes = Arrays
- .asList(Grid.SelectionMode.SINGLE, Grid.SelectionMode.MULTI);
- RadioButtonGroup<Grid.SelectionMode> selectionModeSelect = new RadioButtonGroup<>(
- "Selection mode", selectionModes);
- selectionModeSelect.setSelectedItem(Grid.SelectionMode.SINGLE);
- selectionModeSelect.addValueChangeListener(
- event -> left.setSelectionMode(event.getValue()));
-
- // Drop locations
- List<DropMode> dropLocations = Arrays.asList(DropMode.values());
- RadioButtonGroup<DropMode> dropLocationSelect = new RadioButtonGroup<>(
- "Allowed drop location", dropLocations);
- dropLocationSelect.setSelectedItem(DropMode.BETWEEN);
- dropLocationSelect.addValueChangeListener(
- event -> dropTarget.setDropMode(event.getValue()));
-
- CheckBox transitionCheckBox = new CheckBox("Transition layout", false);
- transitionCheckBox.addValueChangeListener(event -> {
- if (event.getValue()) {
- grids.addStyleName("transitioned");
- } else {
- grids.removeStyleName("transitioned");
- }
- });
-
- CheckBox dropOnSortedGridRows = new CheckBox("Drop on Sorted Grid Rows",
- dropTarget.isDropAllowedOnSortedGridRows());
- dropOnSortedGridRows.addValueChangeListener(event -> {
- dropTarget.setDropAllowedOnSortedGridRows(event.getValue());
- });
-
- RadioButtonGroup<Integer> frozenColumnSelect = new RadioButtonGroup<>(
- "Frozen columns", Arrays.asList(new Integer[] { -1, 0, 1 }));
- frozenColumnSelect.setValue(left.getFrozenColumnCount());
- frozenColumnSelect.addValueChangeListener(event -> {
- left.setFrozenColumnCount(event.getValue());
- right.setFrozenColumnCount(event.getValue());
- });
-
- Layout controls = new HorizontalLayout(selectionModeSelect,
- dropLocationSelect, transitionCheckBox, frozenColumnSelect,
- dropOnSortedGridRows);
-
- addComponents(controls, grids);
-
- getPage().getStyles()
- .add(".transitioned { transform: translate(-30px, 30px);}");
- }
-
- private Grid<Person> createGridAndFillWithData(int numberOfItems) {
- Grid<Person> grid = new Grid<>();
- grid.setWidth("100%");
-
- grid.setItems(generateItems(numberOfItems));
- grid.addColumn(
- person -> person.getFirstName() + " " + person.getLastName())
- .setCaption("Name");
- grid.addColumn(person -> person.getAddress().getStreetAddress())
- .setCaption("Street Address");
- grid.addColumn(person -> person.getAddress().getCity())
- .setCaption("City");
-
- return grid;
+ initializeTestFor(left, right, dragSource, dropTarget);
}
private GridDragSource<Person> applyDragSource(Grid<Person> grid) {
@@ -220,17 +141,4 @@ public class GridDragAndDrop extends AbstractTestUIWithLog {
return dropTarget;
}
- private List<Person> generateItems(int num) {
- return Stream.generate(() -> generateRandomPerson(new Random()))
- .limit(num).collect(Collectors.toList());
- }
-
- private Person generateRandomPerson(Random r) {
- return new Person(TestDataGenerator.getFirstName(r),
- TestDataGenerator.getLastName(r), "foo@bar.com",
- TestDataGenerator.getPhoneNumber(r),
- TestDataGenerator.getStreetAddress(r),
- TestDataGenerator.getPostalCode(r),
- TestDataGenerator.getCity(r));
- }
}
diff --git a/uitest/src/main/java/com/vaadin/tests/components/grid/GridDraggerOneGrid.java b/uitest/src/main/java/com/vaadin/tests/components/grid/GridDraggerOneGrid.java
new file mode 100644
index 0000000000..752d0d07d3
--- /dev/null
+++ b/uitest/src/main/java/com/vaadin/tests/components/grid/GridDraggerOneGrid.java
@@ -0,0 +1,40 @@
+/*
+ * 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.tests.components.grid;
+
+import com.vaadin.annotations.Theme;
+import com.vaadin.annotations.Widgetset;
+import com.vaadin.server.VaadinRequest;
+import com.vaadin.tests.util.Person;
+import com.vaadin.ui.Grid;
+import com.vaadin.ui.components.grid.GridDragger;
+
+@Theme("valo")
+@Widgetset("com.vaadin.DefaultWidgetSet")
+public class GridDraggerOneGrid extends AbstractGridDnD {
+
+ @Override
+ protected void setup(VaadinRequest request) {
+ getUI().setMobileHtml5DndEnabled(true);
+
+ Grid<Person> grid = createGridAndFillWithData(50);
+
+ GridDragger<Person> gridDragger = new GridDragger<>(grid);
+
+ initializeTestFor(gridDragger);
+ }
+
+}
diff --git a/uitest/src/main/java/com/vaadin/tests/components/grid/GridDraggerTwoGrids.java b/uitest/src/main/java/com/vaadin/tests/components/grid/GridDraggerTwoGrids.java
new file mode 100644
index 0000000000..4942dc7810
--- /dev/null
+++ b/uitest/src/main/java/com/vaadin/tests/components/grid/GridDraggerTwoGrids.java
@@ -0,0 +1,59 @@
+/*
+ * 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.tests.components.grid;
+
+import com.vaadin.annotations.Theme;
+import com.vaadin.annotations.Widgetset;
+import com.vaadin.server.VaadinRequest;
+import com.vaadin.tests.util.Person;
+import com.vaadin.ui.CheckBox;
+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.SourceDataProviderUpdater;
+
+@Theme("valo")
+@Widgetset("com.vaadin.DefaultWidgetSet")
+public class GridDraggerTwoGrids extends AbstractGridDnD {
+
+ @Override
+ protected void setup(VaadinRequest request) {
+ getUI().setMobileHtml5DndEnabled(true);
+
+ // Drag source Grid
+ Grid<Person> left = createGridAndFillWithData(50);
+
+ // Drop target Grid
+ Grid<Person> right = createGridAndFillWithData(0);
+
+ GridDragger<Person> gridDragger = new GridDragger<>(left, right);
+
+ CheckBox addItemsToEnd = new CheckBox("Add Items To End", false);
+ addItemsToEnd.addValueChangeListener(
+ event -> gridDragger.setDropIndexCalculator(event.getValue()
+ ? DropIndexCalculator.ALWAYS_DROP_TO_END : null));
+ CheckBox removeItemsFromSource = new CheckBox(
+ "Remove items from source grid", true);
+ removeItemsFromSource.addValueChangeListener(event -> gridDragger
+ .setSourceDataProviderUpdater(event.getValue() ? null
+ : SourceDataProviderUpdater.NOOP));
+
+ controls.addComponents(addItemsToEnd, removeItemsFromSource);
+
+ initializeTestFor(gridDragger);
+ }
+
+}