text: /*!
   * jquery.fancytree.dnd.js
   *
   * Drag-and-drop support.
   * (Extension module for jquery.fancytree.js: https://github.com/mar10/fancytree/)
   *
   * Copyright (c) 2008-2016, Martin Wendt (http://wwWendt.de)
   *
   * Released under the MIT license
   * https://github.com/mar10/fancytree/wiki/LicenseInfo
   *
   * @version 2.17.0
   * @date 2016-04-11T20:10
   */
  
  ;(function($, window, document, undefined) {
  
  "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){
  	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.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: "0.3.0",
  	// 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
  		// 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 = 0,
  			markerAt = "center",
  			instData = this._local,
  			glyph = this.options.glyph || null,
  			$source = sourceNode ? $(sourceNode.span) : null,
  			$target = $(targetNode.span);
  
  		if( !instData.$dropMarker ) {
  			instData.$dropMarker = $("<div id='fancytree-drop-marker'></div>")
  				.hide()
  				.css({"z-index": 1000})
  				.prependTo($(this.$div).parent());
  //                .prependTo("body");
  
  			if( glyph ) {
  				// instData.$dropMarker.addClass(glyph.map.dragHelper);
  				instData.$dropMarker
  					.addClass(glyph.map.dropMarker);
  			}
  		}
  		if( hitMode === "after" || hitMode === "before" || hitMode === "over" ){
  			switch(hitMode){
  			case "before":
  				markerAt = "top";
  				break;
  			case "after":
  				markerAt = "bottom";
  				break;
  			default:
  				markerOffsetX = 8;
  			}
  
  			instData.$dropMarker
  				.toggleClass(classDropAfter, hitMode === "after")
  				.toggleClass(classDropOver, hitMode === "over")
  				.toggleClass(classDropBefore, hitMode === "before")
  				.show()
  				.position($.ui.fancytree.fixPositionOptions({
  					my: "left" + offsetString(markerOffsetX) + " center",
  					at: "left " + markerAt,
  					of: $target
  					}));
  		} 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,
  			that = 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 canceled, when false is returned)
  				// TODO: call this._cancelDrag()?
  				ui.helper.trigger("mouseup")
  					.hide();
  			} else {
  				if( dnd.smartRevert ) {
  					// #567: fix revert position
  					rect = node.li.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 ) {
  							that.ext.dnd._cancelDrag();
  						} else if( event.type === "mousedown" ) {
  							that.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 ( $.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)
  				) {
  				// TODO: maybe add a callback `dragExpand()` here to allow more control
  				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();
  		 }
  	}
  });
  }(jQuery, window, document));
  