diff options
7 files changed, 466 insertions, 12 deletions
diff --git a/client/src/main/java/com/vaadin/client/connectors/grid/GridDragSourceExtensionConnector.java b/client/src/main/java/com/vaadin/client/connectors/grid/GridDragSourceExtensionConnector.java new file mode 100644 index 0000000000..f402f2396d --- /dev/null +++ b/client/src/main/java/com/vaadin/client/connectors/grid/GridDragSourceExtensionConnector.java @@ -0,0 +1,119 @@ +/* + * 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.client.connectors.grid; + +import com.google.gwt.dom.client.NativeEvent; +import com.google.gwt.dom.client.TableRowElement; +import com.vaadin.client.ServerConnector; +import com.vaadin.client.extensions.DragSourceExtensionConnector; +import com.vaadin.client.widget.escalator.RowContainer; +import com.vaadin.client.widgets.Escalator; +import com.vaadin.shared.Range; +import com.vaadin.shared.ui.Connect; +import com.vaadin.shared.ui.grid.GridDragSourceExtensionState; +import com.vaadin.ui.GridDragSourceExtension; + +import elemental.events.Event; +import elemental.json.JsonObject; + +/** + * Adds HTML5 drag and drop functionality to a {@link com.vaadin.client.widgets.Grid + * Grid}'s rows. This is the client side counterpart of {@link + * GridDragSourceExtension}. + * + * @author Vaadin Ltd + * @since + */ +@Connect(GridDragSourceExtension.class) +public class GridDragSourceExtensionConnector extends + DragSourceExtensionConnector { + + private GridConnector gridConnector; + + @Override + protected void extend(ServerConnector target) { + this.gridConnector = (GridConnector) target; + + // Set newly added rows draggable + getGridBody().setNewEscalatorRowCallback( + rows -> rows.forEach(this::setDraggable)); + + // Add drag listeners to body element + addDragListeners(getGridBody().getElement()); + } + + @Override + protected void onDragStart(Event event) { + super.onDragStart(event); + + if (event.getTarget() instanceof TableRowElement) { + TableRowElement row = (TableRowElement) event.getTarget(); + int rowIndex = ((Escalator.AbstractRowContainer) getGridBody()) + .getLogicalRowIndex(row); + + JsonObject rowData = gridConnector.getDataSource().getRow(rowIndex); + + // Set drag data in DataTransfer object + ((NativeEvent) event).getDataTransfer() + .setData(GridDragSourceExtensionState.DATA_TYPE_DRAG_DATA, + getDragData(rowData).toJson()); + } + } + + /** + * 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(GridDragSourceExtensionState.JSONKEY_DRAG_DATA) + ? row.getObject(GridDragSourceExtensionState.JSONKEY_DRAG_DATA) + : row; + } + + @Override + public void onUnregister() { + super.onUnregister(); + + // Remove draggable from all row elements in the escalator + Range visibleRange = getEscalator().getVisibleRowRange(); + for (int i = visibleRange.getStart(); i < visibleRange.getEnd(); i++) { + removeDraggable(getGridBody().getRowElement(i)); + } + + // Remove drag listeners from body element + removeDragListeners(getGridBody().getElement()); + + // Remove callback for newly added rows + getGridBody().setNewEscalatorRowCallback(null); + } + + private Escalator getEscalator() { + return gridConnector.getWidget().getEscalator(); + } + + private RowContainer.BodyRowContainer getGridBody() { + return getEscalator().getBody(); + } + + @Override + public GridDragSourceExtensionState getState() { + return (GridDragSourceExtensionState) super.getState(); + } +} diff --git a/client/src/main/java/com/vaadin/client/extensions/DragSourceExtensionConnector.java b/client/src/main/java/com/vaadin/client/extensions/DragSourceExtensionConnector.java index 3094042826..23797ce886 100644 --- a/client/src/main/java/com/vaadin/client/extensions/DragSourceExtensionConnector.java +++ b/client/src/main/java/com/vaadin/client/extensions/DragSourceExtensionConnector.java @@ -56,29 +56,66 @@ public class DragSourceExtensionConnector extends AbstractExtensionConnector { protected void extend(ServerConnector target) { dragSourceWidget = ((ComponentConnector) target).getWidget(); - Element dragSourceElement = getDraggableElement(); + setDraggable(getDraggableElement()); + addDragListeners(getDraggableElement()); + } + + /** + * Sets the given element draggable and adds class name. + * + * @param element + * Element to be set draggable. + */ + protected void setDraggable(Element element) { + element.setDraggable(Element.DRAGGABLE_TRUE); + element.addClassName(CLASS_DRAGGABLE); + } + + /** + * Removes draggable and class name from the given element. + * + * @param element + * Element to remove draggable from. + */ + protected void removeDraggable(Element element) { + element.setDraggable(Element.DRAGGABLE_FALSE); + element.removeClassName(CLASS_DRAGGABLE); + } - dragSourceElement.setDraggable(Element.DRAGGABLE_TRUE); - dragSourceElement.addClassName(CLASS_DRAGGABLE); + /** + * Adds dragstart and dragend event listeners to the given DOM element. + * + * @param element + * DOM element to attach event listeners to. + */ + protected void addDragListeners(Element element) { + EventTarget target = element.cast(); - EventTarget dragSource = dragSourceElement.cast(); + target.addEventListener(Event.DRAGSTART, dragStartListener); + target.addEventListener(Event.DRAGEND, dragEndListener); + } - // dragstart - dragSource.addEventListener(Event.DRAGSTART, dragStartListener); + /** + * Removes dragstart and dragend event listeners from the given DOM element. + * + * @param element + * DOM element to remove event listeners from. + */ + protected void removeDragListeners(Element element) { + EventTarget target = element.cast(); - // dragend - dragSource.addEventListener(Event.DRAGEND, dragEndListener); + target.removeEventListener(Event.DRAGSTART, dragStartListener); + target.removeEventListener(Event.DRAGEND, dragEndListener); } @Override public void onUnregister() { super.onUnregister(); - EventTarget dragSource = (EventTarget) getDraggableElement(); + Element dragSource = getDraggableElement(); - // Remove listeners - dragSource.removeEventListener(Event.DRAGSTART, dragStartListener); - dragSource.removeEventListener(Event.DRAGEND, dragEndListener); + removeDraggable(dragSource); + removeDragListeners(dragSource); } /** diff --git a/client/src/main/java/com/vaadin/client/widget/escalator/RowContainer.java b/client/src/main/java/com/vaadin/client/widget/escalator/RowContainer.java index 0105aa687c..ca00ca9b63 100644 --- a/client/src/main/java/com/vaadin/client/widget/escalator/RowContainer.java +++ b/client/src/main/java/com/vaadin/client/widget/escalator/RowContainer.java @@ -16,6 +16,9 @@ package com.vaadin.client.widget.escalator; +import java.util.List; +import java.util.function.Consumer; + import com.google.gwt.dom.client.Element; import com.google.gwt.dom.client.TableRowElement; import com.google.gwt.dom.client.TableSectionElement; @@ -116,6 +119,17 @@ public interface RowContainer { @Override public void removeRows(int index, int numberOfRows) throws IndexOutOfBoundsException, IllegalArgumentException; + + /** + * Sets a callback function that is executed when new rows are added to + * the escalator. + * + * @param consumer + * A Consumer function that receives the newly added table row + * elements. + */ + public void setNewEscalatorRowCallback( + Consumer<List<TableRowElement>> consumer); } /** diff --git a/client/src/main/java/com/vaadin/client/widgets/Escalator.java b/client/src/main/java/com/vaadin/client/widgets/Escalator.java index 7bb078a94a..1f223c1d85 100644 --- a/client/src/main/java/com/vaadin/client/widgets/Escalator.java +++ b/client/src/main/java/com/vaadin/client/widgets/Escalator.java @@ -25,7 +25,9 @@ import java.util.List; import java.util.ListIterator; import java.util.Map; import java.util.Map.Entry; +import java.util.Optional; import java.util.TreeMap; +import java.util.function.Consumer; import java.util.logging.Level; import java.util.logging.Logger; @@ -2401,6 +2403,12 @@ public class Escalator extends Widget @Deprecated private int topRowLogicalIndex = 0; + /** + * A callback function to be executed after new rows are added to the + * escalator. + */ + private Consumer<List<TableRowElement>> newEscalatorRowCallback; + private void setTopRowLogicalIndex(int topRowLogicalIndex) { if (LogConfiguration.loggingIsEnabled(Level.INFO)) { Logger.getLogger("Escalator.BodyRowContainer") @@ -2992,6 +3000,10 @@ public class Escalator extends Widget y += spacerContainer.getSpacerHeight(i); } + // Execute the registered callback function for newly created rows + Optional.ofNullable(newEscalatorRowCallback) + .ifPresent(callback -> callback.accept(addedRows)); + return addedRows; } else { return Collections.emptyList(); @@ -3982,6 +3994,12 @@ public class Escalator extends Widget int padding) { spacerContainer.scrollToSpacer(spacerIndex, destination, padding); } + + @Override + public void setNewEscalatorRowCallback( + Consumer<List<TableRowElement>> callback) { + this.newEscalatorRowCallback = callback; + } } private class ColumnConfigurationImpl implements ColumnConfiguration { diff --git a/server/src/main/java/com/vaadin/ui/GridDragSourceExtension.java b/server/src/main/java/com/vaadin/ui/GridDragSourceExtension.java new file mode 100644 index 0000000000..9f7e9ec67d --- /dev/null +++ b/server/src/main/java/com/vaadin/ui/GridDragSourceExtension.java @@ -0,0 +1,125 @@ +/* + * 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; + +import java.util.Optional; + +import com.vaadin.data.provider.DataGenerator; +import com.vaadin.event.dnd.DragSourceExtension; +import com.vaadin.server.SerializableFunction; +import com.vaadin.shared.ui.grid.GridDragSourceExtensionState; + +import elemental.json.JsonObject; + +/** + * Makes a Grid's rows draggable for HTML5 drag and drop functionality. + * + * @param <T> + * The Grid bean type. + * @author Vaadin Ltd. + * @since + */ +public class GridDragSourceExtension<T> extends DragSourceExtension<Grid<T>> { + + /** + * Drag data generator that appends drag data to each row. + */ + private DataGenerator<T> dragDataGenerator; + + /** + * Drag data generator function that is executed for each row. + */ + private SerializableFunction<T, JsonObject> generatorFunction; + + /** + * Extends a Grid and makes it's rows draggable. + * + * @param target + * Grid to be extended. + */ + public GridDragSourceExtension(Grid<T> target) { + super(target); + + // Create drag data generator + dragDataGenerator = this::generateDragData; + + // Add drag data generator to Grid + target.addDataGenerator(dragDataGenerator); + } + + /** + * Drag data generator. Appends drag data to row data json if generator + * function is set by the user of this extension. + * + * @param item + * Row item for data generation. + * @param jsonObject + * Row data in json format. + */ + private void generateDragData(T item, JsonObject jsonObject) { + Optional.ofNullable(generatorFunction).ifPresent(generator -> jsonObject + .put(GridDragSourceExtensionState.JSONKEY_DRAG_DATA, + generator.apply(item))); + } + + /** + * 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. + * <p> + * Example: + * <pre> + * dragSourceExtension.setDragDataGenerator(item -> { + * JsonObject dragData = Json.createObject(); + * dragData.put("someKey", item.getValue()); + * return dragData; + * }); + * </pre> + * + * @param generator + * Function to be executed on row data generation. + */ + public void setDragDataGenerator( + SerializableFunction<T, JsonObject> generator) { + generatorFunction = generator; + } + + /** + * 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(); + + getParent().removeDataGenerator(dragDataGenerator); + } + + @Override + protected GridDragSourceExtensionState getState() { + return (GridDragSourceExtensionState) super.getState(); + } + + @Override + protected GridDragSourceExtensionState getState(boolean markAsDirty) { + return (GridDragSourceExtensionState) super.getState(markAsDirty); + } +} diff --git a/shared/src/main/java/com/vaadin/shared/ui/grid/GridDragSourceExtensionState.java b/shared/src/main/java/com/vaadin/shared/ui/grid/GridDragSourceExtensionState.java new file mode 100644 index 0000000000..39a5917146 --- /dev/null +++ b/shared/src/main/java/com/vaadin/shared/ui/grid/GridDragSourceExtensionState.java @@ -0,0 +1,38 @@ +/* + * 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.shared.ui.grid; + +import com.vaadin.shared.ui.dnd.DragSourceState; + +/** + * State class containing parameters for GridDragSourceExtension. + * + * @author Vaadin Ltd + * @since + */ +public class GridDragSourceExtensionState extends DragSourceState { + + /** + * Data type for storing the dragged row's data. + */ + public static final String DATA_TYPE_DRAG_DATA = "grid-drag-data"; + + /** + * Json key for storing data for a dragged row. + */ + public static final String JSONKEY_DRAG_DATA = "drag-data"; + +} 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 new file mode 100644 index 0000000000..32c147f15f --- /dev/null +++ b/uitest/src/main/java/com/vaadin/tests/components/grid/GridDragAndDrop.java @@ -0,0 +1,103 @@ +/* + * 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 java.util.ArrayList; +import java.util.List; +import java.util.stream.IntStream; + +import com.vaadin.event.dnd.DropTargetExtension; +import com.vaadin.server.VaadinRequest; +import com.vaadin.shared.ui.grid.GridDragSourceExtensionState; +import com.vaadin.tests.components.AbstractTestUIWithLog; +import com.vaadin.ui.Grid; +import com.vaadin.ui.GridDragSourceExtension; +import com.vaadin.ui.HorizontalLayout; +import com.vaadin.ui.Label; +import com.vaadin.ui.Layout; + +import elemental.json.Json; +import elemental.json.JsonObject; + +public class GridDragAndDrop extends AbstractTestUIWithLog { + @Override + protected void setup(VaadinRequest request) { + // Drag source + Grid<Bean> dragSourceComponent = new Grid<>(); + + dragSourceComponent.setItems(createItems(50)); + dragSourceComponent.addColumn(Bean::getId).setCaption("ID"); + dragSourceComponent.addColumn(Bean::getValue).setCaption("Value"); + + GridDragSourceExtension<Bean> dragSource = new GridDragSourceExtension<>( + dragSourceComponent); + dragSource.setDragDataGenerator(bean -> { + JsonObject ret = Json.createObject(); + ret.put("val", bean.getValue()); + return ret; + }); + + Label dropTargetComponent = new Label("Drop here"); + DropTargetExtension<Label> dropTarget = new DropTargetExtension<>( + dropTargetComponent); + + dropTarget.addDropListener(event -> { + log(event.getTransferData( + GridDragSourceExtensionState.DATA_TYPE_DRAG_DATA)); + }); + + Layout layout = new HorizontalLayout(); + layout.addComponents(dragSourceComponent, dropTargetComponent); + + addComponent(layout); + } + + private List<Bean> createItems(int num) { + List<Bean> items = new ArrayList<>(num); + + IntStream.range(0, num) + .forEach(i -> items.add(new Bean("id_" + i, "value_" + i))); + + return items; + } + + public static class Bean { + private String id; + private String value; + + public Bean(String id, String value) { + this.id = id; + this.value = value; + } + + public String getId() { + + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + } +} |