Creating a Drag Source
- Exposing Drag Source API to a Component
- Assigning Server-Side Data to Drag Source
- Drag Start & End Events
With the DragSource
class, you can make any component draggable by the user, and configure the drag operation for it. By default, the entire component is made draggable, instead of a small part of it. The DragSource
class is a configuration object for the dragged item and contains static methods for configuring the given component instance.
Div box1 = new Div();
Div box2 = new Div();
// Make box 1 draggable and store reference to the configuration object.
DragSource<Div> box1DragSource = DragSource.create(box1);
// Access box 2 drag related configuration object without making it draggable.
DragSource<Div> box2DragSource = DragSource.configure(box2);
// Make box 2 draggable.
box2DragSource.setDraggable(true);
add(box1, box2);
The DragSource
configuration object doesn’t itself store any data, as it’s a convenience proxy that facilitates making a component draggable. Creating a new DragSource
instance of a component doesn’t reset any previous configuration, but any changes override the previous configuration.
When a component is set as draggable on the server side, the draggable
attribute is assigned to its topmost element on the browser, thus making it draggable. When the user starts to drag the component, the root element of the component gets the v-dragged
class name in the browser. This can be used to highlight the dragged component to the user.
.v-dragged {
outline: 1px dotted hotpink;
opacity: 0.5;
}
Exposing Drag Source API to a Component
As the DragSource
is an interface, it can also be used as a "mix-in interface". It provides an easy way to add its API to your custom component. This is convenient when you want to reuse the component in many places for drag operation.
public class CardComponent extends Div implements DragSource<CardComponent>, HasStyle {
public CardComponent() {
// all cards are draggable by default
setDraggable(true);
}
// all DragSource methods have default implementations
}
Assigning Server-Side Data to Drag Source
It’s possible to set any Java object as the server-side drag data for the drag
source. This data is provided on the DropEvent
when the drop occurs on a valid drop target — if it’s inside the same UI (i.e., browser window or tab) as the drag source component.
// Continuing from the previous example, CardComponent implements DragSource.
CardComponent card1 = new CardComponent();
CardComponent card2 = new CardComponent();
card1.setDragData("Queen of Hearts");
card2.setDragData(new Card(11, Land.Spade)); // The data can be any object.
The data isn’t sent to the browser, as it’s stored in the component instance as server-side-only data.
It isn’t possible to configure the client-side drag data, the dataTransfer
object, from the server side.
Drag Start & End Events
The DragSource
provides a way to react when the user starts and stops dragging a component. The DragStartEvent
is fired when the drag starts in the browser, which means that you can’t cancel the drag. You should avoid doing any heavy synchronous processing there, since this blocks the user from dragging the component further in the browser. That would have a negative effect on the UX.
// Continuing from the previous example with CardComponent.
card1.addDragStartListener(event -> {
// Highlight suitable drop targets in the UI
getVisibleCards().forEach(target -> {
Card targetCard = target.getCard();
if (targetCard.getLand() == ((Card) card1.getDragData()).getLand()
&& target != card1) {
target.addClassName("possible-drop-zone");
}
})
});
When the user stops dragging the component by either dropping it or by canceling the drag with, for example, the escape key, the DragEndEvent
is fired. The isSuccessful()
method returns true
if the drop occurred on a drop target that accepted the drop, but only for Chrome and Firefox (see the note after the example below).
card1.addDragEndListener(event -> {
getVisibleCards().forEach(target -> target.removeClassName("possible-drop-zone"));
// NOTE: The following is always FALSE for Edge and Safari !!!
if (event.isSuccessful()) {
// Better to put logic for successful drop into DropEvent for the
// DropTarget because of the above.
}
});
Note
|
Handling Drag End in Edge & Safari
Edge and Safari don’t report whether the drop occurred successfully in the DragEnd event. You’ll need to take this into account if your users are using any of these browsers, and do any logic in the DropEvent handler of the DropTarget , instead. For Chrome and Firefox, it works correctly.
|
Effect Allowed & Drop Effect
It’s possible to customize the effectAllowed
for the drag source. This has an effect on what the browser visually presents to the user and should match what’s set in the drop target as the dropEffect
. The DragEndEvent
reports the dropEffect
for the drop event.
The value is determined in priority order by:
-
the desired action
dropEffect
set by the drop target; -
the
effectAllowed
set for the drag source; and -
the modifier keys the user presses and holds when dropping.
When the drop effect is MOVE
, you should move or remove the drag source component from its original location. When the drop effect is NONE
, the drop didn’t occur and dropEvent.isSuccessful()
returns false
.
Customizing the Draggable Element
You can customize the element that is made draggable by overriding the getDraggableElement()
method in the DragSource
interface. This is useful if it’s not the whole component that’s to be draggable, but only a part of it.
/* NOTE: RouteItem is a made up custom component, not a core Vaadin component. */
public class DraggableRouteItem extends RouteItem implements DragSource<RouteItem> {
private Icon dragHandle = VaadinIcon.MENU.create();
public DraggableRouteItem(String destination) {
super(destination);
add(dragHandle);
}
// Instead of allowing the whole item to be draggable, only allow dragging
// from the icon.
@Override
public Element getDraggableElement() {
return dragHandle.getElement();
}
}
Changing the draggable element also changes the drag image that the browser shows under the cursor.
Drag-Image
With the DragSource
interface’s setDragImage
methods, it’s possible to customize a drag-image that the browser shows under the cursor when dragging a component.
Image
is applied in the next drag start event in the browser. The Image
component is fully supported as a drag image.
// Continuing from the previous example.
CardComponent card = new CardComponent();
card.setDragImage(new Image("/cards/ace_of_spades.png", "Ace of Spades"));
The Image
component supports StreamResource to generate the image, dynamically.
Any optional coordinates define the offset of the pointer location from the top left corner of the image. The following example sets the x
offset to 20 pixels and the y
offset to 0 pixels:
card.setDragImage(new Image("/cards/queen_of_hearts.png", "Queen of Hearts"), 20, 0);
Other components can be used as well, but support may vary between browsers. If a given component is a visible element in the viewport, the browser can show it as a drag image. Otherwise, the component must be added to the DOM as an off-screen element specifically for this purpose. Vaadin handles this if the component is not already attached before being set as a drag image. If the default behavior doesn’t meet your requirements, you can override it by adding the component to the DOM and applying your desired styling before invoking the setDragImage()
method.
The following example demonstrates the addition of an off-screen component as a drag image:
Span dragImage = new Span("Drag Image Component");
Style dragImageStyle = dragImage.getElement().getStyle();
dragImageStyle.setPosition(Style.Position.ABSOLUTE);
dragImageStyle.setTop("-100px");
dragImageStyle.setLeft("-100px");
dragImageStyle.setDisplay(Style.Display.NONE);
add(dragImage);
dragSource.setDragImage(dragImage);
For more information about the drag image, see HTML5 Drag & Drop API.
4FFD51BA-4736-44BD-8FCF-0E534A19FB8D