// // Copyright (c) 2015, Brian Frank and Andy Frank // Licensed under the Academic Free License version 3.0 // // History: // 19 Jun 2015 Andy Frank Creation // using concurrent using dom using graphics ************************************************************************** ** DragTarget ************************************************************************** ** ** DragTarget converts an Elem into a drag target for ** a drag-and-drop events. ** ** See also: [docDomkit]`docDomkit::Dnd` ** @Js class DragTarget { ** Convert given Elem into a drag target. static DragTarget bind(Elem elem) { make(elem) } ** Private ctor. private new make(Elem elem) { elem->draggable = true elem.onEvent("dragstart", false) |e| { if (cbDrag == null) return data := cbDrag.call(elem) DndUtil.setData(e.dataTransfer, data) if (cbDragImage != null) { this.dragImage = cbDragImage.call(data) this.dragImage.style->position = "absolute" this.dragImage.style->top = "-1000px" this.dragImage.style->right = "-1000px" Win.cur.doc.body.add(dragImage) e.dataTransfer.setDragImage(dragImage, 0, 0) } } elem.onEvent("dragend", false) |e| { if (cbEnd != null) cbEnd(elem) dragImage?.parent?.remove(dragImage) } } ** Callback to get data payload for drag event. Void onDrag(|Elem->Obj| f) { cbDrag = f } ** Callback to customize the drag image for drag event. Void onDragImage(|Obj->Elem| f) { cbDragImage = f } ** Callback when the drag event has ended. Void onEnd(|Elem| f) { cbEnd = f } private Func? cbDrag private Func? cbDragImage private Func? cbEnd private Elem? dragImage } ************************************************************************** ** DropTarget ************************************************************************** ** ** DropTarget converts an Elem into a drop target for drag and drop ** events. The 'canDrop' callback is used to indicate if 'data' can be ** dropped on this target. The 'onDrop' callback is invoked when a ** drop event completes. ** ** See also: [docDomkit]`docDomkit::Dnd` ** @Js class DropTarget { ** Convert given Elem into a drop target. static DropTarget bind(Elem elem) { make(elem) } ** Private ctor. private new make(Elem elem) { // setup elem positioning if needed pos := elem.style["position"] if (pos != "relative" || pos != "absolute") elem.style["position"] = "relative" // setup events elem.onEvent("dragenter", false) |e| { e.stop data := DndUtil.getData(e.dataTransfer) if (_canDrop(data)) elem.style.addClass("domkit-dnd-over") } elem.onEvent("dragover", false) |e| { e.stop if (cbOver != null) { // TODO: need to translate these to pageX,pageY Int x := e->clientX Int y := e->clientY cbOver(Point(x,y)) } } elem.onEvent("dragleave", false) |e| { if (e.target == elem) elem.style.removeClass("domkit-dnd-over") } elem.onEvent("drop", false) |e| { e.stop elem.style.removeClass("domkit-dnd-over") data := DndUtil.getData(e.dataTransfer) if (_canDrop(data)) cbDrop?.call(data) } } ** Callback to indicate if 'data' can be dropped on this target. Void canDrop(|Obj data->Bool| f) { this.cbCanDrop = f } ** Callback when 'data' is dropped on this target. Void onDrop(|Obj data| f) { this.cbDrop = f } ** Callback when drag target is over this drop target, where ** 'pagePos' is the current drag node. Void onOver(|Point pagePos| f) { this.cbOver = f } private Bool _canDrop(Obj data) { cbCanDrop == null ? true : cbCanDrop.call(data) } private Func? cbCanDrop private Func? cbDrop private Func? cbOver private Int depth } ************************************************************************** ** DndUtil ************************************************************************** ** ** Internal drag and drop utilities. ** @NoDoc @Js class DndUtil { ** Global map of data payloads. private static Int:Obj map() { m := Actor.locals["domkit.dnd.map"] as Int:Obj if (m == null) Actor.locals["domkit.dnd.map"] = m = Int:Obj[:] return m } ** Get the data payload for given transfer instance. static Obj getData(DataTransfer dt) { data := dt.getData("text/plain") if (data.isEmpty) { // if we have no data set then most likely this is file // being dragged in externally into the browser return dt.files } if (data.startsWith("fandnd:")) { // if our own key then return actor local data copy key := 0 try { key = data["fandnd:".size..-1].toInt(10, true) } catch { throw ArgErr("Drag target not found: $data") } val := map[key] ?: throw ArgErr("Drag target not found: $key") return val } // fallback to return original data return data } ** Set the data payload on given transfer instance. static Void setData(DataTransfer dt, Obj data) { map[data.hash] = data dt.setData("text/plain", "fandnd:${data.hash.toStr}") } }