diff options
author | Adam Wagner <wbadam@users.noreply.github.com> | 2017-04-13 11:32:22 +0300 |
---|---|---|
committer | Aleksi Hietanen <aleksi@vaadin.com> | 2017-04-13 11:32:22 +0300 |
commit | 8cf7b41e3f3dddb0a83cbc422511adb24796f85d (patch) | |
tree | eabf83ad3af17110db1f245413aad42aae6f8e97 | |
parent | 107e9dd81f9b00b13250e8239c06c70b82dafcfb (diff) | |
download | vaadin-framework-8cf7b41e3f3dddb0a83cbc422511adb24796f85d.tar.gz vaadin-framework-8cf7b41e3f3dddb0a83cbc422511adb24796f85d.zip |
Grid drag and drop documentation (#9055)
4 files changed, 226 insertions, 59 deletions
diff --git a/documentation/advanced/advanced-dragndrop.asciidoc b/documentation/advanced/advanced-dragndrop.asciidoc index 6e085633c1..963a7a034d 100644 --- a/documentation/advanced/advanced-dragndrop.asciidoc +++ b/documentation/advanced/advanced-dragndrop.asciidoc @@ -143,6 +143,111 @@ compatible browser, such as Mozilla Firefox 3.6 or newer. //// +[[advanced.dragndrop.grid]] +== Drag and Drop Rows in Grid + +It is possible to drag and drop the rows of a Grid component. This allows reordering of rows, dragging rows between different Grids, dragging rows outside of a Grid or dropping data onto rows. + +=== Grid as a Drag Source + +A Grid component's rows can be made draggable by applying [classname]#GridDragSource# extension to the component. The extended Grid's rows become draggable, meaning that each row can be grabbed and moved by the mouse individually. +When the Grid's selection mode is `SelectionMode.MULTI` and multiple rows are selected, it is possible to drag all the visible selected rows by grabbing one of them. However, when the grabbed row is not selected, only that one row will be dragged. + +The following example shows how you can define the allowed drag effect and customize the drag data with the drag data generator. + +[source,java] +---- +Grid<Person> grid = new Grid<>(); +// ... +GridDragSource<Person> dragSource = new GridDragSource<>(grid); + +// set allowed effects +dragSource.setEffectAllowed(EffectAllowed.MOVE); + +// set the drag data generator +dragSource.setDragDataGenerator(person -> { + JsonObject data = Json.createObject(); + data.put("name", person.getFirstName() + " " + person.getLastName()); + data.put("city", person.getAddress().getCity()); + return data; +}); +---- + +The _drag data generator_ defines what data should be transferred when a row is dragged and dropped. The generator is executed for every inserted item and returns a `JsonObject` containing the data to be transferred for that item. The generated data is transferred as a JSON array using the HTML5 DataTransfer's data parameter of type `"text"`. +When no generator is set, the whole row data is transferred as JSON, containing all the data generated by the attached [classname]#DataGenerator# instances, such as the row's content and its key. + +[NOTE] +==== +Note that calling the inherited `setDataTransferText(String data)` method is not supported, since the drag data is set for each row based on the data provided by the generator. +==== + +The [classname]#GridDragStartEvent# is fired when dragging a row has started, and the [classname]#GridDragEndEvent# when the drag has ended, either in a drop or a cancel. + +[source,java] +---- +dragSource.addGridDragStartListener(event -> + // Keep reference to the dragged items + draggedItems = event.getDraggedItems() +); + +// Add drag end listener +dragSource.addGridDragEndListener(event -> { + // If drop was successful, remove dragged items from source Grid + if (event.getDropEffect() == DropEffect.MOVE) { + ((ListDataProvider<Person>) grid.getDataProvider()).getItems() + .removeAll(draggedItems); + grid.getDataProvider().refreshAll(); + + // Remove reference to dragged items + draggedItems = null; + } +}); +---- + +The dragged rows can be accessed from both events using the `getDraggedItems()` method. + +=== Grid as a Drop Target + +To make a Grid component's rows accept a drop event, apply the [classname]#GridDropTarget# extension to the component. When creating the extension, you need to specify where the transferred data can be dropped on. + +[source,java] +---- +Grid<Person> grid = new Grid<>(); +// ... +GridDropTarget<Person> dropTarget = new GridDropTarget<>(grid, DropMode.BETWEEN); +dropTarget.setDropEffect(DropEffect.MOVE); +---- + +The _drop mode_ specifies the behaviour of the row when an element is dragged over or dropped onto it. Use `DropMode.ON_TOP` when you want to drop elements on top of a row and `DropMode.BETWEEN` when you want to drop elements between rows. + +The [classname]#GridDropEvent# is fired when data is dropped onto one of the Grid's rows. The following example shows how you can insert items into the Grid at the drop position. If the drag source is another Grid, you can access the generated drag data with the event's `getDataTransferText()` method. + +[source,java] +---- +dropTarget.addGridDropListener(event -> { + // Accepting dragged items from another Grid in the same UI + event.getDragSourceExtension().ifPresent(source -> { + if (source instanceof GridDragSource) { + // Get the target Grid's items + ListDataProvider<Person> dataProvider = (ListDataProvider<Person>) + event.getComponent().getDataProvider(); + List<Person> items = (List<Person>) dataProvider.getItems(); + + // Calculate the target row's index + int index = items.indexOf(event.getDropTargetRow()) + ( + event.getDropLocation() == DropLocation.BELOW ? 1 : 0); + + // Add dragged items to the target Grid + items.addAll(index, draggedItems); + dataProvider.refreshAll(); + + // Show the dropped data + Notification.show("Dropped row data: " + event.getDataTransferText()); + } + }); +}); +---- + (((range="endofrange", startref="term.advanced.dragndrop"))) diff --git a/documentation/components/components-grid.asciidoc b/documentation/components/components-grid.asciidoc index 1ab9fa89ba..8ecbe85c72 100644 --- a/documentation/components/components-grid.asciidoc +++ b/documentation/components/components-grid.asciidoc @@ -901,6 +901,12 @@ You can scroll to first item with [methodname]#scrollToStart()#, to end with [methodname]#scrollToEnd()#, or to a specific row with [methodname]#scrollTo()#. //// +== Drag and Drop of Rows + +Please refer to the +<<dummy/../../../framework/advanced/advanced-dragndrop#advanced.dragndrop.grid,"Drag and Drop Rows in Grid">> documentation. + +[[advanced.dragndrop.grid]] [[components.grid.stylegeneration]] == Generating Row or Cell Styles diff --git a/server/src/main/java/com/vaadin/ui/GridDragSource.java b/server/src/main/java/com/vaadin/ui/GridDragSource.java index 0cfd9964e2..ef9a608871 100644 --- a/server/src/main/java/com/vaadin/ui/GridDragSource.java +++ b/server/src/main/java/com/vaadin/ui/GridDragSource.java @@ -150,6 +150,14 @@ public class GridDragSource<T> extends DragSourceExtension<Grid<T>> { generatorFunction = generator; } + /** + * Setting the data transfer text for this drag source is not supported. + * + * @throws UnsupportedOperationException + * Setting dataTransferText is not supported, since the drag data is + * set for each row based on the data provided by the generator. + * @see #setDragDataGenerator(SerializableFunction) + */ @Override public void setDataTransferText(String data) throws UnsupportedOperationException { 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 8fed8eabca..3fde1c6bf4 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,15 +15,22 @@ */ package com.vaadin.tests.components.grid; -import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import java.util.stream.IntStream; +import java.util.Random; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; -import com.vaadin.server.Page; +import com.vaadin.data.provider.ListDataProvider; import com.vaadin.server.VaadinRequest; +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.Grid; import com.vaadin.ui.GridDragSource; import com.vaadin.ui.GridDropTarget; @@ -35,41 +42,23 @@ import elemental.json.Json; import elemental.json.JsonObject; public class GridDragAndDrop extends AbstractTestUIWithLog { + + private Set<Person> draggedItems; + @Override protected void setup(VaadinRequest request) { // Drag source Grid - Grid<Bean> dragSourceComponent = new Grid<>(); - dragSourceComponent.setItems(createItems(50, "left")); - dragSourceComponent.addColumn(Bean::getId).setCaption("ID"); - dragSourceComponent.addColumn(Bean::getValue).setCaption("Value"); - - GridDragSource<Bean> dragSource = new GridDragSource<>( - dragSourceComponent); - dragSource.setDragDataGenerator(bean -> { - JsonObject ret = Json.createObject(); - ret.put("generatedId", bean.getId()); - ret.put("generatedValue", bean.getValue()); - return ret; - }); + Grid<Person> left = createGridAndFillWithData(50); + GridDragSource<Person> dragSource = applyDragSource(left); // Drop target Grid - Grid<Bean> dropTargetComponent = new Grid<>(); - dropTargetComponent.setItems(createItems(5, "right")); - dropTargetComponent.addColumn(Bean::getId).setCaption("ID"); - dropTargetComponent.addColumn(Bean::getValue).setCaption("Value"); - - GridDropTarget<Bean> dropTarget = new GridDropTarget<>( - dropTargetComponent, DropMode.ON_TOP); - dropTarget.addGridDropListener(event -> { - log(event.getDataTransferText() + ", targetId=" + event - .getDropTargetRow().getId() + ", location=" + event - .getDropLocation()); - }); + Grid<Person> right = createGridAndFillWithData(5); + GridDropTarget<Person> dropTarget = applyDropTarget(right); // Layout the two grids Layout grids = new HorizontalLayout(); - grids.addComponents(dragSourceComponent, dropTargetComponent); + grids.addComponents(left, right); // Selection modes List<Grid.SelectionMode> selectionModes = Arrays.asList( @@ -77,14 +66,14 @@ public class GridDragAndDrop extends AbstractTestUIWithLog { RadioButtonGroup<Grid.SelectionMode> selectionModeSelect = new RadioButtonGroup<>( "Selection mode", selectionModes); selectionModeSelect.setSelectedItem(Grid.SelectionMode.SINGLE); - selectionModeSelect.addValueChangeListener(event -> dragSourceComponent + 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.ON_TOP); + dropLocationSelect.setSelectedItem(DropMode.BETWEEN); dropLocationSelect.addValueChangeListener( event -> dropTarget.setDropMode(event.getValue())); @@ -92,45 +81,104 @@ public class GridDragAndDrop extends AbstractTestUIWithLog { dropLocationSelect); addComponents(controls, grids); - - // Set dragover styling - Page.getCurrent().getStyles().add(".v-drag-over {color: red;}"); } - private List<Bean> createItems(int num, String prefix) { - List<Bean> items = new ArrayList<>(num); + private Grid<Person> createGridAndFillWithData(int numberOfItems) { + Grid<Person> grid = new Grid<>(); - IntStream.range(0, num) - .forEach(i -> items - .add(new Bean(prefix + "_" + i, "value_" + i))); + 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 items; + return grid; } - public static class Bean { - private String id; - private String value; + private GridDragSource<Person> applyDragSource(Grid<Person> grid) { + GridDragSource<Person> dragSource = new GridDragSource<>(grid); - public Bean(String id, String value) { - this.id = id; - this.value = value; - } + dragSource.setEffectAllowed(EffectAllowed.MOVE); - public String getId() { + // Set data generator + dragSource.setDragDataGenerator(person -> { + JsonObject data = Json.createObject(); + data.put("name", + person.getFirstName() + " " + person.getLastName()); + data.put("city", person.getAddress().getCity()); + return data; + }); - return id; - } + // Add drag start listener + dragSource.addGridDragStartListener(event -> + draggedItems = event.getDraggedItems() + ); + + // Add drag end listener + dragSource.addGridDragEndListener(event -> { + if (event.getDropEffect() == DropEffect.MOVE) { + // If drop is successful, remove dragged item from source Grid + ((ListDataProvider<Person>) grid.getDataProvider()).getItems() + .removeAll(draggedItems); + grid.getDataProvider().refreshAll(); + + // Remove reference to dragged items + draggedItems = null; + } + }); - public void setId(String id) { - this.id = id; - } + return dragSource; + } + + private GridDropTarget<Person> applyDropTarget(Grid<Person> grid) { + // Create and attach extension + GridDropTarget<Person> dropTarget = new GridDropTarget<>(grid, + DropMode.BETWEEN); + dropTarget.setDropEffect(DropEffect.MOVE); + + // Add listener + dropTarget.addGridDropListener(event -> { + event.getDragSourceExtension().ifPresent(source -> { + if (source instanceof GridDragSource) { + ListDataProvider<Person> dataProvider = (ListDataProvider<Person>) event + .getComponent().getDataProvider(); + List<Person> items = (List<Person>) dataProvider.getItems(); + + // Calculate the target row's index + int index = items.indexOf(event.getDropTargetRow()) + ( + event.getDropLocation() == DropLocation.BELOW + ? 1 : 0); + + // Add dragged items to the target Grid + items.addAll(index, draggedItems); + dataProvider.refreshAll(); + + log("dragData=" + event.getDataTransferText() + + ", target=" + + event.getDropTargetRow().getFirstName() + + " " + event.getDropTargetRow().getLastName() + + ", location=" + event.getDropLocation()); + } + }); + }); + + return dropTarget; + } - public String getValue() { - return value; - } + private List<Person> generateItems(int num) { + return Stream.generate(() -> generateRandomPerson(new Random())) + .limit(num).collect(Collectors.toList()); + } - public void setValue(String value) { - this.value = value; - } + 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)); } } |