diff options
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); + } + +} |