Quellcode durchsuchen

Add API for multiple drag data generators (#9321)

* Add ability to set multiple drag data generators for any drag data type
and change default to be accepted by spreadsheet applications such as Excel
tags/8.1.0.alpha8
Adam Wagner vor 7 Jahren
Ursprung
Commit
76a0e04cb0

+ 40
- 27
client/src/main/java/com/vaadin/client/connectors/grid/GridDragSourceConnector.java Datei anzeigen

@@ -16,7 +16,11 @@
package com.vaadin.client.connectors.grid;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;

import com.google.gwt.animation.client.AnimationScheduler;
@@ -35,6 +39,7 @@ import com.vaadin.client.widget.escalator.RowContainer;
import com.vaadin.client.widget.grid.selection.SelectionModel;
import com.vaadin.client.widgets.Escalator;
import com.vaadin.client.widgets.Grid;
import com.vaadin.server.SerializableFunction;
import com.vaadin.shared.Range;
import com.vaadin.shared.ui.Connect;
import com.vaadin.shared.ui.dnd.DragSourceState;
@@ -45,8 +50,6 @@ import com.vaadin.shared.ui.grid.GridState;
import com.vaadin.ui.components.grid.GridDragSource;

import elemental.events.Event;
import elemental.json.Json;
import elemental.json.JsonArray;
import elemental.json.JsonObject;

/**
@@ -199,10 +202,26 @@ public class GridDragSourceConnector extends DragSourceExtensionConnector {
}

@Override
protected String createDataTransferText(NativeEvent dragStartEvent) {
JsonArray dragData = toJsonArray(getDraggedRows(dragStartEvent).stream()
.map(this::getDragData).collect(Collectors.toList()));
return dragData.toJson();
protected Map<String, String> createDataTransferData(
NativeEvent dragStartEvent) {
Map<String, String> dataMap = super
.createDataTransferData(dragStartEvent);

// Add data provided by the generator functions
getDraggedRows(dragStartEvent).forEach(row -> {
Map<String, String> rowDragData = getRowDragData(row);
rowDragData.forEach((type, data) -> {
if (dataMap.containsKey(type)) {
dataMap.put(type, data);
} else {
// Separate data with new line character when multiple rows
// are dragged
dataMap.put(type, dataMap.get(type) + "\n" + data);
}
});
});

return dataMap;
}

@Override
@@ -309,31 +328,25 @@ public class GridDragSourceConnector extends DragSourceExtensionConnector {
}

/**
* Converts a list of {@link JsonObject}s to a {@link JsonArray}.
* Gets drag data provided by the generator functions.
*
* @param objects
* List of json objects.
* @return Json array containing all json objects.
* @param row
* The row data.
* @return The generated drag data type mapped to the corresponding drag
* data. If there are no generator functions, returns an empty map.
*/
private JsonArray toJsonArray(List<JsonObject> objects) {
JsonArray array = Json.createArray();
for (int i = 0; i < objects.size(); i++) {
array.set(i, objects.get(i));
private Map<String, String> getRowDragData(JsonObject row) {
// Collect a map of data types and data that is provided by the
// generator functions set for this drag source
if (row.hasKey(GridDragSourceState.JSONKEY_DRAG_DATA)) {
JsonObject dragData = row
.getObject(GridDragSourceState.JSONKEY_DRAG_DATA);
return Arrays.stream(dragData.keys()).collect(
Collectors.toMap(Function.identity(), dragData::get));
}
return array;
}

/**
* Gets drag data from the row data if exists or returns complete row data
* otherwise.
*
* @param row
* Row data.
* @return Drag data if present or row data otherwise.
*/
private JsonObject getDragData(JsonObject row) {
return row.hasKey(GridDragSourceState.JSONKEY_DRAG_DATA)
? row.getObject(GridDragSourceState.JSONKEY_DRAG_DATA) : row;
// Otherwise return empty map
return Collections.emptyMap();
}

@Override

+ 28
- 25
client/src/main/java/com/vaadin/client/extensions/DragSourceExtensionConnector.java Datei anzeigen

@@ -15,7 +15,7 @@
*/
package com.vaadin.client.extensions;

import java.util.List;
import java.util.LinkedHashMap;
import java.util.Map;

import com.google.gwt.animation.client.AnimationScheduler;
@@ -185,27 +185,25 @@ public class DragSourceExtensionConnector extends AbstractExtensionConnector {
// Set drag image
setDragImage(nativeEvent);

// Set data parameters
List<String> types = getState().types;
Map<String, String> data = getState().data;
for (String type : types) {
nativeEvent.getDataTransfer().setData(type, data.get(type));
}
// Create drag data
Map<String, String> dataMap = createDataTransferData(nativeEvent);

// Always set something as the text data, or DnD won't work in FF !
String dataTransferText = createDataTransferText(nativeEvent);
if (dataTransferText == null) {
dataTransferText = "";
}
if (dataMap != null) {
// Always set something as the text data, or DnD won't work in FF !
dataMap.putIfAbsent(DragSourceState.DATA_TYPE_TEXT, "");

// Override data type "text" when storing special data is needed
nativeEvent.getDataTransfer()
.setData(DragSourceState.DATA_TYPE_TEXT, dataTransferText);
// Set data to the event's data transfer
dataMap.forEach((type, data) -> nativeEvent.getDataTransfer()
.setData(type, data));

// Initiate firing server side dragstart event when there is a
// DragStartListener attached on the server side
if (hasEventListener(DragSourceState.EVENT_DRAGSTART)) {
sendDragStartEventToServer(nativeEvent);
// Initiate firing server side dragstart event when there is a
// DragStartListener attached on the server side
if (hasEventListener(DragSourceState.EVENT_DRAGSTART)) {
sendDragStartEventToServer(nativeEvent);
}
} else {
// If returned data map is null, cancel drag event
nativeEvent.preventDefault();
}

// Stop event bubbling
@@ -255,15 +253,20 @@ public class DragSourceExtensionConnector extends AbstractExtensionConnector {
}

/**
* Creates data of type {@code "text"} for the {@code DataTransfer} object
* of the given event.
* Creates the data map to be set as the {@code DataTransfer} object's data.
*
* @param dragStartEvent
* Event to set the data for.
* @return Textual data to be set for the event or {@literal null}.
* The drag start event
* @return The map from type to data, or {@code null} for not setting any
* data. Returning {@code null} will cancel the drag start.
*/
protected String createDataTransferText(NativeEvent dragStartEvent) {
return getState().data.get(DragSourceState.DATA_TYPE_TEXT);
protected Map<String, String> createDataTransferData(
NativeEvent dragStartEvent) {
Map<String, String> orderedData = new LinkedHashMap<>();
for (String type : getState().types) {
orderedData.put(type, getState().data.get(type));
}
return orderedData;
}

/**

+ 14
- 14
documentation/advanced/advanced-dragndrop.asciidoc Datei anzeigen

@@ -234,7 +234,12 @@ When the Grid's selection mode is `SelectionMode.MULTI` and multiple rows are se
It is important to note that when dragging multiple rows, only the visible selected rows will be set as dragged data.
====

The following example shows how you can define the allowed drag effect and customize the drag data with the drag data generator.
By default, the drag data of type `"text"` will contain the content of each column separated by a tabulator character (`"\t"`).
When multiple rows are dragged, the generated data is combined into one String separated by new line characters (`"\n"`).
You can override the default behaviour and provide a custom drag data for each item by setting a custom _drag data generator_ for the `"text"` type.
The generator is executed for each item and returns a `String` containing the data to be transferred for that item.

The following example shows how you can define the allowed drag effect and customize the drag data by setting a drag data generator.

[source,java]
----
@@ -245,22 +250,16 @@ 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;
// add a drag data generator
dragSource.setDragDataGenerator("text", person -> {
return person.getFirstName() + " " + person.getLastName() +
"\t" + // tabulator character
person.getAddress().getCity();
});
----

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.
====
It is possible to set multiple generators with the `setDragDataGenerator(type, generator)` method.
The generated data will be set as data transfer data with the given type and can then be accessed during drop from the drop event's `getDataTransferData(type)` method.

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.

@@ -306,6 +305,7 @@ 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.
If the drag source Grid uses a custom generator for a different type than `"text"`, you can access it's generated data using the `getDataTransferData(type)` method.

[source,java]
----

+ 78
- 42
server/src/main/java/com/vaadin/ui/components/grid/GridDragSource.java Datei anzeigen

@@ -15,8 +15,9 @@
*/
package com.vaadin.ui.components.grid;

import java.util.HashMap;
import java.util.List;
import java.util.Optional;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

@@ -30,6 +31,7 @@ import com.vaadin.shared.ui.grid.GridDragSourceState;
import com.vaadin.ui.Grid;
import com.vaadin.ui.dnd.DragSourceExtension;

import elemental.json.Json;
import elemental.json.JsonObject;

/**
@@ -51,9 +53,26 @@ public class GridDragSource<T> extends DragSourceExtension<Grid<T>> {
private DataGenerator<T> dragDataGenerator;

/**
* Drag data generator function that is executed for each row.
* Collection of drag data generator functions. Functions are executed for
* each row and results are stored under their corresponding key.
*/
private SerializableFunction<T, JsonObject> generatorFunction;
private final Map<String, SerializableFunction<T, String>> generatorFunctions;

/**
* Default drag data generator for Grid. It creates a list of row values
* separated by a tabulator character ({@code \t}).
* <pre>
* "column1_value\tcolumn2_value\t ... columnN_value"
* </pre>
*/
private final SerializableFunction<T, String> defaultGridGenerator = item -> {
StringBuilder generatedValue = new StringBuilder();
getParent().getColumns().forEach(column -> {
generatedValue.append("\t"); // Tab separated values
generatedValue.append(column.getValueProvider().apply(item));
});
return generatedValue.substring(1);
};

/**
* Extends a Grid and makes it's rows draggable.
@@ -69,6 +88,12 @@ public class GridDragSource<T> extends DragSourceExtension<Grid<T>> {

// Add drag data generator to Grid
target.getDataCommunicator().addDataGenerator(dragDataGenerator);

generatorFunctions = new HashMap<>();

// Set default generator function for "text" parameter
generatorFunctions
.put(DragSourceState.DATA_TYPE_TEXT, defaultGridGenerator);
}

@Override
@@ -113,7 +138,7 @@ public class GridDragSource<T> extends DragSourceExtension<Grid<T>> {

/**
* Drag data generator. Appends drag data to row data json if generator
* function is set by the user of this extension.
* function(s) are set by the user of this extension.
*
* @param item
* Row item for data generation.
@@ -121,49 +146,69 @@ public class GridDragSource<T> extends DragSourceExtension<Grid<T>> {
* Row data in json format.
*/
private void generateDragData(T item, JsonObject jsonObject) {
Optional.ofNullable(generatorFunction)
.ifPresent(generator -> jsonObject.put(
GridDragSourceState.JSONKEY_DRAG_DATA,
generator.apply(item)));
JsonObject generatedValues = Json.createObject();

generatorFunctions.forEach((type, generator) -> {
generatedValues.put(type, generator.apply(item));
});

jsonObject.put(GridDragSourceState.JSONKEY_DRAG_DATA, generatedValues);
}

/**
* Sets a generator function for customizing drag data. The function is
* executed for each item in the Grid during data generation. Return a
* {@link JsonObject} to be appended to the row data.
* Sets a generator function for customizing drag data. The generated value
* will be accessible using the same {@code type} as the generator is set
* here. The function is executed for each item in the Grid during data
* generation. Return a {@link String} to be appended to the row as {@code
* type} data.
* <p>
* Example:
*
* Example, building a JSON object that contains the item's values:
* <pre>
* dragSourceExtension.setDragDataGenerator(item -> {
* JsonObject dragData = Json.createObject();
* dragData.put("someKey", item.getValue());
* return dragData;
* });
* dragSourceExtension.setDragDataGenerator("application/json", item ->
* {
* StringBuilder builder = new StringBuilder();
* builder.append("{");
* getParent().getColumns().forEach(column -> {
* builder.append("\"" + column.getCaption() + "\"");
* builder.append(":");
* builder.append("\"" + column.getValueProvider().apply(item) + "\"");
* builder.append(",");
* });
* builder.setLength(builder.length() - 1); // Remove last comma
* builder.append("}");
* return builder.toString();
* }
* </pre>
*
* @param type
* Type of the generated data. The generated value will be
* accessible during drop using this type.
* @param generator
* Function to be executed on row data generation.
* Function to be executed on row data generation.
*/
public void setDragDataGenerator(
SerializableFunction<T, JsonObject> generator) {
generatorFunction = generator;
public void setDragDataGenerator(String type, SerializableFunction<T, String> generator) {
generatorFunctions.put(type, generator);
}

/**
* Setting the data transfer text for this drag source is not supported.
* Remove the generator function set for the given type.
*
* @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)
* @param type
* Type of the generator to be removed.
*/
@Override
public void setDataTransferText(String data)
throws UnsupportedOperationException {
throw new UnsupportedOperationException(
"Setting dataTransferText is not supported");
public void clearDragDataGenerator(String type) {
generatorFunctions.remove(type);
}

/**
* Returns the drag data generator function for the given type.
*
* @param type
* Type of the generated data.
* @return Drag data generator function for the given type.
*/
public SerializableFunction<T, String> getDragDataGenerator(String type) {
return generatorFunctions.get(type);
}

/**
@@ -196,15 +241,6 @@ public class GridDragSource<T> extends DragSourceExtension<Grid<T>> {
GridDragEndListener.DRAG_END_METHOD);
}

/**
* Returns the generator function for customizing drag data.
*
* @return Drag data generator function.
*/
public SerializableFunction<T, JsonObject> getDragDataGenerator() {
return generatorFunction;
}

@Override
public void remove() {
super.remove();

+ 12
- 9
uitest/src/main/java/com/vaadin/tests/components/grid/GridDragAndDrop.java Datei anzeigen

@@ -40,9 +40,6 @@ import com.vaadin.ui.RadioButtonGroup;
import com.vaadin.ui.components.grid.GridDragSource;
import com.vaadin.ui.components.grid.GridDropTarget;

import elemental.json.Json;
import elemental.json.JsonObject;

@Theme("valo")
@Widgetset("com.vaadin.DefaultWidgetSet")
public class GridDragAndDrop extends AbstractTestUIWithLog {
@@ -109,12 +106,16 @@ public class GridDragAndDrop extends AbstractTestUIWithLog {
dragSource.setEffectAllowed(EffectAllowed.MOVE);

// 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;
dragSource.setDragDataGenerator("application/json", person -> {
StringBuilder builder = new StringBuilder();
builder.append("{");
builder.append("\"First Name\":");
builder.append("\"" + person.getFirstName() + "\"");
builder.append(",");
builder.append("\"Last Name\":");
builder.append("\"" + person.getLastName() + "\"");
builder.append("}");
return builder.toString();
});

// Add drag start listener
@@ -166,6 +167,8 @@ public class GridDragAndDrop extends AbstractTestUIWithLog {
dataProvider.refreshAll();

log("DROP: dragData=" + event.getDataTransferText()
+ ", dragDataJson="
+ event.getDataTransferData("application/json")
+ ", target="
+ event.getDropTargetRow().getFirstName() + " "
+ event.getDropTargetRow().getLastName()

Laden…
Abbrechen
Speichern