com.vaadin.ui.dnd.DragSourceExtension Maven / Gradle / Ivy
/*
* Copyright (C) 2000-2024 Vaadin Ltd
*
* This program is available under Vaadin Commercial License and Service Terms.
*
* See for the full
* license.
*/
package com.vaadin.ui.dnd;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
import com.vaadin.server.AbstractExtension;
import com.vaadin.server.Resource;
import com.vaadin.shared.Registration;
import com.vaadin.shared.ui.dnd.DragSourceRpc;
import com.vaadin.shared.ui.dnd.DragSourceState;
import com.vaadin.shared.ui.dnd.DropEffect;
import com.vaadin.shared.ui.dnd.EffectAllowed;
import com.vaadin.shared.ui.dnd.criteria.Payload;
import com.vaadin.ui.AbstractComponent;
import com.vaadin.ui.dnd.event.DragEndEvent;
import com.vaadin.ui.dnd.event.DragEndListener;
import com.vaadin.ui.dnd.event.DragStartEvent;
import com.vaadin.ui.dnd.event.DragStartListener;
/**
* Extension to make a component drag source for HTML5 drag and drop
* functionality.
*
* @param
* Type of the component to be extended.
* @author Vaadin Ltd
* @since 8.1
*/
public class DragSourceExtension
extends AbstractExtension {
private Registration dragStartListenerHandle;
private Registration dragEndListenerHandle;
/**
* Stores the server side drag data that is available for the drop target if
* it is in the same UI.
*/
private Object dragData;
/**
* Extends {@code target} component and makes it a drag source.
*
* @param target
* Component to be extended.
*/
public DragSourceExtension(T target) {
super.extend(target);
initListeners();
}
/**
* Initializes dragstart and -end event listeners for this drag source to
* capture the active drag source for the UI.
*/
private void initListeners() {
// Set current extension as active drag source in the UI
dragStartListenerHandle = addDragStartListener(
event -> getUI().setActiveDragSource(this));
// Remove current extension as active drag source from the UI
dragEndListenerHandle = addDragEndListener(
event -> getUI().setActiveDragSource(null));
}
@Override
public void attach() {
super.attach();
registerDragSourceRpc();
}
/**
* Registers the server side RPC methods invoked from client side on
* dragstart
and dragend
events.
*
* Override this method if you have custom RPC interface for transmitting
* those events with more data. If just need to do additional things before
* firing the events, then you should override {@link #onDragStart()} and
* {@link #onDragEnd(DropEffect)} instead.
*/
protected void registerDragSourceRpc() {
registerRpc(new DragSourceRpc() {
@Override
public void dragStart() {
onDragStart();
}
@Override
public void dragEnd(DropEffect dropEffect) {
onDragEnd(dropEffect);
}
});
}
/**
* Method invoked when a dragstart
has been sent from client
* side. Fires the {@link DragStartEvent}.
*/
protected void onDragStart() {
DragStartEvent event = new DragStartEvent<>(getParent(),
getState(false).effectAllowed);
fireEvent(event);
}
/**
* Method invoked when a dragend
has been sent from client
* side. Fires the {@link DragEndEvent}.
*
* @param dropEffect
* the drop effect on the dragend
*/
protected void onDragEnd(DropEffect dropEffect) {
DragEndEvent event = new DragEndEvent<>(getParent(), dropEffect);
fireEvent(event);
}
@Override
public void remove() {
super.remove();
// Remove listeners attached on construction
dragStartListenerHandle.remove();
dragEndListenerHandle.remove();
}
/**
* Sets the allowed effects for the current drag source element. Used for
* setting client side {@code DataTransfer.effectAllowed} parameter for the
* drag event.
*
* By default the value is {@link EffectAllowed#UNINITIALIZED} which is
* equivalent to {@link EffectAllowed#ALL}.
*
* @param effect
* Effects to allow for this draggable element. Cannot be {@code
* null}.
*/
public void setEffectAllowed(EffectAllowed effect) {
if (effect == null) {
throw new IllegalArgumentException("Allowed effect cannot be null");
}
if (!Objects.equals(getState(false).effectAllowed, effect)) {
getState().effectAllowed = effect;
}
}
/**
* Returns the allowed effects for the current drag source element. Used to
* set client side {@code DataTransfer.effectAllowed} parameter for the drag
* event.
*
* You can use different types of data to support dragging to different
* targets. Accepted types depend on the drop target and those can be
* platform specific. See
* https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API/Recommended_drag_types
* for examples on different types.
*
* NOTE: IE11 only supports type ' text', which can be set using
* {@link #setDataTransferText(String data)}
*
* @return Effects that are allowed for this draggable element.
*/
public EffectAllowed getEffectAllowed() {
return getState(false).effectAllowed;
}
/**
* Sets data for this drag source element with the given type. The data is
* set for the client side draggable element using {@code
* DataTransfer.setData(type, data)} method.
*
* Note that {@code "text"} is the only cross browser supported data type.
* Use {@link #setDataTransferText(String)} method instead if your
* application supports IE11.
*
* @param type
* Type of the data to be set for the client side draggable
* element, e.g. {@code text/plain}. Cannot be {@code null}.
* @param data
* Data to be set for the client side draggable element. Cannot
* be {@code null}.
*/
public void setDataTransferData(String type, String data) {
if (type == null) {
throw new IllegalArgumentException("Data type cannot be null");
}
if (data == null) {
throw new IllegalArgumentException("Data cannot be null");
}
if (!getState(false).types.contains(type)) {
getState().types.add(type);
}
getState().data.put(type, data);
}
/**
* Returns the data stored with type {@code type} in this drag source
* element.
*
* @param type
* Type of the requested data, e.g. {@code text/plain}.
* @return Data of type {@code type} stored in this drag source element.
*/
public String getDataTransferData(String type) {
return getState(false).data.get(type);
}
/**
* Returns the map of data stored in this drag source element. The returned
* map preserves the order of storage and is unmodifiable.
*
* @return Unmodifiable copy of the map of data in the order the data was
* stored.
*/
public Map getDataTransferData() {
Map data = getState(false).data;
// Create a map of data that preserves the order of types
LinkedHashMap orderedData = new LinkedHashMap<>(
data.size());
getState(false).types
.forEach(type -> orderedData.put(type, data.get(type)));
return Collections.unmodifiableMap(orderedData);
}
/**
* Sets data of type {@code "text"} for this drag source element. The data
* is set for the client side draggable element using the {@code
* DataTransfer.setData("text", data)} method.
*
* Note that {@code "text"} is the only cross browser supported data type.
* Use this method if your application supports IE11.
*
* @param data
* Data to be set for the client side draggable element.
* @see #setDataTransferData(String, String)
*/
public void setDataTransferText(String data) {
setDataTransferData(DragSourceState.DATA_TYPE_TEXT, data);
}
/**
* Returns the data stored with type {@code "text"} in this drag source
* element.
*
* @return Data of type {@code "text"} stored in this drag source element.
*/
public String getDataTransferText() {
return getDataTransferData(DragSourceState.DATA_TYPE_TEXT);
}
/**
* Clears data with the given type for this drag source element when
* present.
*
* @param type
* Type of data to be cleared. Cannot be {@code null}.
*/
public void clearDataTransferData(String type) {
if (type == null) {
throw new IllegalArgumentException("Data type cannot be null");
}
getState().types.remove(type);
getState().data.remove(type);
}
/**
* Clears all data for this drag source element.
*/
public void clearDataTransferData() {
getState().types.clear();
getState().data.clear();
}
/**
* Sets payload for this drag source to use with acceptance criterion. The
* payload is transferred as data type in the data transfer object in the
* following format: {@code "v-item:string:key:value"}. The given value is
* compared to the criterion value when the drag source is dragged on top of
* a drop target that has the suitable criterion.
*
* Note that setting payload in Internet Explorer 11 is not possible due to
* the browser's limitations.
*
* @param key
* key of the payload to be transferred
* @param value
* value of the payload to be transferred
* @see DropTargetExtension#setDropCriterion(String, String)
*/
public void setPayload(String key, String value) {
setPayload(key, String.valueOf(value), Payload.ValueType.STRING);
}
/**
* Sets payload for this drag source to use with acceptance criterion. The
* payload is transferred as data type in the data transfer object in the
* following format: {@code "v-item:integer:key:value"}. The given value is
* compared to the criterion value when the drag source is dragged on top of
* a drop target that has the suitable criterion.
*
* Note that setting payload in Internet Explorer 11 is not possible due to
* the browser's limitations.
*
* @param key
* key of the payload to be transferred
* @param value
* value of the payload to be transferred
* @see DropTargetExtension#setDropCriterion(String,
* com.vaadin.shared.ui.dnd.criteria.ComparisonOperator, int)
* DropTargetExtension#setDropCriterion(String, ComparisonOperator,
* int)
*/
public void setPayload(String key, int value) {
setPayload(key, String.valueOf(value), Payload.ValueType.INTEGER);
}
/**
* Sets payload for this drag source to use with acceptance criterion. The
* payload is transferred as data type in the data transfer object in the
* following format: {@code "v-item:double:key:value"}. The given value is
* compared to the criterion value when the drag source is dragged on top of
* a drop target that has the suitable criterion.
*
* Note that setting payload in Internet Explorer 11 is not possible due to
* the browser's limitations.
*
* @param key
* key of the payload to be transferred
* @param value
* value of the payload to be transferred
* @see DropTargetExtension#setDropCriterion(String,
* com.vaadin.shared.ui.dnd.criteria.ComparisonOperator, double)
* DropTargetExtension#setDropCriterion(String, ComparisonOperator,
* double)
*/
public void setPayload(String key, double value) {
setPayload(key, String.valueOf(value), Payload.ValueType.DOUBLE);
}
private void setPayload(String key, String value,
Payload.ValueType valueType) {
getState().payload.put(key, new Payload(key, value, valueType));
}
/**
* Set server side drag data. This data is available in the drop event and
* can be used to transfer data between drag source and drop target if they
* are in the same UI.
*
* @param data
* Data to transfer to drop event.
*/
public void setDragData(Object data) {
dragData = data;
}
/**
* Get server side drag data. This data is available in the drop event and
* can be used to transfer data between drag source and drop target if they
* are in the same UI.
*
* @return Server side drag data if set, otherwise {@literal null}.
*/
public Object getDragData() {
return dragData;
}
/**
* Attaches dragstart listener for the current drag source.
* {@link DragStartListener#dragStart(DragStartEvent)} is called when
* dragstart event happens on the client side.
*
* @param listener
* Listener to handle dragstart event.
* @return Handle to be used to remove this listener.
*/
public Registration addDragStartListener(DragStartListener listener) {
return addListener(DragSourceState.EVENT_DRAGSTART,
DragStartEvent.class, listener,
DragStartListener.DRAGSTART_METHOD);
}
/**
* Attaches dragend listener for the current drag source.
* {@link DragEndListener#dragEnd(DragEndEvent)} is called when dragend
* event happens on the client side.
*
* @param listener
* Listener to handle dragend event.
* @return Handle to be used to remove this listener.
*/
public Registration addDragEndListener(DragEndListener listener) {
return addListener(DragSourceState.EVENT_DRAGEND, DragEndEvent.class,
listener, DragEndListener.DRAGEND_METHOD);
}
/**
* Set a custom drag image for the current drag source.
*
* @param imageResource
* Resource of the image to be displayed as drag image.
*/
public void setDragImage(Resource imageResource) {
setResource(DragSourceState.RESOURCE_DRAG_IMAGE, imageResource);
}
@Override
protected DragSourceState getState() {
return (DragSourceState) super.getState();
}
@Override
protected DragSourceState getState(boolean markAsDirty) {
return (DragSourceState) super.getState(markAsDirty);
}
/**
* Returns the component this extension is attached to.
*
* @return Extended component.
*/
@Override
@SuppressWarnings("unchecked")
public T getParent() {
return (T) super.getParent();
}
}