I want to create a “DragView” which makes it possible to move its children to any position within that “DragView”, via dragging. Is this doable with the DragSource/DropTarget API vaadin provides?
Or is it better to create it myself with custom javascript and styles, which gives each added child a fixed position based on top and left pixels.
Update, did it myself:
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.UI;
import com.vaadin.flow.component.html.Div;
import elemental.json.JsonObject;
import java.util.Collection;
import java.util.Objects;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Consumer;
public class DragView extends Div {
public final CopyOnWriteArrayList<Consumer<DragFinishEvent>> listeners = new CopyOnWriteArrayList<>();
public final AtomicLong idCounter = new AtomicLong();
public DragView() {
setSizeFull();
getStyle().set("border", "2px solid #ccc");
getStyle().set("overflow", "hidden");
UI.getCurrent().getElement().addEventListener("dragViewItemDragStopped", e -> {
JsonObject d = e.getEventData();
System.out.println(d.toJson());
String cId = d.get("event.detail.cid").asString();
int left = (int) d.get("event.detail.left").asNumber();
int top = (int) d.get("event.detail.top").asNumber();
Component target = null;
for (Component c : getChildren().toList()) {
String id = c.getId().get();
if(cId.equals(id)) {
target = c;
break;
}
}
Objects.requireNonNull(target);
DragFinishEvent finishEvent = new DragFinishEvent(target, left, top);
for (Consumer<DragFinishEvent> listener : listeners) {
listener.accept(finishEvent);
}
})
.addEventData("event.detail.cid")
.addEventData("event.detail.left")
.addEventData("event.detail.top");
}
public DragView onDragFinish(Consumer<DragFinishEvent> listener){
listeners.add(listener);
UI.getCurrent().addDetachListener(e -> {
listeners.remove(listener);
});
return this;
}
@Override
public void add(Collection<Component> components) {
for (Component c : components) {
c.setId(""+idCounter.getAndIncrement());
c.getStyle().set("position", "absolute");
c.getStyle().set("cursor", "pointer");
c.addAttachListener(e -> {
if(!e.isInitialAttach()) return;
UI.getCurrent().getPage().executeJs("" +
" const dragView = $0;\n" +
" const item = $1;\n" +
" item.addEventListener(\"mousedown\", startDrag);\n" +
"\n" +
" function startDrag(e) {\n" +
" const draggable = e.target;\n" +
" const dragViewRect = dragView.getBoundingClientRect();\n" +
"\n" +
" const initialX = e.clientX - draggable.getBoundingClientRect().left;\n" +
" const initialY = e.clientY - draggable.getBoundingClientRect().top;\n" +
"\n" +
" function moveElement(e) {\n" +
" e.preventDefault();\n" +
" const newX = e.clientX - initialX - dragViewRect.left;\n" +
" const newY = e.clientY - initialY - dragViewRect.top;\n" +
"\n" +
" draggable.style.left = newX + \"px\";\n" +
" draggable.style.top = newY + \"px\";\n" +
" }\n" +
"\n" +
" function stopDrag(e) {\n" +
" const event = new CustomEvent(\"dragViewItemDragStopped\", {\n" +
" bubbles: true,\n" +
" detail: {\n" +
" cid: draggable.id,\n" +
" left: parseInt(draggable.style.left),\n" +
" top: parseInt(draggable.style.top)\n" +
" }\n" +
" });\n" +
" dragView.dispatchEvent(event);\n" +
"\n" +
" document.removeEventListener(\"mousemove\", moveElement);\n" +
" document.removeEventListener(\"mouseup\", stopDrag);\n" +
" }\n" +
"\n" +
" document.addEventListener(\"mousemove\", moveElement);\n" +
" document.addEventListener(\"mouseup\", stopDrag);\n" +
"\n" +
" // Move the draggable to cursor position initially\n" +
" const offsetX = e.clientX - dragViewRect.left - initialX;\n" +
" const offsetY = e.clientY - dragViewRect.top - initialY;\n" +
" draggable.style.left = offsetX + \"px\";\n" +
" draggable.style.top = offsetY + \"px\";\n" +
" }",this, c);
});
}
super.add(components);
}
public static class DragFinishEvent {
public Component target;
public int left;
public int top;
public DragFinishEvent(Component target, int left, int top) {
this.target = target;
this.left = left;
this.top = top;
}
}
}