Fixes #8401tags/8.1.0.alpha5
@@ -15,18 +15,21 @@ | |||
*/ | |||
package com.vaadin.client.connectors.grid; | |||
import java.util.List; | |||
import java.util.Map; | |||
import java.util.Objects; | |||
import java.util.Optional; | |||
import com.google.gwt.dom.client.Element; | |||
import com.google.gwt.dom.client.NativeEvent; | |||
import com.google.gwt.dom.client.TableRowElement; | |||
import com.google.gwt.user.client.Window; | |||
import com.vaadin.client.ServerConnector; | |||
import com.vaadin.client.WidgetUtil; | |||
import com.vaadin.client.extensions.DropTargetExtensionConnector; | |||
import com.vaadin.client.widget.escalator.RowContainer; | |||
import com.vaadin.client.widgets.Escalator; | |||
import com.vaadin.shared.ui.Connect; | |||
import com.vaadin.shared.ui.grid.DropLocation; | |||
import com.vaadin.shared.ui.grid.DropMode; | |||
import com.vaadin.shared.ui.grid.GridDropTargetExtensionRpc; | |||
import com.vaadin.shared.ui.grid.GridDropTargetExtensionState; | |||
import com.vaadin.shared.ui.grid.GridState; | |||
@@ -46,6 +49,21 @@ import elemental.json.JsonObject; | |||
public class GridDropTargetExtensionConnector extends | |||
DropTargetExtensionConnector { | |||
// Drag over class name suffixes | |||
private final static String CLASS_SUFFIX_BEFORE = "-before"; | |||
private final static String CLASS_SUFFIX_AFTER = "-after"; | |||
// Drag over class names | |||
private final static String CLASS_DRAG_OVER_BEFORE = | |||
CLASS_DRAG_OVER + CLASS_SUFFIX_BEFORE; | |||
private final static String CLASS_DRAG_OVER_AFTER = | |||
CLASS_DRAG_OVER + CLASS_SUFFIX_AFTER; | |||
/** | |||
* Current drag over class name | |||
*/ | |||
private String dragOverClassName; | |||
private GridConnector gridConnector; | |||
@Override | |||
@@ -60,15 +78,19 @@ public class GridDropTargetExtensionConnector extends | |||
Event dropEvent) { | |||
String rowKey = null; | |||
DropLocation dropLocation = null; | |||
Optional<TableRowElement> targetRow = getTargetRow( | |||
(Element) dropEvent.getTarget()); | |||
if (targetRow.isPresent()) { | |||
rowKey = getRowData(targetRow.get()) | |||
.getString(GridState.JSONKEY_ROWKEY); | |||
dropLocation = getDropLocation(targetRow.get(), | |||
(NativeEvent) dropEvent); | |||
} | |||
getRpcProxy(GridDropTargetExtensionRpc.class) | |||
.drop(dataTransferText, rowKey); | |||
.drop(dataTransferText, rowKey, dropLocation); | |||
} | |||
private JsonObject getRowData(TableRowElement row) { | |||
@@ -77,16 +99,71 @@ public class GridDropTargetExtensionConnector extends | |||
return gridConnector.getDataSource().getRow(rowIndex); | |||
} | |||
/** | |||
* Returns the location of the event within the row. | |||
*/ | |||
private DropLocation getDropLocation(Element target, NativeEvent event) { | |||
if (getState().dropMode == DropMode.BETWEEN) { | |||
if (getRelativeY(target, event) < (target.getOffsetHeight() / 2)) { | |||
return DropLocation.ABOVE; | |||
} else { | |||
return DropLocation.BELOW; | |||
} | |||
} | |||
return DropLocation.ON_TOP; | |||
} | |||
private int getRelativeY(Element element, NativeEvent event) { | |||
int relativeTop = element.getAbsoluteTop() - Window.getScrollTop(); | |||
return WidgetUtil.getTouchOrMouseClientY(event) - relativeTop; | |||
} | |||
@Override | |||
protected void addTargetIndicator(Event event) { | |||
getTargetRow(((Element) event.getTarget())) | |||
.ifPresent(e -> e.addClassName(CLASS_DRAG_OVER)); | |||
protected void setTargetIndicator(Event event) { | |||
getTargetRow(((Element) event.getTarget())).ifPresent(target -> { | |||
// Get required class name | |||
String className = getTargetClassName(target, (NativeEvent) event); | |||
// Add or replace class name if changed | |||
if (!target.hasClassName(className)) { | |||
if (dragOverClassName != null) { | |||
target.removeClassName(dragOverClassName); | |||
} | |||
target.addClassName(className); | |||
dragOverClassName = className; | |||
} | |||
}); | |||
} | |||
private String getTargetClassName(Element target, NativeEvent event) { | |||
String classSuffix; | |||
switch (getDropLocation(target, event)) { | |||
case ABOVE: | |||
classSuffix = CLASS_SUFFIX_BEFORE; | |||
break; | |||
case BELOW: | |||
classSuffix = CLASS_SUFFIX_AFTER; | |||
break; | |||
case ON_TOP: | |||
default: | |||
classSuffix = ""; | |||
break; | |||
} | |||
return CLASS_DRAG_OVER + classSuffix; | |||
} | |||
@Override | |||
protected void removeTargetIndicator(Event event) { | |||
getTargetRow(((Element) event.getTarget())) | |||
.ifPresent(e -> e.removeClassName(CLASS_DRAG_OVER)); | |||
// Remove all possible drag over class names | |||
getTargetRow((Element) event.getTarget()).ifPresent(e -> { | |||
e.removeClassName(CLASS_DRAG_OVER); | |||
e.removeClassName(CLASS_DRAG_OVER_BEFORE); | |||
e.removeClassName(CLASS_DRAG_OVER_AFTER); | |||
}); | |||
} | |||
private Optional<TableRowElement> getTargetRow(Element source) { |
@@ -117,7 +117,7 @@ public class DropTargetExtensionConnector extends AbstractExtensionConnector { | |||
* browser event to be handled | |||
*/ | |||
protected void onDragEnter(Event event) { | |||
addTargetIndicator(event); | |||
setTargetIndicator(event); | |||
} | |||
/** | |||
@@ -137,7 +137,7 @@ public class DropTargetExtensionConnector extends AbstractExtensionConnector { | |||
} | |||
// Add drop target indicator in case the element doesn't have one | |||
addTargetIndicator(event); | |||
setTargetIndicator(event); | |||
// Prevent default to allow drop | |||
nativeEvent.preventDefault(); | |||
@@ -230,7 +230,7 @@ public class DropTargetExtensionConnector extends AbstractExtensionConnector { | |||
* @param event | |||
* The drag enter or dragover event that triggered the indication. | |||
*/ | |||
protected void addTargetIndicator(Event event) { | |||
protected void setTargetIndicator(Event event) { | |||
getDropTargetElement().addClassName(CLASS_DRAG_OVER); | |||
} | |||
@@ -17,11 +17,12 @@ package com.vaadin.event.dnd.grid; | |||
import com.vaadin.event.dnd.DragSourceExtension; | |||
import com.vaadin.event.dnd.DropEvent; | |||
import com.vaadin.shared.ui.grid.DropLocation; | |||
import com.vaadin.ui.AbstractComponent; | |||
import com.vaadin.ui.Grid; | |||
/** | |||
* Server side drop event on an HTML5 drop target {@link Grid} row. | |||
* Drop event on an HTML5 drop target {@link Grid} row. | |||
* | |||
* @param <T> | |||
* The Grid bean type. | |||
@@ -32,9 +33,10 @@ import com.vaadin.ui.Grid; | |||
public class GridDropEvent<T> extends DropEvent<Grid<T>> { | |||
private final T dropTargetRow; | |||
private final DropLocation dropLocation; | |||
/** | |||
* Creates a server side Grid row drop event. | |||
* Creates a Grid row drop event. | |||
* | |||
* @param target | |||
* Grid that received the drop. | |||
@@ -44,16 +46,18 @@ public class GridDropEvent<T> extends DropEvent<Grid<T>> { | |||
* @param dragSourceExtension | |||
* Drag source extension of the component that initiated the drop | |||
* event. | |||
* @param dropTargetRowKey | |||
* Key of the target row that received the drop. | |||
* @param dropTargetRow | |||
* Target row that received the drop. | |||
* @param dropLocation | |||
* Location of the drop within the target row. | |||
*/ | |||
public GridDropEvent(Grid<T> target, String dataTransferText, | |||
DragSourceExtension<? extends AbstractComponent> dragSourceExtension, | |||
String dropTargetRowKey) { | |||
T dropTargetRow, DropLocation dropLocation) { | |||
super(target, dataTransferText, dragSourceExtension); | |||
dropTargetRow = target.getDataCommunicator().getKeyMapper() | |||
.get(dropTargetRowKey); | |||
this.dropTargetRow = dropTargetRow; | |||
this.dropLocation = dropLocation; | |||
} | |||
/** | |||
@@ -64,4 +68,13 @@ public class GridDropEvent<T> extends DropEvent<Grid<T>> { | |||
public T getDropTargetRow() { | |||
return dropTargetRow; | |||
} | |||
/** | |||
* Get the location of the drop within the row. | |||
* | |||
* @return Location of the drop within the row. | |||
*/ | |||
public DropLocation getDropLocation() { | |||
return dropLocation; | |||
} | |||
} |
@@ -19,6 +19,7 @@ import com.vaadin.event.dnd.DropTargetExtension; | |||
import com.vaadin.event.dnd.grid.GridDropEvent; | |||
import com.vaadin.event.dnd.grid.GridDropListener; | |||
import com.vaadin.shared.Registration; | |||
import com.vaadin.shared.ui.grid.DropMode; | |||
import com.vaadin.shared.ui.grid.GridDropTargetExtensionRpc; | |||
import com.vaadin.shared.ui.grid.GridDropTargetExtensionState; | |||
@@ -32,18 +33,47 @@ import com.vaadin.shared.ui.grid.GridDropTargetExtensionState; | |||
* @since | |||
*/ | |||
public class GridDropTargetExtension<T> extends DropTargetExtension<Grid<T>> { | |||
public GridDropTargetExtension(Grid<T> target) { | |||
/** | |||
* Extends a Grid and makes it's rows drop targets for HTML5 drag and drop. | |||
* | |||
* @param target | |||
* Grid to be extended. | |||
* @param dropMode | |||
* Drop mode that describes the allowed drop locations within the | |||
* Grid's row. | |||
* @see GridDropEvent#getDropLocation() | |||
*/ | |||
public GridDropTargetExtension(Grid<T> target, DropMode dropMode) { | |||
super(target); | |||
setDropMode(dropMode); | |||
} | |||
@Override | |||
protected void registerDropTargetRpc(Grid<T> target) { | |||
registerRpc((GridDropTargetExtensionRpc) (dataTransferText, rowKey) -> { | |||
GridDropEvent<T> event = new GridDropEvent<>(target, | |||
dataTransferText, getUI().getActiveDragSource(), rowKey); | |||
/** | |||
* Sets the drop mode of this drop target. | |||
* | |||
* @param dropMode | |||
* Drop mode that describes the allowed drop locations within the | |||
* Grid's row. | |||
* @see GridDropEvent#getDropLocation() | |||
*/ | |||
public void setDropMode(DropMode dropMode) { | |||
if (dropMode == null) { | |||
throw new IllegalArgumentException("Drop mode cannot be null"); | |||
} | |||
getState().dropMode = dropMode; | |||
} | |||
fireEvent(event); | |||
}); | |||
/** | |||
* Gets the drop mode of this drop target. | |||
* | |||
* @return Drop mode that describes the allowed drop locations within the | |||
* Grid's row. | |||
*/ | |||
public DropMode getDropMode() { | |||
return getState(false).dropMode; | |||
} | |||
/** | |||
@@ -60,6 +90,22 @@ public class GridDropTargetExtension<T> extends DropTargetExtension<Grid<T>> { | |||
GridDropListener.DROP_METHOD); | |||
} | |||
@Override | |||
protected void registerDropTargetRpc(Grid<T> target) { | |||
registerRpc( | |||
(GridDropTargetExtensionRpc) (dataTransferText, rowKey, dropLocation) -> { | |||
T dropTargetRow = target.getDataCommunicator() | |||
.getKeyMapper().get(rowKey); | |||
GridDropEvent<T> event = new GridDropEvent<>(target, | |||
dataTransferText, getUI().getActiveDragSource(), | |||
dropTargetRow, dropLocation); | |||
fireEvent(event); | |||
}); | |||
} | |||
@Override | |||
protected GridDropTargetExtensionState getState() { | |||
return (GridDropTargetExtensionState) super.getState(); |
@@ -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.shared.ui.grid; | |||
/** | |||
* Defines drop locations within a Grid row. | |||
* | |||
* @author Vaadin Ltd. | |||
* @since | |||
*/ | |||
public enum DropLocation { | |||
/** | |||
* Drop on top of the row. | |||
*/ | |||
ON_TOP, | |||
/** | |||
* Drop above or before the row. | |||
*/ | |||
ABOVE, | |||
/** | |||
* Drop below or after the row. | |||
*/ | |||
BELOW | |||
} |
@@ -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; | |||
/** | |||
* Defines the locations within the Grid row where an element can be dropped. | |||
* | |||
* @author Vaadin Ltd. | |||
* @since | |||
*/ | |||
public enum DropMode { | |||
/** | |||
* The drop event can happen between Grid rows. The drop is above a row | |||
* when the cursor is over the top 50% of a row, otherwise below the | |||
* row. | |||
*/ | |||
BETWEEN, | |||
/** | |||
* The drop event can happen on top of Grid rows. The target of the drop | |||
* is the row under the cursor at the time of the drop event. | |||
*/ | |||
ON_TOP, | |||
} |
@@ -34,6 +34,9 @@ public interface GridDropTargetExtensionRpc extends ServerRpc { | |||
* object. | |||
* @param rowKey | |||
* Key of the row on which the drop event occured. | |||
* @param dropLocation | |||
* Location of the drop within the row. | |||
*/ | |||
public void drop(String dataTransferText, String rowKey); | |||
public void drop(String dataTransferText, String rowKey, | |||
DropLocation dropLocation); | |||
} |
@@ -25,4 +25,8 @@ import com.vaadin.shared.ui.dnd.DropTargetState; | |||
*/ | |||
public class GridDropTargetExtensionState extends DropTargetState { | |||
/** | |||
* Stores the drop mode of the drop target Grid. | |||
*/ | |||
public DropMode dropMode; | |||
} |
@@ -20,16 +20,16 @@ import java.util.Arrays; | |||
import java.util.List; | |||
import java.util.stream.IntStream; | |||
import com.vaadin.event.dnd.grid.GridDropListener; | |||
import com.vaadin.server.Page; | |||
import com.vaadin.server.VaadinRequest; | |||
import com.vaadin.shared.ui.grid.DropMode; | |||
import com.vaadin.tests.components.AbstractTestUIWithLog; | |||
import com.vaadin.ui.ComboBox; | |||
import com.vaadin.ui.Grid; | |||
import com.vaadin.ui.GridDragSourceExtension; | |||
import com.vaadin.ui.GridDropTargetExtension; | |||
import com.vaadin.ui.HorizontalLayout; | |||
import com.vaadin.ui.Layout; | |||
import com.vaadin.ui.RadioButtonGroup; | |||
import elemental.json.Json; | |||
import elemental.json.JsonObject; | |||
@@ -37,9 +37,10 @@ import elemental.json.JsonObject; | |||
public class GridDragAndDrop extends AbstractTestUIWithLog { | |||
@Override | |||
protected void setup(VaadinRequest request) { | |||
// Drag source Grid | |||
Grid<Bean> dragSourceComponent = new Grid<>(); | |||
dragSourceComponent.setItems(createItems(50)); | |||
dragSourceComponent.setItems(createItems(50, "left")); | |||
dragSourceComponent.addColumn(Bean::getId).setCaption("ID"); | |||
dragSourceComponent.addColumn(Bean::getValue).setCaption("Value"); | |||
@@ -47,48 +48,61 @@ public class GridDragAndDrop extends AbstractTestUIWithLog { | |||
dragSourceComponent); | |||
dragSource.setDragDataGenerator(bean -> { | |||
JsonObject ret = Json.createObject(); | |||
ret.put("val", bean.getValue()); | |||
ret.put("generatedId", bean.getId()); | |||
ret.put("generatedValue", bean.getValue()); | |||
return ret; | |||
}); | |||
// Drop target Grid | |||
Grid<Bean> dropTargetComponent = new Grid<>(); | |||
dropTargetComponent.setItems(createItems(5)); | |||
dropTargetComponent.setItems(createItems(5, "right")); | |||
dropTargetComponent.addColumn(Bean::getId).setCaption("ID"); | |||
dropTargetComponent.addColumn(Bean::getValue).setCaption("Value"); | |||
GridDropTargetExtension<Bean> dropTarget = new GridDropTargetExtension<>( | |||
dropTargetComponent); | |||
dropTargetComponent, DropMode.ON_TOP); | |||
dropTarget.addGridDropListener(event -> { | |||
log(event.getDataTransferText() + ", targetId=" + event | |||
.getDropTargetRow().getId()); | |||
.getDropTargetRow().getId() + ", location=" + event | |||
.getDropLocation()); | |||
}); | |||
// Layout grids | |||
Layout layout = new HorizontalLayout(); | |||
layout.addComponents(dragSourceComponent, dropTargetComponent); | |||
// Selection mode combo box | |||
ComboBox<Grid.SelectionMode> selectionModeSwitch = new ComboBox<>( | |||
"Change selection mode"); | |||
selectionModeSwitch.setItems(Arrays.asList(Grid.SelectionMode.SINGLE, | |||
Grid.SelectionMode.MULTI)); | |||
selectionModeSwitch.setEmptySelectionAllowed(false); | |||
selectionModeSwitch.addValueChangeListener(event -> dragSourceComponent | |||
// Layout the two grids | |||
Layout grids = new HorizontalLayout(); | |||
grids.addComponents(dragSourceComponent, dropTargetComponent); | |||
// 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 -> dragSourceComponent | |||
.setSelectionMode(event.getValue())); | |||
selectionModeSwitch.setSelectedItem(Grid.SelectionMode.SINGLE); | |||
addComponents(selectionModeSwitch, layout); | |||
// Drop locations | |||
List<DropMode> dropLocations = Arrays.asList(DropMode.values()); | |||
RadioButtonGroup<DropMode> dropLocationSelect = new RadioButtonGroup<>( | |||
"Allowed drop location", dropLocations); | |||
dropLocationSelect.setSelectedItem(DropMode.ON_TOP); | |||
dropLocationSelect.addValueChangeListener( | |||
event -> dropTarget.setDropMode(event.getValue())); | |||
Layout controls = new HorizontalLayout(selectionModeSelect, | |||
dropLocationSelect); | |||
addComponents(controls, grids); | |||
// Set dragover styling | |||
Page.getCurrent().getStyles().add(".v-drag-over {color: red;}"); | |||
} | |||
private List<Bean> createItems(int num) { | |||
private List<Bean> createItems(int num, String prefix) { | |||
List<Bean> items = new ArrayList<>(num); | |||
IntStream.range(0, num) | |||
.forEach(i -> items.add(new Bean("id_" + i, "value_" + i))); | |||
.forEach(i -> items | |||
.add(new Bean(prefix + "_" + i, "value_" + i))); | |||
return items; | |||
} |