|
- /*!
- * jquery.fancytree.dnd.js
- *
- * Drag-and-drop support (jQuery UI draggable/droppable).
- * (Extension module for jquery.fancytree.js: https://github.com/mar10/fancytree/)
- *
- * Copyright (c) 2008-2023, Martin Wendt (https://wwWendt.de)
- *
- * Released under the MIT license
- * https://github.com/mar10/fancytree/wiki/LicenseInfo
- *
- * @version 2.38.3
- * @date 2023-02-01T20:52:50Z
- */
-
- (function (factory) {
- if (typeof define === "function" && define.amd) {
- // AMD. Register as an anonymous module.
- define([
- "jquery",
- "jquery-ui/ui/widgets/draggable",
- "jquery-ui/ui/widgets/droppable",
- "./jquery.fancytree",
- ], factory);
- } else if (typeof module === "object" && module.exports) {
- // Node/CommonJS
- require("./jquery.fancytree");
- module.exports = factory(require("jquery"));
- } else {
- // Browser globals
- factory(jQuery);
- }
- })(function ($) {
- "use strict";
-
- /******************************************************************************
- * Private functions and variables
- */
- var didRegisterDnd = false,
- classDropAccept = "fancytree-drop-accept",
- classDropAfter = "fancytree-drop-after",
- classDropBefore = "fancytree-drop-before",
- classDropOver = "fancytree-drop-over",
- classDropReject = "fancytree-drop-reject",
- classDropTarget = "fancytree-drop-target";
-
- /* Convert number to string and prepend +/-; return empty string for 0.*/
- function offsetString(n) {
- // eslint-disable-next-line no-nested-ternary
- return n === 0 ? "" : n > 0 ? "+" + n : "" + n;
- }
-
- //--- Extend ui.draggable event handling --------------------------------------
-
- function _registerDnd() {
- if (didRegisterDnd) {
- return;
- }
-
- // Register proxy-functions for draggable.start/drag/stop
-
- $.ui.plugin.add("draggable", "connectToFancytree", {
- start: function (event, ui) {
- // 'draggable' was renamed to 'ui-draggable' since jQueryUI 1.10
- var draggable =
- $(this).data("ui-draggable") ||
- $(this).data("draggable"),
- sourceNode = ui.helper.data("ftSourceNode") || null;
-
- if (sourceNode) {
- // Adjust helper offset, so cursor is slightly outside top/left corner
- draggable.offset.click.top = -2;
- draggable.offset.click.left = +16;
- // Trigger dragStart event
- // TODO: when called as connectTo..., the return value is ignored(?)
- return sourceNode.tree.ext.dnd._onDragEvent(
- "start",
- sourceNode,
- null,
- event,
- ui,
- draggable
- );
- }
- },
- drag: function (event, ui) {
- var ctx,
- isHelper,
- logObject,
- // 'draggable' was renamed to 'ui-draggable' since jQueryUI 1.10
- draggable =
- $(this).data("ui-draggable") ||
- $(this).data("draggable"),
- sourceNode = ui.helper.data("ftSourceNode") || null,
- prevTargetNode = ui.helper.data("ftTargetNode") || null,
- targetNode = $.ui.fancytree.getNode(event.target),
- dndOpts = sourceNode && sourceNode.tree.options.dnd;
-
- // logObject = sourceNode || prevTargetNode || $.ui.fancytree;
- // logObject.debug("Drag event:", event, event.shiftKey);
- if (event.target && !targetNode) {
- // We got a drag event, but the targetNode could not be found
- // at the event location. This may happen,
- // 1. if the mouse jumped over the drag helper,
- // 2. or if a non-fancytree element is dragged
- // We ignore it:
- isHelper =
- $(event.target).closest(
- "div.fancytree-drag-helper,#fancytree-drop-marker"
- ).length > 0;
- if (isHelper) {
- logObject =
- sourceNode || prevTargetNode || $.ui.fancytree;
- logObject.debug("Drag event over helper: ignored.");
- return;
- }
- }
- ui.helper.data("ftTargetNode", targetNode);
-
- if (dndOpts && dndOpts.updateHelper) {
- ctx = sourceNode.tree._makeHookContext(sourceNode, event, {
- otherNode: targetNode,
- ui: ui,
- draggable: draggable,
- dropMarker: $("#fancytree-drop-marker"),
- });
- dndOpts.updateHelper.call(sourceNode.tree, sourceNode, ctx);
- }
-
- // Leaving a tree node
- if (prevTargetNode && prevTargetNode !== targetNode) {
- prevTargetNode.tree.ext.dnd._onDragEvent(
- "leave",
- prevTargetNode,
- sourceNode,
- event,
- ui,
- draggable
- );
- }
- if (targetNode) {
- if (!targetNode.tree.options.dnd.dragDrop) {
- // not enabled as drop target
- } else if (targetNode === prevTargetNode) {
- // Moving over same node
- targetNode.tree.ext.dnd._onDragEvent(
- "over",
- targetNode,
- sourceNode,
- event,
- ui,
- draggable
- );
- } else {
- // Entering this node first time
- targetNode.tree.ext.dnd._onDragEvent(
- "enter",
- targetNode,
- sourceNode,
- event,
- ui,
- draggable
- );
- targetNode.tree.ext.dnd._onDragEvent(
- "over",
- targetNode,
- sourceNode,
- event,
- ui,
- draggable
- );
- }
- }
- // else go ahead with standard event handling
- },
- stop: function (event, ui) {
- var logObject,
- // 'draggable' was renamed to 'ui-draggable' since jQueryUI 1.10:
- draggable =
- $(this).data("ui-draggable") ||
- $(this).data("draggable"),
- sourceNode = ui.helper.data("ftSourceNode") || null,
- targetNode = ui.helper.data("ftTargetNode") || null,
- dropped = event.type === "mouseup" && event.which === 1;
-
- if (!dropped) {
- logObject = sourceNode || targetNode || $.ui.fancytree;
- logObject.debug("Drag was cancelled");
- }
- if (targetNode) {
- if (dropped) {
- targetNode.tree.ext.dnd._onDragEvent(
- "drop",
- targetNode,
- sourceNode,
- event,
- ui,
- draggable
- );
- }
- targetNode.tree.ext.dnd._onDragEvent(
- "leave",
- targetNode,
- sourceNode,
- event,
- ui,
- draggable
- );
- }
- if (sourceNode) {
- sourceNode.tree.ext.dnd._onDragEvent(
- "stop",
- sourceNode,
- null,
- event,
- ui,
- draggable
- );
- }
- },
- });
-
- didRegisterDnd = true;
- }
-
- /******************************************************************************
- * Drag and drop support
- */
- function _initDragAndDrop(tree) {
- var dnd = tree.options.dnd || null,
- glyph = tree.options.glyph || null;
-
- // Register 'connectToFancytree' option with ui.draggable
- if (dnd) {
- _registerDnd();
- }
- // Attach ui.draggable to this Fancytree instance
- if (dnd && dnd.dragStart) {
- tree.widget.element.draggable(
- $.extend(
- {
- addClasses: false,
- // DT issue 244: helper should be child of scrollParent:
- appendTo: tree.$container,
- // appendTo: "body",
- containment: false,
- // containment: "parent",
- delay: 0,
- distance: 4,
- revert: false,
- scroll: true, // to disable, also set css 'position: inherit' on ul.fancytree-container
- scrollSpeed: 7,
- scrollSensitivity: 10,
- // Delegate draggable.start, drag, and stop events to our handler
- connectToFancytree: true,
- // Let source tree create the helper element
- helper: function (event) {
- var $helper,
- $nodeTag,
- opts,
- sourceNode = $.ui.fancytree.getNode(
- event.target
- );
-
- if (!sourceNode) {
- // #405, DT issue 211: might happen, if dragging a table *header*
- return "<div>ERROR?: helper requested but sourceNode not found</div>";
- }
- opts = sourceNode.tree.options.dnd;
- $nodeTag = $(sourceNode.span);
- // Only event and node argument is available
- $helper = $(
- "<div class='fancytree-drag-helper'><span class='fancytree-drag-helper-img' /></div>"
- )
- .css({ zIndex: 3, position: "relative" }) // so it appears above ext-wide selection bar
- .append(
- $nodeTag
- .find("span.fancytree-title")
- .clone()
- );
-
- // Attach node reference to helper object
- $helper.data("ftSourceNode", sourceNode);
-
- // Support glyph symbols instead of icons
- if (glyph) {
- $helper
- .find(".fancytree-drag-helper-img")
- .addClass(
- glyph.map._addClass +
- " " +
- glyph.map.dragHelper
- );
- }
- // Allow to modify the helper, e.g. to add multi-node-drag feedback
- if (opts.initHelper) {
- opts.initHelper.call(
- sourceNode.tree,
- sourceNode,
- {
- node: sourceNode,
- tree: sourceNode.tree,
- originalEvent: event,
- ui: { helper: $helper },
- }
- );
- }
- // We return an unconnected element, so `draggable` will add this
- // to the parent specified as `appendTo` option
- return $helper;
- },
- start: function (event, ui) {
- var sourceNode = ui.helper.data("ftSourceNode");
- return !!sourceNode; // Abort dragging if no node could be found
- },
- },
- tree.options.dnd.draggable
- )
- );
- }
- // Attach ui.droppable to this Fancytree instance
- if (dnd && dnd.dragDrop) {
- tree.widget.element.droppable(
- $.extend(
- {
- addClasses: false,
- tolerance: "intersect",
- greedy: false,
- /*
- activate: function(event, ui) {
- tree.debug("droppable - activate", event, ui, this);
- },
- create: function(event, ui) {
- tree.debug("droppable - create", event, ui);
- },
- deactivate: function(event, ui) {
- tree.debug("droppable - deactivate", event, ui);
- },
- drop: function(event, ui) {
- tree.debug("droppable - drop", event, ui);
- },
- out: function(event, ui) {
- tree.debug("droppable - out", event, ui);
- },
- over: function(event, ui) {
- tree.debug("droppable - over", event, ui);
- }
- */
- },
- tree.options.dnd.droppable
- )
- );
- }
- }
-
- /******************************************************************************
- *
- */
-
- $.ui.fancytree.registerExtension({
- name: "dnd",
- version: "2.38.3",
- // Default options for this extension.
- options: {
- // Make tree nodes accept draggables
- autoExpandMS: 1000, // Expand nodes after n milliseconds of hovering.
- draggable: null, // Additional options passed to jQuery draggable
- droppable: null, // Additional options passed to jQuery droppable
- focusOnClick: false, // Focus, although draggable cancels mousedown event (#270)
- preventVoidMoves: true, // Prevent dropping nodes 'before self', etc.
- preventRecursiveMoves: true, // Prevent dropping nodes on own descendants
- smartRevert: true, // set draggable.revert = true if drop was rejected
- dropMarkerOffsetX: -24, // absolute position offset for .fancytree-drop-marker relatively to ..fancytree-title (icon/img near a node accepting drop)
- dropMarkerInsertOffsetX: -16, // additional offset for drop-marker with hitMode = "before"/"after"
- // Events (drag support)
- dragStart: null, // Callback(sourceNode, data), return true, to enable dnd
- dragStop: null, // Callback(sourceNode, data)
- initHelper: null, // Callback(sourceNode, data)
- updateHelper: null, // Callback(sourceNode, data)
- // Events (drop support)
- dragEnter: null, // Callback(targetNode, data)
- dragOver: null, // Callback(targetNode, data)
- dragExpand: null, // Callback(targetNode, data), return false to prevent autoExpand
- dragDrop: null, // Callback(targetNode, data)
- dragLeave: null, // Callback(targetNode, data)
- },
-
- treeInit: function (ctx) {
- var tree = ctx.tree;
- this._superApply(arguments);
- // issue #270: draggable eats mousedown events
- if (tree.options.dnd.dragStart) {
- tree.$container.on("mousedown", function (event) {
- // if( !tree.hasFocus() && ctx.options.dnd.focusOnClick ) {
- if (ctx.options.dnd.focusOnClick) {
- // #270
- var node = $.ui.fancytree.getNode(event);
- if (node) {
- node.debug(
- "Re-enable focus that was prevented by jQuery UI draggable."
- );
- // node.setFocus();
- // $(node.span).closest(":tabbable").focus();
- // $(event.target).trigger("focus");
- // $(event.target).closest(":tabbable").trigger("focus");
- }
- setTimeout(function () {
- // #300
- $(event.target).closest(":tabbable").focus();
- }, 10);
- }
- });
- }
- _initDragAndDrop(tree);
- },
- /* Display drop marker according to hitMode ('after', 'before', 'over'). */
- _setDndStatus: function (
- sourceNode,
- targetNode,
- helper,
- hitMode,
- accept
- ) {
- var markerOffsetX,
- pos,
- markerAt = "center",
- instData = this._local,
- dndOpt = this.options.dnd,
- glyphOpt = this.options.glyph,
- $source = sourceNode ? $(sourceNode.span) : null,
- $target = $(targetNode.span),
- $targetTitle = $target.find("span.fancytree-title");
-
- if (!instData.$dropMarker) {
- instData.$dropMarker = $(
- "<div id='fancytree-drop-marker'></div>"
- )
- .hide()
- .css({ "z-index": 1000 })
- .prependTo($(this.$div).parent());
- // .prependTo("body");
-
- if (glyphOpt) {
- instData.$dropMarker.addClass(
- glyphOpt.map._addClass + " " + glyphOpt.map.dropMarker
- );
- }
- }
- if (
- hitMode === "after" ||
- hitMode === "before" ||
- hitMode === "over"
- ) {
- markerOffsetX = dndOpt.dropMarkerOffsetX || 0;
- switch (hitMode) {
- case "before":
- markerAt = "top";
- markerOffsetX += dndOpt.dropMarkerInsertOffsetX || 0;
- break;
- case "after":
- markerAt = "bottom";
- markerOffsetX += dndOpt.dropMarkerInsertOffsetX || 0;
- break;
- }
-
- pos = {
- my: "left" + offsetString(markerOffsetX) + " center",
- at: "left " + markerAt,
- of: $targetTitle,
- };
- if (this.options.rtl) {
- pos.my = "right" + offsetString(-markerOffsetX) + " center";
- pos.at = "right " + markerAt;
- }
- instData.$dropMarker
- .toggleClass(classDropAfter, hitMode === "after")
- .toggleClass(classDropOver, hitMode === "over")
- .toggleClass(classDropBefore, hitMode === "before")
- .toggleClass("fancytree-rtl", !!this.options.rtl)
- .show()
- .position($.ui.fancytree.fixPositionOptions(pos));
- } else {
- instData.$dropMarker.hide();
- }
- if ($source) {
- $source
- .toggleClass(classDropAccept, accept === true)
- .toggleClass(classDropReject, accept === false);
- }
- $target
- .toggleClass(
- classDropTarget,
- hitMode === "after" ||
- hitMode === "before" ||
- hitMode === "over"
- )
- .toggleClass(classDropAfter, hitMode === "after")
- .toggleClass(classDropBefore, hitMode === "before")
- .toggleClass(classDropAccept, accept === true)
- .toggleClass(classDropReject, accept === false);
-
- helper
- .toggleClass(classDropAccept, accept === true)
- .toggleClass(classDropReject, accept === false);
- },
-
- /*
- * Handles drag'n'drop functionality.
- *
- * A standard jQuery drag-and-drop process may generate these calls:
- *
- * start:
- * _onDragEvent("start", sourceNode, null, event, ui, draggable);
- * drag:
- * _onDragEvent("leave", prevTargetNode, sourceNode, event, ui, draggable);
- * _onDragEvent("over", targetNode, sourceNode, event, ui, draggable);
- * _onDragEvent("enter", targetNode, sourceNode, event, ui, draggable);
- * stop:
- * _onDragEvent("drop", targetNode, sourceNode, event, ui, draggable);
- * _onDragEvent("leave", targetNode, sourceNode, event, ui, draggable);
- * _onDragEvent("stop", sourceNode, null, event, ui, draggable);
- */
- _onDragEvent: function (
- eventName,
- node,
- otherNode,
- event,
- ui,
- draggable
- ) {
- // if(eventName !== "over"){
- // this.debug("tree.ext.dnd._onDragEvent(%s, %o, %o) - %o", eventName, node, otherNode, this);
- // }
- var accept,
- nodeOfs,
- parentRect,
- rect,
- relPos,
- relPos2,
- enterResponse,
- hitMode,
- r,
- opts = this.options,
- dnd = opts.dnd,
- ctx = this._makeHookContext(node, event, {
- otherNode: otherNode,
- ui: ui,
- draggable: draggable,
- }),
- res = null,
- self = this,
- $nodeTag = $(node.span);
-
- if (dnd.smartRevert) {
- draggable.options.revert = "invalid";
- }
-
- switch (eventName) {
- case "start":
- if (node.isStatusNode()) {
- res = false;
- } else if (dnd.dragStart) {
- res = dnd.dragStart(node, ctx);
- }
- if (res === false) {
- this.debug("tree.dragStart() cancelled");
- //draggable._clear();
- // NOTE: the return value seems to be ignored (drag is not cancelled, when false is returned)
- // TODO: call this._cancelDrag()?
- ui.helper.trigger("mouseup").hide();
- } else {
- if (dnd.smartRevert) {
- // #567, #593: fix revert position
- // rect = node.li.getBoundingClientRect();
- rect =
- node[
- ctx.tree.nodeContainerAttrName
- ].getBoundingClientRect();
- parentRect = $(
- draggable.options.appendTo
- )[0].getBoundingClientRect();
- draggable.originalPosition.left = Math.max(
- 0,
- rect.left - parentRect.left
- );
- draggable.originalPosition.top = Math.max(
- 0,
- rect.top - parentRect.top
- );
- }
- $nodeTag.addClass("fancytree-drag-source");
- // Register global handlers to allow cancel
- $(document).on(
- "keydown.fancytree-dnd,mousedown.fancytree-dnd",
- function (event) {
- // node.tree.debug("dnd global event", event.type, event.which);
- if (
- event.type === "keydown" &&
- event.which === $.ui.keyCode.ESCAPE
- ) {
- self.ext.dnd._cancelDrag();
- } else if (event.type === "mousedown") {
- self.ext.dnd._cancelDrag();
- }
- }
- );
- }
- break;
-
- case "enter":
- if (
- dnd.preventRecursiveMoves &&
- node.isDescendantOf(otherNode)
- ) {
- r = false;
- } else {
- r = dnd.dragEnter ? dnd.dragEnter(node, ctx) : null;
- }
- if (!r) {
- // convert null, undefined, false to false
- res = false;
- } else if (Array.isArray(r)) {
- // TODO: also accept passing an object of this format directly
- res = {
- over: $.inArray("over", r) >= 0,
- before: $.inArray("before", r) >= 0,
- after: $.inArray("after", r) >= 0,
- };
- } else {
- res = {
- over: r === true || r === "over",
- before: r === true || r === "before",
- after: r === true || r === "after",
- };
- }
- ui.helper.data("enterResponse", res);
- // this.debug("helper.enterResponse: %o", res);
- break;
-
- case "over":
- enterResponse = ui.helper.data("enterResponse");
- hitMode = null;
- if (enterResponse === false) {
- // Don't call dragOver if onEnter returned false.
- // break;
- } else if (typeof enterResponse === "string") {
- // Use hitMode from onEnter if provided.
- hitMode = enterResponse;
- } else {
- // Calculate hitMode from relative cursor position.
- nodeOfs = $nodeTag.offset();
- relPos = {
- x: event.pageX - nodeOfs.left,
- y: event.pageY - nodeOfs.top,
- };
- relPos2 = {
- x: relPos.x / $nodeTag.width(),
- y: relPos.y / $nodeTag.height(),
- };
-
- if (enterResponse.after && relPos2.y > 0.75) {
- hitMode = "after";
- } else if (
- !enterResponse.over &&
- enterResponse.after &&
- relPos2.y > 0.5
- ) {
- hitMode = "after";
- } else if (enterResponse.before && relPos2.y <= 0.25) {
- hitMode = "before";
- } else if (
- !enterResponse.over &&
- enterResponse.before &&
- relPos2.y <= 0.5
- ) {
- hitMode = "before";
- } else if (enterResponse.over) {
- hitMode = "over";
- }
- // Prevent no-ops like 'before source node'
- // TODO: these are no-ops when moving nodes, but not in copy mode
- if (dnd.preventVoidMoves) {
- if (node === otherNode) {
- this.debug(
- " drop over source node prevented"
- );
- hitMode = null;
- } else if (
- hitMode === "before" &&
- otherNode &&
- node === otherNode.getNextSibling()
- ) {
- this.debug(
- " drop after source node prevented"
- );
- hitMode = null;
- } else if (
- hitMode === "after" &&
- otherNode &&
- node === otherNode.getPrevSibling()
- ) {
- this.debug(
- " drop before source node prevented"
- );
- hitMode = null;
- } else if (
- hitMode === "over" &&
- otherNode &&
- otherNode.parent === node &&
- otherNode.isLastSibling()
- ) {
- this.debug(
- " drop last child over own parent prevented"
- );
- hitMode = null;
- }
- }
- // this.debug("hitMode: %s - %s - %s", hitMode, (node.parent === otherNode), node.isLastSibling());
- ui.helper.data("hitMode", hitMode);
- }
- // Auto-expand node (only when 'over' the node, not 'before', or 'after')
- if (
- hitMode !== "before" &&
- hitMode !== "after" &&
- dnd.autoExpandMS &&
- node.hasChildren() !== false &&
- !node.expanded &&
- (!dnd.dragExpand || dnd.dragExpand(node, ctx) !== false)
- ) {
- node.scheduleAction("expand", dnd.autoExpandMS);
- }
- if (hitMode && dnd.dragOver) {
- // TODO: http://code.google.com/p/dynatree/source/detail?r=625
- ctx.hitMode = hitMode;
- res = dnd.dragOver(node, ctx);
- }
- accept = res !== false && hitMode !== null;
- if (dnd.smartRevert) {
- draggable.options.revert = !accept;
- }
- this._local._setDndStatus(
- otherNode,
- node,
- ui.helper,
- hitMode,
- accept
- );
- break;
-
- case "drop":
- hitMode = ui.helper.data("hitMode");
- if (hitMode && dnd.dragDrop) {
- ctx.hitMode = hitMode;
- dnd.dragDrop(node, ctx);
- }
- break;
-
- case "leave":
- // Cancel pending expand request
- node.scheduleAction("cancel");
- ui.helper.data("enterResponse", null);
- ui.helper.data("hitMode", null);
- this._local._setDndStatus(
- otherNode,
- node,
- ui.helper,
- "out",
- undefined
- );
- if (dnd.dragLeave) {
- dnd.dragLeave(node, ctx);
- }
- break;
-
- case "stop":
- $nodeTag.removeClass("fancytree-drag-source");
- $(document).off(".fancytree-dnd");
- if (dnd.dragStop) {
- dnd.dragStop(node, ctx);
- }
- break;
-
- default:
- $.error("Unsupported drag event: " + eventName);
- }
- return res;
- },
-
- _cancelDrag: function () {
- var dd = $.ui.ddmanager.current;
- if (dd) {
- dd.cancel();
- }
- },
- });
- // Value returned by `require('jquery.fancytree..')`
- return $.ui.fancytree;
- }); // End of closure
|