* Make it possible for grid drop target to accept dragged data when grid is empty (#9068) * Make return type of getDropTargetRow() optionaltags/8.1.0.beta1
} | } | ||||
@Override | @Override | ||||
protected void addTargetClassIndicator(NativeEvent event) { | |||||
protected void addDragOverStyle(NativeEvent event) { | |||||
getTargetRow(((Element) event.getEventTarget().cast())) | getTargetRow(((Element) event.getEventTarget().cast())) | ||||
.ifPresent(target -> { | .ifPresent(target -> { | ||||
} | } | ||||
@Override | @Override | ||||
protected void removeTargetClassIndicator(NativeEvent event) { | |||||
protected void removeDragOverStyle(NativeEvent event) { | |||||
// Remove all possible style names | // Remove all possible style names | ||||
getTargetRow((Element) event.getEventTarget().cast()).ifPresent(e -> { | getTargetRow((Element) event.getEventTarget().cast()).ifPresent(e -> { |
*/ | */ | ||||
protected static final String STYLE_SUFFIX_DRAG_BOTTOM = "-drag-bottom"; | protected static final String STYLE_SUFFIX_DRAG_BOTTOM = "-drag-bottom"; | ||||
/** | |||||
* Style name suffix for indicating that the element is drop target. | |||||
*/ | |||||
protected static final String STYLE_SUFFIX_DROPTARGET = "-droptarget"; | |||||
// Create event listeners | // Create event listeners | ||||
private final EventListener dragEnterListener = this::onDragEnter; | private final EventListener dragEnterListener = this::onDragEnter; | ||||
private final EventListener dragOverListener = this::onDragOver; | private final EventListener dragOverListener = this::onDragOver; | ||||
addDropListeners(getDropTargetElement()); | addDropListeners(getDropTargetElement()); | ||||
((AbstractComponentConnector) target).onDropTargetAttached(); | ((AbstractComponentConnector) target).onDropTargetAttached(); | ||||
// Add drop target indicator to the drop target element | |||||
addDropTargetStyle(); | |||||
} | } | ||||
/** | /** | ||||
removeDropListeners(getDropTargetElement()); | removeDropListeners(getDropTargetElement()); | ||||
((AbstractComponentConnector) getParent()).onDropTargetDetached(); | ((AbstractComponentConnector) getParent()).onDropTargetDetached(); | ||||
// Remove drop target indicator | |||||
removeDropTargetStyle(); | |||||
} | } | ||||
/** | /** | ||||
+ STYLE_SUFFIX_DRAG_CENTER; | + STYLE_SUFFIX_DRAG_CENTER; | ||||
if (isDropAllowed(nativeEvent)) { | if (isDropAllowed(nativeEvent)) { | ||||
addTargetClassIndicator(nativeEvent); | |||||
addDragOverStyle(nativeEvent); | |||||
setDropEffect(nativeEvent); | setDropEffect(nativeEvent); | ||||
if (isDropAllowed(nativeEvent)) { | if (isDropAllowed(nativeEvent)) { | ||||
setDropEffect(nativeEvent); | setDropEffect(nativeEvent); | ||||
// Add drop target indicator in case the element doesn't have one | |||||
addTargetClassIndicator(nativeEvent); | |||||
// Add drag over indicator in case the element doesn't have one | |||||
addDragOverStyle(nativeEvent); | |||||
// Prevent default to allow drop | // Prevent default to allow drop | ||||
nativeEvent.preventDefault(); | nativeEvent.preventDefault(); | ||||
nativeEvent.getDataTransfer() | nativeEvent.getDataTransfer() | ||||
.setDropEffect(DataTransfer.DropEffect.NONE); | .setDropEffect(DataTransfer.DropEffect.NONE); | ||||
// Remove drop target indicator | |||||
removeTargetClassIndicator(nativeEvent); | |||||
// Remove drag over indicator | |||||
removeDragOverStyle(nativeEvent); | |||||
} | } | ||||
} | } | ||||
* browser event to be handled | * browser event to be handled | ||||
*/ | */ | ||||
protected void onDragLeave(Event event) { | protected void onDragLeave(Event event) { | ||||
removeTargetClassIndicator((NativeEvent) event); | |||||
removeDragOverStyle((NativeEvent) event); | |||||
} | } | ||||
/** | /** | ||||
.getDropEffect(nativeEvent.getDataTransfer()), nativeEvent); | .getDropEffect(nativeEvent.getDataTransfer()), nativeEvent); | ||||
} | } | ||||
removeTargetClassIndicator(nativeEvent); | |||||
removeDragOverStyle(nativeEvent); | |||||
} | } | ||||
private boolean isDropAllowed(NativeEvent event) { | private boolean isDropAllowed(NativeEvent event) { | ||||
} | } | ||||
/** | /** | ||||
* Add class that indicates that the component is a target. | |||||
* Add class name for the drop target element indicating that data can be | |||||
* dropped onto it. The class name has the following format: | |||||
* <pre> | |||||
* [primaryStyleName]-droptarget | |||||
* </pre> | |||||
* The added class name is update | |||||
* automatically by the framework when the primary style name changes. | |||||
*/ | |||||
protected void addDropTargetStyle() { | |||||
getDropTargetElement().addClassName( | |||||
getStylePrimaryName(getDropTargetElement()) | |||||
+ STYLE_SUFFIX_DROPTARGET); | |||||
} | |||||
/** | |||||
* Remove class name from the drop target element indication that data can | |||||
* be dropped onto it. | |||||
*/ | |||||
protected void removeDropTargetStyle() { | |||||
getDropTargetElement().removeClassName( | |||||
getStylePrimaryName(getDropTargetElement()) | |||||
+ STYLE_SUFFIX_DROPTARGET); | |||||
} | |||||
/** | |||||
* Add class that indicates that the component is a target while data is | |||||
* being dragged over it. | |||||
* <p> | * <p> | ||||
* This is triggered on {@link #onDragEnter(Event) dragenter} and | * This is triggered on {@link #onDragEnter(Event) dragenter} and | ||||
* {@link #onDragOver(Event) dragover} events pending if the drop is | * {@link #onDragOver(Event) dragover} events pending if the drop is | ||||
* @param event | * @param event | ||||
* the dragenter or dragover event that triggered the indication. | * the dragenter or dragover event that triggered the indication. | ||||
*/ | */ | ||||
protected void addTargetClassIndicator(NativeEvent event) { | |||||
protected void addDragOverStyle(NativeEvent event) { | |||||
getDropTargetElement().addClassName(styleDragCenter); | getDropTargetElement().addClassName(styleDragCenter); | ||||
} | } | ||||
/** | /** | ||||
* Remove the drag target indicator class name from the target element. | |||||
* Remove the drag over indicator class name from the target element. | |||||
* <p> | * <p> | ||||
* This is triggered on {@link #onDrop(Event) drop}, | * This is triggered on {@link #onDrop(Event) drop}, | ||||
* {@link #onDragLeave(Event) dragleave} and {@link #onDragOver(Event) | * {@link #onDragLeave(Event) dragleave} and {@link #onDragOver(Event) | ||||
* @param event | * @param event | ||||
* the event that triggered the removal of the indicator | * the event that triggered the removal of the indicator | ||||
*/ | */ | ||||
protected void removeTargetClassIndicator(NativeEvent event) { | |||||
protected void removeDragOverStyle(NativeEvent event) { | |||||
getDropTargetElement().removeClassName(styleDragCenter); | getDropTargetElement().removeClassName(styleDragCenter); | ||||
} | } | ||||
return new Function('event', script)(event); | return new Function('event', script)(event); | ||||
}-*/; | }-*/; | ||||
private native boolean getStylePrimaryName(Element element) | |||||
/*-{ | |||||
return @com.google.gwt.user.client.ui.UIObject::getStylePrimaryName(Lcom/google/gwt/dom/client/Element;)(element); | |||||
}-*/; | |||||
@Override | @Override | ||||
public DropTargetState getState() { | public DropTargetState getState() { | ||||
return (DropTargetState) super.getState(); | return (DropTargetState) super.getState(); |
=== CSS Style Rules | === CSS Style Rules | ||||
When dragging data over a drop target and the drag over criteria passes, a style name is applied to indicate that the element accepts drops. This style name is the primary style name with `-drag-center` suffix, e.g. `v-label-drag-center`. | |||||
Each drop target element have an applied style name, the primary style name with `-droptarget` suffix, e.g. `v-label-droptarget`, to indicate that it is a potential target for data to be dropped onto it. | |||||
When dragging data over a drop target and the drag over criteria passes, a style name is applied to indicate that the element accepts the drop. This style name is the primary style name with `-drag-center` suffix, e.g. `v-label-drag-center`. | |||||
//// | //// | ||||
List<Person> items = (List<Person>) dataProvider.getItems(); | List<Person> items = (List<Person>) dataProvider.getItems(); | ||||
// Calculate the target row's index | // Calculate the target row's index | ||||
int index = items.indexOf(event.getDropTargetRow()) + ( | |||||
int index = items.size(); | |||||
if (event.getDropTargetRow().isPresent()) { | |||||
index = items.indexOf(event.getDropTargetRow().get()) + ( | |||||
event.getDropLocation() == DropLocation.BELOW ? 1 : 0); | event.getDropLocation() == DropLocation.BELOW ? 1 : 0); | ||||
} | |||||
// Add dragged items to the target Grid | // Add dragged items to the target Grid | ||||
items.addAll(index, draggedItems); | items.addAll(index, draggedItems); | ||||
==== CSS Style Rules | ==== CSS Style Rules | ||||
A drop target Grid's body has the style name `v-grid-body-droptarget` to indicate that it is a potential target for data to be dropped. | |||||
When dragging data over a drop target Grid's row, depending on the drop mode and the mouse position relative to the row, a style name is applied to the row to indicate the drop location. | When dragging data over a drop target Grid's row, depending on the drop mode and the mouse position relative to the row, a style name is applied to the row to indicate the drop location. | ||||
`v-grid-row-drag-center` indicates ON_TOP, `v-grid-row-drag-top` indicates ABOVE and `v-grid-row-drag-bottom` indicates BELOW locations. | `v-grid-row-drag-center` indicates ON_TOP, `v-grid-row-drag-top` indicates ABOVE and `v-grid-row-drag-bottom` indicates BELOW locations. | ||||
package com.vaadin.ui.components.grid; | package com.vaadin.ui.components.grid; | ||||
import java.util.Map; | import java.util.Map; | ||||
import java.util.Optional; | |||||
import com.vaadin.shared.ui.dnd.DropEffect; | import com.vaadin.shared.ui.dnd.DropEffect; | ||||
import com.vaadin.shared.ui.grid.DropLocation; | import com.vaadin.shared.ui.grid.DropLocation; | ||||
/** | /** | ||||
* Get the row item source of this event. | * Get the row item source of this event. | ||||
* | * | ||||
* @return The row item this event was originated from. | |||||
* @return The optional row item if the event was originated from a row, | |||||
* otherwise an empty optional. | |||||
*/ | */ | ||||
public T getDropTargetRow() { | |||||
return dropTargetRow; | |||||
public Optional<T> getDropTargetRow() { | |||||
return Optional.ofNullable(dropTargetRow); | |||||
} | } | ||||
/** | /** |
background: darken($v-grid-drag-indicator-color, 10%); | background: darken($v-grid-drag-indicator-color, 10%); | ||||
} | } | ||||
} | } | ||||
// Expand Grid's body to cover the whole Grid | |||||
.#{$primary-stylename}-body-droptarget { | |||||
width: 100%; | |||||
height: 100%; | |||||
} | |||||
} | } | ||||
GridDragSource<Person> dragSource = applyDragSource(left); | GridDragSource<Person> dragSource = applyDragSource(left); | ||||
// Drop target Grid | // Drop target Grid | ||||
Grid<Person> right = createGridAndFillWithData(5); | |||||
Grid<Person> right = createGridAndFillWithData(0); | |||||
GridDropTarget<Person> dropTarget = applyDropTarget(right); | GridDropTarget<Person> dropTarget = applyDropTarget(right); | ||||
// Layout the two grids | // Layout the two grids | ||||
List<Person> items = (List<Person>) dataProvider.getItems(); | List<Person> items = (List<Person>) dataProvider.getItems(); | ||||
// Calculate the target row's index | // Calculate the target row's index | ||||
int index = items.indexOf(event.getDropTargetRow()) | |||||
+ (event.getDropLocation() == DropLocation.BELOW ? 1 | |||||
: 0); | |||||
int index = items.size(); | |||||
if (event.getDropTargetRow().isPresent()) { | |||||
index = items.indexOf(event.getDropTargetRow().get()) | |||||
+ (event.getDropLocation() == DropLocation.BELOW | |||||
? 1 : 0); | |||||
} | |||||
// Add dragged items to the target Grid | // Add dragged items to the target Grid | ||||
items.addAll(index, draggedItems); | items.addAll(index, draggedItems); | ||||
+ ", dragDataJson=" | + ", dragDataJson=" | ||||
+ event.getDataTransferData("application/json") | + event.getDataTransferData("application/json") | ||||
+ ", target=" | + ", target=" | ||||
+ event.getDropTargetRow().getFirstName() + " " | |||||
+ event.getDropTargetRow().getLastName() | |||||
+ (event.getDropTargetRow().isPresent() ? | |||||
event.getDropTargetRow().get().getFirstName() + " " | |||||
+ event.getDropTargetRow().get() | |||||
.getLastName() : "[BODY]") | |||||
+ ", location=" + event.getDropLocation()); | + ", location=" + event.getDropLocation()); | ||||
} | } | ||||
}); | }); |