1/*!
2 * jquery.fancytree.js
3 * Tree view control with support for lazy loading and much more.
4 * https://github.com/mar10/fancytree/
5 *
6 * Copyright (c) 2008-2023, Martin Wendt (https://wwWendt.de)
7 * Released under the MIT license
8 * https://github.com/mar10/fancytree/wiki/LicenseInfo
9 *
10 * @version 2.38.3
11 * @date 2023-02-01T20:52:50Z
12 */
13
14/** Core Fancytree module.
15 */
16
17// UMD wrapper for the Fancytree core module
18(function (factory) {
19	if (typeof define === "function" && define.amd) {
20		// AMD. Register as an anonymous module.
21		define(["jquery", "./jquery.fancytree.ui-deps"], factory);
22	} else if (typeof module === "object" && module.exports) {
23		// Node/CommonJS
24		require("./jquery.fancytree.ui-deps");
25		module.exports = factory(require("jquery"));
26	} else {
27		// Browser globals
28		factory(jQuery);
29	}
30})(function ($) {
31	"use strict";
32
33	// prevent duplicate loading
34	if ($.ui && $.ui.fancytree) {
35		$.ui.fancytree.warn("Fancytree: ignored duplicate include");
36		return;
37	}
38
39	/******************************************************************************
40	 * Private functions and variables
41	 */
42
43	var i,
44		attr,
45		FT = null, // initialized below
46		TEST_IMG = new RegExp(/\.|\//), // strings are considered image urls if they contain '.' or '/'
47		REX_HTML = /[&<>"'/]/g, // Escape those characters
48		REX_TOOLTIP = /[<>"'/]/g, // Don't escape `&` in tooltips
49		RECURSIVE_REQUEST_ERROR = "$recursive_request",
50		INVALID_REQUEST_TARGET_ERROR = "$request_target_invalid",
51		ENTITY_MAP = {
52			"&": "&amp;",
53			"<": "&lt;",
54			">": "&gt;",
55			'"': "&quot;",
56			"'": "&#39;",
57			"/": "&#x2F;",
58		},
59		IGNORE_KEYCODES = { 16: true, 17: true, 18: true },
60		SPECIAL_KEYCODES = {
61			8: "backspace",
62			9: "tab",
63			10: "return",
64			13: "return",
65			// 16: null, 17: null, 18: null,  // ignore shift, ctrl, alt
66			19: "pause",
67			20: "capslock",
68			27: "esc",
69			32: "space",
70			33: "pageup",
71			34: "pagedown",
72			35: "end",
73			36: "home",
74			37: "left",
75			38: "up",
76			39: "right",
77			40: "down",
78			45: "insert",
79			46: "del",
80			59: ";",
81			61: "=",
82			// 91: null, 93: null,  // ignore left and right meta
83			96: "0",
84			97: "1",
85			98: "2",
86			99: "3",
87			100: "4",
88			101: "5",
89			102: "6",
90			103: "7",
91			104: "8",
92			105: "9",
93			106: "*",
94			107: "+",
95			109: "-",
96			110: ".",
97			111: "/",
98			112: "f1",
99			113: "f2",
100			114: "f3",
101			115: "f4",
102			116: "f5",
103			117: "f6",
104			118: "f7",
105			119: "f8",
106			120: "f9",
107			121: "f10",
108			122: "f11",
109			123: "f12",
110			144: "numlock",
111			145: "scroll",
112			173: "-",
113			186: ";",
114			187: "=",
115			188: ",",
116			189: "-",
117			190: ".",
118			191: "/",
119			192: "`",
120			219: "[",
121			220: "\\",
122			221: "]",
123			222: "'",
124		},
125		MODIFIERS = {
126			16: "shift",
127			17: "ctrl",
128			18: "alt",
129			91: "meta",
130			93: "meta",
131		},
132		MOUSE_BUTTONS = { 0: "", 1: "left", 2: "middle", 3: "right" },
133		// Boolean attributes that can be set with equivalent class names in the LI tags
134		// Note: v2.23: checkbox and hideCheckbox are *not* in this list
135		CLASS_ATTRS =
136			"active expanded focus folder lazy radiogroup selected unselectable unselectableIgnore".split(
137				" "
138			),
139		CLASS_ATTR_MAP = {},
140		// Top-level Fancytree attributes, that can be set by dict
141		TREE_ATTRS = "columns types".split(" "),
142		// TREE_ATTR_MAP = {},
143		// Top-level FancytreeNode attributes, that can be set by dict
144		NODE_ATTRS =
145			"checkbox expanded extraClasses folder icon iconTooltip key lazy partsel radiogroup refKey selected statusNodeType title tooltip type unselectable unselectableIgnore unselectableStatus".split(
146				" "
147			),
148		NODE_ATTR_MAP = {},
149		// Mapping of lowercase -> real name (because HTML5 data-... attribute only supports lowercase)
150		NODE_ATTR_LOWERCASE_MAP = {},
151		// Attribute names that should NOT be added to node.data
152		NONE_NODE_DATA_MAP = {
153			active: true,
154			children: true,
155			data: true,
156			focus: true,
157		};
158
159	for (i = 0; i < CLASS_ATTRS.length; i++) {
160		CLASS_ATTR_MAP[CLASS_ATTRS[i]] = true;
161	}
162	for (i = 0; i < NODE_ATTRS.length; i++) {
163		attr = NODE_ATTRS[i];
164		NODE_ATTR_MAP[attr] = true;
165		if (attr !== attr.toLowerCase()) {
166			NODE_ATTR_LOWERCASE_MAP[attr.toLowerCase()] = attr;
167		}
168	}
169	// for(i=0; i<TREE_ATTRS.length; i++) {
170	// 	TREE_ATTR_MAP[TREE_ATTRS[i]] = true;
171	// }
172
173	function _assert(cond, msg) {
174		// TODO: see qunit.js extractStacktrace()
175		if (!cond) {
176			msg = msg ? ": " + msg : "";
177			msg = "Fancytree assertion failed" + msg;
178
179			// consoleApply("assert", [!!cond, msg]);
180
181			// #1041: Raised exceptions may not be visible in the browser
182			// console if inside promise chains, so we also print directly:
183			$.ui.fancytree.error(msg);
184
185			// Throw exception:
186			$.error(msg);
187		}
188	}
189
190	function _hasProp(object, property) {
191		return Object.prototype.hasOwnProperty.call(object, property);
192	}
193
194	/* Replacement for the deprecated `jQuery.isFunction()`. */
195	function _isFunction(obj) {
196		return typeof obj === "function";
197	}
198
199	/* Replacement for the deprecated `jQuery.trim()`. */
200	function _trim(text) {
201		return text == null ? "" : text.trim();
202	}
203
204	/* Replacement for the deprecated `jQuery.isArray()`. */
205	var _isArray = Array.isArray;
206
207	_assert($.ui, "Fancytree requires jQuery UI (http://jqueryui.com)");
208
209	function consoleApply(method, args) {
210		var i,
211			s,
212			fn = window.console ? window.console[method] : null;
213
214		if (fn) {
215			try {
216				fn.apply(window.console, args);
217			} catch (e) {
218				// IE 8?
219				s = "";
220				for (i = 0; i < args.length; i++) {
221					s += args[i];
222				}
223				fn(s);
224			}
225		}
226	}
227
228	/* support: IE8 Polyfil for Date.now() */
229	if (!Date.now) {
230		Date.now = function now() {
231			return new Date().getTime();
232		};
233	}
234
235	/*Return true if x is a FancytreeNode.*/
236	function _isNode(x) {
237		return !!(x.tree && x.statusNodeType !== undefined);
238	}
239
240	/** Return true if dotted version string is equal or higher than requested version.
241	 *
242	 * See http://jsfiddle.net/mar10/FjSAN/
243	 */
244	function isVersionAtLeast(dottedVersion, major, minor, patch) {
245		var i,
246			v,
247			t,
248			verParts = $.map(_trim(dottedVersion).split("."), function (e) {
249				return parseInt(e, 10);
250			}),
251			testParts = $.map(
252				Array.prototype.slice.call(arguments, 1),
253				function (e) {
254					return parseInt(e, 10);
255				}
256			);
257
258		for (i = 0; i < testParts.length; i++) {
259			v = verParts[i] || 0;
260			t = testParts[i] || 0;
261			if (v !== t) {
262				return v > t;
263			}
264		}
265		return true;
266	}
267
268	/**
269	 * Deep-merge a list of objects (but replace array-type options).
270	 *
271	 * jQuery's $.extend(true, ...) method does a deep merge, that also merges Arrays.
272	 * This variant is used to merge extension defaults with user options, and should
273	 * merge objects, but override arrays (for example the `triggerStart: [...]` option
274	 * of ext-edit). Also `null` values are copied over and not skipped.
275	 *
276	 * See issue #876
277	 *
278	 * Example:
279	 * _simpleDeepMerge({}, o1, o2);
280	 */
281	function _simpleDeepMerge() {
282		var options,
283			name,
284			src,
285			copy,
286			clone,
287			target = arguments[0] || {},
288			i = 1,
289			length = arguments.length;
290
291		// Handle case when target is a string or something (possible in deep copy)
292		if (typeof target !== "object" && !_isFunction(target)) {
293			target = {};
294		}
295		if (i === length) {
296			throw Error("need at least two args");
297		}
298		for (; i < length; i++) {
299			// Only deal with non-null/undefined values
300			if ((options = arguments[i]) != null) {
301				// Extend the base object
302				for (name in options) {
303					if (_hasProp(options, name)) {
304						src = target[name];
305						copy = options[name];
306						// Prevent never-ending loop
307						if (target === copy) {
308							continue;
309						}
310						// Recurse if we're merging plain objects
311						// (NOTE: unlike $.extend, we don't merge arrays, but replace them)
312						if (copy && $.isPlainObject(copy)) {
313							clone = src && $.isPlainObject(src) ? src : {};
314							// Never move original objects, clone them
315							target[name] = _simpleDeepMerge(clone, copy);
316							// Don't bring in undefined values
317						} else if (copy !== undefined) {
318							target[name] = copy;
319						}
320					}
321				}
322			}
323		}
324		// Return the modified object
325		return target;
326	}
327
328	/** Return a wrapper that calls sub.methodName() and exposes
329	 *  this             : tree
330	 *  this._local      : tree.ext.EXTNAME
331	 *  this._super      : base.methodName.call()
332	 *  this._superApply : base.methodName.apply()
333	 */
334	function _makeVirtualFunction(methodName, tree, base, extension, extName) {
335		// $.ui.fancytree.debug("_makeVirtualFunction", methodName, tree, base, extension, extName);
336		// if(rexTestSuper && !rexTestSuper.test(func)){
337		//     // extension.methodName() doesn't call _super(), so no wrapper required
338		//     return func;
339		// }
340		// Use an immediate function as closure
341		var proxy = (function () {
342			var prevFunc = tree[methodName], // org. tree method or prev. proxy
343				baseFunc = extension[methodName], //
344				_local = tree.ext[extName],
345				_super = function () {
346					return prevFunc.apply(tree, arguments);
347				},
348				_superApply = function (args) {
349					return prevFunc.apply(tree, args);
350				};
351
352			// Return the wrapper function
353			return function () {
354				var prevLocal = tree._local,
355					prevSuper = tree._super,
356					prevSuperApply = tree._superApply;
357
358				try {
359					tree._local = _local;
360					tree._super = _super;
361					tree._superApply = _superApply;
362					return baseFunc.apply(tree, arguments);
363				} finally {
364					tree._local = prevLocal;
365					tree._super = prevSuper;
366					tree._superApply = prevSuperApply;
367				}
368			};
369		})(); // end of Immediate Function
370		return proxy;
371	}
372
373	/**
374	 * Subclass `base` by creating proxy functions
375	 */
376	function _subclassObject(tree, base, extension, extName) {
377		// $.ui.fancytree.debug("_subclassObject", tree, base, extension, extName);
378		for (var attrName in extension) {
379			if (typeof extension[attrName] === "function") {
380				if (typeof tree[attrName] === "function") {
381					// override existing method
382					tree[attrName] = _makeVirtualFunction(
383						attrName,
384						tree,
385						base,
386						extension,
387						extName
388					);
389				} else if (attrName.charAt(0) === "_") {
390					// Create private methods in tree.ext.EXTENSION namespace
391					tree.ext[extName][attrName] = _makeVirtualFunction(
392						attrName,
393						tree,
394						base,
395						extension,
396						extName
397					);
398				} else {
399					$.error(
400						"Could not override tree." +
401							attrName +
402							". Use prefix '_' to create tree." +
403							extName +
404							"._" +
405							attrName
406					);
407				}
408			} else {
409				// Create member variables in tree.ext.EXTENSION namespace
410				if (attrName !== "options") {
411					tree.ext[extName][attrName] = extension[attrName];
412				}
413			}
414		}
415	}
416
417	function _getResolvedPromise(context, argArray) {
418		if (context === undefined) {
419			return $.Deferred(function () {
420				this.resolve();
421			}).promise();
422		}
423		return $.Deferred(function () {
424			this.resolveWith(context, argArray);
425		}).promise();
426	}
427
428	function _getRejectedPromise(context, argArray) {
429		if (context === undefined) {
430			return $.Deferred(function () {
431				this.reject();
432			}).promise();
433		}
434		return $.Deferred(function () {
435			this.rejectWith(context, argArray);
436		}).promise();
437	}
438
439	function _makeResolveFunc(deferred, context) {
440		return function () {
441			deferred.resolveWith(context);
442		};
443	}
444
445	function _getElementDataAsDict($el) {
446		// Evaluate 'data-NAME' attributes with special treatment for 'data-json'.
447		var d = $.extend({}, $el.data()),
448			json = d.json;
449
450		delete d.fancytree; // added to container by widget factory (old jQuery UI)
451		delete d.uiFancytree; // added to container by widget factory
452
453		if (json) {
454			delete d.json;
455			// <li data-json='...'> is already returned as object (http://api.jquery.com/data/#data-html5)
456			d = $.extend(d, json);
457		}
458		return d;
459	}
460
461	function _escapeTooltip(s) {
462		return ("" + s).replace(REX_TOOLTIP, function (s) {
463			return ENTITY_MAP[s];
464		});
465	}
466
467	// TODO: use currying
468	function _makeNodeTitleMatcher(s) {
469		s = s.toLowerCase();
470		return function (node) {
471			return node.title.toLowerCase().indexOf(s) >= 0;
472		};
473	}
474
475	function _makeNodeTitleStartMatcher(s) {
476		var reMatch = new RegExp("^" + s, "i");
477		return function (node) {
478			return reMatch.test(node.title);
479		};
480	}
481
482	/******************************************************************************
483	 * FancytreeNode
484	 */
485
486	/**
487	 * Creates a new FancytreeNode
488	 *
489	 * @class FancytreeNode
490	 * @classdesc A FancytreeNode represents the hierarchical data model and operations.
491	 *
492	 * @param {FancytreeNode} parent
493	 * @param {NodeData} obj
494	 *
495	 * @property {Fancytree} tree The tree instance
496	 * @property {FancytreeNode} parent The parent node
497	 * @property {string} key Node id (must be unique inside the tree)
498	 * @property {string} title Display name (may contain HTML)
499	 * @property {object} data Contains all extra data that was passed on node creation
500	 * @property {FancytreeNode[] | null | undefined} children Array of child nodes.<br>
501	 *     For lazy nodes, null or undefined means 'not yet loaded'. Use an empty array
502	 *     to define a node that has no children.
503	 * @property {boolean} expanded Use isExpanded(), setExpanded() to access this property.
504	 * @property {string} extraClasses Additional CSS classes, added to the node's `<span>`.<br>
505	 *     Note: use `node.add/remove/toggleClass()` to modify.
506	 * @property {boolean} folder Folder nodes have different default icons and click behavior.<br>
507	 *     Note: Also non-folders may have children.
508	 * @property {string} statusNodeType null for standard nodes. Otherwise type of special system node: 'error', 'loading', 'nodata', or 'paging'.
509	 * @property {boolean} lazy True if this node is loaded on demand, i.e. on first expansion.
510	 * @property {boolean} selected Use isSelected(), setSelected() to access this property.
511	 * @property {string} tooltip Alternative description used as hover popup
512	 * @property {string} iconTooltip Description used as hover popup for icon. @since 2.27
513	 * @property {string} type Node type, used with tree.types map. @since 2.27
514	 */
515	function FancytreeNode(parent, obj) {
516		var i, l, name, cl;
517
518		this.parent = parent;
519		this.tree = parent.tree;
520		this.ul = null;
521		this.li = null; // <li id='key' ftnode=this> tag
522		this.statusNodeType = null; // if this is a temp. node to display the status of its parent
523		this._isLoading = false; // if this node itself is loading
524		this._error = null; // {message: '...'} if a load error occurred
525		this.data = {};
526
527		// TODO: merge this code with node.toDict()
528		// copy attributes from obj object
529		for (i = 0, l = NODE_ATTRS.length; i < l; i++) {
530			name = NODE_ATTRS[i];
531			this[name] = obj[name];
532		}
533		// unselectableIgnore and unselectableStatus imply unselectable
534		if (
535			this.unselectableIgnore != null ||
536			this.unselectableStatus != null
537		) {
538			this.unselectable = true;
539		}
540		if (obj.hideCheckbox) {
541			$.error(
542				"'hideCheckbox' node option was removed in v2.23.0: use 'checkbox: false'"
543			);
544		}
545		// node.data += obj.data
546		if (obj.data) {
547			$.extend(this.data, obj.data);
548		}
549		// Copy all other attributes to this.data.NAME
550		for (name in obj) {
551			if (
552				!NODE_ATTR_MAP[name] &&
553				(this.tree.options.copyFunctionsToData ||
554					!_isFunction(obj[name])) &&
555				!NONE_NODE_DATA_MAP[name]
556			) {
557				// node.data.NAME = obj.NAME
558				this.data[name] = obj[name];
559			}
560		}
561
562		// Fix missing key
563		if (this.key == null) {
564			// test for null OR undefined
565			if (this.tree.options.defaultKey) {
566				this.key = "" + this.tree.options.defaultKey(this);
567				_assert(this.key, "defaultKey() must return a unique key");
568			} else {
569				this.key = "_" + FT._nextNodeKey++;
570			}
571		} else {
572			this.key = "" + this.key; // Convert to string (#217)
573		}
574
575		// Fix tree.activeNode
576		// TODO: not elegant: we use obj.active as marker to set tree.activeNode
577		// when loading from a dictionary.
578		if (obj.active) {
579			_assert(
580				this.tree.activeNode === null,
581				"only one active node allowed"
582			);
583			this.tree.activeNode = this;
584		}
585		if (obj.selected) {
586			// #186
587			this.tree.lastSelectedNode = this;
588		}
589		// TODO: handle obj.focus = true
590
591		// Create child nodes
592		cl = obj.children;
593		if (cl) {
594			if (cl.length) {
595				this._setChildren(cl);
596			} else {
597				// if an empty array was passed for a lazy node, keep it, in order to mark it 'loaded'
598				this.children = this.lazy ? [] : null;
599			}
600		} else {
601			this.children = null;
602		}
603		// Add to key/ref map (except for root node)
604		//	if( parent ) {
605		this.tree._callHook("treeRegisterNode", this.tree, true, this);
606		//	}
607	}
608
609	FancytreeNode.prototype = /** @lends FancytreeNode# */ {
610		/* Return the direct child FancytreeNode with a given key, index. */
611		_findDirectChild: function (ptr) {
612			var i,
613				l,
614				cl = this.children;
615
616			if (cl) {
617				if (typeof ptr === "string") {
618					for (i = 0, l = cl.length; i < l; i++) {
619						if (cl[i].key === ptr) {
620							return cl[i];
621						}
622					}
623				} else if (typeof ptr === "number") {
624					return this.children[ptr];
625				} else if (ptr.parent === this) {
626					return ptr;
627				}
628			}
629			return null;
630		},
631		// TODO: activate()
632		// TODO: activateSilently()
633		/* Internal helper called in recursive addChildren sequence.*/
634		_setChildren: function (children) {
635			_assert(
636				children && (!this.children || this.children.length === 0),
637				"only init supported"
638			);
639			this.children = [];
640			for (var i = 0, l = children.length; i < l; i++) {
641				this.children.push(new FancytreeNode(this, children[i]));
642			}
643			this.tree._callHook(
644				"treeStructureChanged",
645				this.tree,
646				"setChildren"
647			);
648		},
649		/**
650		 * Append (or insert) a list of child nodes.
651		 *
652		 * @param {NodeData[]} children array of child node definitions (also single child accepted)
653		 * @param {FancytreeNode | string | Integer} [insertBefore] child node (or key or index of such).
654		 *     If omitted, the new children are appended.
655		 * @returns {FancytreeNode} first child added
656		 *
657		 * @see FancytreeNode#applyPatch
658		 */
659		addChildren: function (children, insertBefore) {
660			var i,
661				l,
662				pos,
663				origFirstChild = this.getFirstChild(),
664				origLastChild = this.getLastChild(),
665				firstNode = null,
666				nodeList = [];
667
668			if ($.isPlainObject(children)) {
669				children = [children];
670			}
671			if (!this.children) {
672				this.children = [];
673			}
674			for (i = 0, l = children.length; i < l; i++) {
675				nodeList.push(new FancytreeNode(this, children[i]));
676			}
677			firstNode = nodeList[0];
678			if (insertBefore == null) {
679				this.children = this.children.concat(nodeList);
680			} else {
681				// Returns null if insertBefore is not a direct child:
682				insertBefore = this._findDirectChild(insertBefore);
683				pos = $.inArray(insertBefore, this.children);
684				_assert(pos >= 0, "insertBefore must be an existing child");
685				// insert nodeList after children[pos]
686				this.children.splice.apply(
687					this.children,
688					[pos, 0].concat(nodeList)
689				);
690			}
691			if (origFirstChild && !insertBefore) {
692				// #708: Fast path -- don't render every child of root, just the new ones!
693				// #723, #729: but only if it's appended to an existing child list
694				for (i = 0, l = nodeList.length; i < l; i++) {
695					nodeList[i].render(); // New nodes were never rendered before
696				}
697				// Adjust classes where status may have changed
698				// Has a first child
699				if (origFirstChild !== this.getFirstChild()) {
700					// Different first child -- recompute classes
701					origFirstChild.renderStatus();
702				}
703				if (origLastChild !== this.getLastChild()) {
704					// Different last child -- recompute classes
705					origLastChild.renderStatus();
706				}
707			} else if (!this.parent || this.parent.ul || this.tr) {
708				// render if the parent was rendered (or this is a root node)
709				this.render();
710			}
711			if (this.tree.options.selectMode === 3) {
712				this.fixSelection3FromEndNodes();
713			}
714			this.triggerModifyChild(
715				"add",
716				nodeList.length === 1 ? nodeList[0] : null
717			);
718			return firstNode;
719		},
720		/**
721		 * Add class to node's span tag and to .extraClasses.
722		 *
723		 * @param {string} className class name
724		 *
725		 * @since 2.17
726		 */
727		addClass: function (className) {
728			return this.toggleClass(className, true);
729		},
730		/**
731		 * Append or prepend a node, or append a child node.
732		 *
733		 * This a convenience function that calls addChildren()
734		 *
735		 * @param {NodeData} node node definition
736		 * @param {string} [mode=child] 'before', 'after', 'firstChild', or 'child' ('over' is a synonym for 'child')
737		 * @returns {FancytreeNode} new node
738		 */
739		addNode: function (node, mode) {
740			if (mode === undefined || mode === "over") {
741				mode = "child";
742			}
743			switch (mode) {
744				case "after":
745					return this.getParent().addChildren(
746						node,
747						this.getNextSibling()
748					);
749				case "before":
750					return this.getParent().addChildren(node, this);
751				case "firstChild":
752					// Insert before the first child if any
753					var insertBefore = this.children ? this.children[0] : null;
754					return this.addChildren(node, insertBefore);
755				case "child":
756				case "over":
757					return this.addChildren(node);
758			}
759			_assert(false, "Invalid mode: " + mode);
760		},
761		/**Add child status nodes that indicate 'More...', etc.
762		 *
763		 * This also maintains the node's `partload` property.
764		 * @param {boolean|object} node optional node definition. Pass `false` to remove all paging nodes.
765		 * @param {string} [mode='child'] 'child'|firstChild'
766		 * @since 2.15
767		 */
768		addPagingNode: function (node, mode) {
769			var i, n;
770
771			mode = mode || "child";
772			if (node === false) {
773				for (i = this.children.length - 1; i >= 0; i--) {
774					n = this.children[i];
775					if (n.statusNodeType === "paging") {
776						this.removeChild(n);
777					}
778				}
779				this.partload = false;
780				return;
781			}
782			node = $.extend(
783				{
784					title: this.tree.options.strings.moreData,
785					statusNodeType: "paging",
786					icon: false,
787				},
788				node
789			);
790			this.partload = true;
791			return this.addNode(node, mode);
792		},
793		/**
794		 * Append new node after this.
795		 *
796		 * This a convenience function that calls addNode(node, 'after')
797		 *
798		 * @param {NodeData} node node definition
799		 * @returns {FancytreeNode} new node
800		 */
801		appendSibling: function (node) {
802			return this.addNode(node, "after");
803		},
804		/**
805		 * (experimental) Apply a modification (or navigation) operation.
806		 *
807		 * @param {string} cmd
808		 * @param {object} [opts]
809		 * @see Fancytree#applyCommand
810		 * @since 2.32
811		 */
812		applyCommand: function (cmd, opts) {
813			return this.tree.applyCommand(cmd, this, opts);
814		},
815		/**
816		 * Modify existing child nodes.
817		 *
818		 * @param {NodePatch} patch
819		 * @returns {$.Promise}
820		 * @see FancytreeNode#addChildren
821		 */
822		applyPatch: function (patch) {
823			// patch [key, null] means 'remove'
824			if (patch === null) {
825				this.remove();
826				return _getResolvedPromise(this);
827			}
828			// TODO: make sure that root node is not collapsed or modified
829			// copy (most) attributes to node.ATTR or node.data.ATTR
830			var name,
831				promise,
832				v,
833				IGNORE_MAP = { children: true, expanded: true, parent: true }; // TODO: should be global
834
835			for (name in patch) {
836				if (_hasProp(patch, name)) {
837					v = patch[name];
838					if (!IGNORE_MAP[name] && !_isFunction(v)) {
839						if (NODE_ATTR_MAP[name]) {
840							this[name] = v;
841						} else {
842							this.data[name] = v;
843						}
844					}
845				}
846			}
847			// Remove and/or create children
848			if (_hasProp(patch, "children")) {
849				this.removeChildren();
850				if (patch.children) {
851					// only if not null and not empty list
852					// TODO: addChildren instead?
853					this._setChildren(patch.children);
854				}
855				// TODO: how can we APPEND or INSERT child nodes?
856			}
857			if (this.isVisible()) {
858				this.renderTitle();
859				this.renderStatus();
860			}
861			// Expand collapse (final step, since this may be async)
862			if (_hasProp(patch, "expanded")) {
863				promise = this.setExpanded(patch.expanded);
864			} else {
865				promise = _getResolvedPromise(this);
866			}
867			return promise;
868		},
869		/** Collapse all sibling nodes.
870		 * @returns {$.Promise}
871		 */
872		collapseSiblings: function () {
873			return this.tree._callHook("nodeCollapseSiblings", this);
874		},
875		/** Copy this node as sibling or child of `node`.
876		 *
877		 * @param {FancytreeNode} node source node
878		 * @param {string} [mode=child] 'before' | 'after' | 'child'
879		 * @param {Function} [map] callback function(NodeData, FancytreeNode) that could modify the new node
880		 * @returns {FancytreeNode} new
881		 */
882		copyTo: function (node, mode, map) {
883			return node.addNode(this.toDict(true, map), mode);
884		},
885		/** Count direct and indirect children.
886		 *
887		 * @param {boolean} [deep=true] pass 'false' to only count direct children
888		 * @returns {int} number of child nodes
889		 */
890		countChildren: function (deep) {
891			var cl = this.children,
892				i,
893				l,
894				n;
895			if (!cl) {
896				return 0;
897			}
898			n = cl.length;
899			if (deep !== false) {
900				for (i = 0, l = n; i < l; i++) {
901					n += cl[i].countChildren();
902				}
903			}
904			return n;
905		},
906		// TODO: deactivate()
907		/** Write to browser console if debugLevel >= 4 (prepending node info)
908		 *
909		 * @param {*} msg string or object or array of such
910		 */
911		debug: function (msg) {
912			if (this.tree.options.debugLevel >= 4) {
913				Array.prototype.unshift.call(arguments, this.toString());
914				consoleApply("log", arguments);
915			}
916		},
917		/** Deprecated.
918		 * @deprecated since 2014-02-16. Use resetLazy() instead.
919		 */
920		discard: function () {
921			this.warn(
922				"FancytreeNode.discard() is deprecated since 2014-02-16. Use .resetLazy() instead."
923			);
924			return this.resetLazy();
925		},
926		/** Remove DOM elements for all descendents. May be called on .collapse event
927		 * to keep the DOM small.
928		 * @param {boolean} [includeSelf=false]
929		 */
930		discardMarkup: function (includeSelf) {
931			var fn = includeSelf ? "nodeRemoveMarkup" : "nodeRemoveChildMarkup";
932			this.tree._callHook(fn, this);
933		},
934		/** Write error to browser console if debugLevel >= 1 (prepending tree info)
935		 *
936		 * @param {*} msg string or object or array of such
937		 */
938		error: function (msg) {
939			if (this.tree.options.debugLevel >= 1) {
940				Array.prototype.unshift.call(arguments, this.toString());
941				consoleApply("error", arguments);
942			}
943		},
944		/**Find all nodes that match condition (excluding self).
945		 *
946		 * @param {string | function(node)} match title string to search for, or a
947		 *     callback function that returns `true` if a node is matched.
948		 * @returns {FancytreeNode[]} array of nodes (may be empty)
949		 */
950		findAll: function (match) {
951			match = _isFunction(match) ? match : _makeNodeTitleMatcher(match);
952			var res = [];
953			this.visit(function (n) {
954				if (match(n)) {
955					res.push(n);
956				}
957			});
958			return res;
959		},
960		/**Find first node that matches condition (excluding self).
961		 *
962		 * @param {string | function(node)} match title string to search for, or a
963		 *     callback function that returns `true` if a node is matched.
964		 * @returns {FancytreeNode} matching node or null
965		 * @see FancytreeNode#findAll
966		 */
967		findFirst: function (match) {
968			match = _isFunction(match) ? match : _makeNodeTitleMatcher(match);
969			var res = null;
970			this.visit(function (n) {
971				if (match(n)) {
972					res = n;
973					return false;
974				}
975			});
976			return res;
977		},
978		/** Find a node relative to self.
979		 *
980		 * @param {number|string} where The keyCode that would normally trigger this move,
981		 *		or a keyword ('down', 'first', 'last', 'left', 'parent', 'right', 'up').
982		 * @returns {FancytreeNode}
983		 * @since v2.31
984		 */
985		findRelatedNode: function (where, includeHidden) {
986			return this.tree.findRelatedNode(this, where, includeHidden);
987		},
988		/* Apply selection state (internal use only) */
989		_changeSelectStatusAttrs: function (state) {
990			var changed = false,
991				opts = this.tree.options,
992				unselectable = FT.evalOption(
993					"unselectable",
994					this,
995					this,
996					opts,
997					false
998				),
999				unselectableStatus = FT.evalOption(
1000					"unselectableStatus",
1001					this,
1002					this,
1003					opts,
1004					undefined
1005				);
1006
1007			if (unselectable && unselectableStatus != null) {
1008				state = unselectableStatus;
1009			}
1010			switch (state) {
1011				case false:
1012					changed = this.selected || this.partsel;
1013					this.selected = false;
1014					this.partsel = false;
1015					break;
1016				case true:
1017					changed = !this.selected || !this.partsel;
1018					this.selected = true;
1019					this.partsel = true;
1020					break;
1021				case undefined:
1022					changed = this.selected || !this.partsel;
1023					this.selected = false;
1024					this.partsel = true;
1025					break;
1026				default:
1027					_assert(false, "invalid state: " + state);
1028			}
1029			// this.debug("fixSelection3AfterLoad() _changeSelectStatusAttrs()", state, changed);
1030			if (changed) {
1031				this.renderStatus();
1032			}
1033			return changed;
1034		},
1035		/**
1036		 * Fix selection status, after this node was (de)selected in multi-hier mode.
1037		 * This includes (de)selecting all children.
1038		 */
1039		fixSelection3AfterClick: function (callOpts) {
1040			var flag = this.isSelected();
1041
1042			// this.debug("fixSelection3AfterClick()");
1043
1044			this.visit(function (node) {
1045				node._changeSelectStatusAttrs(flag);
1046				if (node.radiogroup) {
1047					// #931: don't (de)select this branch
1048					return "skip";
1049				}
1050			});
1051			this.fixSelection3FromEndNodes(callOpts);
1052		},
1053		/**
1054		 * Fix selection status for multi-hier mode.
1055		 * Only end-nodes are considered to update the descendants branch and parents.
1056		 * Should be called after this node has loaded new children or after
1057		 * children have been modified using the API.
1058		 */
1059		fixSelection3FromEndNodes: function (callOpts) {
1060			var opts = this.tree.options;
1061
1062			// this.debug("fixSelection3FromEndNodes()");
1063			_assert(opts.selectMode === 3, "expected selectMode 3");
1064
1065			// Visit all end nodes and adjust their parent's `selected` and `partsel`
1066			// attributes. Return selection state true, false, or undefined.
1067			function _walk(node) {
1068				var i,
1069					l,
1070					child,
1071					s,
1072					state,
1073					allSelected,
1074					someSelected,
1075					unselIgnore,
1076					unselState,
1077					children = node.children;
1078
1079				if (children && children.length) {
1080					// check all children recursively
1081					allSelected = true;
1082					someSelected = false;
1083
1084					for (i = 0, l = children.length; i < l; i++) {
1085						child = children[i];
1086						// the selection state of a node is not relevant; we need the end-nodes
1087						s = _walk(child);
1088						// if( !child.unselectableIgnore ) {
1089						unselIgnore = FT.evalOption(
1090							"unselectableIgnore",
1091							child,
1092							child,
1093							opts,
1094							false
1095						);
1096						if (!unselIgnore) {
1097							if (s !== false) {
1098								someSelected = true;
1099							}
1100							if (s !== true) {
1101								allSelected = false;
1102							}
1103						}
1104					}
1105					// eslint-disable-next-line no-nested-ternary
1106					state = allSelected
1107						? true
1108						: someSelected
1109						? undefined
1110						: false;
1111				} else {
1112					// This is an end-node: simply report the status
1113					unselState = FT.evalOption(
1114						"unselectableStatus",
1115						node,
1116						node,
1117						opts,
1118						undefined
1119					);
1120					state = unselState == null ? !!node.selected : !!unselState;
1121				}
1122				// #939: Keep a `partsel` flag that was explicitly set on a lazy node
1123				if (
1124					node.partsel &&
1125					!node.selected &&
1126					node.lazy &&
1127					node.children == null
1128				) {
1129					state = undefined;
1130				}
1131				node._changeSelectStatusAttrs(state);
1132				return state;
1133			}
1134			_walk(this);
1135
1136			// Update parent's state
1137			this.visitParents(function (node) {
1138				var i,
1139					l,
1140					child,
1141					state,
1142					unselIgnore,
1143					unselState,
1144					children = node.children,
1145					allSelected = true,
1146					someSelected = false;
1147
1148				for (i = 0, l = children.length; i < l; i++) {
1149					child = children[i];
1150					unselIgnore = FT.evalOption(
1151						"unselectableIgnore",
1152						child,
1153						child,
1154						opts,
1155						false
1156					);
1157					if (!unselIgnore) {
1158						unselState = FT.evalOption(
1159							"unselectableStatus",
1160							child,
1161							child,
1162							opts,
1163							undefined
1164						);
1165						state =
1166							unselState == null
1167								? !!child.selected
1168								: !!unselState;
1169						// When fixing the parents, we trust the sibling status (i.e.
1170						// we don't recurse)
1171						if (state || child.partsel) {
1172							someSelected = true;
1173						}
1174						if (!state) {
1175							allSelected = false;
1176						}
1177					}
1178				}
1179				// eslint-disable-next-line no-nested-ternary
1180				state = allSelected ? true : someSelected ? undefined : false;
1181				node._changeSelectStatusAttrs(state);
1182			});
1183		},
1184		// TODO: focus()
1185		/**
1186		 * Update node data. If dict contains 'children', then also replace
1187		 * the hole sub tree.
1188		 * @param {NodeData} dict
1189		 *
1190		 * @see FancytreeNode#addChildren
1191		 * @see FancytreeNode#applyPatch
1192		 */
1193		fromDict: function (dict) {
1194			// copy all other attributes to this.data.xxx
1195			for (var name in dict) {
1196				if (NODE_ATTR_MAP[name]) {
1197					// node.NAME = dict.NAME
1198					this[name] = dict[name];
1199				} else if (name === "data") {
1200					// node.data += dict.data
1201					$.extend(this.data, dict.data);
1202				} else if (
1203					!_isFunction(dict[name]) &&
1204					!NONE_NODE_DATA_MAP[name]
1205				) {
1206					// node.data.NAME = dict.NAME
1207					this.data[name] = dict[name];
1208				}
1209			}
1210			if (dict.children) {
1211				// recursively set children and render
1212				this.removeChildren();
1213				this.addChildren(dict.children);
1214			}
1215			this.renderTitle();
1216			/*
1217			var children = dict.children;
1218			if(children === undefined){
1219				this.data = $.extend(this.data, dict);
1220				this.render();
1221				return;
1222			}
1223			dict = $.extend({}, dict);
1224			dict.children = undefined;
1225			this.data = $.extend(this.data, dict);
1226			this.removeChildren();
1227			this.addChild(children);
1228			*/
1229		},
1230		/** Return the list of child nodes (undefined for unexpanded lazy nodes).
1231		 * @returns {FancytreeNode[] | undefined}
1232		 */
1233		getChildren: function () {
1234			if (this.hasChildren() === undefined) {
1235				// TODO: only required for lazy nodes?
1236				return undefined; // Lazy node: unloaded, currently loading, or load error
1237			}
1238			return this.children;
1239		},
1240		/** Return the first child node or null.
1241		 * @returns {FancytreeNode | null}
1242		 */
1243		getFirstChild: function () {
1244			return this.children ? this.children[0] : null;
1245		},
1246		/** Return the 0-based child index.
1247		 * @returns {int}
1248		 */
1249		getIndex: function () {
1250			// return this.parent.children.indexOf(this);
1251			return $.inArray(this, this.parent.children); // indexOf doesn't work in IE7
1252		},
1253		/** Return the hierarchical child index (1-based, e.g. '3.2.4').
1254		 * @param {string} [separator="."]
1255		 * @param {int} [digits=1]
1256		 * @returns {string}
1257		 */
1258		getIndexHier: function (separator, digits) {
1259			separator = separator || ".";
1260			var s,
1261				res = [];
1262			$.each(this.getParentList(false, true), function (i, o) {
1263				s = "" + (o.getIndex() + 1);
1264				if (digits) {
1265					// prepend leading zeroes
1266					s = ("0000000" + s).substr(-digits);
1267				}
1268				res.push(s);
1269			});
1270			return res.join(separator);
1271		},
1272		/** Return the parent keys separated by options.keyPathSeparator, e.g. "/id_1/id_17/id_32".
1273		 *
1274		 * (Unlike `node.getPath()`, this method prepends a "/" and inverts the first argument.)
1275		 *
1276		 * @see FancytreeNode#getPath
1277		 * @param {boolean} [excludeSelf=false]
1278		 * @returns {string}
1279		 */
1280		getKeyPath: function (excludeSelf) {
1281			var sep = this.tree.options.keyPathSeparator;
1282
1283			return sep + this.getPath(!excludeSelf, "key", sep);
1284		},
1285		/** Return the last child of this node or null.
1286		 * @returns {FancytreeNode | null}
1287		 */
1288		getLastChild: function () {
1289			return this.children
1290				? this.children[this.children.length - 1]
1291				: null;
1292		},
1293		/** Return node depth. 0: System root node, 1: visible top-level node, 2: first sub-level, ... .
1294		 * @returns {int}
1295		 */
1296		getLevel: function () {
1297			var level = 0,
1298				dtn = this.parent;
1299			while (dtn) {
1300				level++;
1301				dtn = dtn.parent;
1302			}
1303			return level;
1304		},
1305		/** Return the successor node (under the same parent) or null.
1306		 * @returns {FancytreeNode | null}
1307		 */
1308		getNextSibling: function () {
1309			// TODO: use indexOf, if available: (not in IE6)
1310			if (this.parent) {
1311				var i,
1312					l,
1313					ac = this.parent.children;
1314
1315				for (i = 0, l = ac.length - 1; i < l; i++) {
1316					// up to length-2, so next(last) = null
1317					if (ac[i] === this) {
1318						return ac[i + 1];
1319					}
1320				}
1321			}
1322			return null;
1323		},
1324		/** Return the parent node (null for the system root node).
1325		 * @returns {FancytreeNode | null}
1326		 */
1327		getParent: function () {
1328			// TODO: return null for top-level nodes?
1329			return this.parent;
1330		},
1331		/** Return an array of all parent nodes (top-down).
1332		 * @param {boolean} [includeRoot=false] Include the invisible system root node.
1333		 * @param {boolean} [includeSelf=false] Include the node itself.
1334		 * @returns {FancytreeNode[]}
1335		 */
1336		getParentList: function (includeRoot, includeSelf) {
1337			var l = [],
1338				dtn = includeSelf ? this : this.parent;
1339			while (dtn) {
1340				if (includeRoot || dtn.parent) {
1341					l.unshift(dtn);
1342				}
1343				dtn = dtn.parent;
1344			}
1345			return l;
1346		},
1347		/** Return a string representing the hierachical node path, e.g. "a/b/c".
1348		 * @param {boolean} [includeSelf=true]
1349		 * @param {string | function} [part="title"] node property name or callback
1350		 * @param {string} [separator="/"]
1351		 * @returns {string}
1352		 * @since v2.31
1353		 */
1354		getPath: function (includeSelf, part, separator) {
1355			includeSelf = includeSelf !== false;
1356			part = part || "title";
1357			separator = separator || "/";
1358
1359			var val,
1360				path = [],
1361				isFunc = _isFunction(part);
1362
1363			this.visitParents(function (n) {
1364				if (n.parent) {
1365					val = isFunc ? part(n) : n[part];
1366					path.unshift(val);
1367				}
1368			}, includeSelf);
1369			return path.join(separator);
1370		},
1371		/** Return the predecessor node (under the same parent) or null.
1372		 * @returns {FancytreeNode | null}
1373		 */
1374		getPrevSibling: function () {
1375			if (this.parent) {
1376				var i,
1377					l,
1378					ac = this.parent.children;
1379
1380				for (i = 1, l = ac.length; i < l; i++) {
1381					// start with 1, so prev(first) = null
1382					if (ac[i] === this) {
1383						return ac[i - 1];
1384					}
1385				}
1386			}
1387			return null;
1388		},
1389		/**
1390		 * Return an array of selected descendant nodes.
1391		 * @param {boolean} [stopOnParents=false] only return the topmost selected
1392		 *     node (useful with selectMode 3)
1393		 * @returns {FancytreeNode[]}
1394		 */
1395		getSelectedNodes: function (stopOnParents) {
1396			var nodeList = [];
1397			this.visit(function (node) {
1398				if (node.selected) {
1399					nodeList.push(node);
1400					if (stopOnParents === true) {
1401						return "skip"; // stop processing this branch
1402					}
1403				}
1404			});
1405			return nodeList;
1406		},
1407		/** Return true if node has children. Return undefined if not sure, i.e. the node is lazy and not yet loaded).
1408		 * @returns {boolean | undefined}
1409		 */
1410		hasChildren: function () {
1411			if (this.lazy) {
1412				if (this.children == null) {
1413					// null or undefined: Not yet loaded
1414					return undefined;
1415				} else if (this.children.length === 0) {
1416					// Loaded, but response was empty
1417					return false;
1418				} else if (
1419					this.children.length === 1 &&
1420					this.children[0].isStatusNode()
1421				) {
1422					// Currently loading or load error
1423					return undefined;
1424				}
1425				return true;
1426			}
1427			return !!(this.children && this.children.length);
1428		},
1429		/**
1430		 * Return true if node has `className` defined in .extraClasses.
1431		 *
1432		 * @param {string} className class name (separate multiple classes by space)
1433		 * @returns {boolean}
1434		 *
1435		 * @since 2.32
1436		 */
1437		hasClass: function (className) {
1438			return (
1439				(" " + (this.extraClasses || "") + " ").indexOf(
1440					" " + className + " "
1441				) >= 0
1442			);
1443		},
1444		/** Return true if node has keyboard focus.
1445		 * @returns {boolean}
1446		 */
1447		hasFocus: function () {
1448			return this.tree.hasFocus() && this.tree.focusNode === this;
1449		},
1450		/** Write to browser console if debugLevel >= 3 (prepending node info)
1451		 *
1452		 * @param {*} msg string or object or array of such
1453		 */
1454		info: function (msg) {
1455			if (this.tree.options.debugLevel >= 3) {
1456				Array.prototype.unshift.call(arguments, this.toString());
1457				consoleApply("info", arguments);
1458			}
1459		},
1460		/** Return true if node is active (see also FancytreeNode#isSelected).
1461		 * @returns {boolean}
1462		 */
1463		isActive: function () {
1464			return this.tree.activeNode === this;
1465		},
1466		/** Return true if node is vertically below `otherNode`, i.e. rendered in a subsequent row.
1467		 * @param {FancytreeNode} otherNode
1468		 * @returns {boolean}
1469		 * @since 2.28
1470		 */
1471		isBelowOf: function (otherNode) {
1472			return this.getIndexHier(".", 5) > otherNode.getIndexHier(".", 5);
1473		},
1474		/** Return true if node is a direct child of otherNode.
1475		 * @param {FancytreeNode} otherNode
1476		 * @returns {boolean}
1477		 */
1478		isChildOf: function (otherNode) {
1479			return this.parent && this.parent === otherNode;
1480		},
1481		/** Return true, if node is a direct or indirect sub node of otherNode.
1482		 * @param {FancytreeNode} otherNode
1483		 * @returns {boolean}
1484		 */
1485		isDescendantOf: function (otherNode) {
1486			if (!otherNode || otherNode.tree !== this.tree) {
1487				return false;
1488			}
1489			var p = this.parent;
1490			while (p) {
1491				if (p === otherNode) {
1492					return true;
1493				}
1494				if (p === p.parent) {
1495					$.error("Recursive parent link: " + p);
1496				}
1497				p = p.parent;
1498			}
1499			return false;
1500		},
1501		/** Return true if node is expanded.
1502		 * @returns {boolean}
1503		 */
1504		isExpanded: function () {
1505			return !!this.expanded;
1506		},
1507		/** Return true if node is the first node of its parent's children.
1508		 * @returns {boolean}
1509		 */
1510		isFirstSibling: function () {
1511			var p = this.parent;
1512			return !p || p.children[0] === this;
1513		},
1514		/** Return true if node is a folder, i.e. has the node.folder attribute set.
1515		 * @returns {boolean}
1516		 */
1517		isFolder: function () {
1518			return !!this.folder;
1519		},
1520		/** Return true if node is the last node of its parent's children.
1521		 * @returns {boolean}
1522		 */
1523		isLastSibling: function () {
1524			var p = this.parent;
1525			return !p || p.children[p.children.length - 1] === this;
1526		},
1527		/** Return true if node is lazy (even if data was already loaded)
1528		 * @returns {boolean}
1529		 */
1530		isLazy: function () {
1531			return !!this.lazy;
1532		},
1533		/** Return true if node is lazy and loaded. For non-lazy nodes always return true.
1534		 * @returns {boolean}
1535		 */
1536		isLoaded: function () {
1537			return !this.lazy || this.hasChildren() !== undefined; // Also checks if the only child is a status node
1538		},
1539		/** Return true if children are currently beeing loaded, i.e. a Ajax request is pending.
1540		 * @returns {boolean}
1541		 */
1542		isLoading: function () {
1543			return !!this._isLoading;
1544		},
1545		/*
1546		 * @deprecated since v2.4.0:  Use isRootNode() instead
1547		 */
1548		isRoot: function () {
1549			return this.isRootNode();
1550		},
1551		/** Return true if node is partially selected (tri-state).
1552		 * @returns {boolean}
1553		 * @since 2.23
1554		 */
1555		isPartsel: function () {
1556			return !this.selected && !!this.partsel;
1557		},
1558		/** (experimental) Return true if this is partially loaded.
1559		 * @returns {boolean}
1560		 * @since 2.15
1561		 */
1562		isPartload: function () {
1563			return !!this.partload;
1564		},
1565		/** Return true if this is the (invisible) system root node.
1566		 * @returns {boolean}
1567		 * @since 2.4
1568		 */
1569		isRootNode: function () {
1570			return this.tree.rootNode === this;
1571		},
1572		/** Return true if node is selected, i.e. has a checkmark set (see also FancytreeNode#isActive).
1573		 * @returns {boolean}
1574		 */
1575		isSelected: function () {
1576			return !!this.selected;
1577		},
1578		/** Return true if this node is a temporarily generated system node like
1579		 * 'loading', 'paging', or 'error' (node.statusNodeType contains the type).
1580		 * @returns {boolean}
1581		 */
1582		isStatusNode: function () {
1583			return !!this.statusNodeType;
1584		},
1585		/** Return true if this node is a status node of type 'paging'.
1586		 * @returns {boolean}
1587		 * @since 2.15
1588		 */
1589		isPagingNode: function () {
1590			return this.statusNodeType === "paging";
1591		},
1592		/** Return true if this a top level node, i.e. a direct child of the (invisible) system root node.
1593		 * @returns {boolean}
1594		 * @since 2.4
1595		 */
1596		isTopLevel: function () {
1597			return this.tree.rootNode === this.parent;
1598		},
1599		/** Return true if node is lazy and not yet loaded. For non-lazy nodes always return false.
1600		 * @returns {boolean}
1601		 */
1602		isUndefined: function () {
1603			return this.hasChildren() === undefined; // also checks if the only child is a status node
1604		},
1605		/** Return true if all parent nodes are expanded. Note: this does not check
1606		 * whether the node is scrolled into the visible part of the screen.
1607		 * @returns {boolean}
1608		 */
1609		isVisible: function () {
1610			var i,
1611				l,
1612				n,
1613				hasFilter = this.tree.enableFilter,
1614				parents = this.getParentList(false, false);
1615
1616			// TODO: check $(n.span).is(":visible")
1617			// i.e. return false for nodes (but not parents) that are hidden
1618			// by a filter
1619			if (hasFilter && !this.match && !this.subMatchCount) {
1620				// this.debug( "isVisible: HIDDEN (" + hasFilter + ", " + this.match + ", " + this.match + ")" );
1621				return false;
1622			}
1623
1624			for (i = 0, l = parents.length; i < l; i++) {
1625				n = parents[i];
1626
1627				if (!n.expanded) {
1628					// this.debug("isVisible: HIDDEN (parent collapsed)");
1629					return false;
1630				}
1631				// if (hasFilter && !n.match && !n.subMatchCount) {
1632				// 	this.debug("isVisible: HIDDEN (" + hasFilter + ", " + this.match + ", " + this.match + ")");
1633				// 	return false;
1634				// }
1635			}
1636			// this.debug("isVisible: VISIBLE");
1637			return true;
1638		},
1639		/** Deprecated.
1640		 * @deprecated since 2014-02-16: use load() instead.
1641		 */
1642		lazyLoad: function (discard) {
1643			$.error(
1644				"FancytreeNode.lazyLoad() is deprecated since 2014-02-16. Use .load() instead."
1645			);
1646		},
1647		/**
1648		 * Load all children of a lazy node if neccessary. The <i>expanded</i> state is maintained.
1649		 * @param {boolean} [forceReload=false] Pass true to discard any existing nodes before. Otherwise this method does nothing if the node was already loaded.
1650		 * @returns {$.Promise}
1651		 */
1652		load: function (forceReload) {
1653			var res,
1654				source,
1655				self = this,
1656				wasExpanded = this.isExpanded();
1657
1658			_assert(this.isLazy(), "load() requires a lazy node");
1659			// _assert( forceReload || this.isUndefined(), "Pass forceReload=true to re-load a lazy node" );
1660			if (!forceReload && !this.isUndefined()) {
1661				return _getResolvedPromise(this);
1662			}
1663			if (this.isLoaded()) {
1664				this.resetLazy(); // also collapses
1665			}
1666			// This method is also called by setExpanded() and loadKeyPath(), so we
1667			// have to avoid recursion.
1668			source = this.tree._triggerNodeEvent("lazyLoad", this);
1669			if (source === false) {
1670				// #69
1671				return _getResolvedPromise(this);
1672			}
1673			_assert(
1674				typeof source !== "boolean",
1675				"lazyLoad event must return source in data.result"
1676			);
1677			res = this.tree._callHook("nodeLoadChildren", this, source);
1678			if (wasExpanded) {
1679				this.expanded = true;
1680				res.always(function () {
1681					self.render();
1682				});
1683			} else {
1684				res.always(function () {
1685					self.renderStatus(); // fix expander icon to 'loaded'
1686				});
1687			}
1688			return res;
1689		},
1690		/** Expand all parents and optionally scroll into visible area as neccessary.
1691		 * Promise is resolved, when lazy loading and animations are done.
1692		 * @param {object} [opts] passed to `setExpanded()`.
1693		 *     Defaults to {noAnimation: false, noEvents: false, scrollIntoView: true}
1694		 * @returns {$.Promise}
1695		 */
1696		makeVisible: function (opts) {
1697			var i,
1698				self = this,
1699				deferreds = [],
1700				dfd = new $.Deferred(),
1701				parents = this.getParentList(false, false),
1702				len = parents.length,
1703				effects = !(opts && opts.noAnimation === true),
1704				scroll = !(opts && opts.scrollIntoView === false);
1705
1706			// Expand bottom-up, so only the top node is animated
1707			for (i = len - 1; i >= 0; i--) {
1708				// self.debug("pushexpand" + parents[i]);
1709				deferreds.push(parents[i].setExpanded(true, opts));
1710			}
1711			$.when.apply($, deferreds).done(function () {
1712				// All expands have finished
1713				// self.debug("expand DONE", scroll);
1714				if (scroll) {
1715					self.scrollIntoView(effects).done(function () {
1716						// self.debug("scroll DONE");
1717						dfd.resolve();
1718					});
1719				} else {
1720					dfd.resolve();
1721				}
1722			});
1723			return dfd.promise();
1724		},
1725		/** Move this node to targetNode.
1726		 *  @param {FancytreeNode} targetNode
1727		 *  @param {string} mode <pre>
1728		 *      'child': append this node as last child of targetNode.
1729		 *               This is the default. To be compatble with the D'n'd
1730		 *               hitMode, we also accept 'over'.
1731		 *      'firstChild': add this node as first child of targetNode.
1732		 *      'before': add this node as sibling before targetNode.
1733		 *      'after': add this node as sibling after targetNode.</pre>
1734		 *  @param {function} [map] optional callback(FancytreeNode) to allow modifcations
1735		 */
1736		moveTo: function (targetNode, mode, map) {
1737			if (mode === undefined || mode === "over") {
1738				mode = "child";
1739			} else if (mode === "firstChild") {
1740				if (targetNode.children && targetNode.children.length) {
1741					mode = "before";
1742					targetNode = targetNode.children[0];
1743				} else {
1744					mode = "child";
1745				}
1746			}
1747			var pos,
1748				tree = this.tree,
1749				prevParent = this.parent,
1750				targetParent =
1751					mode === "child" ? targetNode : targetNode.parent;
1752
1753			if (this === targetNode) {
1754				return;
1755			} else if (!this.parent) {
1756				$.error("Cannot move system root");
1757			} else if (targetParent.isDescendantOf(this)) {
1758				$.error("Cannot move a node to its own descendant");
1759			}
1760			if (targetParent !== prevParent) {
1761				prevParent.triggerModifyChild("remove", this);
1762			}
1763			// Unlink this node from current parent
1764			if (this.parent.children.length === 1) {
1765				if (this.parent === targetParent) {
1766					return; // #258
1767				}
1768				this.parent.children = this.parent.lazy ? [] : null;
1769				this.parent.expanded = false;
1770			} else {
1771				pos = $.inArray(this, this.parent.children);
1772				_assert(pos >= 0, "invalid source parent");
1773				this.parent.children.splice(pos, 1);
1774			}
1775			// Remove from source DOM parent
1776			// if(this.parent.ul){
1777			// 	this.parent.ul.removeChild(this.li);
1778			// }
1779
1780			// Insert this node to target parent's child list
1781			this.parent = targetParent;
1782			if (targetParent.hasChildren()) {
1783				switch (mode) {
1784					case "child":
1785						// Append to existing target children
1786						targetParent.children.push(this);
1787						break;
1788					case "before":
1789						// Insert this node before target node
1790						pos = $.inArray(targetNode, targetParent.children);
1791						_assert(pos >= 0, "invalid target parent");
1792						targetParent.children.splice(pos, 0, this);
1793						break;
1794					case "after":
1795						// Insert this node after target node
1796						pos = $.inArray(targetNode, targetParent.children);
1797						_assert(pos >= 0, "invalid target parent");
1798						targetParent.children.splice(pos + 1, 0, this);
1799						break;
1800					default:
1801						$.error("Invalid mode " + mode);
1802				}
1803			} else {
1804				targetParent.children = [this];
1805			}
1806			// Parent has no <ul> tag yet:
1807			// if( !targetParent.ul ) {
1808			// 	// This is the parent's first child: create UL tag
1809			// 	// (Hidden, because it will be
1810			// 	targetParent.ul = document.createElement("ul");
1811			// 	targetParent.ul.style.display = "none";
1812			// 	targetParent.li.appendChild(targetParent.ul);
1813			// }
1814			// // Issue 319: Add to target DOM parent (only if node was already rendered(expanded))
1815			// if(this.li){
1816			// 	targetParent.ul.appendChild(this.li);
1817			// }
1818
1819			// Let caller modify the nodes
1820			if (map) {
1821				targetNode.visit(map, true);
1822			}
1823			if (targetParent === prevParent) {
1824				targetParent.triggerModifyChild("move", this);
1825			} else {
1826				// prevParent.triggerModifyChild("remove", this);
1827				targetParent.triggerModifyChild("add", this);
1828			}
1829			// Handle cross-tree moves
1830			if (tree !== targetNode.tree) {
1831				// Fix node.tree for all source nodes
1832				// 	_assert(false, "Cross-tree move is not yet implemented.");
1833				this.warn("Cross-tree moveTo is experimental!");
1834				this.visit(function (n) {
1835					// TODO: fix selection state and activation, ...
1836					n.tree = targetNode.tree;
1837				}, true);
1838			}
1839
1840			// A collaposed node won't re-render children, so we have to remove it manually
1841			// if( !targetParent.expanded ){
1842			//   prevParent.ul.removeChild(this.li);
1843			// }
1844			tree._callHook("treeStructureChanged", tree, "moveTo");
1845
1846			// Update HTML markup
1847			if (!prevParent.isDescendantOf(targetParent)) {
1848				prevParent.render();
1849			}
1850			if (
1851				!targetParent.isDescendantOf(prevParent) &&
1852				targetParent !== prevParent
1853			) {
1854				targetParent.render();
1855			}
1856			// TODO: fix selection state
1857			// TODO: fix active state
1858
1859			/*
1860			var tree = this.tree;
1861			var opts = tree.options;
1862			var pers = tree.persistence;
1863
1864			// Always expand, if it's below minExpandLevel
1865			// tree.logDebug ("%s._addChildNode(%o), l=%o", this, ftnode, ftnode.getLevel());
1866			if ( opts.minExpandLevel >= ftnode.getLevel() ) {
1867				// tree.logDebug ("Force expand for %o", ftnode);
1868				this.bExpanded = true;
1869			}
1870
1871			// In multi-hier mode, update the parents selection state
1872			// DT issue #82: only if not initializing, because the children may not exist yet
1873			// if( !ftnode.data.isStatusNode() && opts.selectMode==3 && !isInitializing )
1874			// 	ftnode._fixSelectionState();
1875
1876			// In multi-hier mode, update the parents selection state
1877			if( ftnode.bSelected && opts.selectMode==3 ) {
1878				var p = this;
1879				while( p ) {
1880					if( !p.hasSubSel )
1881						p._setSubSel(true);
1882					p = p.parent;
1883				}
1884			}
1885			// render this node and the new child
1886			if ( tree.bEnableUpdate )
1887				this.render();
1888			return ftnode;
1889			*/
1890		},
1891		/** Set focus relative to this node and optionally activate.
1892		 *
1893		 * 'left' collapses the node if it is expanded, or move to the parent
1894		 * otherwise.
1895		 * 'right' expands the node if it is collapsed, or move to the first
1896		 * child otherwise.
1897		 *
1898		 * @param {string|number} where 'down', 'first', 'last', 'left', 'parent', 'right', or 'up'.
1899		 *   (Alternatively the keyCode that would normally trigger this move,
1900		 *   e.g. `$.ui.keyCode.LEFT` = 'left'.
1901		 * @param {boolean} [activate=true]
1902		 * @returns {$.Promise}
1903		 */
1904		navigate: function (where, activate) {
1905			var node,
1906				KC = $.ui.keyCode;
1907
1908			// Handle optional expand/collapse action for LEFT/RIGHT
1909			switch (where) {
1910				case "left":
1911				case KC.LEFT:
1912					if (this.expanded) {
1913						return this.setExpanded(false);
1914					}
1915					break;
1916				case "right":
1917				case KC.RIGHT:
1918					if (!this.expanded && (this.children || this.lazy)) {
1919						return this.setExpanded();
1920					}
1921					break;
1922			}
1923			// Otherwise activate or focus the related node
1924			node = this.findRelatedNode(where);
1925			if (node) {
1926				// setFocus/setActive will scroll later (if autoScroll is specified)
1927				try {
1928					node.makeVisible({ scrollIntoView: false });
1929				} catch (e) {} // #272
1930				if (activate === false) {
1931					node.setFocus();
1932					return _getResolvedPromise();
1933				}
1934				return node.setActive();
1935			}
1936			this.warn("Could not find related node '" + where + "'.");
1937			return _getResolvedPromise();
1938		},
1939		/**
1940		 * Remove this node (not allowed for system root).
1941		 */
1942		remove: function () {
1943			return this.parent.removeChild(this);
1944		},
1945		/**
1946		 * Remove childNode from list of direct children.
1947		 * @param {FancytreeNode} childNode
1948		 */
1949		removeChild: function (childNode) {
1950			return this.tree._callHook("nodeRemoveChild", this, childNode);
1951		},
1952		/**
1953		 * Remove all child nodes and descendents. This converts the node into a leaf.<br>
1954		 * If this was a lazy node, it is still considered 'loaded'; call node.resetLazy()
1955		 * in order to trigger lazyLoad on next expand.
1956		 */
1957		removeChildren: function () {
1958			return this.tree._callHook("nodeRemoveChildren", this);
1959		},
1960		/**
1961		 * Remove class from node's span tag and .extraClasses.
1962		 *
1963		 * @param {string} className class name
1964		 *
1965		 * @since 2.17
1966		 */
1967		removeClass: function (className) {
1968			return this.toggleClass(className, false);
1969		},
1970		/**
1971		 * This method renders and updates all HTML markup that is required
1972		 * to display this node in its current state.<br>
1973		 * Note:
1974		 * <ul>
1975		 * <li>It should only be neccessary to call this method after the node object
1976		 *     was modified by direct access to its properties, because the common
1977		 *     API methods (node.setTitle(), moveTo(), addChildren(), remove(), ...)
1978		 *     already handle this.
1979		 * <li> {@link FancytreeNode#renderTitle} and {@link FancytreeNode#renderStatus}
1980		 *     are implied. If changes are more local, calling only renderTitle() or
1981		 *     renderStatus() may be sufficient and faster.
1982		 * </ul>
1983		 *
1984		 * @param {boolean} [force=false] re-render, even if html markup was already created
1985		 * @param {boolean} [deep=false] also render all descendants, even if parent is collapsed
1986		 */
1987		render: function (force, deep) {
1988			return this.tree._callHook("nodeRender", this, force, deep);
1989		},
1990		/** Create HTML markup for the node's outer `<span>` (expander, checkbox, icon, and title).
1991		 * Implies {@link FancytreeNode#renderStatus}.
1992		 * @see Fancytree_Hooks#nodeRenderTitle
1993		 */
1994		renderTitle: function () {
1995			return this.tree._callHook("nodeRenderTitle", this);
1996		},
1997		/** Update element's CSS classes according to node state.
1998		 * @see Fancytree_Hooks#nodeRenderStatus
1999		 */
2000		renderStatus: function () {
2001			return this.tree._callHook("nodeRenderStatus", this);
2002		},
2003		/**
2004		 * (experimental) Replace this node with `source`.
2005		 * (Currently only available for paging nodes.)
2006		 * @param {NodeData[]} source List of child node definitions
2007		 * @since 2.15
2008		 */
2009		replaceWith: function (source) {
2010			var res,
2011				parent = this.parent,
2012				pos = $.inArray(this, parent.children),
2013				self = this;
2014
2015			_assert(
2016				this.isPagingNode(),
2017				"replaceWith() currently requires a paging status node"
2018			);
2019
2020			res = this.tree._callHook("nodeLoadChildren", this, source);
2021			res.done(function (data) {
2022				// New nodes are currently children of `this`.
2023				var children = self.children;
2024				// Prepend newly loaded child nodes to `this`
2025				// Move new children after self
2026				for (i = 0; i < children.length; i++) {
2027					children[i].parent = parent;
2028				}
2029				parent.children.splice.apply(
2030					parent.children,
2031					[pos + 1, 0].concat(children)
2032				);
2033
2034				// Remove self
2035				self.children = null;
2036				self.remove();
2037				// Redraw new nodes
2038				parent.render();
2039				// TODO: set node.partload = false if this was tha last paging node?
2040				// parent.addPagingNode(false);
2041			}).fail(function () {
2042				self.setExpanded();
2043			});
2044			return res;
2045			// $.error("Not implemented: replaceWith()");
2046		},
2047		/**
2048		 * Remove all children, collapse, and set the lazy-flag, so that the lazyLoad
2049		 * event is triggered on next expand.
2050		 */
2051		resetLazy: function () {
2052			this.removeChildren();
2053			this.expanded = false;
2054			this.lazy = true;
2055			this.children = undefined;
2056			this.renderStatus();
2057		},
2058		/** Schedule activity for delayed execution (cancel any pending request).
2059		 *  scheduleAction('cancel') will only cancel a pending request (if any).
2060		 * @param {string} mode
2061		 * @param {number} ms
2062		 */
2063		scheduleAction: function (mode, ms) {
2064			if (this.tree.timer) {
2065				clearTimeout(this.tree.timer);
2066				this.tree.debug("clearTimeout(%o)", this.tree.timer);
2067			}
2068			this.tree.timer = null;
2069			var self = this; // required for closures
2070			switch (mode) {
2071				case "cancel":
2072					// Simply made sure that timer was cleared
2073					break;
2074				case "expand":
2075					this.tree.timer = setTimeout(function () {
2076						self.tree.debug("setTimeout: trigger expand");
2077						self.setExpanded(true);
2078					}, ms);
2079					break;
2080				case "activate":
2081					this.tree.timer = setTimeout(function () {
2082						self.tree.debug("setTimeout: trigger activate");
2083						self.setActive(true);
2084					}, ms);
2085					break;
2086				default:
2087					$.error("Invalid mode " + mode);
2088			}
2089			// this.tree.debug("setTimeout(%s, %s): %s", mode, ms, this.tree.timer);
2090		},
2091		/**
2092		 *
2093		 * @param {boolean | PlainObject} [effects=false] animation options.
2094		 * @param {object} [options=null] {topNode: null, effects: ..., parent: ...} this node will remain visible in
2095		 *     any case, even if `this` is outside the scroll pane.
2096		 * @returns {$.Promise}
2097		 */
2098		scrollIntoView: function (effects, options) {
2099			if (options !== undefined && _isNode(options)) {
2100				throw Error(
2101					"scrollIntoView() with 'topNode' option is deprecated since 2014-05-08. Use 'options.topNode' instead."
2102				);
2103			}
2104			// The scroll parent is typically the plain tree's <UL> container.
2105			// For ext-table, we choose the nearest parent that has `position: relative`
2106			// and `overflow` set.
2107			// (This default can be overridden by the local or global `scrollParent` option.)
2108			var opts = $.extend(
2109					{
2110						effects:
2111							effects === true
2112								? { duration: 200, queue: false }
2113								: effects,
2114						scrollOfs: this.tree.options.scrollOfs,
2115						scrollParent: this.tree.options.scrollParent,
2116						topNode: null,
2117					},
2118					options
2119				),
2120				$scrollParent = opts.scrollParent,
2121				$container = this.tree.$container,
2122				overflowY = $container.css("overflow-y");
2123
2124			if (!$scrollParent) {
2125				if (this.tree.tbody) {
2126					$scrollParent = $container.scrollParent();
2127				} else if (overflowY === "scroll" || overflowY === "auto") {
2128					$scrollParent = $container;
2129				} else {
2130					// #922 plain tree in a non-fixed-sized UL scrolls inside its parent
2131					$scrollParent = $container.scrollParent();
2132				}
2133			} else if (!$scrollParent.jquery) {
2134				// Make sure we have a jQuery object
2135				$scrollParent = $($scrollParent);
2136			}
2137			if (
2138				$scrollParent[0] === document ||
2139				$scrollParent[0] === document.body
2140			) {
2141				// `document` may be returned by $().scrollParent(), if nothing is found,
2142				// but would not work: (see #894)
2143				this.debug(
2144					"scrollIntoView(): normalizing scrollParent to 'window':",
2145					$scrollParent[0]
2146				);
2147				$scrollParent = $(window);
2148			}
2149			// eslint-disable-next-line one-var
2150			var topNodeY,
2151				nodeY,
2152				horzScrollbarHeight,
2153				containerOffsetTop,
2154				dfd = new $.Deferred(),
2155				self = this,
2156				nodeHeight = $(this.span).height(),
2157				topOfs = opts.scrollOfs.top || 0,
2158				bottomOfs = opts.scrollOfs.bottom || 0,
2159				containerHeight = $scrollParent.height(),
2160				scrollTop = $scrollParent.scrollTop(),
2161				$animateTarget = $scrollParent,
2162				isParentWindow = $scrollParent[0] === window,
2163				topNode = opts.topNode || null,
2164				newScrollTop = null;
2165
2166			// this.debug("scrollIntoView(), scrollTop=" + scrollTop, opts.scrollOfs);
2167			// _assert($(this.span).is(":visible"), "scrollIntoView node is invisible"); // otherwise we cannot calc offsets
2168			if (this.isRootNode() || !this.isVisible()) {
2169				// We cannot calc offsets for hidden elements
2170				this.info("scrollIntoView(): node is invisible.");
2171				return _getResolvedPromise();
2172			}
2173			if (isParentWindow) {
2174				nodeY = $(this.span).offset().top;
2175				topNodeY =
2176					topNode && topNode.span ? $(topNode.span).offset().top : 0;
2177				$animateTarget = $("html,body");
2178			} else {
2179				_assert(
2180					$scrollParent[0] !== document &&
2181						$scrollParent[0] !== document.body,
2182					"scrollParent should be a simple element or `window`, not document or body."
2183				);
2184
2185				containerOffsetTop = $scrollParent.offset().top;
2186				nodeY =
2187					$(this.span).offset().top - containerOffsetTop + scrollTop; // relative to scroll parent
2188				topNodeY = topNode
2189					? $(topNode.span).offset().top -
2190					  containerOffsetTop +
2191					  scrollTop
2192					: 0;
2193				horzScrollbarHeight = Math.max(
2194					0,
2195					$scrollParent.innerHeight() - $scrollParent[0].clientHeight
2196				);
2197				containerHeight -= horzScrollbarHeight;
2198			}
2199
2200			// this.debug("    scrollIntoView(), nodeY=" + nodeY + ", containerHeight=" + containerHeight);
2201			if (nodeY < scrollTop + topOfs) {
2202				// Node is above visible container area
2203				newScrollTop = nodeY - topOfs;
2204				// this.debug("    scrollIntoView(), UPPER newScrollTop=" + newScrollTop);
2205			} else if (
2206				nodeY + nodeHeight >
2207				scrollTop + containerHeight - bottomOfs
2208			) {
2209				newScrollTop = nodeY + nodeHeight - containerHeight + bottomOfs;
2210				// this.debug("    scrollIntoView(), LOWER newScrollTop=" + newScrollTop);
2211				// If a topNode was passed, make sure that it is never scrolled
2212				// outside the upper border
2213				if (topNode) {
2214					_assert(
2215						topNode.isRootNode() || topNode.isVisible(),
2216						"topNode must be visible"
2217					);
2218					if (topNodeY < newScrollTop) {
2219						newScrollTop = topNodeY - topOfs;
2220						// this.debug("    scrollIntoView(), TOP newScrollTop=" + newScrollTop);
2221					}
2222				}
2223			}
2224
2225			if (newScrollTop === null) {
2226				dfd.resolveWith(this);
2227			} else {
2228				// this.debug("    scrollIntoView(), SET newScrollTop=" + newScrollTop);
2229				if (opts.effects) {
2230					opts.effects.complete = function () {
2231						dfd.resolveWith(self);
2232					};
2233					$animateTarget.stop(true).animate(
2234						{
2235							scrollTop: newScrollTop,
2236						},
2237						opts.effects
2238					);
2239				} else {
2240					$animateTarget[0].scrollTop = newScrollTop;
2241					dfd.resolveWith(this);
2242				}
2243			}
2244			return dfd.promise();
2245		},
2246
2247		/**Activate this node.
2248		 *
2249		 * The `cell` option requires the ext-table and ext-ariagrid extensions.
2250		 *
2251		 * @param {boolean} [flag=true] pass false to deactivate
2252		 * @param {object} [opts] additional options. Defaults to {noEvents: false, noFocus: false, cell: null}
2253		 * @returns {$.Promise}
2254		 */
2255		setActive: function (flag, opts) {
2256			return this.tree._callHook("nodeSetActive", this, flag, opts);
2257		},
2258		/**Expand or collapse this node. Promise is resolved, when lazy loading and animations are done.
2259		 * @param {boolean} [flag=true] pass false to collapse
2260		 * @param {object} [opts] additional options. Defaults to {noAnimation: false, noEvents: false}
2261		 * @returns {$.Promise}
2262		 */
2263		setExpanded: function (flag, opts) {
2264			return this.tree._callHook("nodeSetExpanded", this, flag, opts);
2265		},
2266		/**Set keyboard focus to this node.
2267		 * @param {boolean} [flag=true] pass false to blur
2268		 * @see Fancytree#setFocus
2269		 */
2270		setFocus: function (flag) {
2271			return this.tree._callHook("nodeSetFocus", this, flag);
2272		},
2273		/**Select this node, i.e. check the checkbox.
2274		 * @param {boolean} [flag=true] pass false to deselect
2275		 * @param {object} [opts] additional options. Defaults to {noEvents: false, p
2276		 *     propagateDown: null, propagateUp: null, callback: null }
2277		 */
2278		setSelected: function (flag, opts) {
2279			return this.tree._callHook("nodeSetSelected", this, flag, opts);
2280		},
2281		/**Mark a lazy node as 'error', 'loading', 'nodata', or 'ok'.
2282		 * @param {string} status 'error'|'loading'|'nodata'|'ok'
2283		 * @param {string} [message]
2284		 * @param {string} [details]
2285		 */
2286		setStatus: function (status, message, details) {
2287			return this.tree._callHook(
2288				"nodeSetStatus",
2289				this,
2290				status,
2291				message,
2292				details
2293			);
2294		},
2295		/**Rename this node.
2296		 * @param {string} title
2297		 */
2298		setTitle: function (title) {
2299			this.title = title;
2300			this.renderTitle();
2301			this.triggerModify("rename");
2302		},
2303		/**Sort child list by title.
2304		 * @param {function} [cmp] custom compare function(a, b) that returns -1, 0, or 1 (defaults to sort by title).
2305		 * @param {boolean} [deep=false] pass true to sort all descendant nodes
2306		 */
2307		sortChildren: function (cmp, deep) {
2308			var i,
2309				l,
2310				cl = this.children;
2311
2312			if (!cl) {
2313				return;
2314			}
2315			cmp =
2316				cmp ||
2317				function (a, b) {
2318					var x = a.title.toLowerCase(),
2319						y = b.title.toLowerCase();
2320
2321					// eslint-disable-next-line no-nested-ternary
2322					return x === y ? 0 : x > y ? 1 : -1;
2323				};
2324			cl.sort(cmp);
2325			if (deep) {
2326				for (i = 0, l = cl.length; i < l; i++) {
2327					if (cl[i].children) {
2328						cl[i].sortChildren(cmp, "$norender$");
2329					}
2330				}
2331			}
2332			if (deep !== "$norender$") {
2333				this.render();
2334			}
2335			this.triggerModifyChild("sort");
2336		},
2337		/** Convert node (or whole branch) into a plain object.
2338		 *
2339		 * The result is compatible with node.addChildren().
2340		 *
2341		 * @param {boolean} [recursive=false] include child nodes
2342		 * @param {function} [callback] callback(dict, node) is called for every node, in order to allow modifications.
2343		 *     Return `false` to ignore this node or `"skip"` to include this node without its children.
2344		 * @returns {NodeData}
2345		 */
2346		toDict: function (recursive, callback) {
2347			var i,
2348				l,
2349				node,
2350				res,
2351				dict = {},
2352				self = this;
2353
2354			$.each(NODE_ATTRS, function (i, a) {
2355				if (self[a] || self[a] === false) {
2356					dict[a] = self[a];
2357				}
2358			});
2359			if (!$.isEmptyObject(this.data)) {
2360				dict.data = $.extend({}, this.data);
2361				if ($.isEmptyObject(dict.data)) {
2362					delete dict.data;
2363				}
2364			}
2365			if (callback) {
2366				res = callback(dict, self);
2367				if (res === false) {
2368					return false; // Don't include this node nor its children
2369				}
2370				if (res === "skip") {
2371					recursive = false; // Include this node, but not the children
2372				}
2373			}
2374			if (recursive) {
2375				if (_isArray(this.children)) {
2376					dict.children = [];
2377					for (i = 0, l = this.children.length; i < l; i++) {
2378						node = this.children[i];
2379						if (!node.isStatusNode()) {
2380							res = node.toDict(true, callback);
2381							if (res !== false) {
2382								dict.children.push(res);
2383							}
2384						}
2385					}
2386				}
2387			}
2388			return dict;
2389		},
2390		/**
2391		 * Set, clear, or toggle class of node's span tag and .extraClasses.
2392		 *
2393		 * @param {string} className class name (separate multiple classes by space)
2394		 * @param {boolean} [flag] true/false to add/remove class. If omitted, class is toggled.
2395		 * @returns {boolean} true if a class was added
2396		 *
2397		 * @since 2.17
2398		 */
2399		toggleClass: function (value, flag) {
2400			var className,
2401				hasClass,
2402				rnotwhite = /\S+/g,
2403				classNames = value.match(rnotwhite) || [],
2404				i = 0,
2405				wasAdded = false,
2406				statusElem = this[this.tree.statusClassPropName],
2407				curClasses = " " + (this.extraClasses || "") + " ";
2408
2409			// this.info("toggleClass('" + value + "', " + flag + ")", curClasses);
2410			// Modify DOM element directly if it already exists
2411			if (statusElem) {
2412				$(statusElem).toggleClass(value, flag);
2413			}
2414			// Modify node.extraClasses to make this change persistent
2415			// Toggle if flag was not passed
2416			while ((className = classNames[i++])) {
2417				hasClass = curClasses.indexOf(" " + className + " ") >= 0;
2418				flag = flag === undefined ? !hasClass : !!flag;
2419				if (flag) {
2420					if (!hasClass) {
2421						curClasses += className + " ";
2422						wasAdded = true;
2423					}
2424				} else {
2425					while (curClasses.indexOf(" " + className + " ") > -1) {
2426						curClasses = curClasses.replace(
2427							" " + className + " ",
2428							" "
2429						);
2430					}
2431				}
2432			}
2433			this.extraClasses = _trim(curClasses);
2434			// this.info("-> toggleClass('" + value + "', " + flag + "): '" + this.extraClasses + "'");
2435			return wasAdded;
2436		},
2437		/** Flip expanded status. */
2438		toggleExpanded: function () {
2439			return this.tree._callHook("nodeToggleExpanded", this);
2440		},
2441		/** Flip selection status. */
2442		toggleSelected: function () {
2443			return this.tree._callHook("nodeToggleSelected", this);
2444		},
2445		toString: function () {
2446			return "FancytreeNode@" + this.key + "[title='" + this.title + "']";
2447			// return "<FancytreeNode(#" + this.key + ", '" + this.title + "')>";
2448		},
2449		/**
2450		 * Trigger `modifyChild` event on a parent to signal that a child was modified.
2451		 * @param {string} operation Type of change: 'add', 'remove', 'rename', 'move', 'data', ...
2452		 * @param {FancytreeNode} [childNode]
2453		 * @param {object} [extra]
2454		 */
2455		triggerModifyChild: function (operation, childNode, extra) {
2456			var data,
2457				modifyChild = this.tree.options.modifyChild;
2458
2459			if (modifyChild) {
2460				if (childNode && childNode.parent !== this) {
2461					$.error(
2462						"childNode " + childNode + " is not a child of " + this
2463					);
2464				}
2465				data = {
2466					node: this,
2467					tree: this.tree,
2468					operation: operation,
2469					childNode: childNode || null,
2470				};
2471				if (extra) {
2472					$.extend(data, extra);
2473				}
2474				modifyChild({ type: "modifyChild" }, data);
2475			}
2476		},
2477		/**
2478		 * Trigger `modifyChild` event on node.parent(!).
2479		 * @param {string} operation Type of change: 'add', 'remove', 'rename', 'move', 'data', ...
2480		 * @param {object} [extra]
2481		 */
2482		triggerModify: function (operation, extra) {
2483			this.parent.triggerModifyChild(operation, this, extra);
2484		},
2485		/** Call fn(node) for all child nodes in hierarchical order (depth-first).<br>
2486		 * Stop iteration, if fn() returns false. Skip current branch, if fn() returns "skip".<br>
2487		 * Return false if iteration was stopped.
2488		 *
2489		 * @param {function} fn the callback function.
2490		 *     Return false to stop iteration, return "skip" to skip this node and
2491		 *     its children only.
2492		 * @param {boolean} [includeSelf=false]
2493		 * @returns {boolean}
2494		 */
2495		visit: function (fn, includeSelf) {
2496			var i,
2497				l,
2498				res = true,
2499				children = this.children;
2500
2501			if (includeSelf === true) {
2502				res = fn(this);
2503				if (res === false || res === "skip") {
2504					return res;
2505				}
2506			}
2507			if (children) {
2508				for (i = 0, l = children.length; i < l; i++) {
2509					res = children[i].visit(fn, true);
2510					if (res === false) {
2511						break;
2512					}
2513				}
2514			}
2515			return res;
2516		},
2517		/** Call fn(node) for all child nodes and recursively load lazy children.<br>
2518		 * <b>Note:</b> If you need this method, you probably should consider to review
2519		 * your architecture! Recursivley loading nodes is a perfect way for lazy
2520		 * programmers to flood the server with requests ;-)
2521		 *
2522		 * @param {function} [fn] optional callback function.
2523		 *     Return false to stop iteration, return "skip" to skip this node and
2524		 *     its children only.
2525		 * @param {boolean} [includeSelf=false]
2526		 * @returns {$.Promise}
2527		 * @since 2.4
2528		 */
2529		visitAndLoad: function (fn, includeSelf, _recursion) {
2530			var dfd,
2531				res,
2532				loaders,
2533				node = this;
2534
2535			// node.debug("visitAndLoad");
2536			if (fn && includeSelf === true) {
2537				res = fn(node);
2538				if (res === false || res === "skip") {
2539					return _recursion ? res : _getResolvedPromise();
2540				}
2541			}
2542			if (!node.children && !node.lazy) {
2543				return _getResolvedPromise();
2544			}
2545			dfd = new $.Deferred();
2546			loaders = [];
2547			// node.debug("load()...");
2548			node.load().done(function () {
2549				// node.debug("load()... done.");
2550				for (var i = 0, l = node.children.length; i < l; i++) {
2551					res = node.children[i].visitAndLoad(fn, true, true);
2552					if (res === false) {
2553						dfd.reject();
2554						break;
2555					} else if (res !== "skip") {
2556						loaders.push(res); // Add promise to the list
2557					}
2558				}
2559				$.when.apply(this, loaders).then(function () {
2560					dfd.resolve();
2561				});
2562			});
2563			return dfd.promise();
2564		},
2565		/** Call fn(node) for all parent nodes, bottom-up, including invisible system root.<br>
2566		 * Stop iteration, if fn() returns false.<br>
2567		 * Return false if iteration was stopped.
2568		 *
2569		 * @param {function} fn the callback function.
2570		 *     Return false to stop iteration, return "skip" to skip this node and children only.
2571		 * @param {boolean} [includeSelf=false]
2572		 * @returns {boolean}
2573		 */
2574		visitParents: function (fn, includeSelf) {
2575			// Visit parent nodes (bottom up)
2576			if (includeSelf && fn(this) === false) {
2577				return false;
2578			}
2579			var p = this.parent;
2580			while (p) {
2581				if (fn(p) === false) {
2582					return false;
2583				}
2584				p = p.parent;
2585			}
2586			return true;
2587		},
2588		/** Call fn(node) for all sibling nodes.<br>
2589		 * Stop iteration, if fn() returns false.<br>
2590		 * Return false if iteration was stopped.
2591		 *
2592		 * @param {function} fn the callback function.
2593		 *     Return false to stop iteration.
2594		 * @param {boolean} [includeSelf=false]
2595		 * @returns {boolean}
2596		 */
2597		visitSiblings: function (fn, includeSelf) {
2598			var i,
2599				l,
2600				n,
2601				ac = this.parent.children;
2602
2603			for (i = 0, l = ac.length; i < l; i++) {
2604				n = ac[i];
2605				if (includeSelf || n !== this) {
2606					if (fn(n) === false) {
2607						return false;
2608					}
2609				}
2610			}
2611			return true;
2612		},
2613		/** Write warning to browser console if debugLevel >= 2 (prepending node info)
2614		 *
2615		 * @param {*} msg string or object or array of such
2616		 */
2617		warn: function (msg) {
2618			if (this.tree.options.debugLevel >= 2) {
2619				Array.prototype.unshift.call(arguments, this.toString());
2620				consoleApply("warn", arguments);
2621			}
2622		},
2623	};
2624
2625	/******************************************************************************
2626	 * Fancytree
2627	 */
2628	/**
2629	 * Construct a new tree object.
2630	 *
2631	 * @class Fancytree
2632	 * @classdesc The controller behind a fancytree.
2633	 * This class also contains 'hook methods': see {@link Fancytree_Hooks}.
2634	 *
2635	 * @param {Widget} widget
2636	 *
2637	 * @property {string} _id Automatically generated unique tree instance ID, e.g. "1".
2638	 * @property {string} _ns Automatically generated unique tree namespace, e.g. ".fancytree-1".
2639	 * @property {FancytreeNode} activeNode Currently active node or null.
2640	 * @property {string} ariaPropName Property name of FancytreeNode that contains the element which will receive the aria attributes.
2641	 *     Typically "li", but "tr" for table extension.
2642	 * @property {jQueryObject} $container Outer `<ul>` element (or `<table>` element for ext-table).
2643	 * @property {jQueryObject} $div A jQuery object containing the element used to instantiate the tree widget (`widget.element`)
2644	 * @property {object|array} columns Recommended place to store shared column meta data. @since 2.27
2645	 * @property {object} data Metadata, i.e. properties that may be passed to `source` in addition to a children array.
2646	 * @property {object} ext Hash of all active plugin instances.
2647	 * @property {FancytreeNode} focusNode Currently focused node or null.
2648	 * @property {FancytreeNode} lastSelectedNode Used to implement selectMode 1 (single select)
2649	 * @property {string} nodeContainerAttrName Property name of FancytreeNode that contains the outer element of single nodes.
2650	 *     Typically "li", but "tr" for table extension.
2651	 * @property {FancytreeOptions} options Current options, i.e. default options + options passed to constructor.
2652	 * @property {FancytreeNode} rootNode Invisible system root node.
2653	 * @property {string} statusClassPropName Property name of FancytreeNode that contains the element which will receive the status classes.
2654	 *     Typically "span", but "tr" for table extension.
2655	 * @property {object} types Map for shared type specific meta data, used with node.type attribute. @since 2.27
2656	 * @property {object} viewport See ext-vieport. @since v2.31
2657	 * @property {object} widget Base widget instance.
2658	 */
2659	function Fancytree(widget) {
2660		this.widget = widget;
2661		this.$div = widget.element;
2662		this.options = widget.options;
2663		if (this.options) {
2664			if (this.options.lazyload !== undefined) {
2665				$.error(
2666					"The 'lazyload' event is deprecated since 2014-02-25. Use 'lazyLoad' (with uppercase L) instead."
2667				);
2668			}
2669			if (this.options.loaderror !== undefined) {
2670				$.error(
2671					"The 'loaderror' event was renamed since 2014-07-03. Use 'loadError' (with uppercase E) instead."
2672				);
2673			}
2674			if (this.options.fx !== undefined) {
2675				$.error(
2676					"The 'fx' option was replaced by 'toggleEffect' since 2014-11-30."
2677				);
2678			}
2679			if (this.options.removeNode !== undefined) {
2680				$.error(
2681					"The 'removeNode' event was replaced by 'modifyChild' since 2.20 (2016-09-10)."
2682				);
2683			}
2684		}
2685		this.ext = {}; // Active extension instances
2686		this.types = {};
2687		this.columns = {};
2688		// allow to init tree.data.foo from <div data-foo=''>
2689		this.data = _getElementDataAsDict(this.$div);
2690		// TODO: use widget.uuid instead?
2691		this._id = "" + (this.options.treeId || $.ui.fancytree._nextId++);
2692		// TODO: use widget.eventNamespace instead?
2693		this._ns = ".fancytree-" + this._id; // append for namespaced events
2694		this.activeNode = null;
2695		this.focusNode = null;
2696		this._hasFocus = null;
2697		this._tempCache = {};
2698		this._lastMousedownNode = null;
2699		this._enableUpdate = true;
2700		this.lastSelectedNode = null;
2701		this.systemFocusElement = null;
2702		this.lastQuicksearchTerm = "";
2703		this.lastQuicksearchTime = 0;
2704		this.viewport = null; // ext-grid
2705
2706		this.statusClassPropName = "span";
2707		this.ariaPropName = "li";
2708		this.nodeContainerAttrName = "li";
2709
2710		// Remove previous markup if any
2711		this.$div.find(">ul.fancytree-container").remove();
2712
2713		// Create a node without parent.
2714		var fakeParent = { tree: this },
2715			$ul;
2716		this.rootNode = new FancytreeNode(fakeParent, {
2717			title: "root",
2718			key: "root_" + this._id,
2719			children: null,
2720			expanded: true,
2721		});
2722		this.rootNode.parent = null;
2723
2724		// Create root markup
2725		$ul = $("<ul>", {
2726			id: "ft-id-" + this._id,
2727			class: "ui-fancytree fancytree-container fancytree-plain",
2728		}).appendTo(this.$div);
2729		this.$container = $ul;
2730		this.rootNode.ul = $ul[0];
2731
2732		if (this.options.debugLevel == null) {
2733			this.options.debugLevel = FT.debugLevel;
2734		}
2735		// // Add container to the TAB chain
2736		// // See http://www.w3.org/TR/wai-aria-practices/#focus_activedescendant
2737		// // #577: Allow to set tabindex to "0", "-1" and ""
2738		// this.$container.attr("tabindex", this.options.tabindex);
2739
2740		// if( this.options.rtl ) {
2741		// 	this.$container.attr("DIR", "RTL").addClass("fancytree-rtl");
2742		// // }else{
2743		// //	this.$container.attr("DIR", null).removeClass("fancytree-rtl");
2744		// }
2745		// if(this.options.aria){
2746		// 	this.$container.attr("role", "tree");
2747		// 	if( this.options.selectMode !== 1 ) {
2748		// 		this.$container.attr("aria-multiselectable", true);
2749		// 	}
2750		// }
2751	}
2752
2753	Fancytree.prototype = /** @lends Fancytree# */ {
2754		/* Return a context object that can be re-used for _callHook().
2755		 * @param {Fancytree | FancytreeNode | EventData} obj
2756		 * @param {Event} originalEvent
2757		 * @param {Object} extra
2758		 * @returns {EventData}
2759		 */
2760		_makeHookContext: function (obj, originalEvent, extra) {
2761			var ctx, tree;
2762			if (obj.node !== undefined) {
2763				// obj is already a context object
2764				if (originalEvent && obj.originalEvent !== originalEvent) {
2765					$.error("invalid args");
2766				}
2767				ctx = obj;
2768			} else if (obj.tree) {
2769				// obj is a FancytreeNode
2770				tree = obj.tree;
2771				ctx = {
2772					node: obj,
2773					tree: tree,
2774					widget: tree.widget,
2775					options: tree.widget.options,
2776					originalEvent: originalEvent,
2777					typeInfo: tree.types[obj.type] || {},
2778				};
2779			} else if (obj.widget) {
2780				// obj is a Fancytree
2781				ctx = {
2782					node: null,
2783					tree: obj,
2784					widget: obj.widget,
2785					options: obj.widget.options,
2786					originalEvent: originalEvent,
2787				};
2788			} else {
2789				$.error("invalid args");
2790			}
2791			if (extra) {
2792				$.extend(ctx, extra);
2793			}
2794			return ctx;
2795		},
2796		/* Trigger a hook function: funcName(ctx, [...]).
2797		 *
2798		 * @param {string} funcName
2799		 * @param {Fancytree|FancytreeNode|EventData} contextObject
2800		 * @param {any}  [_extraArgs] optional additional arguments
2801		 * @returns {any}
2802		 */
2803		_callHook: function (funcName, contextObject, _extraArgs) {
2804			var ctx = this._makeHookContext(contextObject),
2805				fn = this[funcName],
2806				args = Array.prototype.slice.call(arguments, 2);
2807			if (!_isFunction(fn)) {
2808				$.error("_callHook('" + funcName + "') is not a function");
2809			}
2810			args.unshift(ctx);
2811			// this.debug("_hook", funcName, ctx.node && ctx.node.toString() || ctx.tree.toString(), args);
2812			return fn.apply(this, args);
2813		},
2814		_setExpiringValue: function (key, value, ms) {
2815			this._tempCache[key] = {
2816				value: value,
2817				expire: Date.now() + (+ms || 50),
2818			};
2819		},
2820		_getExpiringValue: function (key) {
2821			var entry = this._tempCache[key];
2822			if (entry && entry.expire > Date.now()) {
2823				return entry.value;
2824			}
2825			delete this._tempCache[key];
2826			return null;
2827		},
2828		/* Check if this tree has extension `name` enabled.
2829		 *
2830		 * @param {string} name name of the required extension
2831		 */
2832		_usesExtension: function (name) {
2833			return $.inArray(name, this.options.extensions) >= 0;
2834		},
2835		/* Check if current extensions dependencies are met and throw an error if not.
2836		 *
2837		 * This method may be called inside the `treeInit` hook for custom extensions.
2838		 *
2839		 * @param {string} name name of the required extension
2840		 * @param {boolean} [required=true] pass `false` if the extension is optional, but we want to check for order if it is present
2841		 * @param {boolean} [before] `true` if `name` must be included before this, `false` otherwise (use `null` if order doesn't matter)
2842		 * @param {string} [message] optional error message (defaults to a descriptve error message)
2843		 */
2844		_requireExtension: function (name, required, before, message) {
2845			if (before != null) {
2846				before = !!before;
2847			}
2848			var thisName = this._local.name,
2849				extList = this.options.extensions,
2850				isBefore =
2851					$.inArray(name, extList) < $.inArray(thisName, extList),
2852				isMissing = required && this.ext[name] == null,
2853				badOrder = !isMissing && before != null && before !== isBefore;
2854
2855			_assert(
2856				thisName && thisName !== name,
2857				"invalid or same name '" + thisName + "' (require yourself?)"
2858			);
2859
2860			if (isMissing || badOrder) {
2861				if (!message) {
2862					if (isMissing || required) {
2863						message =
2864							"'" +
2865							thisName +
2866							"' extension requires '" +
2867							name +
2868							"'";
2869						if (badOrder) {
2870							message +=
2871								" to be registered " +
2872								(before ? "before" : "after") +
2873								" itself";
2874						}
2875					} else {
2876						message =
2877							"If used together, `" +
2878							name +
2879							"` must be registered " +
2880							(before ? "before" : "after") +
2881							" `" +
2882							thisName +
2883							"`";
2884					}
2885				}
2886				$.error(message);
2887				return false;
2888			}
2889			return true;
2890		},
2891		/** Activate node with a given key and fire focus and activate events.
2892		 *
2893		 * A previously activated node will be deactivated.
2894		 * If activeVisible option is set, all parents will be expanded as necessary.
2895		 * Pass key = false, to deactivate the current node only.
2896		 * @param {string} key
2897		 * @param {object} [opts] additional options. Defaults to {noEvents: false, noFocus: false}
2898		 * @returns {FancytreeNode} activated node (null, if not found)
2899		 */
2900		activateKey: function (key, opts) {
2901			var node = this.getNodeByKey(key);
2902			if (node) {
2903				node.setActive(true, opts);
2904			} else if (this.activeNode) {
2905				this.activeNode.setActive(false, opts);
2906			}
2907			return node;
2908		},
2909		/** (experimental) Add child status nodes that indicate 'More...', ....
2910		 * @param {boolean|object} node optional node definition. Pass `false` to remove all paging nodes.
2911		 * @param {string} [mode='append'] 'child'|firstChild'
2912		 * @since 2.15
2913		 */
2914		addPagingNode: function (node, mode) {
2915			return this.rootNode.addPagingNode(node, mode);
2916		},
2917		/**
2918		 * (experimental) Apply a modification (or navigation) operation.
2919		 *
2920		 * Valid commands:
2921		 *   - 'moveUp', 'moveDown'
2922		 *   - 'indent', 'outdent'
2923		 *   - 'remove'
2924		 *   - 'edit', 'addChild', 'addSibling': (reqires ext-edit extension)
2925		 *   - 'cut', 'copy', 'paste': (use an internal singleton 'clipboard')
2926		 *   - 'down', 'first', 'last', 'left', 'parent', 'right', 'up': navigate
2927		 *
2928		 * @param {string} cmd
2929		 * @param {FancytreeNode} [node=active_node]
2930		 * @param {object} [opts] Currently unused
2931		 *
2932		 * @since 2.32
2933		 */
2934		applyCommand: function (cmd, node, opts_) {
2935			var // clipboard,
2936				refNode;
2937			// opts = $.extend(
2938			// 	{ setActive: true, clipboard: CLIPBOARD },
2939			// 	opts_
2940			// );
2941
2942			node = node || this.getActiveNode();
2943			// clipboard = opts.clipboard;
2944
2945			switch (cmd) {
2946				// Sorting and indentation:
2947				case "moveUp":
2948					refNode = node.getPrevSibling();
2949					if (refNode) {
2950						node.moveTo(refNode, "before");
2951						node.setActive();
2952					}
2953					break;
2954				case "moveDown":
2955					refNode = node.getNextSibling();
2956					if (refNode) {
2957						node.moveTo(refNode, "after");
2958						node.setActive();
2959					}
2960					break;
2961				case "indent":
2962					refNode = node.getPrevSibling();
2963					if (refNode) {
2964						node.moveTo(refNode, "child");
2965						refNode.setExpanded();
2966						node.setActive();
2967					}
2968					break;
2969				case "outdent":
2970					if (!node.isTopLevel()) {
2971						node.moveTo(node.getParent(), "after");
2972						node.setActive();
2973					}
2974					break;
2975				// Remove:
2976				case "remove":
2977					refNode = node.getPrevSibling() || node.getParent();
2978					node.remove();
2979					if (refNode) {
2980						refNode.setActive();
2981					}
2982					break;
2983				// Add, edit (requires ext-edit):
2984				case "addChild":
2985					node.editCreateNode("child", "");
2986					break;
2987				case "addSibling":
2988					node.editCreateNode("after", "");
2989					break;
2990				case "rename":
2991					node.editStart();
2992					break;
2993				// Simple clipboard simulation:
2994				// case "cut":
2995				// 	clipboard = { mode: cmd, data: node };
2996				// 	break;
2997				// case "copy":
2998				// 	clipboard = {
2999				// 		mode: cmd,
3000				// 		data: node.toDict(function(d, n) {
3001				// 			delete d.key;
3002				// 		}),
3003				// 	};
3004				// 	break;
3005				// case "clear":
3006				// 	clipboard = null;
3007				// 	break;
3008				// case "paste":
3009				// 	if (clipboard.mode === "cut") {
3010				// 		// refNode = node.getPrevSibling();
3011				// 		clipboard.data.moveTo(node, "child");
3012				// 		clipboard.data.setActive();
3013				// 	} else if (clipboard.mode === "copy") {
3014				// 		node.addChildren(clipboard.data).setActive();
3015				// 	}
3016				// 	break;
3017				// Navigation commands:
3018				case "down":
3019				case "first":
3020				case "last":
3021				case "left":
3022				case "parent":
3023				case "right":
3024				case "up":
3025					return node.navigate(cmd);
3026				default:
3027					$.error("Unhandled command: '" + cmd + "'");
3028			}
3029		},
3030		/** (experimental) Modify existing data model.
3031		 *
3032		 * @param {Array} patchList array of [key, NodePatch] arrays
3033		 * @returns {$.Promise} resolved, when all patches have been applied
3034		 * @see TreePatch
3035		 */
3036		applyPatch: function (patchList) {
3037			var dfd,
3038				i,
3039				p2,
3040				key,
3041				patch,
3042				node,
3043				patchCount = patchList.length,
3044				deferredList = [];
3045
3046			for (i = 0; i < patchCount; i++) {
3047				p2 = patchList[i];
3048				_assert(
3049					p2.length === 2,
3050					"patchList must be an array of length-2-arrays"
3051				);
3052				key = p2[0];
3053				patch = p2[1];
3054				node = key === null ? this.rootNode : this.getNodeByKey(key);
3055				if (node) {
3056					dfd = new $.Deferred();
3057					deferredList.push(dfd);
3058					node.applyPatch(patch).always(_makeResolveFunc(dfd, node));
3059				} else {
3060					this.warn("could not find node with key '" + key + "'");
3061				}
3062			}
3063			// Return a promise that is resolved, when ALL patches were applied
3064			return $.when.apply($, deferredList).promise();
3065		},
3066		/* TODO: implement in dnd extension
3067		cancelDrag: function() {
3068				var dd = $.ui.ddmanager.current;
3069				if(dd){
3070					dd.cancel();
3071				}
3072			},
3073		*/
3074		/** Remove all nodes.
3075		 * @since 2.14
3076		 */
3077		clear: function (source) {
3078			this._callHook("treeClear", this);
3079		},
3080		/** Return the number of nodes.
3081		 * @returns {integer}
3082		 */
3083		count: function () {
3084			return this.rootNode.countChildren();
3085		},
3086		/** Write to browser console if debugLevel >= 4 (prepending tree name)
3087		 *
3088		 * @param {*} msg string or object or array of such
3089		 */
3090		debug: function (msg) {
3091			if (this.options.debugLevel >= 4) {
3092				Array.prototype.unshift.call(arguments, this.toString());
3093				consoleApply("log", arguments);
3094			}
3095		},
3096		/** Destroy this widget, restore previous markup and cleanup resources.
3097		 *
3098		 * @since 2.34
3099		 */
3100		destroy: function () {
3101			this.widget.destroy();
3102		},
3103		/** Enable (or disable) the tree control.
3104		 *
3105		 * @param {boolean} [flag=true] pass false to disable
3106		 * @since 2.30
3107		 */
3108		enable: function (flag) {
3109			if (flag === false) {
3110				this.widget.disable();
3111			} else {
3112				this.widget.enable();
3113			}
3114		},
3115		/** Temporarily suppress rendering to improve performance on bulk-updates.
3116		 *
3117		 * @param {boolean} flag
3118		 * @returns {boolean} previous status
3119		 * @since 2.19
3120		 */
3121		enableUpdate: function (flag) {
3122			flag = flag !== false;
3123			if (!!this._enableUpdate === !!flag) {
3124				return flag;
3125			}
3126			this._enableUpdate = flag;
3127			if (flag) {
3128				this.debug("enableUpdate(true): redraw "); //, this._dirtyRoots);
3129				this._callHook("treeStructureChanged", this, "enableUpdate");
3130				this.render();
3131			} else {
3132				// 	this._dirtyRoots = null;
3133				this.debug("enableUpdate(false)...");
3134			}
3135			return !flag; // return previous value
3136		},
3137		/** Write error to browser console if debugLevel >= 1 (prepending tree info)
3138		 *
3139		 * @param {*} msg string or object or array of such
3140		 */
3141		error: function (msg) {
3142			if (this.options.debugLevel >= 1) {
3143				Array.prototype.unshift.call(arguments, this.toString());
3144				consoleApply("error", arguments);
3145			}
3146		},
3147		/** Expand (or collapse) all parent nodes.
3148		 *
3149		 * This convenience method uses `tree.visit()` and `tree.setExpanded()`
3150		 * internally.
3151		 *
3152		 * @param {boolean} [flag=true] pass false to collapse
3153		 * @param {object} [opts] passed to setExpanded()
3154		 * @since 2.30
3155		 */
3156		expandAll: function (flag, opts) {
3157			var prev = this.enableUpdate(false);
3158
3159			flag = flag !== false;
3160			this.visit(function (node) {
3161				if (
3162					node.hasChildren() !== false &&
3163					node.isExpanded() !== flag
3164				) {
3165					node.setExpanded(flag, opts);
3166				}
3167			});
3168			this.enableUpdate(prev);
3169		},
3170		/**Find all nodes that matches condition.
3171		 *
3172		 * @param {string | function(node)} match title string to search for, or a
3173		 *     callback function that returns `true` if a node is matched.
3174		 * @returns {FancytreeNode[]} array of nodes (may be empty)
3175		 * @see FancytreeNode#findAll
3176		 * @since 2.12
3177		 */
3178		findAll: function (match) {
3179			return this.rootNode.findAll(match);
3180		},
3181		/**Find first node that matches condition.
3182		 *
3183		 * @param {string | function(node)} match title string to search for, or a
3184		 *     callback function that returns `true` if a node is matched.
3185		 * @returns {FancytreeNode} matching node or null
3186		 * @see FancytreeNode#findFirst
3187		 * @since 2.12
3188		 */
3189		findFirst: function (match) {
3190			return this.rootNode.findFirst(match);
3191		},
3192		/** Find the next visible node that starts with `match`, starting at `startNode`
3193		 * and wrap-around at the end.
3194		 *
3195		 * @param {string|function} match
3196		 * @param {FancytreeNode} [startNode] defaults to first node
3197		 * @returns {FancytreeNode} matching node or null
3198		 */
3199		findNextNode: function (match, startNode) {
3200			//, visibleOnly) {
3201			var res = null,
3202				firstNode = this.getFirstChild();
3203
3204			match =
3205				typeof match === "string"
3206					? _makeNodeTitleStartMatcher(match)
3207					: match;
3208			startNode = startNode || firstNode;
3209
3210			function _checkNode(n) {
3211				// console.log("_check " + n)
3212				if (match(n)) {
3213					res = n;
3214				}
3215				if (res || n === startNode) {
3216					return false;
3217				}
3218			}
3219			this.visitRows(_checkNode, {
3220				start: startNode,
3221				includeSelf: false,
3222			});
3223			// Wrap around search
3224			if (!res && startNode !== firstNode) {
3225				this.visitRows(_checkNode, {
3226					start: firstNode,
3227					includeSelf: true,
3228				});
3229			}
3230			return res;
3231		},
3232		/** Find a node relative to another node.
3233		 *
3234		 * @param {FancytreeNode} node
3235		 * @param {string|number} where 'down', 'first', 'last', 'left', 'parent', 'right', or 'up'.
3236		 *   (Alternatively the keyCode that would normally trigger this move,
3237		 *   e.g. `$.ui.keyCode.LEFT` = 'left'.
3238		 * @param {boolean} [includeHidden=false] Not yet implemented
3239		 * @returns {FancytreeNode|null}
3240		 * @since v2.31
3241		 */
3242		findRelatedNode: function (node, where, includeHidden) {
3243			var res = null,
3244				KC = $.ui.keyCode;
3245
3246			switch (where) {
3247				case "parent":
3248				case KC.BACKSPACE:
3249					if (node.parent && node.parent.parent) {
3250						res = node.parent;
3251					}
3252					break;
3253				case "first":
3254				case KC.HOME:
3255					// First visible node
3256					this.visit(function (n) {
3257						if (n.isVisible()) {
3258							res = n;
3259							return false;
3260						}
3261					});
3262					break;
3263				case "last":
3264				case KC.END:
3265					this.visit(function (n) {
3266						// last visible node
3267						if (n.isVisible()) {
3268							res = n;
3269						}
3270					});
3271					break;
3272				case "left":
3273				case KC.LEFT:
3274					if (node.expanded) {
3275						node.setExpanded(false);
3276					} else if (node.parent && node.parent.parent) {
3277						res = node.parent;
3278					}
3279					break;
3280				case "right":
3281				case KC.RIGHT:
3282					if (!node.expanded && (node.children || node.lazy)) {
3283						node.setExpanded();
3284						res = node;
3285					} else if (node.children && node.children.length) {
3286						res = node.children[0];
3287					}
3288					break;
3289				case "up":
3290				case KC.UP:
3291					this.visitRows(
3292						function (n) {
3293							res = n;
3294							return false;
3295						},
3296						{ start: node, reverse: true, includeSelf: false }
3297					);
3298					break;
3299				case "down":
3300				case KC.DOWN:
3301					this.visitRows(
3302						function (n) {
3303							res = n;
3304							return false;
3305						},
3306						{ start: node, includeSelf: false }
3307					);
3308					break;
3309				default:
3310					this.tree.warn("Unknown relation '" + where + "'.");
3311			}
3312			return res;
3313		},
3314		// TODO: fromDict
3315		/**
3316		 * Generate INPUT elements that can be submitted with html forms.
3317		 *
3318		 * In selectMode 3 only the topmost selected nodes are considered, unless
3319		 * `opts.stopOnParents: false` is passed.
3320		 *
3321		 * @example
3322		 * // Generate input elements for active and selected nodes
3323		 * tree.generateFormElements();
3324		 * // Generate input elements selected nodes, using a custom `name` attribute
3325		 * tree.generateFormElements("cust_sel", false);
3326		 * // Generate input elements using a custom filter
3327		 * tree.generateFormElements(true, true, { filter: function(node) {
3328		 *     return node.isSelected() && node.data.yes;
3329		 * }});
3330		 *
3331		 * @param {boolean | string} [selected=true] Pass false to disable, pass a string to override the field name (default: 'ft_ID[]')
3332		 * @param {boolean | string} [active=true] Pass false to disable, pass a string to override the field name (default: 'ft_ID_active')
3333		 * @param {object} [opts] default { filter: null, stopOnParents: true }
3334		 */
3335		generateFormElements: function (selected, active, opts) {
3336			opts = opts || {};
3337
3338			var nodeList,
3339				selectedName =
3340					typeof selected === "string"
3341						? selected
3342						: "ft_" + this._id + "[]",
3343				activeName =
3344					typeof active === "string"
3345						? active
3346						: "ft_" + this._id + "_active",
3347				id = "fancytree_result_" + this._id,
3348				$result = $("#" + id),
3349				stopOnParents =
3350					this.options.selectMode === 3 &&
3351					opts.stopOnParents !== false;
3352
3353			if ($result.length) {
3354				$result.empty();
3355			} else {
3356				$result = $("<div>", {
3357					id: id,
3358				})
3359					.hide()
3360					.insertAfter(this.$container);
3361			}
3362			if (active !== false && this.activeNode) {
3363				$result.append(
3364					$("<input>", {
3365						type: "radio",
3366						name: activeName,
3367						value: this.activeNode.key,
3368						checked: true,
3369					})
3370				);
3371			}
3372			function _appender(node) {
3373				$result.append(
3374					$("<input>", {
3375						type: "checkbox",
3376						name: selectedName,
3377						value: node.key,
3378						checked: true,
3379					})
3380				);
3381			}
3382			if (opts.filter) {
3383				this.visit(function (node) {
3384					var res = opts.filter(node);
3385					if (res === "skip") {
3386						return res;
3387					}
3388					if (res !== false) {
3389						_appender(node);
3390					}
3391				});
3392			} else if (selected !== false) {
3393				nodeList = this.getSelectedNodes(stopOnParents);
3394				$.each(nodeList, function (idx, node) {
3395					_appender(node);
3396				});
3397			}
3398		},
3399		/**
3400		 * Return the currently active node or null.
3401		 * @returns {FancytreeNode}
3402		 */
3403		getActiveNode: function () {
3404			return this.activeNode;
3405		},
3406		/** Return the first top level node if any (not the invisible root node).
3407		 * @returns {FancytreeNode | null}
3408		 */
3409		getFirstChild: function () {
3410			return this.rootNode.getFirstChild();
3411		},
3412		/**
3413		 * Return node that has keyboard focus or null.
3414		 * @returns {FancytreeNode}
3415		 */
3416		getFocusNode: function () {
3417			return this.focusNode;
3418		},
3419		/**
3420		 * Return current option value.
3421		 * (Note: this is the preferred variant of `$().fancytree("option", "KEY")`)
3422		 *
3423		 * @param {string} name option name (may contain '.')
3424		 * @returns {any}
3425		 */
3426		getOption: function (optionName) {
3427			return this.widget.option(optionName);
3428		},
3429		/**
3430		 * Return node with a given key or null if not found.
3431		 *
3432		 * @param {string} key
3433		 * @param {FancytreeNode} [searchRoot] only search below this node
3434		 * @returns {FancytreeNode | null}
3435		 */
3436		getNodeByKey: function (key, searchRoot) {
3437			// Search the DOM by element ID (assuming this is faster than traversing all nodes).
3438			var el, match;
3439			// TODO: use tree.keyMap if available
3440			// TODO: check opts.generateIds === true
3441			if (!searchRoot) {
3442				el = document.getElementById(this.options.idPrefix + key);
3443				if (el) {
3444					return el.ftnode ? el.ftnode : null;
3445				}
3446			}
3447			// Not found in the DOM, but still may be in an unrendered part of tree
3448			searchRoot = searchRoot || this.rootNode;
3449			match = null;
3450			key = "" + key; // Convert to string (#1005)
3451			searchRoot.visit(function (node) {
3452				if (node.key === key) {
3453					match = node;
3454					return false; // Stop iteration
3455				}
3456			}, true);
3457			return match;
3458		},
3459		/** Return the invisible system root node.
3460		 * @returns {FancytreeNode}
3461		 */
3462		getRootNode: function () {
3463			return this.rootNode;
3464		},
3465		/**
3466		 * Return an array of selected nodes.
3467		 *
3468		 * Note: you cannot send this result via Ajax directly. Instead the
3469		 * node object need to be converted to plain objects, for example
3470		 * by using `$.map()` and `node.toDict()`.
3471		 * @param {boolean} [stopOnParents=false] only return the topmost selected
3472		 *     node (useful with selectMode 3)
3473		 * @returns {FancytreeNode[]}
3474		 */
3475		getSelectedNodes: function (stopOnParents) {
3476			return this.rootNode.getSelectedNodes(stopOnParents);
3477		},
3478		/** Return true if the tree control has keyboard focus
3479		 * @returns {boolean}
3480		 */
3481		hasFocus: function () {
3482			// var ae = document.activeElement,
3483			// 	hasFocus = !!(
3484			// 		ae && $(ae).closest(".fancytree-container").length
3485			// 	);
3486
3487			// if (hasFocus !== !!this._hasFocus) {
3488			// 	this.warn(
3489			// 		"hasFocus(): fix inconsistent container state, now: " +
3490			// 			hasFocus
3491			// 	);
3492			// 	this._hasFocus = hasFocus;
3493			// 	this.$container.toggleClass("fancytree-treefocus", hasFocus);
3494			// }
3495			// return hasFocus;
3496			return !!this._hasFocus;
3497		},
3498		/** Write to browser console if debugLevel >= 3 (prepending tree name)
3499		 * @param {*} msg string or object or array of such
3500		 */
3501		info: function (msg) {
3502			if (this.options.debugLevel >= 3) {
3503				Array.prototype.unshift.call(arguments, this.toString());
3504				consoleApply("info", arguments);
3505			}
3506		},
3507		/** Return true if any node is currently beeing loaded, i.e. a Ajax request is pending.
3508		 * @returns {boolean}
3509		 * @since 2.32
3510		 */
3511		isLoading: function () {
3512			var res = false;
3513
3514			this.rootNode.visit(function (n) {
3515				// also visit rootNode
3516				if (n._isLoading || n._requestId) {
3517					res = true;
3518					return false;
3519				}
3520			}, true);
3521			return res;
3522		},
3523		/*
3524		TODO: isInitializing: function() {
3525			return ( this.phase=="init" || this.phase=="postInit" );
3526		},
3527		TODO: isReloading: function() {
3528			return ( this.phase=="init" || this.phase=="postInit" ) && this.options.persist && this.persistence.cookiesFound;
3529		},
3530		TODO: isUserEvent: function() {
3531			return ( this.phase=="userEvent" );
3532		},
3533		*/
3534
3535		/**
3536		 * Make sure that a node with a given ID is loaded, by traversing - and
3537		 * loading - its parents. This method is meant for lazy hierarchies.
3538		 * A callback is executed for every node as we go.
3539		 * @example
3540		 * // Resolve using node.key:
3541		 * tree.loadKeyPath("/_3/_23/_26/_27", function(node, status){
3542		 *   if(status === "loaded") {
3543		 *     console.log("loaded intermediate node " + node);
3544		 *   }else if(status === "ok") {
3545		 *     node.activate();
3546		 *   }
3547		 * });
3548		 * // Use deferred promise:
3549		 * tree.loadKeyPath("/_3/_23/_26/_27").progress(function(data){
3550		 *   if(data.status === "loaded") {
3551		 *     console.log("loaded intermediate node " + data.node);
3552		 *   }else if(data.status === "ok") {
3553		 *     node.activate();
3554		 *   }
3555		 * }).done(function(){
3556		 *    ...
3557		 * });
3558		 * // Custom path segment resolver:
3559		 * tree.loadKeyPath("/321/431/21/2", {
3560		 *   matchKey: function(node, key){
3561		 *     return node.data.refKey === key;
3562		 *   },
3563		 *   callback: function(node, status){
3564		 *     if(status === "loaded") {
3565		 *       console.log("loaded intermediate node " + node);
3566		 *     }else if(status === "ok") {
3567		 *       node.activate();
3568		 *     }
3569		 *   }
3570		 * });
3571		 * @param {string | string[]} keyPathList one or more key paths (e.g. '/3/2_1/7')
3572		 * @param {function | object} optsOrCallback callback(node, status) is called for every visited node ('loading', 'loaded', 'ok', 'error').
3573		 *     Pass an object to define custom key matchers for the path segments: {callback: function, matchKey: function}.
3574		 * @returns {$.Promise}
3575		 */
3576		loadKeyPath: function (keyPathList, optsOrCallback) {
3577			var callback,
3578				i,
3579				path,
3580				self = this,
3581				dfd = new $.Deferred(),
3582				parent = this.getRootNode(),
3583				sep = this.options.keyPathSeparator,
3584				pathSegList = [],
3585				opts = $.extend({}, optsOrCallback);
3586
3587			// Prepare options
3588			if (typeof optsOrCallback === "function") {
3589				callback = optsOrCallback;
3590			} else if (optsOrCallback && optsOrCallback.callback) {
3591				callback = optsOrCallback.callback;
3592			}
3593			opts.callback = function (ctx, node, status) {
3594				if (callback) {
3595					callback.call(ctx, node, status);
3596				}
3597				dfd.notifyWith(ctx, [{ node: node, status: status }]);
3598			};
3599			if (opts.matchKey == null) {
3600				opts.matchKey = function (node, key) {
3601					return node.key === key;
3602				};
3603			}
3604			// Convert array of path strings to array of segment arrays
3605			if (!_isArray(keyPathList)) {
3606				keyPathList = [keyPathList];
3607			}
3608			for (i = 0; i < keyPathList.length; i++) {
3609				path = keyPathList[i];
3610				// strip leading slash
3611				if (path.charAt(0) === sep) {
3612					path = path.substr(1);
3613				}
3614				// segListMap[path] = { parent: parent, segList: path.split(sep) };
3615				pathSegList.push(path.split(sep));
3616				// targetList.push({ parent: parent, segList: path.split(sep)/* , path: path*/});
3617			}
3618			// The timeout forces async behavior always (even if nodes are all loaded)
3619			// This way a potential progress() event will fire.
3620			setTimeout(function () {
3621				self._loadKeyPathImpl(dfd, opts, parent, pathSegList).done(
3622					function () {
3623						dfd.resolve();
3624					}
3625				);
3626			}, 0);
3627			return dfd.promise();
3628		},
3629		/*
3630		 * Resolve a list of paths, relative to one parent node.
3631		 */
3632		_loadKeyPathImpl: function (dfd, opts, parent, pathSegList) {
3633			var deferredList,
3634				i,
3635				key,
3636				node,
3637				nodeKey,
3638				remain,
3639				remainMap,
3640				tmpParent,
3641				segList,
3642				subDfd,
3643				self = this;
3644
3645			function __findChild(parent, key) {
3646				// console.log("__findChild", key, parent);
3647				var i,
3648					l,
3649					cl = parent.children;
3650
3651				if (cl) {
3652					for (i = 0, l = cl.length; i < l; i++) {
3653						if (opts.matchKey(cl[i], key)) {
3654							return cl[i];
3655						}
3656					}
3657				}
3658				return null;
3659			}
3660
3661			// console.log("_loadKeyPathImpl, parent=", parent, ", pathSegList=", pathSegList);
3662
3663			// Pass 1:
3664			// Handle all path segments for nodes that are already loaded.
3665			// Collect distinct top-most lazy nodes in a map.
3666			// Note that we can use node.key to de-dupe entries, even if a custom matcher would
3667			// look for other node attributes.
3668			// map[node.key] => {node: node, pathList: [list of remaining rest-paths]}
3669			remainMap = {};
3670
3671			for (i = 0; i < pathSegList.length; i++) {
3672				segList = pathSegList[i];
3673				// target = targetList[i];
3674
3675				// Traverse and pop path segments (i.e. keys), until we hit a lazy, unloaded node
3676				tmpParent = parent;
3677				while (segList.length) {
3678					key = segList.shift();
3679					node = __findChild(tmpParent, key);
3680					if (!node) {
3681						this.warn(
3682							"loadKeyPath: key not found: " +
3683								key +
3684								" (parent: " +
3685								tmpParent +
3686								")"
3687						);
3688						opts.callback(this, key, "error");
3689						break;
3690					} else if (segList.length === 0) {
3691						opts.callback(this, node, "ok");
3692						break;
3693					} else if (!node.lazy || node.hasChildren() !== undefined) {
3694						opts.callback(this, node, "loaded");
3695						tmpParent = node;
3696					} else {
3697						opts.callback(this, node, "loaded");
3698						key = node.key; //target.segList.join(sep);
3699						if (remainMap[key]) {
3700							remainMap[key].pathSegList.push(segList);
3701						} else {
3702							remainMap[key] = {
3703								parent: node,
3704								pathSegList: [segList],
3705							};
3706						}
3707						break;
3708					}
3709				}
3710			}
3711			// console.log("_loadKeyPathImpl AFTER pass 1, remainMap=", remainMap);
3712
3713			// Now load all lazy nodes and continue iteration for remaining paths
3714			deferredList = [];
3715
3716			// Avoid jshint warning 'Don't make functions within a loop.':
3717			function __lazyload(dfd, parent, pathSegList) {
3718				// console.log("__lazyload", parent, "pathSegList=", pathSegList);
3719				opts.callback(self, parent, "loading");
3720				parent
3721					.load()
3722					.done(function () {
3723						self._loadKeyPathImpl
3724							.call(self, dfd, opts, parent, pathSegList)
3725							.always(_makeResolveFunc(dfd, self));
3726					})
3727					.fail(function (errMsg) {
3728						self.warn("loadKeyPath: error loading lazy " + parent);
3729						opts.callback(self, node, "error");
3730						dfd.rejectWith(self);
3731					});
3732			}
3733			// remainMap contains parent nodes, each with a list of relative sub-paths.
3734			// We start loading all of them now, and pass the the list to each loader.
3735			for (nodeKey in remainMap) {
3736				if (_hasProp(remainMap, nodeKey)) {
3737					remain = remainMap[nodeKey];
3738					// console.log("for(): remain=", remain, "remainMap=", remainMap);
3739					// key = remain.segList.shift();
3740					// node = __findChild(remain.parent, key);
3741					// if (node == null) {  // #576
3742					// 	// Issue #576, refactored for v2.27:
3743					// 	// The root cause was, that sometimes the wrong parent was used here
3744					// 	// to find the next segment.
3745					// 	// Falling back to getNodeByKey() was a hack that no longer works if a custom
3746					// 	// matcher is used, because we cannot assume that a single segment-key is unique
3747					// 	// throughout the tree.
3748					// 	self.error("loadKeyPath: error loading child by key '" + key + "' (parent: " + target.parent + ")", target);
3749					// 	// 	node = self.getNodeByKey(key);
3750					// 	continue;
3751					// }
3752					subDfd = new $.Deferred();
3753					deferredList.push(subDfd);
3754					__lazyload(subDfd, remain.parent, remain.pathSegList);
3755				}
3756			}
3757			// Return a promise that is resolved, when ALL paths were loaded
3758			return $.when.apply($, deferredList).promise();
3759		},
3760		/** Re-fire beforeActivate, activate, and (optional) focus events.
3761		 * Calling this method in the `init` event, will activate the node that
3762		 * was marked 'active' in the source data, and optionally set the keyboard
3763		 * focus.
3764		 * @param [setFocus=false]
3765		 */
3766		reactivate: function (setFocus) {
3767			var res,
3768				node = this.activeNode;
3769
3770			if (!node) {
3771				return _getResolvedPromise();
3772			}
3773			this.activeNode = null; // Force re-activating
3774			res = node.setActive(true, { noFocus: true });
3775			if (setFocus) {
3776				node.setFocus();
3777			}
3778			return res;
3779		},
3780		/** Reload tree from source and return a promise.
3781		 * @param [source] optional new source (defaults to initial source data)
3782		 * @returns {$.Promise}
3783		 */
3784		reload: function (source) {
3785			this._callHook("treeClear", this);
3786			return this._callHook("treeLoad", this, source);
3787		},
3788		/**Render tree (i.e. create DOM elements for all top-level nodes).
3789		 * @param {boolean} [force=false] create DOM elemnts, even if parent is collapsed
3790		 * @param {boolean} [deep=false]
3791		 */
3792		render: function (force, deep) {
3793			return this.rootNode.render(force, deep);
3794		},
3795		/**(De)select all nodes.
3796		 * @param {boolean} [flag=true]
3797		 * @since 2.28
3798		 */
3799		selectAll: function (flag) {
3800			this.visit(function (node) {
3801				node.setSelected(flag);
3802			});
3803		},
3804		// TODO: selectKey: function(key, select)
3805		// TODO: serializeArray: function(stopOnParents)
3806		/**
3807		 * @param {boolean} [flag=true]
3808		 */
3809		setFocus: function (flag) {
3810			return this._callHook("treeSetFocus", this, flag);
3811		},
3812		/**
3813		 * Set current option value.
3814		 * (Note: this is the preferred variant of `$().fancytree("option", "KEY", VALUE)`)
3815		 * @param {string} name option name (may contain '.')
3816		 * @param {any} new value
3817		 */
3818		setOption: function (optionName, value) {
3819			return this.widget.option(optionName, value);
3820		},
3821		/**
3822		 * Call console.time() when in debug mode (verbose >= 4).
3823		 *
3824		 * @param {string} label
3825		 */
3826		debugTime: function (label) {
3827			if (this.options.debugLevel >= 4) {
3828				window.console.time(this + " - " + label);
3829			}
3830		},
3831		/**
3832		 * Call console.timeEnd() when in debug mode (verbose >= 4).
3833		 *
3834		 * @param {string} label
3835		 */
3836		debugTimeEnd: function (label) {
3837			if (this.options.debugLevel >= 4) {
3838				window.console.timeEnd(this + " - " + label);
3839			}
3840		},
3841		/**
3842		 * Return all nodes as nested list of {@link NodeData}.
3843		 *
3844		 * @param {boolean} [includeRoot=false] Returns the hidden system root node (and its children)
3845		 * @param {function} [callback] callback(dict, node) is called for every node, in order to allow modifications.
3846		 *     Return `false` to ignore this node or "skip" to include this node without its children.
3847		 * @returns {Array | object}
3848		 * @see FancytreeNode#toDict
3849		 */
3850		toDict: function (includeRoot, callback) {
3851			var res = this.rootNode.toDict(true, callback);
3852			return includeRoot ? res : res.children;
3853		},
3854		/* Implicitly called for string conversions.
3855		 * @returns {string}
3856		 */
3857		toString: function () {
3858			return "Fancytree@" + this._id;
3859			// return "<Fancytree(#" + this._id + ")>";
3860		},
3861		/* _trigger a widget event with additional node ctx.
3862		 * @see EventData
3863		 */
3864		_triggerNodeEvent: function (type, node, originalEvent, extra) {
3865			// this.debug("_trigger(" + type + "): '" + ctx.node.title + "'", ctx);
3866			var ctx = this._makeHookContext(node, originalEvent, extra),
3867				res = this.widget._trigger(type, originalEvent, ctx);
3868			if (res !== false && ctx.result !== undefined) {
3869				return ctx.result;
3870			}
3871			return res;
3872		},
3873		/* _trigger a widget event with additional tree data. */
3874		_triggerTreeEvent: function (type, originalEvent, extra) {
3875			// this.debug("_trigger(" + type + ")", ctx);
3876			var ctx = this._makeHookContext(this, originalEvent, extra),
3877				res = this.widget._trigger(type, originalEvent, ctx);
3878
3879			if (res !== false && ctx.result !== undefined) {
3880				return ctx.result;
3881			}
3882			return res;
3883		},
3884		/** Call fn(node) for all nodes in hierarchical order (depth-first).
3885		 *
3886		 * @param {function} fn the callback function.
3887		 *     Return false to stop iteration, return "skip" to skip this node and children only.
3888		 * @returns {boolean} false, if the iterator was stopped.
3889		 */
3890		visit: function (fn) {
3891			return this.rootNode.visit(fn, false);
3892		},
3893		/** Call fn(node) for all nodes in vertical order, top down (or bottom up).<br>
3894		 * Stop iteration, if fn() returns false.<br>
3895		 * Return false if iteration was stopped.
3896		 *
3897		 * @param {function} fn the callback function.
3898		 *     Return false to stop iteration, return "skip" to skip this node and children only.
3899		 * @param {object} [options]
3900		 *     Defaults:
3901		 *     {start: First top node, reverse: false, includeSelf: true, includeHidden: false}
3902		 * @returns {boolean} false if iteration was cancelled
3903		 * @since 2.28
3904		 */
3905		visitRows: function (fn, opts) {
3906			if (!this.rootNode.hasChildren()) {
3907				return false;
3908			}
3909			if (opts && opts.reverse) {
3910				delete opts.reverse;
3911				return this._visitRowsUp(fn, opts);
3912			}
3913			opts = opts || {};
3914
3915			var i,
3916				nextIdx,
3917				parent,
3918				res,
3919				siblings,
3920				siblingOfs = 0,
3921				skipFirstNode = opts.includeSelf === false,
3922				includeHidden = !!opts.includeHidden,
3923				checkFilter = !includeHidden && this.enableFilter,
3924				node = opts.start || this.rootNode.children[0];
3925
3926			parent = node.parent;
3927			while (parent) {
3928				// visit siblings
3929				siblings = parent.children;
3930				nextIdx = siblings.indexOf(node) + siblingOfs;
3931				_assert(
3932					nextIdx >= 0,
3933					"Could not find " +
3934						node +
3935						" in parent's children: " +
3936						parent
3937				);
3938
3939				for (i = nextIdx; i < siblings.length; i++) {
3940					node = siblings[i];
3941					if (checkFilter && !node.match && !node.subMatchCount) {
3942						continue;
3943					}
3944					if (!skipFirstNode && fn(node) === false) {
3945						return false;
3946					}
3947					skipFirstNode = false;
3948					// Dive into node's child nodes
3949					if (
3950						node.children &&
3951						node.children.length &&
3952						(includeHidden || node.expanded)
3953					) {
3954						// Disable warning: Functions declared within loops referencing an outer
3955						// scoped variable may lead to confusing semantics:
3956						/*jshint -W083 */
3957						res = node.visit(function (n) {
3958							if (checkFilter && !n.match && !n.subMatchCount) {
3959								return "skip";
3960							}
3961							if (fn(n) === false) {
3962								return false;
3963							}
3964							if (!includeHidden && n.children && !n.expanded) {
3965								return "skip";
3966							}
3967						}, false);
3968						/*jshint +W083 */
3969						if (res === false) {
3970							return false;
3971						}
3972					}
3973				}
3974				// Visit parent nodes (bottom up)
3975				node = parent;
3976				parent = parent.parent;
3977				siblingOfs = 1; //
3978			}
3979			return true;
3980		},
3981		/* Call fn(node) for all nodes in vertical order, bottom up.
3982		 */
3983		_visitRowsUp: function (fn, opts) {
3984			var children,
3985				idx,
3986				parent,
3987				includeHidden = !!opts.includeHidden,
3988				node = opts.start || this.rootNode.children[0];
3989
3990			while (true) {
3991				parent = node.parent;
3992				children = parent.children;
3993
3994				if (children[0] === node) {
3995					// If this is already the first sibling, goto parent
3996					node = parent;
3997					if (!node.parent) {
3998						break; // first node of the tree
3999					}
4000					children = parent.children;
4001				} else {
4002					// Otherwise, goto prev. sibling
4003					idx = children.indexOf(node);
4004					node = children[idx - 1];
4005					// If the prev. sibling has children, follow down to last descendant
4006					while (
4007						// See: https://github.com/eslint/eslint/issues/11302
4008						// eslint-disable-next-line no-unmodified-loop-condition
4009						(includeHidden || node.expanded) &&
4010						node.children &&
4011						node.children.length
4012					) {
4013						children = node.children;
4014						parent = node;
4015						node = children[children.length - 1];
4016					}
4017				}
4018				// Skip invisible
4019				if (!includeHidden && !node.isVisible()) {
4020					continue;
4021				}
4022				if (fn(node) === false) {
4023					return false;
4024				}
4025			}
4026		},
4027		/** Write warning to browser console if debugLevel >= 2 (prepending tree info)
4028		 *
4029		 * @param {*} msg string or object or array of such
4030		 */
4031		warn: function (msg) {
4032			if (this.options.debugLevel >= 2) {
4033				Array.prototype.unshift.call(arguments, this.toString());
4034				consoleApply("warn", arguments);
4035			}
4036		},
4037	};
4038
4039	/**
4040	 * These additional methods of the {@link Fancytree} class are 'hook functions'
4041	 * that can be used and overloaded by extensions.
4042	 *
4043	 * @see [writing extensions](https://github.com/mar10/fancytree/wiki/TutorialExtensions)
4044	 * @mixin Fancytree_Hooks
4045	 */
4046	$.extend(
4047		Fancytree.prototype,
4048		/** @lends Fancytree_Hooks# */
4049		{
4050			/** Default handling for mouse click events.
4051			 *
4052			 * @param {EventData} ctx
4053			 */
4054			nodeClick: function (ctx) {
4055				var activate,
4056					expand,
4057					// event = ctx.originalEvent,
4058					targetType = ctx.targetType,
4059					node = ctx.node;
4060
4061				// this.debug("ftnode.onClick(" + event.type + "): ftnode:" + this + ", button:" + event.button + ", which: " + event.which, ctx);
4062				// TODO: use switch
4063				// TODO: make sure clicks on embedded <input> doesn't steal focus (see table sample)
4064				if (targetType === "expander") {
4065					if (node.isLoading()) {
4066						// #495: we probably got a click event while a lazy load is pending.
4067						// The 'expanded' state is not yet set, so 'toggle' would expand
4068						// and trigger lazyLoad again.
4069						// It would be better to allow to collapse/expand the status node
4070						// while loading (instead of ignoring), but that would require some
4071						// more work.
4072						node.debug("Got 2nd click while loading: ignored");
4073						return;
4074					}
4075					// Clicking the expander icon always expands/collapses
4076					this._callHook("nodeToggleExpanded", ctx);
4077				} else if (targetType === "checkbox") {
4078					// Clicking the checkbox always (de)selects
4079					this._callHook("nodeToggleSelected", ctx);
4080					if (ctx.options.focusOnSelect) {
4081						// #358
4082						this._callHook("nodeSetFocus", ctx, true);
4083					}
4084				} else {
4085					// Honor `clickFolderMode` for
4086					expand = false;
4087					activate = true;
4088					if (node.folder) {
4089						switch (ctx.options.clickFolderMode) {
4090							case 2: // expand only
4091								expand = true;
4092								activate = false;
4093								break;
4094							case 3: // expand and activate
4095								activate = true;
4096								expand = true; //!node.isExpanded();
4097								break;
4098							// else 1 or 4: just activate
4099						}
4100					}
4101					if (activate) {
4102						this.nodeSetFocus(ctx);
4103						this._callHook("nodeSetActive", ctx, true);
4104					}
4105					if (expand) {
4106						if (!activate) {
4107							// this._callHook("nodeSetFocus", ctx);
4108						}
4109						// this._callHook("nodeSetExpanded", ctx, true);
4110						this._callHook("nodeToggleExpanded", ctx);
4111					}
4112				}
4113				// Make sure that clicks stop, otherwise <a href='#'> jumps to the top
4114				// if(event.target.localName === "a" && event.target.className === "fancytree-title"){
4115				// 	event.preventDefault();
4116				// }
4117				// TODO: return promise?
4118			},
4119			/** Collapse all other  children of same parent.
4120			 *
4121			 * @param {EventData} ctx
4122			 * @param {object} callOpts
4123			 */
4124			nodeCollapseSiblings: function (ctx, callOpts) {
4125				// TODO: return promise?
4126				var ac,
4127					i,
4128					l,
4129					node = ctx.node;
4130
4131				if (node.parent) {
4132					ac = node.parent.children;
4133					for (i = 0, l = ac.length; i < l; i++) {
4134						if (ac[i] !== node && ac[i].expanded) {
4135							this._callHook(
4136								"nodeSetExpanded",
4137								ac[i],
4138								false,
4139								callOpts
4140							);
4141						}
4142					}
4143				}
4144			},
4145			/** Default handling for mouse douleclick events.
4146			 * @param {EventData} ctx
4147			 */
4148			nodeDblclick: function (ctx) {
4149				// TODO: return promise?
4150				if (
4151					ctx.targetType === "title" &&
4152					ctx.options.clickFolderMode === 4
4153				) {
4154					// this.nodeSetFocus(ctx);
4155					// this._callHook("nodeSetActive", ctx, true);
4156					this._callHook("nodeToggleExpanded", ctx);
4157				}
4158				// TODO: prevent text selection on dblclicks
4159				if (ctx.targetType === "title") {
4160					ctx.originalEvent.preventDefault();
4161				}
4162			},
4163			/** Default handling for mouse keydown events.
4164			 *
4165			 * NOTE: this may be called with node == null if tree (but no node) has focus.
4166			 * @param {EventData} ctx
4167			 */
4168			nodeKeydown: function (ctx) {
4169				// TODO: return promise?
4170				var matchNode,
4171					stamp,
4172					_res,
4173					focusNode,
4174					event = ctx.originalEvent,
4175					node = ctx.node,
4176					tree = ctx.tree,
4177					opts = ctx.options,
4178					which = event.which,
4179					// #909: Use event.key, to get unicode characters.
4180					// We can't use `/\w/.test(key)`, because that would
4181					// only detect plain ascii alpha-numerics. But we still need
4182					// to ignore modifier-only, whitespace, cursor-keys, etc.
4183					key = event.key || String.fromCharCode(which),
4184					specialModifiers = !!(
4185						event.altKey ||
4186						event.ctrlKey ||
4187						event.metaKey
4188					),
4189					isAlnum =
4190						!MODIFIERS[which] &&
4191						!SPECIAL_KEYCODES[which] &&
4192						!specialModifiers,
4193					$target = $(event.target),
4194					handled = true,
4195					activate = !(event.ctrlKey || !opts.autoActivate);
4196
4197				// (node || FT).debug("ftnode.nodeKeydown(" + event.type + "): ftnode:" + this + ", charCode:" + event.charCode + ", keyCode: " + event.keyCode + ", which: " + event.which);
4198				// FT.debug( "eventToString(): " + FT.eventToString(event) + ", key='" + key + "', isAlnum: " + isAlnum );
4199
4200				// Set focus to active (or first node) if no other node has the focus yet
4201				if (!node) {
4202					focusNode = this.getActiveNode() || this.getFirstChild();
4203					if (focusNode) {
4204						focusNode.setFocus();
4205						node = ctx.node = this.focusNode;
4206						node.debug("Keydown force focus on active node");
4207					}
4208				}
4209
4210				if (
4211					opts.quicksearch &&
4212					isAlnum &&
4213					!$target.is(":input:enabled")
4214				) {
4215					// Allow to search for longer streaks if typed in quickly
4216					stamp = Date.now();
4217					if (stamp - tree.lastQuicksearchTime > 500) {
4218						tree.lastQuicksearchTerm = "";
4219					}
4220					tree.lastQuicksearchTime = stamp;
4221					tree.lastQuicksearchTerm += key;
4222					// tree.debug("quicksearch find", tree.lastQuicksearchTerm);
4223					matchNode = tree.findNextNode(
4224						tree.lastQuicksearchTerm,
4225						tree.getActiveNode()
4226					);
4227					if (matchNode) {
4228						matchNode.setActive();
4229					}
4230					event.preventDefault();
4231					return;
4232				}
4233				switch (FT.eventToString(event)) {
4234					case "+":
4235					case "=": // 187: '+' @ Chrome, Safari
4236						tree.nodeSetExpanded(ctx, true);
4237						break;
4238					case "-":
4239						tree.nodeSetExpanded(ctx, false);
4240						break;
4241					case "space":
4242						if (node.isPagingNode()) {
4243							tree._triggerNodeEvent("clickPaging", ctx, event);
4244						} else if (
4245							FT.evalOption("checkbox", node, node, opts, false)
4246						) {
4247							// #768
4248							tree.nodeToggleSelected(ctx);
4249						} else {
4250							tree.nodeSetActive(ctx, true);
4251						}
4252						break;
4253					case "return":
4254						tree.nodeSetActive(ctx, true);
4255						break;
4256					case "home":
4257					case "end":
4258					case "backspace":
4259					case "left":
4260					case "right":
4261					case "up":
4262					case "down":
4263						_res = node.navigate(event.which, activate);
4264						break;
4265					default:
4266						handled = false;
4267				}
4268				if (handled) {
4269					event.preventDefault();
4270				}
4271			},
4272
4273			// /** Default handling for mouse keypress events. */
4274			// nodeKeypress: function(ctx) {
4275			//     var event = ctx.originalEvent;
4276			// },
4277
4278			// /** Trigger lazyLoad event (async). */
4279			// nodeLazyLoad: function(ctx) {
4280			//     var node = ctx.node;
4281			//     if(this._triggerNodeEvent())
4282			// },
4283			/** Load child nodes (async).
4284			 *
4285			 * @param {EventData} ctx
4286			 * @param {object[]|object|string|$.Promise|function} source
4287			 * @returns {$.Promise} The deferred will be resolved as soon as the (ajax)
4288			 *     data was rendered.
4289			 */
4290			nodeLoadChildren: function (ctx, source) {
4291				var ajax,
4292					delay,
4293					ajaxDfd = null,
4294					resultDfd,
4295					isAsync = true,
4296					tree = ctx.tree,
4297					node = ctx.node,
4298					nodePrevParent = node.parent,
4299					tag = "nodeLoadChildren",
4300					requestId = Date.now();
4301
4302				// `source` is a callback: use the returned result instead:
4303				if (_isFunction(source)) {
4304					source = source.call(tree, { type: "source" }, ctx);
4305					_assert(
4306						!_isFunction(source),
4307						"source callback must not return another function"
4308					);
4309				}
4310				// `source` is already a promise:
4311				if (_isFunction(source.then)) {
4312					// _assert(_isFunction(source.always), "Expected jQuery?");
4313					ajaxDfd = source;
4314				} else if (source.url) {
4315					// `source` is an Ajax options object
4316					ajax = $.extend({}, ctx.options.ajax, source);
4317					if (ajax.debugDelay) {
4318						// Simulate a slow server
4319						delay = ajax.debugDelay;
4320						delete ajax.debugDelay; // remove debug option
4321						if (_isArray(delay)) {
4322							// random delay range [min..max]
4323							delay =
4324								delay[0] +
4325								Math.random() * (delay[1] - delay[0]);
4326						}
4327						node.warn(
4328							"nodeLoadChildren waiting debugDelay " +
4329								Math.round(delay) +
4330								" ms ..."
4331						);
4332						ajaxDfd = $.Deferred(function (ajaxDfd) {
4333							setTimeout(function () {
4334								$.ajax(ajax)
4335									.done(function () {
4336										ajaxDfd.resolveWith(this, arguments);
4337									})
4338									.fail(function () {
4339										ajaxDfd.rejectWith(this, arguments);
4340									});
4341							}, delay);
4342						});
4343					} else {
4344						ajaxDfd = $.ajax(ajax);
4345					}
4346				} else if ($.isPlainObject(source) || _isArray(source)) {
4347					// `source` is already a constant dict or list, but we convert
4348					// to a thenable for unified processing.
4349					// 2020-01-03: refactored.
4350					// `ajaxDfd = $.when(source)` would do the trick, but the returned
4351					// promise will resolve async, which broke some tests and
4352					// would probably also break current implementations out there.
4353					// So we mock-up a thenable that resolves synchronously:
4354					ajaxDfd = {
4355						then: function (resolve, reject) {
4356							resolve(source, null, null);
4357						},
4358					};
4359					isAsync = false;
4360				} else {
4361					$.error("Invalid source type: " + source);
4362				}
4363
4364				// Check for overlapping requests
4365				if (node._requestId) {
4366					node.warn(
4367						"Recursive load request #" +
4368							requestId +
4369							" while #" +
4370							node._requestId +
4371							" is pending."
4372					);
4373					node._requestId = requestId;
4374					// 	node.debug("Send load request #" + requestId);
4375				}
4376
4377				if (isAsync) {
4378					tree.debugTime(tag);
4379					tree.nodeSetStatus(ctx, "loading");
4380				}
4381
4382				// The async Ajax request has now started...
4383				// Defer the deferred:
4384				// we want to be able to reject invalid responses, even if
4385				// the raw HTTP Ajax XHR resolved as Ok.
4386				// We use the ajaxDfd.then() syntax here, which is compatible with
4387				// jQuery and ECMA6.
4388				// However resultDfd is a jQuery deferred, which is currently the
4389				// expected result type of nodeLoadChildren()
4390				resultDfd = new $.Deferred();
4391				ajaxDfd.then(
4392					function (data, textStatus, jqXHR) {
4393						// ajaxDfd was resolved, but we reject or resolve resultDfd
4394						// depending on the response data
4395						var errorObj, res;
4396
4397						if (
4398							(source.dataType === "json" ||
4399								source.dataType === "jsonp") &&
4400							typeof data === "string"
4401						) {
4402							$.error(
4403								"Ajax request returned a string (did you get the JSON dataType wrong?)."
4404							);
4405						}
4406						if (node._requestId && node._requestId > requestId) {
4407							// The expected request time stamp is later than `requestId`
4408							// (which was kept as as closure variable to this handler function)
4409							// node.warn("Ignored load response for obsolete request #" + requestId + " (expected #" + node._requestId + ")");
4410							resultDfd.rejectWith(this, [
4411								RECURSIVE_REQUEST_ERROR,
4412							]);
4413							return;
4414							// } else {
4415							// 	node.debug("Response returned for load request #" + requestId);
4416						}
4417						if (node.parent === null && nodePrevParent !== null) {
4418							resultDfd.rejectWith(this, [
4419								INVALID_REQUEST_TARGET_ERROR,
4420							]);
4421							return;
4422						}
4423						// Allow to adjust the received response data in the `postProcess` event.
4424						if (ctx.options.postProcess) {
4425							// The handler may either
4426							//   - modify `ctx.response` in-place (and leave `ctx.result` undefined)
4427							//     => res = undefined
4428							//   - return a replacement in `ctx.result`
4429							//     => res = <new data>
4430							//   If res contains an `error` property, an error status is displayed
4431							try {
4432								res = tree._triggerNodeEvent(
4433									"postProcess",
4434									ctx,
4435									ctx.originalEvent,
4436									{
4437										response: data,
4438										error: null,
4439										dataType: source.dataType,
4440									}
4441								);
4442								if (res.error) {
4443									tree.warn(
4444										"postProcess returned error:",
4445										res
4446									);
4447								}
4448							} catch (e) {
4449								res = {
4450									error: e,
4451									message: "" + e,
4452									details: "postProcess failed",
4453								};
4454							}
4455							if (res.error) {
4456								// Either postProcess failed with an exception, or the returned
4457								// result object has an 'error' property attached:
4458								errorObj = $.isPlainObject(res.error)
4459									? res.error
4460									: { message: res.error };
4461								errorObj = tree._makeHookContext(
4462									node,
4463									null,
4464									errorObj
4465								);
4466								resultDfd.rejectWith(this, [errorObj]);
4467								return;
4468							}
4469							if (
4470								_isArray(res) ||
4471								($.isPlainObject(res) && _isArray(res.children))
4472							) {
4473								// Use `ctx.result` if valid
4474								// (otherwise use existing data, which may have been modified in-place)
4475								data = res;
4476							}
4477						} else if (
4478							data &&
4479							_hasProp(data, "d") &&
4480							ctx.options.enableAspx
4481						) {
4482							// Process ASPX WebMethod JSON object inside "d" property
4483							// (only if no postProcess event was defined)
4484							if (ctx.options.enableAspx === 42) {
4485								tree.warn(
4486									"The default for enableAspx will change to `false` in the fututure. " +
4487										"Pass `enableAspx: true` or implement postProcess to silence this warning."
4488								);
4489							}
4490							data =
4491								typeof data.d === "string"
4492									? $.parseJSON(data.d)
4493									: data.d;
4494						}
4495						resultDfd.resolveWith(this, [data]);
4496					},
4497					function (jqXHR, textStatus, errorThrown) {
4498						// ajaxDfd was rejected, so we reject resultDfd as well
4499						var errorObj = tree._makeHookContext(node, null, {
4500							error: jqXHR,
4501							args: Array.prototype.slice.call(arguments),
4502							message: errorThrown,
4503							details: jqXHR.status + ": " + errorThrown,
4504						});
4505						resultDfd.rejectWith(this, [errorObj]);
4506					}
4507				);
4508
4509				// The async Ajax request has now started.
4510				// resultDfd will be resolved/rejected after the response arrived,
4511				// was postProcessed, and checked.
4512				// Now we implement the UI update and add the data to the tree.
4513				// We also return this promise to the caller.
4514				resultDfd
4515					.done(function (data) {
4516						tree.nodeSetStatus(ctx, "ok");
4517						var children, metaData, noDataRes;
4518
4519						if ($.isPlainObject(data)) {
4520							// We got {foo: 'abc', children: [...]}
4521							// Copy extra properties to tree.data.foo
4522							_assert(
4523								node.isRootNode(),
4524								"source may only be an object for root nodes (expecting an array of child objects otherwise)"
4525							);
4526							_assert(
4527								_isArray(data.children),
4528								"if an object is passed as source, it must contain a 'children' array (all other properties are added to 'tree.data')"
4529							);
4530							metaData = data;
4531							children = data.children;
4532							delete metaData.children;
4533							// Copy some attributes to tree.data
4534							$.each(TREE_ATTRS, function (i, attr) {
4535								if (metaData[attr] !== undefined) {
4536									tree[attr] = metaData[attr];
4537									delete metaData[attr];
4538								}
4539							});
4540							// Copy all other attributes to tree.data.NAME
4541							$.extend(tree.data, metaData);
4542						} else {
4543							children = data;
4544						}
4545						_assert(
4546							_isArray(children),
4547							"expected array of children"
4548						);
4549						node._setChildren(children);
4550
4551						if (tree.options.nodata && children.length === 0) {
4552							if (_isFunction(tree.options.nodata)) {
4553								noDataRes = tree.options.nodata.call(
4554									tree,
4555									{ type: "nodata" },
4556									ctx
4557								);
4558							} else if (
4559								tree.options.nodata === true &&
4560								node.isRootNode()
4561							) {
4562								noDataRes = tree.options.strings.noData;
4563							} else if (
4564								typeof tree.options.nodata === "string" &&
4565								node.isRootNode()
4566							) {
4567								noDataRes = tree.options.nodata;
4568							}
4569							if (noDataRes) {
4570								node.setStatus("nodata", noDataRes);
4571							}
4572						}
4573						// trigger fancytreeloadchildren
4574						tree._triggerNodeEvent("loadChildren", node);
4575					})
4576					.fail(function (error) {
4577						var ctxErr;
4578
4579						if (error === RECURSIVE_REQUEST_ERROR) {
4580							node.warn(
4581								"Ignored response for obsolete load request #" +
4582									requestId +
4583									" (expected #" +
4584									node._requestId +
4585									")"
4586							);
4587							return;
4588						} else if (error === INVALID_REQUEST_TARGET_ERROR) {
4589							node.warn(
4590								"Lazy parent node was removed while loading: discarding response."
4591							);
4592							return;
4593						} else if (error.node && error.error && error.message) {
4594							// error is already a context object
4595							ctxErr = error;
4596						} else {
4597							ctxErr = tree._makeHookContext(node, null, {
4598								error: error, // it can be jqXHR or any custom error
4599								args: Array.prototype.slice.call(arguments),
4600								message: error
4601									? error.message || error.toString()
4602									: "",
4603							});
4604							if (ctxErr.message === "[object Object]") {
4605								ctxErr.message = "";
4606							}
4607						}
4608						node.warn(
4609							"Load children failed (" + ctxErr.message + ")",
4610							ctxErr
4611						);
4612						if (
4613							tree._triggerNodeEvent(
4614								"loadError",
4615								ctxErr,
4616								null
4617							) !== false
4618						) {
4619							tree.nodeSetStatus(
4620								ctx,
4621								"error",
4622								ctxErr.message,
4623								ctxErr.details
4624							);
4625						}
4626					})
4627					.always(function () {
4628						node._requestId = null;
4629						if (isAsync) {
4630							tree.debugTimeEnd(tag);
4631						}
4632					});
4633
4634				return resultDfd.promise();
4635			},
4636			/** [Not Implemented]  */
4637			nodeLoadKeyPath: function (ctx, keyPathList) {
4638				// TODO: implement and improve
4639				// http://code.google.com/p/dynatree/issues/detail?id=222
4640			},
4641			/**
4642			 * Remove a single direct child of ctx.node.
4643			 * @param {EventData} ctx
4644			 * @param {FancytreeNode} childNode dircect child of ctx.node
4645			 */
4646			nodeRemoveChild: function (ctx, childNode) {
4647				var idx,
4648					node = ctx.node,
4649					// opts = ctx.options,
4650					subCtx = $.extend({}, ctx, { node: childNode }),
4651					children = node.children;
4652
4653				// FT.debug("nodeRemoveChild()", node.toString(), childNode.toString());
4654
4655				if (children.length === 1) {
4656					_assert(childNode === children[0], "invalid single child");
4657					return this.nodeRemoveChildren(ctx);
4658				}
4659				if (
4660					this.activeNode &&
4661					(childNode === this.activeNode ||
4662						this.activeNode.isDescendantOf(childNode))
4663				) {
4664					this.activeNode.setActive(false); // TODO: don't fire events
4665				}
4666				if (
4667					this.focusNode &&
4668					(childNode === this.focusNode ||
4669						this.focusNode.isDescendantOf(childNode))
4670				) {
4671					this.focusNode = null;
4672				}
4673				// TODO: persist must take care to clear select and expand cookies
4674				this.nodeRemoveMarkup(subCtx);
4675				this.nodeRemoveChildren(subCtx);
4676				idx = $.inArray(childNode, children);
4677				_assert(idx >= 0, "invalid child");
4678				// Notify listeners
4679				node.triggerModifyChild("remove", childNode);
4680				// Unlink to support GC
4681				childNode.visit(function (n) {
4682					n.parent = null;
4683				}, true);
4684				this._callHook("treeRegisterNode", this, false, childNode);
4685				// remove from child list
4686				children.splice(idx, 1);
4687			},
4688			/**Remove HTML markup for all descendents of ctx.node.
4689			 * @param {EventData} ctx
4690			 */
4691			nodeRemoveChildMarkup: function (ctx) {
4692				var node = ctx.node;
4693
4694				// FT.debug("nodeRemoveChildMarkup()", node.toString());
4695				// TODO: Unlink attr.ftnode to support GC
4696				if (node.ul) {
4697					if (node.isRootNode()) {
4698						$(node.ul).empty();
4699					} else {
4700						$(node.ul).remove();
4701						node.ul = null;
4702					}
4703					node.visit(function (n) {
4704						n.li = n.ul = null;
4705					});
4706				}
4707			},
4708			/**Remove all descendants of ctx.node.
4709			 * @param {EventData} ctx
4710			 */
4711			nodeRemoveChildren: function (ctx) {
4712				var //subCtx,
4713					tree = ctx.tree,
4714					node = ctx.node,
4715					children = node.children;
4716				// opts = ctx.options;
4717
4718				// FT.debug("nodeRemoveChildren()", node.toString());
4719				if (!children) {
4720					return;
4721				}
4722				if (this.activeNode && this.activeNode.isDescendantOf(node)) {
4723					this.activeNode.setActive(false); // TODO: don't fire events
4724				}
4725				if (this.focusNode && this.focusNode.isDescendantOf(node)) {
4726					this.focusNode = null;
4727				}
4728				// TODO: persist must take care to clear select and expand cookies
4729				this.nodeRemoveChildMarkup(ctx);
4730				// Unlink children to support GC
4731				// TODO: also delete this.children (not possible using visit())
4732				// subCtx = $.extend({}, ctx);
4733				node.triggerModifyChild("remove", null);
4734				node.visit(function (n) {
4735					n.parent = null;
4736					tree._callHook("treeRegisterNode", tree, false, n);
4737				});
4738				if (node.lazy) {
4739					// 'undefined' would be interpreted as 'not yet loaded' for lazy nodes
4740					node.children = [];
4741				} else {
4742					node.children = null;
4743				}
4744				if (!node.isRootNode()) {
4745					node.expanded = false; // #449, #459
4746				}
4747				this.nodeRenderStatus(ctx);
4748			},
4749			/**Remove HTML markup for ctx.node and all its descendents.
4750			 * @param {EventData} ctx
4751			 */
4752			nodeRemoveMarkup: function (ctx) {
4753				var node = ctx.node;
4754				// FT.debug("nodeRemoveMarkup()", node.toString());
4755				// TODO: Unlink attr.ftnode to support GC
4756				if (node.li) {
4757					$(node.li).remove();
4758					node.li = null;
4759				}
4760				this.nodeRemoveChildMarkup(ctx);
4761			},
4762			/**
4763			 * Create `<li><span>..</span> .. </li>` tags for this node.
4764			 *
4765			 * This method takes care that all HTML markup is created that is required
4766			 * to display this node in its current state.
4767			 *
4768			 * Call this method to create new nodes, or after the strucuture
4769			 * was changed (e.g. after moving this node or adding/removing children)
4770			 * nodeRenderTitle() and nodeRenderStatus() are implied.
4771			 *
4772			 * ```html
4773			 * <li id='KEY' ftnode=NODE>
4774			 *     <span class='fancytree-node fancytree-expanded fancytree-has-children fancytree-lastsib fancytree-exp-el fancytree-ico-e'>
4775			 *         <span class="fancytree-expander"></span>
4776			 *         <span class="fancytree-checkbox"></span> // only present in checkbox mode
4777			 *         <span class="fancytree-icon"></span>
4778			 *         <a href="#" class="fancytree-title"> Node 1 </a>
4779			 *     </span>
4780			 *     <ul> // only present if node has children
4781			 *         <li id='KEY' ftnode=NODE> child1 ... </li>
4782			 *         <li id='KEY' ftnode=NODE> child2 ... </li>
4783			 *     </ul>
4784			 * </li>
4785			 * ```
4786			 *
4787			 * @param {EventData} ctx
4788			 * @param {boolean} [force=false] re-render, even if html markup was already created
4789			 * @param {boolean} [deep=false] also render all descendants, even if parent is collapsed
4790			 * @param {boolean} [collapsed=false] force root node to be collapsed, so we can apply animated expand later
4791			 */
4792			nodeRender: function (ctx, force, deep, collapsed, _recursive) {
4793				/* This method must take care of all cases where the current data mode
4794				 * (i.e. node hierarchy) does not match the current markup.
4795				 *
4796				 * - node was not yet rendered:
4797				 *   create markup
4798				 * - node was rendered: exit fast
4799				 * - children have been added
4800				 * - children have been removed
4801				 */
4802				var childLI,
4803					childNode1,
4804					childNode2,
4805					i,
4806					l,
4807					next,
4808					subCtx,
4809					node = ctx.node,
4810					tree = ctx.tree,
4811					opts = ctx.options,
4812					aria = opts.aria,
4813					firstTime = false,
4814					parent = node.parent,
4815					isRootNode = !parent,
4816					children = node.children,
4817					successorLi = null;
4818				// FT.debug("nodeRender(" + !!force + ", " + !!deep + ")", node.toString());
4819
4820				if (tree._enableUpdate === false) {
4821					// tree.debug("no render", tree._enableUpdate);
4822					return;
4823				}
4824				if (!isRootNode && !parent.ul) {
4825					// Calling node.collapse on a deep, unrendered node
4826					return;
4827				}
4828				_assert(isRootNode || parent.ul, "parent UL must exist");
4829
4830				// Render the node
4831				if (!isRootNode) {
4832					// Discard markup on force-mode, or if it is not linked to parent <ul>
4833					if (
4834						node.li &&
4835						(force || node.li.parentNode !== node.parent.ul)
4836					) {
4837						if (node.li.parentNode === node.parent.ul) {
4838							// #486: store following node, so we can insert the new markup there later
4839							successorLi = node.li.nextSibling;
4840						} else {
4841							// May happen, when a top-level node was dropped over another
4842							this.debug(
4843								"Unlinking " +
4844									node +
4845									" (must be child of " +
4846									node.parent +
4847									")"
4848							);
4849						}
4850						//	            this.debug("nodeRemoveMarkup...");
4851						this.nodeRemoveMarkup(ctx);
4852					}
4853					// Create <li><span /> </li>
4854					// node.debug("render...");
4855					if (node.li) {
4856						// this.nodeRenderTitle(ctx);
4857						this.nodeRenderStatus(ctx);
4858					} else {
4859						// node.debug("render... really");
4860						firstTime = true;
4861						node.li = document.createElement("li");
4862						node.li.ftnode = node;
4863
4864						if (node.key && opts.generateIds) {
4865							node.li.id = opts.idPrefix + node.key;
4866						}
4867						node.span = document.createElement("span");
4868						node.span.className = "fancytree-node";
4869						if (aria && !node.tr) {
4870							$(node.li).attr("role", "treeitem");
4871						}
4872						node.li.appendChild(node.span);
4873
4874						// Create inner HTML for the <span> (expander, checkbox, icon, and title)
4875						this.nodeRenderTitle(ctx);
4876
4877						// Allow tweaking and binding, after node was created for the first time
4878						if (opts.createNode) {
4879							opts.createNode.call(
4880								tree,
4881								{ type: "createNode" },
4882								ctx
4883							);
4884						}
4885					}
4886					// Allow tweaking after node state was rendered
4887					if (opts.renderNode) {
4888						opts.renderNode.call(tree, { type: "renderNode" }, ctx);
4889					}
4890				}
4891
4892				// Visit child nodes
4893				if (children) {
4894					if (isRootNode || node.expanded || deep === true) {
4895						// Create a UL to hold the children
4896						if (!node.ul) {
4897							node.ul = document.createElement("ul");
4898							if (
4899								(collapsed === true && !_recursive) ||
4900								!node.expanded
4901							) {
4902								// hide top UL, so we can use an animation to show it later
4903								node.ul.style.display = "none";
4904							}
4905							if (aria) {
4906								$(node.ul).attr("role", "group");
4907							}
4908							if (node.li) {
4909								// issue #67
4910								node.li.appendChild(node.ul);
4911							} else {
4912								node.tree.$div.append(node.ul);
4913							}
4914						}
4915						// Add child markup
4916						for (i = 0, l = children.length; i < l; i++) {
4917							subCtx = $.extend({}, ctx, { node: children[i] });
4918							this.nodeRender(subCtx, force, deep, false, true);
4919						}
4920						// Remove <li> if nodes have moved to another parent
4921						childLI = node.ul.firstChild;
4922						while (childLI) {
4923							childNode2 = childLI.ftnode;
4924							if (childNode2 && childNode2.parent !== node) {
4925								node.debug(
4926									"_fixParent: remove missing " + childNode2,
4927									childLI
4928								);
4929								next = childLI.nextSibling;
4930								childLI.parentNode.removeChild(childLI);
4931								childLI = next;
4932							} else {
4933								childLI = childLI.nextSibling;
4934							}
4935						}
4936						// Make sure, that <li> order matches node.children order.
4937						childLI = node.ul.firstChild;
4938						for (i = 0, l = children.length - 1; i < l; i++) {
4939							childNode1 = children[i];
4940							childNode2 = childLI.ftnode;
4941							if (childNode1 === childNode2) {
4942								childLI = childLI.nextSibling;
4943							} else {
4944								// node.debug("_fixOrder: mismatch at index " + i + ": " + childNode1 + " != " + childNode2);
4945								node.ul.insertBefore(
4946									childNode1.li,
4947									childNode2.li
4948								);
4949							}
4950						}
4951					}
4952				} else {
4953					// No children: remove markup if any
4954					if (node.ul) {
4955						// alert("remove child markup for " + node);
4956						this.warn("remove child markup for " + node);
4957						this.nodeRemoveChildMarkup(ctx);
4958					}
4959				}
4960				if (!isRootNode) {
4961					// Update element classes according to node state
4962					// this.nodeRenderStatus(ctx);
4963					// Finally add the whole structure to the DOM, so the browser can render
4964					if (firstTime) {
4965						// #486: successorLi is set, if we re-rendered (i.e. discarded)
4966						// existing markup, which  we want to insert at the same position.
4967						// (null is equivalent to append)
4968						// 		parent.ul.appendChild(node.li);
4969						parent.ul.insertBefore(node.li, successorLi);
4970					}
4971				}
4972			},
4973			/** Create HTML inside the node's outer `<span>` (i.e. expander, checkbox,
4974			 * icon, and title).
4975			 *
4976			 * nodeRenderStatus() is implied.
4977			 * @param {EventData} ctx
4978			 * @param {string} [title] optinal new title
4979			 */
4980			nodeRenderTitle: function (ctx, title) {
4981				// set node connector images, links and text
4982				var checkbox,
4983					className,
4984					icon,
4985					nodeTitle,
4986					role,
4987					tabindex,
4988					tooltip,
4989					iconTooltip,
4990					node = ctx.node,
4991					tree = ctx.tree,
4992					opts = ctx.options,
4993					aria = opts.aria,
4994					level = node.getLevel(),
4995					ares = [];
4996
4997				if (title !== undefined) {
4998					node.title = title;
4999				}
5000				if (!node.span || tree._enableUpdate === false) {
5001					// Silently bail out if node was not rendered yet, assuming
5002					// node.render() will be called as the node becomes visible
5003					return;
5004				}
5005				// Connector (expanded, expandable or simple)
5006				role =
5007					aria && node.hasChildren() !== false
5008						? " role='button'"
5009						: "";
5010				if (level < opts.minExpandLevel) {
5011					if (!node.lazy) {
5012						node.expanded = true;
5013					}
5014					if (level > 1) {
5015						ares.push(
5016							"<span " +
5017								role +
5018								" class='fancytree-expander fancytree-expander-fixed'></span>"
5019						);
5020					}
5021					// .. else (i.e. for root level) skip expander/connector alltogether
5022				} else {
5023					ares.push(
5024						"<span " + role + " class='fancytree-expander'></span>"
5025					);
5026				}
5027				// Checkbox mode
5028				checkbox = FT.evalOption("checkbox", node, node, opts, false);
5029
5030				if (checkbox && !node.isStatusNode()) {
5031					role = aria ? " role='checkbox'" : "";
5032					className = "fancytree-checkbox";
5033					if (
5034						checkbox === "radio" ||
5035						(node.parent && node.parent.radiogroup)
5036					) {
5037						className += " fancytree-radio";
5038					}
5039					ares.push(
5040						"<span " + role + " class='" + className + "'></span>"
5041					);
5042				}
5043				// Folder or doctype icon
5044				if (node.data.iconClass !== undefined) {
5045					// 2015-11-16
5046					// Handle / warn about backward compatibility
5047					if (node.icon) {
5048						$.error(
5049							"'iconClass' node option is deprecated since v2.14.0: use 'icon' only instead"
5050						);
5051					} else {
5052						node.warn(
5053							"'iconClass' node option is deprecated since v2.14.0: use 'icon' instead"
5054						);
5055						node.icon = node.data.iconClass;
5056					}
5057				}
5058				// If opts.icon is a callback and returns something other than undefined, use that
5059				// else if node.icon is a boolean or string, use that
5060				// else if opts.icon is a boolean or string, use that
5061				// else show standard icon (which may be different for folders or documents)
5062				icon = FT.evalOption("icon", node, node, opts, true);
5063				// if( typeof icon !== "boolean" ) {
5064				// 	// icon is defined, but not true/false: must be a string
5065				// 	icon = "" + icon;
5066				// }
5067				if (icon !== false) {
5068					role = aria ? " role='presentation'" : "";
5069
5070					iconTooltip = FT.evalOption(
5071						"iconTooltip",
5072						node,
5073						node,
5074						opts,
5075						null
5076					);
5077					iconTooltip = iconTooltip
5078						? " title='" + _escapeTooltip(iconTooltip) + "'"
5079						: "";
5080
5081					if (typeof icon === "string") {
5082						if (TEST_IMG.test(icon)) {
5083							// node.icon is an image url. Prepend imagePath
5084							icon =
5085								icon.charAt(0) === "/"
5086									? icon
5087									: (opts.imagePath || "") + icon;
5088							ares.push(
5089								"<img src='" +
5090									icon +
5091									"' class='fancytree-icon'" +
5092									iconTooltip +
5093									" alt='' />"
5094							);
5095						} else {
5096							ares.push(
5097								"<span " +
5098									role +
5099									" class='fancytree-custom-icon " +
5100									icon +
5101									"'" +
5102									iconTooltip +
5103									"></span>"
5104							);
5105						}
5106					} else if (icon.text) {
5107						ares.push(
5108							"<span " +
5109								role +
5110								" class='fancytree-custom-icon " +
5111								(icon.addClass || "") +
5112								"'" +
5113								iconTooltip +
5114								">" +
5115								FT.escapeHtml(icon.text) +
5116								"</span>"
5117						);
5118					} else if (icon.html) {
5119						ares.push(
5120							"<span " +
5121								role +
5122								" class='fancytree-custom-icon " +
5123								(icon.addClass || "") +
5124								"'" +
5125								iconTooltip +
5126								">" +
5127								icon.html +
5128								"</span>"
5129						);
5130					} else {
5131						// standard icon: theme css will take care of this
5132						ares.push(
5133							"<span " +
5134								role +
5135								" class='fancytree-icon'" +
5136								iconTooltip +
5137								"></span>"
5138						);
5139					}
5140				}
5141				// Node title
5142				nodeTitle = "";
5143				if (opts.renderTitle) {
5144					nodeTitle =
5145						opts.renderTitle.call(
5146							tree,
5147							{ type: "renderTitle" },
5148							ctx
5149						) || "";
5150				}
5151				if (!nodeTitle) {
5152					tooltip = FT.evalOption("tooltip", node, node, opts, null);
5153					if (tooltip === true) {
5154						tooltip = node.title;
5155					}
5156					// if( node.tooltip ) {
5157					// 	tooltip = node.tooltip;
5158					// } else if ( opts.tooltip ) {
5159					// 	tooltip = opts.tooltip === true ? node.title : opts.tooltip.call(tree, node);
5160					// }
5161					tooltip = tooltip
5162						? " title='" + _escapeTooltip(tooltip) + "'"
5163						: "";
5164					tabindex = opts.titlesTabbable ? " tabindex='0'" : "";
5165
5166					nodeTitle =
5167						"<span class='fancytree-title'" +
5168						tooltip +
5169						tabindex +
5170						">" +
5171						(opts.escapeTitles
5172							? FT.escapeHtml(node.title)
5173							: node.title) +
5174						"</span>";
5175				}
5176				ares.push(nodeTitle);
5177				// Note: this will trigger focusout, if node had the focus
5178				//$(node.span).html(ares.join("")); // it will cleanup the jQuery data currently associated with SPAN (if any), but it executes more slowly
5179				node.span.innerHTML = ares.join("");
5180				// Update CSS classes
5181				this.nodeRenderStatus(ctx);
5182				if (opts.enhanceTitle) {
5183					ctx.$title = $(">span.fancytree-title", node.span);
5184					nodeTitle =
5185						opts.enhanceTitle.call(
5186							tree,
5187							{ type: "enhanceTitle" },
5188							ctx
5189						) || "";
5190				}
5191			},
5192			/** Update element classes according to node state.
5193			 * @param {EventData} ctx
5194			 */
5195			nodeRenderStatus: function (ctx) {
5196				// Set classes for current status
5197				var $ariaElem,
5198					node = ctx.node,
5199					tree = ctx.tree,
5200					opts = ctx.options,
5201					// 	nodeContainer = node[tree.nodeContainerAttrName],
5202					hasChildren = node.hasChildren(),
5203					isLastSib = node.isLastSibling(),
5204					aria = opts.aria,
5205					cn = opts._classNames,
5206					cnList = [],
5207					statusElem = node[tree.statusClassPropName];
5208
5209				if (!statusElem || tree._enableUpdate === false) {
5210					// if this function is called for an unrendered node, ignore it (will be updated on nect render anyway)
5211					return;
5212				}
5213				if (aria) {
5214					$ariaElem = $(node.tr || node.li);
5215				}
5216				// Build a list of class names that we will add to the node <span>
5217				cnList.push(cn.node);
5218				if (tree.activeNode === node) {
5219					cnList.push(cn.active);
5220					// 		$(">span.fancytree-title", statusElem).attr("tabindex", "0");
5221					// 		tree.$container.removeAttr("tabindex");
5222					// }else{
5223					// 		$(">span.fancytree-title", statusElem).removeAttr("tabindex");
5224					// 		tree.$container.attr("tabindex", "0");
5225				}
5226				if (tree.focusNode === node) {
5227					cnList.push(cn.focused);
5228				}
5229				if (node.expanded) {
5230					cnList.push(cn.expanded);
5231				}
5232				if (aria) {
5233					if (hasChildren === false) {
5234						$ariaElem.removeAttr("aria-expanded");
5235					} else {
5236						$ariaElem.attr("aria-expanded", Boolean(node.expanded));
5237					}
5238				}
5239				if (node.folder) {
5240					cnList.push(cn.folder);
5241				}
5242				if (hasChildren !== false) {
5243					cnList.push(cn.hasChildren);
5244				}
5245				// TODO: required?
5246				if (isLastSib) {
5247					cnList.push(cn.lastsib);
5248				}
5249				if (node.lazy && node.children == null) {
5250					cnList.push(cn.lazy);
5251				}
5252				if (node.partload) {
5253					cnList.push(cn.partload);
5254				}
5255				if (node.partsel) {
5256					cnList.push(cn.partsel);
5257				}
5258				if (FT.evalOption("unselectable", node, node, opts, false)) {
5259					cnList.push(cn.unselectable);
5260				}
5261				if (node._isLoading) {
5262					cnList.push(cn.loading);
5263				}
5264				if (node._error) {
5265					cnList.push(cn.error);
5266				}
5267				if (node.statusNodeType) {
5268					cnList.push(cn.statusNodePrefix + node.statusNodeType);
5269				}
5270				if (node.selected) {
5271					cnList.push(cn.selected);
5272					if (aria) {
5273						$ariaElem.attr("aria-selected", true);
5274					}
5275				} else if (aria) {
5276					$ariaElem.attr("aria-selected", false);
5277				}
5278				if (node.extraClasses) {
5279					cnList.push(node.extraClasses);
5280				}
5281				// IE6 doesn't correctly evaluate multiple class names,
5282				// so we create combined class names that can be used in the CSS
5283				if (hasChildren === false) {
5284					cnList.push(
5285						cn.combinedExpanderPrefix + "n" + (isLastSib ? "l" : "")
5286					);
5287				} else {
5288					cnList.push(
5289						cn.combinedExpanderPrefix +
5290							(node.expanded ? "e" : "c") +
5291							(node.lazy && node.children == null ? "d" : "") +
5292							(isLastSib ? "l" : "")
5293					);
5294				}
5295				cnList.push(
5296					cn.combinedIconPrefix +
5297						(node.expanded ? "e" : "c") +
5298						(node.folder ? "f" : "")
5299				);
5300				// node.span.className = cnList.join(" ");
5301				statusElem.className = cnList.join(" ");
5302
5303				// TODO: we should not set this in the <span> tag also, if we set it here:
5304				// Maybe most (all) of the classes should be set in LI instead of SPAN?
5305				if (node.li) {
5306					// #719: we have to consider that there may be already other classes:
5307					$(node.li).toggleClass(cn.lastsib, isLastSib);
5308				}
5309			},
5310			/** Activate node.
5311			 * flag defaults to true.
5312			 * If flag is true, the node is activated (must be a synchronous operation)
5313			 * If flag is false, the node is deactivated (must be a synchronous operation)
5314			 * @param {EventData} ctx
5315			 * @param {boolean} [flag=true]
5316			 * @param {object} [opts] additional options. Defaults to {noEvents: false, noFocus: false}
5317			 * @returns {$.Promise}
5318			 */
5319			nodeSetActive: function (ctx, flag, callOpts) {
5320				// Handle user click / [space] / [enter], according to clickFolderMode.
5321				callOpts = callOpts || {};
5322				var subCtx,
5323					node = ctx.node,
5324					tree = ctx.tree,
5325					opts = ctx.options,
5326					noEvents = callOpts.noEvents === true,
5327					noFocus = callOpts.noFocus === true,
5328					scroll = callOpts.scrollIntoView !== false,
5329					isActive = node === tree.activeNode;
5330
5331				// flag defaults to true
5332				flag = flag !== false;
5333				// node.debug("nodeSetActive", flag);
5334
5335				if (isActive === flag) {
5336					// Nothing to do
5337					return _getResolvedPromise(node);
5338				}
5339				// #1042: don't scroll between mousedown/-up when clicking an embedded link
5340				if (
5341					scroll &&
5342					ctx.originalEvent &&
5343					$(ctx.originalEvent.target).is("a,:checkbox")
5344				) {
5345					node.info("Not scrolling while clicking an embedded link.");
5346					scroll = false;
5347				}
5348				if (
5349					flag &&
5350					!noEvents &&
5351					this._triggerNodeEvent(
5352						"beforeActivate",
5353						node,
5354						ctx.originalEvent
5355					) === false
5356				) {
5357					// Callback returned false
5358					return _getRejectedPromise(node, ["rejected"]);
5359				}
5360				if (flag) {
5361					if (tree.activeNode) {
5362						_assert(
5363							tree.activeNode !== node,
5364							"node was active (inconsistency)"
5365						);
5366						subCtx = $.extend({}, ctx, { node: tree.activeNode });
5367						tree.nodeSetActive(subCtx, false);
5368						_assert(
5369							tree.activeNode === null,
5370							"deactivate was out of sync?"
5371						);
5372					}
5373
5374					if (opts.activeVisible) {
5375						// If no focus is set (noFocus: true) and there is no focused node, this node is made visible.
5376						// scroll = noFocus && tree.focusNode == null;
5377						// #863: scroll by default (unless `scrollIntoView: false` was passed)
5378						node.makeVisible({ scrollIntoView: scroll });
5379					}
5380					tree.activeNode = node;
5381					tree.nodeRenderStatus(ctx);
5382					if (!noFocus) {
5383						tree.nodeSetFocus(ctx);
5384					}
5385					if (!noEvents) {
5386						tree._triggerNodeEvent(
5387							"activate",
5388							node,
5389							ctx.originalEvent
5390						);
5391					}
5392				} else {
5393					_assert(
5394						tree.activeNode === node,
5395						"node was not active (inconsistency)"
5396					);
5397					tree.activeNode = null;
5398					this.nodeRenderStatus(ctx);
5399					if (!noEvents) {
5400						ctx.tree._triggerNodeEvent(
5401							"deactivate",
5402							node,
5403							ctx.originalEvent
5404						);
5405					}
5406				}
5407				return _getResolvedPromise(node);
5408			},
5409			/** Expand or collapse node, return Deferred.promise.
5410			 *
5411			 * @param {EventData} ctx
5412			 * @param {boolean} [flag=true]
5413			 * @param {object} [opts] additional options. Defaults to `{noAnimation: false, noEvents: false}`
5414			 * @returns {$.Promise} The deferred will be resolved as soon as the (lazy)
5415			 *     data was retrieved, rendered, and the expand animation finished.
5416			 */
5417			nodeSetExpanded: function (ctx, flag, callOpts) {
5418				callOpts = callOpts || {};
5419				var _afterLoad,
5420					dfd,
5421					i,
5422					l,
5423					parents,
5424					prevAC,
5425					node = ctx.node,
5426					tree = ctx.tree,
5427					opts = ctx.options,
5428					noAnimation = callOpts.noAnimation === true,
5429					noEvents = callOpts.noEvents === true;
5430
5431				// flag defaults to true
5432				flag = flag !== false;
5433
5434				// node.debug("nodeSetExpanded(" + flag + ")");
5435
5436				if ($(node.li).hasClass(opts._classNames.animating)) {
5437					node.warn(
5438						"setExpanded(" + flag + ") while animating: ignored."
5439					);
5440					return _getRejectedPromise(node, ["recursion"]);
5441				}
5442
5443				if ((node.expanded && flag) || (!node.expanded && !flag)) {
5444					// Nothing to do
5445					// node.debug("nodeSetExpanded(" + flag + "): nothing to do");
5446					return _getResolvedPromise(node);
5447				} else if (flag && !node.lazy && !node.hasChildren()) {
5448					// Prevent expanding of empty nodes
5449					// return _getRejectedPromise(node, ["empty"]);
5450					return _getResolvedPromise(node);
5451				} else if (!flag && node.getLevel() < opts.minExpandLevel) {
5452					// Prevent collapsing locked levels
5453					return _getRejectedPromise(node, ["locked"]);
5454				} else if (
5455					!noEvents &&
5456					this._triggerNodeEvent(
5457						"beforeExpand",
5458						node,
5459						ctx.originalEvent
5460					) === false
5461				) {
5462					// Callback returned false
5463					return _getRejectedPromise(node, ["rejected"]);
5464				}
5465				// If this node inside a collpased node, no animation and scrolling is needed
5466				if (!noAnimation && !node.isVisible()) {
5467					noAnimation = callOpts.noAnimation = true;
5468				}
5469
5470				dfd = new $.Deferred();
5471
5472				// Auto-collapse mode: collapse all siblings
5473				if (flag && !node.expanded && opts.autoCollapse) {
5474					parents = node.getParentList(false, true);
5475					prevAC = opts.autoCollapse;
5476					try {
5477						opts.autoCollapse = false;
5478						for (i = 0, l = parents.length; i < l; i++) {
5479							// TODO: should return promise?
5480							this._callHook(
5481								"nodeCollapseSiblings",
5482								parents[i],
5483								callOpts
5484							);
5485						}
5486					} finally {
5487						opts.autoCollapse = prevAC;
5488					}
5489				}
5490				// Trigger expand/collapse after expanding
5491				dfd.done(function () {
5492					var lastChild = node.getLastChild();
5493
5494					if (
5495						flag &&
5496						opts.autoScroll &&
5497						!noAnimation &&
5498						lastChild &&
5499						tree._enableUpdate
5500					) {
5501						// Scroll down to last child, but keep current node visible
5502						lastChild
5503							.scrollIntoView(true, { topNode: node })
5504							.always(function () {
5505								if (!noEvents) {
5506									ctx.tree._triggerNodeEvent(
5507										flag ? "expand" : "collapse",
5508										ctx
5509									);
5510								}
5511							});
5512					} else {
5513						if (!noEvents) {
5514							ctx.tree._triggerNodeEvent(
5515								flag ? "expand" : "collapse",
5516								ctx
5517							);
5518						}
5519					}
5520				});
5521				// vvv Code below is executed after loading finished:
5522				_afterLoad = function (callback) {
5523					var cn = opts._classNames,
5524						isVisible,
5525						isExpanded,
5526						effect = opts.toggleEffect;
5527
5528					node.expanded = flag;
5529					tree._callHook(
5530						"treeStructureChanged",
5531						ctx,
5532						flag ? "expand" : "collapse"
5533					);
5534					// Create required markup, but make sure the top UL is hidden, so we
5535					// can animate later
5536					tree._callHook("nodeRender", ctx, false, false, true);
5537
5538					// Hide children, if node is collapsed
5539					if (node.ul) {
5540						isVisible = node.ul.style.display !== "none";
5541						isExpanded = !!node.expanded;
5542						if (isVisible === isExpanded) {
5543							node.warn(
5544								"nodeSetExpanded: UL.style.display already set"
5545							);
5546						} else if (!effect || noAnimation) {
5547							node.ul.style.display =
5548								node.expanded || !parent ? "" : "none";
5549						} else {
5550							// The UI toggle() effect works with the ext-wide extension,
5551							// while jQuery.animate() has problems when the title span
5552							// has position: absolute.
5553							// Since jQuery UI 1.12, the blind effect requires the parent
5554							// element to have 'position: relative'.
5555							// See #716, #717
5556							$(node.li).addClass(cn.animating); // #717
5557
5558							if (_isFunction($(node.ul)[effect.effect])) {
5559								// tree.debug( "use jquery." + effect.effect + " method" );
5560								$(node.ul)[effect.effect]({
5561									duration: effect.duration,
5562									always: function () {
5563										// node.debug("fancytree-animating end: " + node.li.className);
5564										$(this).removeClass(cn.animating); // #716
5565										$(node.li).removeClass(cn.animating); // #717
5566										callback();
5567									},
5568								});
5569							} else {
5570								// The UI toggle() effect works with the ext-wide extension,
5571								// while jQuery.animate() has problems when the title span
5572								// has positon: absolute.
5573								// Since jQuery UI 1.12, the blind effect requires the parent
5574								// element to have 'position: relative'.
5575								// See #716, #717
5576								// tree.debug("use specified effect (" + effect.effect + ") with the jqueryui.toggle method");
5577
5578								// try to stop an animation that might be already in progress
5579								$(node.ul).stop(true, true); //< does not work after resetLazy has been called for a node whose animation wasn't complete and effect was "blind"
5580
5581								// dirty fix to remove a defunct animation (effect: "blind") after resetLazy has been called
5582								$(node.ul)
5583									.parent()
5584									.find(".ui-effects-placeholder")
5585									.remove();
5586
5587								$(node.ul).toggle(
5588									effect.effect,
5589									effect.options,
5590									effect.duration,
5591									function () {
5592										// node.debug("fancytree-animating end: " + node.li.className);
5593										$(this).removeClass(cn.animating); // #716
5594										$(node.li).removeClass(cn.animating); // #717
5595										callback();
5596									}
5597								);
5598							}
5599							return;
5600						}
5601					}
5602					callback();
5603				};
5604				// ^^^ Code above is executed after loading finshed.
5605
5606				// Load lazy nodes, if any. Then continue with _afterLoad()
5607				if (flag && node.lazy && node.hasChildren() === undefined) {
5608					// node.debug("nodeSetExpanded: load start...");
5609					node.load()
5610						.done(function () {
5611							// node.debug("nodeSetExpanded: load done");
5612							if (dfd.notifyWith) {
5613								// requires jQuery 1.6+
5614								dfd.notifyWith(node, ["loaded"]);
5615							}
5616							_afterLoad(function () {
5617								dfd.resolveWith(node);
5618							});
5619						})
5620						.fail(function (errMsg) {
5621							_afterLoad(function () {
5622								dfd.rejectWith(node, [
5623									"load failed (" + errMsg + ")",
5624								]);
5625							});
5626						});
5627					/*
5628					var source = tree._triggerNodeEvent("lazyLoad", node, ctx.originalEvent);
5629					_assert(typeof source !== "boolean", "lazyLoad event must return source in data.result");
5630					node.debug("nodeSetExpanded: load start...");
5631					this._callHook("nodeLoadChildren", ctx, source).done(function(){
5632						node.debug("nodeSetExpanded: load done");
5633						if(dfd.notifyWith){ // requires jQuery 1.6+
5634							dfd.notifyWith(node, ["loaded"]);
5635						}
5636						_afterLoad.call(tree);
5637					}).fail(function(errMsg){
5638						dfd.rejectWith(node, ["load failed (" + errMsg + ")"]);
5639					});
5640					*/
5641				} else {
5642					_afterLoad(function () {
5643						dfd.resolveWith(node);
5644					});
5645				}
5646				// node.debug("nodeSetExpanded: returns");
5647				return dfd.promise();
5648			},
5649			/** Focus or blur this node.
5650			 * @param {EventData} ctx
5651			 * @param {boolean} [flag=true]
5652			 */
5653			nodeSetFocus: function (ctx, flag) {
5654				// ctx.node.debug("nodeSetFocus(" + flag + ")");
5655				var ctx2,
5656					tree = ctx.tree,
5657					node = ctx.node,
5658					opts = tree.options,
5659					// et = ctx.originalEvent && ctx.originalEvent.type,
5660					isInput = ctx.originalEvent
5661						? $(ctx.originalEvent.target).is(":input")
5662						: false;
5663
5664				flag = flag !== false;
5665
5666				// (node || tree).debug("nodeSetFocus(" + flag + "), event: " + et + ", isInput: "+ isInput);
5667				// Blur previous node if any
5668				if (tree.focusNode) {
5669					if (tree.focusNode === node && flag) {
5670						// node.debug("nodeSetFocus(" + flag + "): nothing to do");
5671						return;
5672					}
5673					ctx2 = $.extend({}, ctx, { node: tree.focusNode });
5674					tree.focusNode = null;
5675					this._triggerNodeEvent("blur", ctx2);
5676					this._callHook("nodeRenderStatus", ctx2);
5677				}
5678				// Set focus to container and node
5679				if (flag) {
5680					if (!this.hasFocus()) {
5681						node.debug("nodeSetFocus: forcing container focus");
5682						this._callHook("treeSetFocus", ctx, true, {
5683							calledByNode: true,
5684						});
5685					}
5686					node.makeVisible({ scrollIntoView: false });
5687					tree.focusNode = node;
5688					if (opts.titlesTabbable) {
5689						if (!isInput) {
5690							// #621
5691							$(node.span).find(".fancytree-title").focus();
5692						}
5693					}
5694					if (opts.aria) {
5695						// Set active descendant to node's span ID (create one, if needed)
5696						$(tree.$container).attr(
5697							"aria-activedescendant",
5698							$(node.tr || node.li)
5699								.uniqueId()
5700								.attr("id")
5701						);
5702						// "ftal_" + opts.idPrefix + node.key);
5703					}
5704					// $(node.span).find(".fancytree-title").focus();
5705					this._triggerNodeEvent("focus", ctx);
5706
5707					// determine if we have focus on or inside tree container
5708					var hasFancytreeFocus =
5709						document.activeElement === tree.$container.get(0) ||
5710						$(document.activeElement, tree.$container).length >= 1;
5711
5712					if (!hasFancytreeFocus) {
5713						// We cannot set KB focus to a node, so use the tree container
5714						// #563, #570: IE scrolls on every call to .focus(), if the container
5715						// is partially outside the viewport. So do it only, when absolutely
5716						// necessary.
5717						$(tree.$container).focus();
5718					}
5719
5720					// if( opts.autoActivate ){
5721					// 	tree.nodeSetActive(ctx, true);
5722					// }
5723					if (opts.autoScroll) {
5724						node.scrollIntoView();
5725					}
5726					this._callHook("nodeRenderStatus", ctx);
5727				}
5728			},
5729			/** (De)Select node, return new status (sync).
5730			 *
5731			 * @param {EventData} ctx
5732			 * @param {boolean} [flag=true]
5733			 * @param {object} [opts] additional options. Defaults to {noEvents: false,
5734			 *     propagateDown: null, propagateUp: null,
5735			 *     callback: null,
5736			 *     }
5737			 * @returns {boolean} previous status
5738			 */
5739			nodeSetSelected: function (ctx, flag, callOpts) {
5740				callOpts = callOpts || {};
5741				var node = ctx.node,
5742					tree = ctx.tree,
5743					opts = ctx.options,
5744					noEvents = callOpts.noEvents === true,
5745					parent = node.parent;
5746
5747				// flag defaults to true
5748				flag = flag !== false;
5749
5750				// node.debug("nodeSetSelected(" + flag + ")", ctx);
5751
5752				// Cannot (de)select unselectable nodes directly (only by propagation or
5753				// by setting the `.selected` property)
5754				if (FT.evalOption("unselectable", node, node, opts, false)) {
5755					return;
5756				}
5757
5758				// Remember the user's intent, in case down -> up propagation prevents
5759				// applying it to node.selected
5760				node._lastSelectIntent = flag; // Confusing use of '!'
5761
5762				// Nothing to do?
5763				if (!!node.selected === flag) {
5764					if (opts.selectMode === 3 && node.partsel && !flag) {
5765						// If propagation prevented selecting this node last time, we still
5766						// want to allow to apply setSelected(false) now
5767					} else {
5768						return flag;
5769					}
5770				}
5771
5772				if (
5773					!noEvents &&
5774					this._triggerNodeEvent(
5775						"beforeSelect",
5776						node,
5777						ctx.originalEvent
5778					) === false
5779				) {
5780					return !!node.selected;
5781				}
5782				if (flag && opts.selectMode === 1) {
5783					// single selection mode (we don't uncheck all tree nodes, for performance reasons)
5784					if (tree.lastSelectedNode) {
5785						tree.lastSelectedNode.setSelected(false);
5786					}
5787					node.selected = flag;
5788				} else if (
5789					opts.selectMode === 3 &&
5790					parent &&
5791					!parent.radiogroup &&
5792					!node.radiogroup
5793				) {
5794					// multi-hierarchical selection mode
5795					node.selected = flag;
5796					node.fixSelection3AfterClick(callOpts);
5797				} else if (parent && parent.radiogroup) {
5798					node.visitSiblings(function (n) {
5799						n._changeSelectStatusAttrs(flag && n === node);
5800					}, true);
5801				} else {
5802					// default: selectMode: 2, multi selection mode
5803					node.selected = flag;
5804				}
5805				this.nodeRenderStatus(ctx);
5806				tree.lastSelectedNode = flag ? node : null;
5807				if (!noEvents) {
5808					tree._triggerNodeEvent("select", ctx);
5809				}
5810			},
5811			/** Show node status (ok, loading, error, nodata) using styles and a dummy child node.
5812			 *
5813			 * @param {EventData} ctx
5814			 * @param status
5815			 * @param message
5816			 * @param details
5817			 * @since 2.3
5818			 */
5819			nodeSetStatus: function (ctx, status, message, details) {
5820				var node = ctx.node,
5821					tree = ctx.tree;
5822
5823				function _clearStatusNode() {
5824					// Remove dedicated dummy node, if any
5825					var firstChild = node.children ? node.children[0] : null;
5826					if (firstChild && firstChild.isStatusNode()) {
5827						try {
5828							// I've seen exceptions here with loadKeyPath...
5829							if (node.ul) {
5830								node.ul.removeChild(firstChild.li);
5831								firstChild.li = null; // avoid leaks (DT issue 215)
5832							}
5833						} catch (e) {}
5834						if (node.children.length === 1) {
5835							node.children = [];
5836						} else {
5837							node.children.shift();
5838						}
5839						tree._callHook(
5840							"treeStructureChanged",
5841							ctx,
5842							"clearStatusNode"
5843						);
5844					}
5845				}
5846				function _setStatusNode(data, type) {
5847					// Create/modify the dedicated dummy node for 'loading...' or
5848					// 'error!' status. (only called for direct child of the invisible
5849					// system root)
5850					var firstChild = node.children ? node.children[0] : null;
5851					if (firstChild && firstChild.isStatusNode()) {
5852						$.extend(firstChild, data);
5853						firstChild.statusNodeType = type;
5854						tree._callHook("nodeRenderTitle", firstChild);
5855					} else {
5856						node._setChildren([data]);
5857						tree._callHook(
5858							"treeStructureChanged",
5859							ctx,
5860							"setStatusNode"
5861						);
5862						node.children[0].statusNodeType = type;
5863						tree.render();
5864					}
5865					return node.children[0];
5866				}
5867
5868				switch (status) {
5869					case "ok":
5870						_clearStatusNode();
5871						node._isLoading = false;
5872						node._error = null;
5873						node.renderStatus();
5874						break;
5875					case "loading":
5876						if (!node.parent) {
5877							_setStatusNode(
5878								{
5879									title:
5880										tree.options.strings.loading +
5881										(message ? " (" + message + ")" : ""),
5882									// icon: true,  // needed for 'loding' icon
5883									checkbox: false,
5884									tooltip: details,
5885								},
5886								status
5887							);
5888						}
5889						node._isLoading = true;
5890						node._error = null;
5891						node.renderStatus();
5892						break;
5893					case "error":
5894						_setStatusNode(
5895							{
5896								title:
5897									tree.options.strings.loadError +
5898									(message ? " (" + message + ")" : ""),
5899								// icon: false,
5900								checkbox: false,
5901								tooltip: details,
5902							},
5903							status
5904						);
5905						node._isLoading = false;
5906						node._error = { message: message, details: details };
5907						node.renderStatus();
5908						break;
5909					case "nodata":
5910						_setStatusNode(
5911							{
5912								title: message || tree.options.strings.noData,
5913								// icon: false,
5914								checkbox: false,
5915								tooltip: details,
5916							},
5917							status
5918						);
5919						node._isLoading = false;
5920						node._error = null;
5921						node.renderStatus();
5922						break;
5923					default:
5924						$.error("invalid node status " + status);
5925				}
5926			},
5927			/**
5928			 *
5929			 * @param {EventData} ctx
5930			 */
5931			nodeToggleExpanded: function (ctx) {
5932				return this.nodeSetExpanded(ctx, !ctx.node.expanded);
5933			},
5934			/**
5935			 * @param {EventData} ctx
5936			 */
5937			nodeToggleSelected: function (ctx) {
5938				var node = ctx.node,
5939					flag = !node.selected;
5940
5941				// In selectMode: 3 this node may be unselected+partsel, even if
5942				// setSelected(true) was called before, due to `unselectable` children.
5943				// In this case, we now toggle as `setSelected(false)`
5944				if (
5945					node.partsel &&
5946					!node.selected &&
5947					node._lastSelectIntent === true
5948				) {
5949					flag = false;
5950					node.selected = true; // so it is not considered 'nothing to do'
5951				}
5952				node._lastSelectIntent = flag;
5953				return this.nodeSetSelected(ctx, flag);
5954			},
5955			/** Remove all nodes.
5956			 * @param {EventData} ctx
5957			 */
5958			treeClear: function (ctx) {
5959				var tree = ctx.tree;
5960				tree.activeNode = null;
5961				tree.focusNode = null;
5962				tree.$div.find(">ul.fancytree-container").empty();
5963				// TODO: call destructors and remove reference loops
5964				tree.rootNode.children = null;
5965				tree._callHook("treeStructureChanged", ctx, "clear");
5966			},
5967			/** Widget was created (called only once, even it re-initialized).
5968			 * @param {EventData} ctx
5969			 */
5970			treeCreate: function (ctx) {},
5971			/** Widget was destroyed.
5972			 * @param {EventData} ctx
5973			 */
5974			treeDestroy: function (ctx) {
5975				this.$div.find(">ul.fancytree-container").remove();
5976				if (this.$source) {
5977					this.$source.removeClass("fancytree-helper-hidden");
5978				}
5979			},
5980			/** Widget was (re-)initialized.
5981			 * @param {EventData} ctx
5982			 */
5983			treeInit: function (ctx) {
5984				var tree = ctx.tree,
5985					opts = tree.options;
5986
5987				//this.debug("Fancytree.treeInit()");
5988				// Add container to the TAB chain
5989				// See http://www.w3.org/TR/wai-aria-practices/#focus_activedescendant
5990				// #577: Allow to set tabindex to "0", "-1" and ""
5991				tree.$container.attr("tabindex", opts.tabindex);
5992
5993				// Copy some attributes to tree.data
5994				$.each(TREE_ATTRS, function (i, attr) {
5995					if (opts[attr] !== undefined) {
5996						tree.info("Move option " + attr + " to tree");
5997						tree[attr] = opts[attr];
5998						delete opts[attr];
5999					}
6000				});
6001
6002				if (opts.checkboxAutoHide) {
6003					tree.$container.addClass("fancytree-checkbox-auto-hide");
6004				}
6005				if (opts.rtl) {
6006					tree.$container
6007						.attr("DIR", "RTL")
6008						.addClass("fancytree-rtl");
6009				} else {
6010					tree.$container
6011						.removeAttr("DIR")
6012						.removeClass("fancytree-rtl");
6013				}
6014				if (opts.aria) {
6015					tree.$container.attr("role", "tree");
6016					if (opts.selectMode !== 1) {
6017						tree.$container.attr("aria-multiselectable", true);
6018					}
6019				}
6020				this.treeLoad(ctx);
6021			},
6022			/** Parse Fancytree from source, as configured in the options.
6023			 * @param {EventData} ctx
6024			 * @param {object} [source] optional new source (use last data otherwise)
6025			 */
6026			treeLoad: function (ctx, source) {
6027				var metaData,
6028					type,
6029					$ul,
6030					tree = ctx.tree,
6031					$container = ctx.widget.element,
6032					dfd,
6033					// calling context for root node
6034					rootCtx = $.extend({}, ctx, { node: this.rootNode });
6035
6036				if (tree.rootNode.children) {
6037					this.treeClear(ctx);
6038				}
6039				source = source || this.options.source;
6040
6041				if (!source) {
6042					type = $container.data("type") || "html";
6043					switch (type) {
6044						case "html":
6045							// There should be an embedded `<ul>` with initial nodes,
6046							// but another `<ul class='fancytree-container'>` is appended
6047							// to the tree's <div> on startup anyway.
6048							$ul = $container
6049								.find(">ul")
6050								.not(".fancytree-container")
6051								.first();
6052
6053							if ($ul.length) {
6054								$ul.addClass(
6055									"ui-fancytree-source fancytree-helper-hidden"
6056								);
6057								source = $.ui.fancytree.parseHtml($ul);
6058								// allow to init tree.data.foo from <ul data-foo=''>
6059								this.data = $.extend(
6060									this.data,
6061									_getElementDataAsDict($ul)
6062								);
6063							} else {
6064								FT.warn(
6065									"No `source` option was passed and container does not contain `<ul>`: assuming `source: []`."
6066								);
6067								source = [];
6068							}
6069							break;
6070						case "json":
6071							source = $.parseJSON($container.text());
6072							// $container already contains the <ul>, but we remove the plain (json) text
6073							// $container.empty();
6074							$container
6075								.contents()
6076								.filter(function () {
6077									return this.nodeType === 3;
6078								})
6079								.remove();
6080							if ($.isPlainObject(source)) {
6081								// We got {foo: 'abc', children: [...]}
6082								_assert(
6083									_isArray(source.children),
6084									"if an object is passed as source, it must contain a 'children' array (all other properties are added to 'tree.data')"
6085								);
6086								metaData = source;
6087								source = source.children;
6088								delete metaData.children;
6089								// Copy some attributes to tree.data
6090								$.each(TREE_ATTRS, function (i, attr) {
6091									if (metaData[attr] !== undefined) {
6092										tree[attr] = metaData[attr];
6093										delete metaData[attr];
6094									}
6095								});
6096								// Copy extra properties to tree.data.foo
6097								$.extend(tree.data, metaData);
6098							}
6099							break;
6100						default:
6101							$.error("Invalid data-type: " + type);
6102					}
6103				} else if (typeof source === "string") {
6104					// TODO: source is an element ID
6105					$.error("Not implemented");
6106				}
6107
6108				// preInit is fired when the widget markup is created, but nodes
6109				// not yet loaded
6110				tree._triggerTreeEvent("preInit", null);
6111
6112				// Trigger fancytreeinit after nodes have been loaded
6113				dfd = this.nodeLoadChildren(rootCtx, source)
6114					.done(function () {
6115						tree._callHook(
6116							"treeStructureChanged",
6117							ctx,
6118							"loadChildren"
6119						);
6120						tree.render();
6121						if (ctx.options.selectMode === 3) {
6122							tree.rootNode.fixSelection3FromEndNodes();
6123						}
6124						if (tree.activeNode && tree.options.activeVisible) {
6125							tree.activeNode.makeVisible();
6126						}
6127						tree._triggerTreeEvent("init", null, { status: true });
6128					})
6129					.fail(function () {
6130						tree.render();
6131						tree._triggerTreeEvent("init", null, { status: false });
6132					});
6133				return dfd;
6134			},
6135			/** Node was inserted into or removed from the tree.
6136			 * @param {EventData} ctx
6137			 * @param {boolean} add
6138			 * @param {FancytreeNode} node
6139			 */
6140			treeRegisterNode: function (ctx, add, node) {
6141				ctx.tree._callHook(
6142					"treeStructureChanged",
6143					ctx,
6144					add ? "addNode" : "removeNode"
6145				);
6146			},
6147			/** Widget got focus.
6148			 * @param {EventData} ctx
6149			 * @param {boolean} [flag=true]
6150			 */
6151			treeSetFocus: function (ctx, flag, callOpts) {
6152				var targetNode;
6153
6154				flag = flag !== false;
6155
6156				// this.debug("treeSetFocus(" + flag + "), callOpts: ", callOpts, this.hasFocus());
6157				// this.debug("    focusNode: " + this.focusNode);
6158				// this.debug("    activeNode: " + this.activeNode);
6159				if (flag !== this.hasFocus()) {
6160					this._hasFocus = flag;
6161					if (!flag && this.focusNode) {
6162						// Node also looses focus if widget blurs
6163						this.focusNode.setFocus(false);
6164					} else if (flag && (!callOpts || !callOpts.calledByNode)) {
6165						$(this.$container).focus();
6166					}
6167					this.$container.toggleClass("fancytree-treefocus", flag);
6168					this._triggerTreeEvent(flag ? "focusTree" : "blurTree");
6169					if (flag && !this.activeNode) {
6170						// #712: Use last mousedowned node ('click' event fires after focusin)
6171						targetNode =
6172							this._lastMousedownNode || this.getFirstChild();
6173						if (targetNode) {
6174							targetNode.setFocus();
6175						}
6176					}
6177				}
6178			},
6179			/** Widget option was set using `$().fancytree("option", "KEY", VALUE)`.
6180			 *
6181			 * Note: `key` may reference a nested option, e.g. 'dnd5.scroll'.
6182			 * In this case `value`contains the complete, modified `dnd5` option hash.
6183			 * We can check for changed values like
6184			 *     if( value.scroll !== tree.options.dnd5.scroll ) {...}
6185			 *
6186			 * @param {EventData} ctx
6187			 * @param {string} key option name
6188			 * @param {any} value option value
6189			 */
6190			treeSetOption: function (ctx, key, value) {
6191				var tree = ctx.tree,
6192					callDefault = true,
6193					callCreate = false,
6194					callRender = false;
6195
6196				switch (key) {
6197					case "aria":
6198					case "checkbox":
6199					case "icon":
6200					case "minExpandLevel":
6201					case "tabindex":
6202						// tree._callHook("treeCreate", tree);
6203						callCreate = true;
6204						callRender = true;
6205						break;
6206					case "checkboxAutoHide":
6207						tree.$container.toggleClass(
6208							"fancytree-checkbox-auto-hide",
6209							!!value
6210						);
6211						break;
6212					case "escapeTitles":
6213					case "tooltip":
6214						callRender = true;
6215						break;
6216					case "rtl":
6217						if (value === false) {
6218							tree.$container
6219								.removeAttr("DIR")
6220								.removeClass("fancytree-rtl");
6221						} else {
6222							tree.$container
6223								.attr("DIR", "RTL")
6224								.addClass("fancytree-rtl");
6225						}
6226						callRender = true;
6227						break;
6228					case "source":
6229						callDefault = false;
6230						tree._callHook("treeLoad", tree, value);
6231						callRender = true;
6232						break;
6233				}
6234				tree.debug(
6235					"set option " +
6236						key +
6237						"=" +
6238						value +
6239						" <" +
6240						typeof value +
6241						">"
6242				);
6243				if (callDefault) {
6244					if (this.widget._super) {
6245						// jQuery UI 1.9+
6246						this.widget._super.call(this.widget, key, value);
6247					} else {
6248						// jQuery UI <= 1.8, we have to manually invoke the _setOption method from the base widget
6249						$.Widget.prototype._setOption.call(
6250							this.widget,
6251							key,
6252							value
6253						);
6254					}
6255				}
6256				if (callCreate) {
6257					tree._callHook("treeCreate", tree);
6258				}
6259				if (callRender) {
6260					tree.render(true, false); // force, not-deep
6261				}
6262			},
6263			/** A Node was added, removed, moved, or it's visibility changed.
6264			 * @param {EventData} ctx
6265			 */
6266			treeStructureChanged: function (ctx, type) {},
6267		}
6268	);
6269
6270	/*******************************************************************************
6271	 * jQuery UI widget boilerplate
6272	 */
6273
6274	/**
6275	 * The plugin (derrived from [jQuery.Widget](http://api.jqueryui.com/jQuery.widget/)).
6276	 *
6277	 * **Note:**
6278	 * These methods implement the standard jQuery UI widget API.
6279	 * It is recommended to use methods of the {Fancytree} instance instead
6280	 *
6281	 * @example
6282	 * // DEPRECATED: Access jQuery UI widget methods and members:
6283	 * var tree = $("#tree").fancytree("getTree");
6284	 * var node = $("#tree").fancytree("getActiveNode");
6285	 *
6286	 * // RECOMMENDED: Use the Fancytree object API
6287	 * var tree = $.ui.fancytree.getTree("#tree");
6288	 * var node = tree.getActiveNode();
6289	 *
6290	 * // or you may already have stored the tree instance upon creation:
6291	 * import {createTree, version} from 'jquery.fancytree'
6292	 * const tree = createTree('#tree', { ... });
6293	 * var node = tree.getActiveNode();
6294	 *
6295	 * @see {Fancytree_Static#getTree}
6296	 * @deprecated Use methods of the {Fancytree} instance instead
6297	 * @mixin Fancytree_Widget
6298	 */
6299
6300	$.widget(
6301		"ui.fancytree",
6302		/** @lends Fancytree_Widget# */
6303		{
6304			/**These options will be used as defaults
6305			 * @type {FancytreeOptions}
6306			 */
6307			options: {
6308				activeVisible: true,
6309				ajax: {
6310					type: "GET",
6311					cache: false, // false: Append random '_' argument to the request url to prevent caching.
6312					// timeout: 0, // >0: Make sure we get an ajax error if server is unreachable
6313					dataType: "json", // Expect json format and pass json object to callbacks.
6314				},
6315				aria: true,
6316				autoActivate: true,
6317				autoCollapse: false,
6318				autoScroll: false,
6319				checkbox: false,
6320				clickFolderMode: 4,
6321				copyFunctionsToData: false,
6322				debugLevel: null, // 0..4 (null: use global setting $.ui.fancytree.debugLevel)
6323				disabled: false, // TODO: required anymore?
6324				enableAspx: 42, // TODO: this is truethy, but distinguishable from true: default will change to false in the future
6325				escapeTitles: false,
6326				extensions: [],
6327				focusOnSelect: false,
6328				generateIds: false,
6329				icon: true,
6330				idPrefix: "ft_",
6331				keyboard: true,
6332				keyPathSeparator: "/",
6333				minExpandLevel: 1,
6334				nodata: true, // (bool, string, or callback) display message, when no data available
6335				quicksearch: false,
6336				rtl: false,
6337				scrollOfs: { top: 0, bottom: 0 },
6338				scrollParent: null,
6339				selectMode: 2,
6340				strings: {
6341					loading: "Loading...", // &#8230; would be escaped when escapeTitles is true
6342					loadError: "Load error!",
6343					moreData: "More...",
6344					noData: "No data.",
6345				},
6346				tabindex: "0",
6347				titlesTabbable: false,
6348				toggleEffect: { effect: "slideToggle", duration: 200 }, //< "toggle" or "slideToggle" to use jQuery instead of jQueryUI for toggleEffect animation
6349				tooltip: false,
6350				treeId: null,
6351				_classNames: {
6352					active: "fancytree-active",
6353					animating: "fancytree-animating",
6354					combinedExpanderPrefix: "fancytree-exp-",
6355					combinedIconPrefix: "fancytree-ico-",
6356					error: "fancytree-error",
6357					expanded: "fancytree-expanded",
6358					focused: "fancytree-focused",
6359					folder: "fancytree-folder",
6360					hasChildren: "fancytree-has-children",
6361					lastsib: "fancytree-lastsib",
6362					lazy: "fancytree-lazy",
6363					loading: "fancytree-loading",
6364					node: "fancytree-node",
6365					partload: "fancytree-partload",
6366					partsel: "fancytree-partsel",
6367					radio: "fancytree-radio",
6368					selected: "fancytree-selected",
6369					statusNodePrefix: "fancytree-statusnode-",
6370					unselectable: "fancytree-unselectable",
6371				},
6372				// events
6373				lazyLoad: null,
6374				postProcess: null,
6375			},
6376			_deprecationWarning: function (name) {
6377				var tree = this.tree;
6378
6379				if (tree && tree.options.debugLevel >= 3) {
6380					tree.warn(
6381						"$().fancytree('" +
6382							name +
6383							"') is deprecated (see https://wwwendt.de/tech/fancytree/doc/jsdoc/Fancytree_Widget.html"
6384					);
6385				}
6386			},
6387			/* Set up the widget, Called on first $().fancytree() */
6388			_create: function () {
6389				this.tree = new Fancytree(this);
6390
6391				this.$source =
6392					this.source || this.element.data("type") === "json"
6393						? this.element
6394						: this.element.find(">ul").first();
6395				// Subclass Fancytree instance with all enabled extensions
6396				var extension,
6397					extName,
6398					i,
6399					opts = this.options,
6400					extensions = opts.extensions,
6401					base = this.tree;
6402
6403				for (i = 0; i < extensions.length; i++) {
6404					extName = extensions[i];
6405					extension = $.ui.fancytree._extensions[extName];
6406					if (!extension) {
6407						$.error(
6408							"Could not apply extension '" +
6409								extName +
6410								"' (it is not registered, did you forget to include it?)"
6411						);
6412					}
6413					// Add extension options as tree.options.EXTENSION
6414					// 	_assert(!this.tree.options[extName], "Extension name must not exist as option name: " + extName);
6415
6416					// console.info("extend " + extName, extension.options, this.tree.options[extName])
6417					// issue #876: we want to replace custom array-options, not merge them
6418					this.tree.options[extName] = _simpleDeepMerge(
6419						{},
6420						extension.options,
6421						this.tree.options[extName]
6422					);
6423					// this.tree.options[extName] = $.extend(true, {}, extension.options, this.tree.options[extName]);
6424
6425					// console.info("extend " + extName + " =>", this.tree.options[extName])
6426					// console.info("extend " + extName + " org default =>", extension.options)
6427
6428					// Add a namespace tree.ext.EXTENSION, to hold instance data
6429					_assert(
6430						this.tree.ext[extName] === undefined,
6431						"Extension name must not exist as Fancytree.ext attribute: '" +
6432							extName +
6433							"'"
6434					);
6435					// this.tree[extName] = extension;
6436					this.tree.ext[extName] = {};
6437					// Subclass Fancytree methods using proxies.
6438					_subclassObject(this.tree, base, extension, extName);
6439					// current extension becomes base for the next extension
6440					base = extension;
6441				}
6442				//
6443				if (opts.icons !== undefined) {
6444					// 2015-11-16
6445					if (opts.icon === true) {
6446						this.tree.warn(
6447							"'icons' tree option is deprecated since v2.14.0: use 'icon' instead"
6448						);
6449						opts.icon = opts.icons;
6450					} else {
6451						$.error(
6452							"'icons' tree option is deprecated since v2.14.0: use 'icon' only instead"
6453						);
6454					}
6455				}
6456				if (opts.iconClass !== undefined) {
6457					// 2015-11-16
6458					if (opts.icon) {
6459						$.error(
6460							"'iconClass' tree option is deprecated since v2.14.0: use 'icon' only instead"
6461						);
6462					} else {
6463						this.tree.warn(
6464							"'iconClass' tree option is deprecated since v2.14.0: use 'icon' instead"
6465						);
6466						opts.icon = opts.iconClass;
6467					}
6468				}
6469				if (opts.tabbable !== undefined) {
6470					// 2016-04-04
6471					opts.tabindex = opts.tabbable ? "0" : "-1";
6472					this.tree.warn(
6473						"'tabbable' tree option is deprecated since v2.17.0: use 'tabindex='" +
6474							opts.tabindex +
6475							"' instead"
6476					);
6477				}
6478				//
6479				this.tree._callHook("treeCreate", this.tree);
6480				// Note: 'fancytreecreate' event is fired by widget base class
6481				//        this.tree._triggerTreeEvent("create");
6482			},
6483
6484			/* Called on every $().fancytree() */
6485			_init: function () {
6486				this.tree._callHook("treeInit", this.tree);
6487				// TODO: currently we call bind after treeInit, because treeInit
6488				// might change tree.$container.
6489				// It would be better, to move event binding into hooks altogether
6490				this._bind();
6491			},
6492
6493			/* Use the _setOption method to respond to changes to options. */
6494			_setOption: function (key, value) {
6495				return this.tree._callHook(
6496					"treeSetOption",
6497					this.tree,
6498					key,
6499					value
6500				);
6501			},
6502
6503			/** Use the destroy method to clean up any modifications your widget has made to the DOM */
6504			_destroy: function () {
6505				this._unbind();
6506				this.tree._callHook("treeDestroy", this.tree);
6507				// In jQuery UI 1.8, you must invoke the destroy method from the base widget
6508				// $.Widget.prototype.destroy.call(this);
6509				// TODO: delete tree and nodes to make garbage collect easier?
6510				// TODO: In jQuery UI 1.9 and above, you would define _destroy instead of destroy and not call the base method
6511			},
6512
6513			// -------------------------------------------------------------------------
6514
6515			/* Remove all event handlers for our namespace */
6516			_unbind: function () {
6517				var ns = this.tree._ns;
6518				this.element.off(ns);
6519				this.tree.$container.off(ns);
6520				$(document).off(ns);
6521			},
6522			/* Add mouse and kyboard handlers to the container */
6523			_bind: function () {
6524				var self = this,
6525					opts = this.options,
6526					tree = this.tree,
6527					ns = tree._ns;
6528				// selstartEvent = ( $.support.selectstart ? "selectstart" : "mousedown" )
6529
6530				// Remove all previuous handlers for this tree
6531				this._unbind();
6532
6533				//alert("keydown" + ns + "foc=" + tree.hasFocus() + tree.$container);
6534				// tree.debug("bind events; container: ", tree.$container);
6535				tree.$container
6536					.on("focusin" + ns + " focusout" + ns, function (event) {
6537						var node = FT.getNode(event),
6538							flag = event.type === "focusin";
6539
6540						if (!flag && node && $(event.target).is("a")) {
6541							// #764
6542							node.debug(
6543								"Ignored focusout on embedded <a> element."
6544							);
6545							return;
6546						}
6547						// tree.treeOnFocusInOut.call(tree, event);
6548						// tree.debug("Tree container got event " + event.type, node, event, FT.getEventTarget(event));
6549						if (flag) {
6550							if (tree._getExpiringValue("focusin")) {
6551								// #789: IE 11 may send duplicate focusin events
6552								tree.debug("Ignored double focusin.");
6553								return;
6554							}
6555							tree._setExpiringValue("focusin", true, 50);
6556
6557							if (!node) {
6558								// #789: IE 11 may send focusin before mousdown(?)
6559								node = tree._getExpiringValue("mouseDownNode");
6560								if (node) {
6561									tree.debug(
6562										"Reconstruct mouse target for focusin from recent event."
6563									);
6564								}
6565							}
6566						}
6567						if (node) {
6568							// For example clicking into an <input> that is part of a node
6569							tree._callHook(
6570								"nodeSetFocus",
6571								tree._makeHookContext(node, event),
6572								flag
6573							);
6574						} else {
6575							if (
6576								tree.tbody &&
6577								$(event.target).parents(
6578									"table.fancytree-container > thead"
6579								).length
6580							) {
6581								// #767: ignore events in the table's header
6582								tree.debug(
6583									"Ignore focus event outside table body.",
6584									event
6585								);
6586							} else {
6587								tree._callHook("treeSetFocus", tree, flag);
6588							}
6589						}
6590					})
6591					.on(
6592						"selectstart" + ns,
6593						"span.fancytree-title",
6594						function (event) {
6595							// prevent mouse-drags to select text ranges
6596							// tree.debug("<span title> got event " + event.type);
6597							event.preventDefault();
6598						}
6599					)
6600					.on("keydown" + ns, function (event) {
6601						// TODO: also bind keyup and keypress
6602						// tree.debug("got event " + event.type + ", hasFocus:" + tree.hasFocus());
6603						// if(opts.disabled || opts.keyboard === false || !tree.hasFocus() ){
6604						if (opts.disabled || opts.keyboard === false) {
6605							return true;
6606						}
6607						var res,
6608							node = tree.focusNode, // node may be null
6609							ctx = tree._makeHookContext(node || tree, event),
6610							prevPhase = tree.phase;
6611
6612						try {
6613							tree.phase = "userEvent";
6614							// If a 'fancytreekeydown' handler returns false, skip the default
6615							// handling (implemented by tree.nodeKeydown()).
6616							if (node) {
6617								res = tree._triggerNodeEvent(
6618									"keydown",
6619									node,
6620									event
6621								);
6622							} else {
6623								res = tree._triggerTreeEvent("keydown", event);
6624							}
6625							if (res === "preventNav") {
6626								res = true; // prevent keyboard navigation, but don't prevent default handling of embedded input controls
6627							} else if (res !== false) {
6628								res = tree._callHook("nodeKeydown", ctx);
6629							}
6630							return res;
6631						} finally {
6632							tree.phase = prevPhase;
6633						}
6634					})
6635					.on("mousedown" + ns, function (event) {
6636						var et = FT.getEventTarget(event);
6637						// self.tree.debug("event(" + event.type + "): node: ", et.node);
6638						// #712: Store the clicked node, so we can use it when we get a focusin event
6639						//       ('click' event fires after focusin)
6640						// tree.debug("event(" + event.type + "): node: ", et.node);
6641						tree._lastMousedownNode = et ? et.node : null;
6642						// #789: Store the node also for a short period, so we can use it
6643						// in a *resulting* focusin event
6644						tree._setExpiringValue(
6645							"mouseDownNode",
6646							tree._lastMousedownNode
6647						);
6648					})
6649					.on("click" + ns + " dblclick" + ns, function (event) {
6650						if (opts.disabled) {
6651							return true;
6652						}
6653						var ctx,
6654							et = FT.getEventTarget(event),
6655							node = et.node,
6656							tree = self.tree,
6657							prevPhase = tree.phase;
6658
6659						// self.tree.debug("event(" + event.type + "): node: ", node);
6660						if (!node) {
6661							return true; // Allow bubbling of other events
6662						}
6663						ctx = tree._makeHookContext(node, event);
6664						// self.tree.debug("event(" + event.type + "): node: ", node);
6665						try {
6666							tree.phase = "userEvent";
6667							switch (event.type) {
6668								case "click":
6669									ctx.targetType = et.type;
6670									if (node.isPagingNode()) {
6671										return (
6672											tree._triggerNodeEvent(
6673												"clickPaging",
6674												ctx,
6675												event
6676											) === true
6677										);
6678									}
6679									return tree._triggerNodeEvent(
6680										"click",
6681										ctx,
6682										event
6683									) === false
6684										? false
6685										: tree._callHook("nodeClick", ctx);
6686								case "dblclick":
6687									ctx.targetType = et.type;
6688									return tree._triggerNodeEvent(
6689										"dblclick",
6690										ctx,
6691										event
6692									) === false
6693										? false
6694										: tree._callHook("nodeDblclick", ctx);
6695							}
6696						} finally {
6697							tree.phase = prevPhase;
6698						}
6699					});
6700			},
6701			/** Return the active node or null.
6702			 * @returns {FancytreeNode}
6703			 * @deprecated Use methods of the Fancytree instance instead (<a href="Fancytree_Widget.html">example above</a>).
6704			 */
6705			getActiveNode: function () {
6706				this._deprecationWarning("getActiveNode");
6707				return this.tree.activeNode;
6708			},
6709			/** Return the matching node or null.
6710			 * @param {string} key
6711			 * @returns {FancytreeNode}
6712			 * @deprecated Use methods of the Fancytree instance instead (<a href="Fancytree_Widget.html">example above</a>).
6713			 */
6714			getNodeByKey: function (key) {
6715				this._deprecationWarning("getNodeByKey");
6716				return this.tree.getNodeByKey(key);
6717			},
6718			/** Return the invisible system root node.
6719			 * @returns {FancytreeNode}
6720			 * @deprecated Use methods of the Fancytree instance instead (<a href="Fancytree_Widget.html">example above</a>).
6721			 */
6722			getRootNode: function () {
6723				this._deprecationWarning("getRootNode");
6724				return this.tree.rootNode;
6725			},
6726			/** Return the current tree instance.
6727			 * @returns {Fancytree}
6728			 * @deprecated Use `$.ui.fancytree.getTree()` instead (<a href="Fancytree_Widget.html">example above</a>).
6729			 */
6730			getTree: function () {
6731				this._deprecationWarning("getTree");
6732				return this.tree;
6733			},
6734		}
6735	);
6736
6737	// $.ui.fancytree was created by the widget factory. Create a local shortcut:
6738	FT = $.ui.fancytree;
6739
6740	/**
6741	 * Static members in the `$.ui.fancytree` namespace.
6742	 * This properties and methods can be accessed without instantiating a concrete
6743	 * Fancytree instance.
6744	 *
6745	 * @example
6746	 * // Access static members:
6747	 * var node = $.ui.fancytree.getNode(element);
6748	 * alert($.ui.fancytree.version);
6749	 *
6750	 * @mixin Fancytree_Static
6751	 */
6752	$.extend(
6753		$.ui.fancytree,
6754		/** @lends Fancytree_Static# */
6755		{
6756			/** Version number `"MAJOR.MINOR.PATCH"`
6757			 * @type {string} */
6758			version: "2.38.3", // Set to semver by 'grunt release'
6759			/** @type {string}
6760			 * @description `"production" for release builds` */
6761			buildType: "production", // Set to 'production' by 'grunt build'
6762			/** @type {int}
6763			 * @description 0: silent .. 5: verbose (default: 3 for release builds). */
6764			debugLevel: 3, // Set to 3 by 'grunt build'
6765			// Used by $.ui.fancytree.debug() and as default for tree.options.debugLevel
6766
6767			_nextId: 1,
6768			_nextNodeKey: 1,
6769			_extensions: {},
6770			// focusTree: null,
6771
6772			/** Expose class object as `$.ui.fancytree._FancytreeClass`.
6773			 * Useful to extend `$.ui.fancytree._FancytreeClass.prototype`.
6774			 * @type {Fancytree}
6775			 */
6776			_FancytreeClass: Fancytree,
6777			/** Expose class object as $.ui.fancytree._FancytreeNodeClass
6778			 * Useful to extend `$.ui.fancytree._FancytreeNodeClass.prototype`.
6779			 * @type {FancytreeNode}
6780			 */
6781			_FancytreeNodeClass: FancytreeNode,
6782			/* Feature checks to provide backwards compatibility */
6783			jquerySupports: {
6784				// http://jqueryui.com/upgrade-guide/1.9/#deprecated-offset-option-merged-into-my-and-at
6785				positionMyOfs: isVersionAtLeast($.ui.version, 1, 9),
6786			},
6787			/** Throw an error if condition fails (debug method).
6788			 * @param {boolean} cond
6789			 * @param {string} msg
6790			 */
6791			assert: function (cond, msg) {
6792				return _assert(cond, msg);
6793			},
6794			/** Create a new Fancytree instance on a target element.
6795			 *
6796			 * @param {Element | jQueryObject | string} el Target DOM element or selector
6797			 * @param {FancytreeOptions} [opts] Fancytree options
6798			 * @returns {Fancytree} new tree instance
6799			 * @example
6800			 * var tree = $.ui.fancytree.createTree("#tree", {
6801			 *     source: {url: "my/webservice"}
6802			 * }); // Create tree for this matching element
6803			 *
6804			 * @since 2.25
6805			 */
6806			createTree: function (el, opts) {
6807				var $tree = $(el).fancytree(opts);
6808				return FT.getTree($tree);
6809			},
6810			/** Return a function that executes *fn* at most every *timeout* ms.
6811			 * @param {integer} timeout
6812			 * @param {function} fn
6813			 * @param {boolean} [invokeAsap=false]
6814			 * @param {any} [ctx]
6815			 */
6816			debounce: function (timeout, fn, invokeAsap, ctx) {
6817				var timer;
6818				if (arguments.length === 3 && typeof invokeAsap !== "boolean") {
6819					ctx = invokeAsap;
6820					invokeAsap = false;
6821				}
6822				return function () {
6823					var args = arguments;
6824					ctx = ctx || this;
6825					// eslint-disable-next-line no-unused-expressions
6826					invokeAsap && !timer && fn.apply(ctx, args);
6827					clearTimeout(timer);
6828					timer = setTimeout(function () {
6829						// eslint-disable-next-line no-unused-expressions
6830						invokeAsap || fn.apply(ctx, args);
6831						timer = null;
6832					}, timeout);
6833				};
6834			},
6835			/** Write message to console if debugLevel >= 4
6836			 * @param {string} msg
6837			 */
6838			debug: function (msg) {
6839				if ($.ui.fancytree.debugLevel >= 4) {
6840					consoleApply("log", arguments);
6841				}
6842			},
6843			/** Write error message to console if debugLevel >= 1.
6844			 * @param {string} msg
6845			 */
6846			error: function (msg) {
6847				if ($.ui.fancytree.debugLevel >= 1) {
6848					consoleApply("error", arguments);
6849				}
6850			},
6851			/** Convert `<`, `>`, `&`, `"`, `'`, and `/` to the equivalent entities.
6852			 *
6853			 * @param {string} s
6854			 * @returns {string}
6855			 */
6856			escapeHtml: function (s) {
6857				return ("" + s).replace(REX_HTML, function (s) {
6858					return ENTITY_MAP[s];
6859				});
6860			},
6861			/** Make jQuery.position() arguments backwards compatible, i.e. if
6862			 * jQuery UI version <= 1.8, convert
6863			 *   { my: "left+3 center", at: "left bottom", of: $target }
6864			 * to
6865			 *   { my: "left center", at: "left bottom", of: $target, offset: "3  0" }
6866			 *
6867			 * See http://jqueryui.com/upgrade-guide/1.9/#deprecated-offset-option-merged-into-my-and-at
6868			 * and http://jsfiddle.net/mar10/6xtu9a4e/
6869			 *
6870			 * @param {object} opts
6871			 * @returns {object} the (potentially modified) original opts hash object
6872			 */
6873			fixPositionOptions: function (opts) {
6874				if (opts.offset || ("" + opts.my + opts.at).indexOf("%") >= 0) {
6875					$.error(
6876						"expected new position syntax (but '%' is not supported)"
6877					);
6878				}
6879				if (!$.ui.fancytree.jquerySupports.positionMyOfs) {
6880					var // parse 'left+3 center' into ['left+3 center', 'left', '+3', 'center', undefined]
6881						myParts = /(\w+)([+-]?\d+)?\s+(\w+)([+-]?\d+)?/.exec(
6882							opts.my
6883						),
6884						atParts = /(\w+)([+-]?\d+)?\s+(\w+)([+-]?\d+)?/.exec(
6885							opts.at
6886						),
6887						// convert to numbers
6888						dx =
6889							(myParts[2] ? +myParts[2] : 0) +
6890							(atParts[2] ? +atParts[2] : 0),
6891						dy =
6892							(myParts[4] ? +myParts[4] : 0) +
6893							(atParts[4] ? +atParts[4] : 0);
6894
6895					opts = $.extend({}, opts, {
6896						// make a copy and overwrite
6897						my: myParts[1] + " " + myParts[3],
6898						at: atParts[1] + " " + atParts[3],
6899					});
6900					if (dx || dy) {
6901						opts.offset = "" + dx + " " + dy;
6902					}
6903				}
6904				return opts;
6905			},
6906			/** Return a {node: FancytreeNode, type: TYPE} object for a mouse event.
6907			 *
6908			 * @param {Event} event Mouse event, e.g. click, ...
6909			 * @returns {object} Return a {node: FancytreeNode, type: TYPE} object
6910			 *     TYPE: 'title' | 'prefix' | 'expander' | 'checkbox' | 'icon' | undefined
6911			 */
6912			getEventTarget: function (event) {
6913				var $target,
6914					tree,
6915					tcn = event && event.target ? event.target.className : "",
6916					res = { node: this.getNode(event.target), type: undefined };
6917				// We use a fast version of $(res.node).hasClass()
6918				// See http://jsperf.com/test-for-classname/2
6919				if (/\bfancytree-title\b/.test(tcn)) {
6920					res.type = "title";
6921				} else if (/\bfancytree-expander\b/.test(tcn)) {
6922					res.type =
6923						res.node.hasChildren() === false
6924							? "prefix"
6925							: "expander";
6926					// }else if( /\bfancytree-checkbox\b/.test(tcn) || /\bfancytree-radio\b/.test(tcn) ){
6927				} else if (/\bfancytree-checkbox\b/.test(tcn)) {
6928					res.type = "checkbox";
6929				} else if (/\bfancytree(-custom)?-icon\b/.test(tcn)) {
6930					res.type = "icon";
6931				} else if (/\bfancytree-node\b/.test(tcn)) {
6932					// Somewhere near the title
6933					res.type = "title";
6934				} else if (event && event.target) {
6935					$target = $(event.target);
6936					if ($target.is("ul[role=group]")) {
6937						// #nnn: Clicking right to a node may hit the surrounding UL
6938						tree = res.node && res.node.tree;
6939						(tree || FT).debug("Ignoring click on outer UL.");
6940						res.node = null;
6941					} else if ($target.closest(".fancytree-title").length) {
6942						// #228: clicking an embedded element inside a title
6943						res.type = "title";
6944					} else if ($target.closest(".fancytree-checkbox").length) {
6945						// E.g. <svg> inside checkbox span
6946						res.type = "checkbox";
6947					} else if ($target.closest(".fancytree-expander").length) {
6948						res.type = "expander";
6949					}
6950				}
6951				return res;
6952			},
6953			/** Return a string describing the affected node region for a mouse event.
6954			 *
6955			 * @param {Event} event Mouse event, e.g. click, mousemove, ...
6956			 * @returns {string} 'title' | 'prefix' | 'expander' | 'checkbox' | 'icon' | undefined
6957			 */
6958			getEventTargetType: function (event) {
6959				return this.getEventTarget(event).type;
6960			},
6961			/** Return a FancytreeNode instance from element, event, or jQuery object.
6962			 *
6963			 * @param {Element | jQueryObject | Event} el
6964			 * @returns {FancytreeNode} matching node or null
6965			 */
6966			getNode: function (el) {
6967				if (el instanceof FancytreeNode) {
6968					return el; // el already was a FancytreeNode
6969				} else if (el instanceof $) {
6970					el = el[0]; // el was a jQuery object: use the DOM element
6971				} else if (el.originalEvent !== undefined) {
6972					el = el.target; // el was an Event
6973				}
6974				while (el) {
6975					if (el.ftnode) {
6976						return el.ftnode;
6977					}
6978					el = el.parentNode;
6979				}
6980				return null;
6981			},
6982			/** Return a Fancytree instance, from element, index, event, or jQueryObject.
6983			 *
6984			 * @param {Element | jQueryObject | Event | integer | string} [el]
6985			 * @returns {Fancytree} matching tree or null
6986			 * @example
6987			 * $.ui.fancytree.getTree();  // Get first Fancytree instance on page
6988			 * $.ui.fancytree.getTree(1);  // Get second Fancytree instance on page
6989			 * $.ui.fancytree.getTree(event);  // Get tree for this mouse- or keyboard event
6990			 * $.ui.fancytree.getTree("foo");  // Get tree for this `opts.treeId`
6991			 * $.ui.fancytree.getTree("#tree");  // Get tree for this matching element
6992			 *
6993			 * @since 2.13
6994			 */
6995			getTree: function (el) {
6996				var widget,
6997					orgEl = el;
6998
6999				if (el instanceof Fancytree) {
7000					return el; // el already was a Fancytree
7001				}
7002				if (el === undefined) {
7003					el = 0; // get first tree
7004				}
7005				if (typeof el === "number") {
7006					el = $(".fancytree-container").eq(el); // el was an integer: return nth instance
7007				} else if (typeof el === "string") {
7008					// `el` may be a treeId or a selector:
7009					el = $("#ft-id-" + orgEl).eq(0);
7010					if (!el.length) {
7011						el = $(orgEl).eq(0); // el was a selector: use first match
7012					}
7013				} else if (
7014					el instanceof Element ||
7015					el instanceof HTMLDocument
7016				) {
7017					el = $(el);
7018				} else if (el instanceof $) {
7019					el = el.eq(0); // el was a jQuery object: use the first
7020				} else if (el.originalEvent !== undefined) {
7021					el = $(el.target); // el was an Event
7022				}
7023				// el is a jQuery object wit one element here
7024				el = el.closest(":ui-fancytree");
7025				widget = el.data("ui-fancytree") || el.data("fancytree"); // the latter is required by jQuery <= 1.8
7026				return widget ? widget.tree : null;
7027			},
7028			/** Return an option value that has a default, but may be overridden by a
7029			 * callback or a node instance attribute.
7030			 *
7031			 * Evaluation sequence:
7032			 *
7033			 * If `tree.options.<optionName>` is a callback that returns something, use that.
7034			 * Else if `node.<optionName>` is defined, use that.
7035			 * Else if `tree.options.<optionName>` is a value, use that.
7036			 * Else use `defaultValue`.
7037			 *
7038			 * @param {string} optionName name of the option property (on node and tree)
7039			 * @param {FancytreeNode} node passed to the callback
7040			 * @param {object} nodeObject where to look for the local option property, e.g. `node` or `node.data`
7041			 * @param {object} treeOption where to look for the tree option, e.g. `tree.options` or `tree.options.dnd5`
7042			 * @param {any} [defaultValue]
7043			 * @returns {any}
7044			 *
7045			 * @example
7046			 * // Check for node.foo, tree,options.foo(), and tree.options.foo:
7047			 * $.ui.fancytree.evalOption("foo", node, node, tree.options);
7048			 * // Check for node.data.bar, tree,options.qux.bar(), and tree.options.qux.bar:
7049			 * $.ui.fancytree.evalOption("bar", node, node.data, tree.options.qux);
7050			 *
7051			 * @since 2.22
7052			 */
7053			evalOption: function (
7054				optionName,
7055				node,
7056				nodeObject,
7057				treeOptions,
7058				defaultValue
7059			) {
7060				var ctx,
7061					res,
7062					tree = node.tree,
7063					treeOpt = treeOptions[optionName],
7064					nodeOpt = nodeObject[optionName];
7065
7066				if (_isFunction(treeOpt)) {
7067					ctx = {
7068						node: node,
7069						tree: tree,
7070						widget: tree.widget,
7071						options: tree.widget.options,
7072						typeInfo: tree.types[node.type] || {},
7073					};
7074					res = treeOpt.call(tree, { type: optionName }, ctx);
7075					if (res == null) {
7076						res = nodeOpt;
7077					}
7078				} else {
7079					res = nodeOpt == null ? treeOpt : nodeOpt;
7080				}
7081				if (res == null) {
7082					res = defaultValue; // no option set at all: return default
7083				}
7084				return res;
7085			},
7086			/** Set expander, checkbox, or node icon, supporting string and object format.
7087			 *
7088			 * @param {Element | jQueryObject} span
7089			 * @param {string} baseClass
7090			 * @param {string | object} icon
7091			 * @since 2.27
7092			 */
7093			setSpanIcon: function (span, baseClass, icon) {
7094				var $span = $(span);
7095
7096				if (typeof icon === "string") {
7097					$span.attr("class", baseClass + " " + icon);
7098				} else {
7099					// support object syntax: { text: ligature, addClasse: classname }
7100					if (icon.text) {
7101						$span.text("" + icon.text);
7102					} else if (icon.html) {
7103						span.innerHTML = icon.html;
7104					}
7105					$span.attr(
7106						"class",
7107						baseClass + " " + (icon.addClass || "")
7108					);
7109				}
7110			},
7111			/** Convert a keydown or mouse event to a canonical string like 'ctrl+a',
7112			 * 'ctrl+shift+f2', 'shift+leftdblclick'.
7113			 *
7114			 * This is especially handy for switch-statements in event handlers.
7115			 *
7116			 * @param {event}
7117			 * @returns {string}
7118			 *
7119			 * @example
7120
7121			switch( $.ui.fancytree.eventToString(event) ) {
7122				case "-":
7123					tree.nodeSetExpanded(ctx, false);
7124					break;
7125				case "shift+return":
7126					tree.nodeSetActive(ctx, true);
7127					break;
7128				case "down":
7129					res = node.navigate(event.which, activate);
7130					break;
7131				default:
7132					handled = false;
7133			}
7134			if( handled ){
7135				event.preventDefault();
7136			}
7137			*/
7138			eventToString: function (event) {
7139				// Poor-man's hotkeys. See here for a complete implementation:
7140				//   https://github.com/jeresig/jquery.hotkeys
7141				var which = event.which,
7142					et = event.type,
7143					s = [];
7144
7145				if (event.altKey) {
7146					s.push("alt");
7147				}
7148				if (event.ctrlKey) {
7149					s.push("ctrl");
7150				}
7151				if (event.metaKey) {
7152					s.push("meta");
7153				}
7154				if (event.shiftKey) {
7155					s.push("shift");
7156				}
7157
7158				if (et === "click" || et === "dblclick") {
7159					s.push(MOUSE_BUTTONS[event.button] + et);
7160				} else if (et === "wheel") {
7161					s.push(et);
7162				} else if (!IGNORE_KEYCODES[which]) {
7163					s.push(
7164						SPECIAL_KEYCODES[which] ||
7165							String.fromCharCode(which).toLowerCase()
7166					);
7167				}
7168				return s.join("+");
7169			},
7170			/** Write message to console if debugLevel >= 3
7171			 * @param {string} msg
7172			 */
7173			info: function (msg) {
7174				if ($.ui.fancytree.debugLevel >= 3) {
7175					consoleApply("info", arguments);
7176				}
7177			},
7178			/* @deprecated: use eventToString(event) instead.
7179			 */
7180			keyEventToString: function (event) {
7181				this.warn(
7182					"keyEventToString() is deprecated: use eventToString()"
7183				);
7184				return this.eventToString(event);
7185			},
7186			/** Return a wrapped handler method, that provides `this._super`.
7187			 *
7188			 * @example
7189				// Implement `opts.createNode` event to add the 'draggable' attribute
7190				$.ui.fancytree.overrideMethod(ctx.options, "createNode", function(event, data) {
7191					// Default processing if any
7192					this._super.apply(this, arguments);
7193					// Add 'draggable' attribute
7194					data.node.span.draggable = true;
7195				});
7196			 *
7197			 * @param {object} instance
7198			 * @param {string} methodName
7199			 * @param {function} handler
7200			 * @param {object} [context] optional context
7201			 */
7202			overrideMethod: function (instance, methodName, handler, context) {
7203				var prevSuper,
7204					_super = instance[methodName] || $.noop;
7205
7206				instance[methodName] = function () {
7207					var self = context || this;
7208
7209					try {
7210						prevSuper = self._super;
7211						self._super = _super;
7212						return handler.apply(self, arguments);
7213					} finally {
7214						self._super = prevSuper;
7215					}
7216				};
7217			},
7218			/**
7219			 * Parse tree data from HTML <ul> markup
7220			 *
7221			 * @param {jQueryObject} $ul
7222			 * @returns {NodeData[]}
7223			 */
7224			parseHtml: function ($ul) {
7225				var classes,
7226					className,
7227					extraClasses,
7228					i,
7229					iPos,
7230					l,
7231					tmp,
7232					tmp2,
7233					$children = $ul.find(">li"),
7234					children = [];
7235
7236				$children.each(function () {
7237					var allData,
7238						lowerCaseAttr,
7239						$li = $(this),
7240						$liSpan = $li.find(">span", this).first(),
7241						$liA = $liSpan.length ? null : $li.find(">a").first(),
7242						d = { tooltip: null, data: {} };
7243
7244					if ($liSpan.length) {
7245						d.title = $liSpan.html();
7246					} else if ($liA && $liA.length) {
7247						// If a <li><a> tag is specified, use it literally and extract href/target.
7248						d.title = $liA.html();
7249						d.data.href = $liA.attr("href");
7250						d.data.target = $liA.attr("target");
7251						d.tooltip = $liA.attr("title");
7252					} else {
7253						// If only a <li> tag is specified, use the trimmed string up to
7254						// the next child <ul> tag.
7255						d.title = $li.html();
7256						iPos = d.title.search(/<ul/i);
7257						if (iPos >= 0) {
7258							d.title = d.title.substring(0, iPos);
7259						}
7260					}
7261					d.title = _trim(d.title);
7262
7263					// Make sure all fields exist
7264					for (i = 0, l = CLASS_ATTRS.length; i < l; i++) {
7265						d[CLASS_ATTRS[i]] = undefined;
7266					}
7267					// Initialize to `true`, if class is set and collect extraClasses
7268					classes = this.className.split(" ");
7269					extraClasses = [];
7270					for (i = 0, l = classes.length; i < l; i++) {
7271						className = classes[i];
7272						if (CLASS_ATTR_MAP[className]) {
7273							d[className] = true;
7274						} else {
7275							extraClasses.push(className);
7276						}
7277					}
7278					d.extraClasses = extraClasses.join(" ");
7279
7280					// Parse node options from ID, title and class attributes
7281					tmp = $li.attr("title");
7282					if (tmp) {
7283						d.tooltip = tmp; // overrides <a title='...'>
7284					}
7285					tmp = $li.attr("id");
7286					if (tmp) {
7287						d.key = tmp;
7288					}
7289					// Translate hideCheckbox -> checkbox:false
7290					if ($li.attr("hideCheckbox")) {
7291						d.checkbox = false;
7292					}
7293					// Add <li data-NAME='...'> as node.data.NAME
7294					allData = _getElementDataAsDict($li);
7295					if (allData && !$.isEmptyObject(allData)) {
7296						// #507: convert data-hidecheckbox (lower case) to hideCheckbox
7297						for (lowerCaseAttr in NODE_ATTR_LOWERCASE_MAP) {
7298							if (_hasProp(allData, lowerCaseAttr)) {
7299								allData[
7300									NODE_ATTR_LOWERCASE_MAP[lowerCaseAttr]
7301								] = allData[lowerCaseAttr];
7302								delete allData[lowerCaseAttr];
7303							}
7304						}
7305						// #56: Allow to set special node.attributes from data-...
7306						for (i = 0, l = NODE_ATTRS.length; i < l; i++) {
7307							tmp = NODE_ATTRS[i];
7308							tmp2 = allData[tmp];
7309							if (tmp2 != null) {
7310								delete allData[tmp];
7311								d[tmp] = tmp2;
7312							}
7313						}
7314						// All other data-... goes to node.data...
7315						$.extend(d.data, allData);
7316					}
7317					// Recursive reading of child nodes, if LI tag contains an UL tag
7318					$ul = $li.find(">ul").first();
7319					if ($ul.length) {
7320						d.children = $.ui.fancytree.parseHtml($ul);
7321					} else {
7322						d.children = d.lazy ? undefined : null;
7323					}
7324					children.push(d);
7325					// FT.debug("parse ", d, children);
7326				});
7327				return children;
7328			},
7329			/** Add Fancytree extension definition to the list of globally available extensions.
7330			 *
7331			 * @param {object} definition
7332			 */
7333			registerExtension: function (definition) {
7334				_assert(
7335					definition.name != null,
7336					"extensions must have a `name` property."
7337				);
7338				_assert(
7339					definition.version != null,
7340					"extensions must have a `version` property."
7341				);
7342				$.ui.fancytree._extensions[definition.name] = definition;
7343			},
7344			/** Replacement for the deprecated `jQuery.trim()`.
7345			 *
7346			 * @param {string} text
7347			 */
7348			trim: _trim,
7349			/** Inverse of escapeHtml().
7350			 *
7351			 * @param {string} s
7352			 * @returns {string}
7353			 */
7354			unescapeHtml: function (s) {
7355				var e = document.createElement("div");
7356				e.innerHTML = s;
7357				return e.childNodes.length === 0
7358					? ""
7359					: e.childNodes[0].nodeValue;
7360			},
7361			/** Write warning message to console if debugLevel >= 2.
7362			 * @param {string} msg
7363			 */
7364			warn: function (msg) {
7365				if ($.ui.fancytree.debugLevel >= 2) {
7366					consoleApply("warn", arguments);
7367				}
7368			},
7369		}
7370	);
7371
7372	// Value returned by `require('jquery.fancytree')`
7373	return $.ui.fancytree;
7374}); // End of closure
7375
7376// Extending Fancytree
7377// ===================
7378//
7379// See also the [live demo](https://wwWendt.de/tech/fancytree/demo/sample-ext-childcounter.html) of this code.
7380//
7381// Every extension should have a comment header containing some information
7382// about the author, copyright and licensing. Also a pointer to the latest
7383// source code.
7384// Prefix with `/*!` so the comment is not removed by the minifier.
7385
7386/*!
7387 * jquery.fancytree.childcounter.js
7388 *
7389 * Add a child counter bubble to tree nodes.
7390 * (Extension module for jquery.fancytree.js: https://github.com/mar10/fancytree/)
7391 *
7392 * Copyright (c) 2008-2023, Martin Wendt (https://wwWendt.de)
7393 *
7394 * Released under the MIT license
7395 * https://github.com/mar10/fancytree/wiki/LicenseInfo
7396 *
7397 * @version 2.38.3
7398 * @date 2023-02-01T20:52:50Z
7399 */
7400
7401// To keep the global namespace clean, we wrap everything in a closure.
7402// The UMD wrapper pattern defines the dependencies on jQuery and the
7403// Fancytree core module, and makes sure that we can use the `require()`
7404// syntax with package loaders.
7405
7406(function (factory) {
7407	if (typeof define === "function" && define.amd) {
7408		// AMD. Register as an anonymous module.
7409		define(["jquery", "./jquery.fancytree"], factory);
7410	} else if (typeof module === "object" && module.exports) {
7411		// Node/CommonJS
7412		require("./jquery.fancytree");
7413		module.exports = factory(require("jquery"));
7414	} else {
7415		// Browser globals
7416		factory(jQuery);
7417	}
7418})(function ($) {
7419	// Consider to use [strict mode](http://ejohn.org/blog/ecmascript-5-strict-mode-json-and-more/)
7420	"use strict";
7421
7422	// The [coding guidelines](http://contribute.jquery.org/style-guide/js/)
7423	// require jshint /eslint compliance.
7424	// But for this sample, we want to allow unused variables for demonstration purpose.
7425
7426	/*eslint-disable no-unused-vars */
7427
7428	// Adding methods
7429	// --------------
7430
7431	// New member functions can be added to the `Fancytree` class.
7432	// This function will be available for every tree instance:
7433	//
7434	//     var tree = $.ui.fancytree.getTree("#tree");
7435	//     tree.countSelected(false);
7436
7437	$.ui.fancytree._FancytreeClass.prototype.countSelected = function (
7438		topOnly
7439	) {
7440		var tree = this,
7441			treeOptions = tree.options;
7442
7443		return tree.getSelectedNodes(topOnly).length;
7444	};
7445
7446	// The `FancytreeNode` class can also be easily extended. This would be called
7447	// like
7448	//     node.updateCounters();
7449	//
7450	// It is also good practice to add a docstring comment.
7451	/**
7452	 * [ext-childcounter] Update counter badges for `node` and its parents.
7453	 * May be called in the `loadChildren` event, to update parents of lazy loaded
7454	 * nodes.
7455	 * @alias FancytreeNode#updateCounters
7456	 * @requires jquery.fancytree.childcounters.js
7457	 */
7458	$.ui.fancytree._FancytreeNodeClass.prototype.updateCounters = function () {
7459		var node = this,
7460			$badge = $("span.fancytree-childcounter", node.span),
7461			extOpts = node.tree.options.childcounter,
7462			count = node.countChildren(extOpts.deep);
7463
7464		node.data.childCounter = count;
7465		if (
7466			(count || !extOpts.hideZeros) &&
7467			(!node.isExpanded() || !extOpts.hideExpanded)
7468		) {
7469			if (!$badge.length) {
7470				$badge = $("<span class='fancytree-childcounter'/>").appendTo(
7471					$(
7472						"span.fancytree-icon,span.fancytree-custom-icon",
7473						node.span
7474					)
7475				);
7476			}
7477			$badge.text(count);
7478		} else {
7479			$badge.remove();
7480		}
7481		if (extOpts.deep && !node.isTopLevel() && !node.isRootNode()) {
7482			node.parent.updateCounters();
7483		}
7484	};
7485
7486	// Finally, we can extend the widget API and create functions that are called
7487	// like so:
7488	//
7489	//     $("#tree").fancytree("widgetMethod1", "abc");
7490
7491	$.ui.fancytree.prototype.widgetMethod1 = function (arg1) {
7492		var tree = this.tree;
7493		return arg1;
7494	};
7495
7496	// Register a Fancytree extension
7497	// ------------------------------
7498	// A full blown extension, extension is available for all trees and can be
7499	// enabled like so (see also the [live demo](https://wwWendt.de/tech/fancytree/demo/sample-ext-childcounter.html)):
7500	//
7501	//    <script src="../src/jquery.fancytree.js"></script>
7502	//    <script src="../src/jquery.fancytree.childcounter.js"></script>
7503	//    ...
7504	//
7505	//     $("#tree").fancytree({
7506	//         extensions: ["childcounter"],
7507	//         childcounter: {
7508	//             hideExpanded: true
7509	//         },
7510	//         ...
7511	//     });
7512	//
7513
7514	/* 'childcounter' extension */
7515	$.ui.fancytree.registerExtension({
7516		// Every extension must be registered by a unique name.
7517		name: "childcounter",
7518		// Version information should be compliant with [semver](http://semver.org)
7519		version: "2.38.3",
7520
7521		// Extension specific options and their defaults.
7522		// This options will be available as `tree.options.childcounter.hideExpanded`
7523
7524		options: {
7525			deep: true,
7526			hideZeros: true,
7527			hideExpanded: false,
7528		},
7529
7530		// Attributes other than `options` (or functions) can be defined here, and
7531		// will be added to the tree.ext.EXTNAME namespace, in this case `tree.ext.childcounter.foo`.
7532		// They can also be accessed as `this._local.foo` from within the extension
7533		// methods.
7534		foo: 42,
7535
7536		// Local functions are prefixed with an underscore '_'.
7537		// Callable as `this._local._appendCounter()`.
7538
7539		_appendCounter: function (bar) {
7540			var tree = this;
7541		},
7542
7543		// **Override virtual methods for this extension.**
7544		//
7545		// Fancytree implements a number of 'hook methods', prefixed by 'node...' or 'tree...'.
7546		// with a `ctx` argument (see [EventData](https://wwWendt.de/tech/fancytree/doc/jsdoc/global.html#EventData)
7547		// for details) and an extended calling context:<br>
7548		// `this`       : the Fancytree instance<br>
7549		// `this._local`: the namespace that contains extension attributes and private methods (same as this.ext.EXTNAME)<br>
7550		// `this._super`: the virtual function that was overridden (member of previous extension or Fancytree)
7551		//
7552		// See also the [complete list of available hook functions](https://wwWendt.de/tech/fancytree/doc/jsdoc/Fancytree_Hooks.html).
7553
7554		/* Init */
7555		// `treeInit` is triggered when a tree is initalized. We can set up classes or
7556		// bind event handlers here...
7557		treeInit: function (ctx) {
7558			var tree = this, // same as ctx.tree,
7559				opts = ctx.options,
7560				extOpts = ctx.options.childcounter;
7561			// Optionally check for dependencies with other extensions
7562			/* this._requireExtension("glyph", false, false); */
7563			// Call the base implementation
7564			this._superApply(arguments);
7565			// Add a class to the tree container
7566			this.$container.addClass("fancytree-ext-childcounter");
7567		},
7568
7569		// Destroy this tree instance (we only call the default implementation, so
7570		// this method could as well be omitted).
7571
7572		treeDestroy: function (ctx) {
7573			this._superApply(arguments);
7574		},
7575
7576		// Overload the `renderTitle` hook, to append a counter badge
7577		nodeRenderTitle: function (ctx, title) {
7578			var node = ctx.node,
7579				extOpts = ctx.options.childcounter,
7580				count =
7581					node.data.childCounter == null
7582						? node.countChildren(extOpts.deep)
7583						: +node.data.childCounter;
7584			// Let the base implementation render the title
7585			// We use `_super()` instead of `_superApply()` here, since it is a little bit
7586			// more performant when called often
7587			this._super(ctx, title);
7588			// Append a counter badge
7589			if (
7590				(count || !extOpts.hideZeros) &&
7591				(!node.isExpanded() || !extOpts.hideExpanded)
7592			) {
7593				$(
7594					"span.fancytree-icon,span.fancytree-custom-icon",
7595					node.span
7596				).append(
7597					$("<span class='fancytree-childcounter'/>").text(count)
7598				);
7599			}
7600		},
7601		// Overload the `setExpanded` hook, so the counters are updated
7602		nodeSetExpanded: function (ctx, flag, callOpts) {
7603			var tree = ctx.tree,
7604				node = ctx.node;
7605			// Let the base implementation expand/collapse the node, then redraw the title
7606			// after the animation has finished
7607			return this._superApply(arguments).always(function () {
7608				tree.nodeRenderTitle(ctx);
7609			});
7610		},
7611
7612		// End of extension definition
7613	});
7614	// Value returned by `require('jquery.fancytree..')`
7615	return $.ui.fancytree;
7616}); // End of closure
7617
7618/*!
7619 *
7620 * jquery.fancytree.clones.js
7621 * Support faster lookup of nodes by key and shared ref-ids.
7622 * (Extension module for jquery.fancytree.js: https://github.com/mar10/fancytree/)
7623 *
7624 * Copyright (c) 2008-2023, Martin Wendt (https://wwWendt.de)
7625 *
7626 * Released under the MIT license
7627 * https://github.com/mar10/fancytree/wiki/LicenseInfo
7628 *
7629 * @version 2.38.3
7630 * @date 2023-02-01T20:52:50Z
7631 */
7632
7633(function (factory) {
7634	if (typeof define === "function" && define.amd) {
7635		// AMD. Register as an anonymous module.
7636		define(["jquery", "./jquery.fancytree"], factory);
7637	} else if (typeof module === "object" && module.exports) {
7638		// Node/CommonJS
7639		require("./jquery.fancytree");
7640		module.exports = factory(require("jquery"));
7641	} else {
7642		// Browser globals
7643		factory(jQuery);
7644	}
7645})(function ($) {
7646	"use strict";
7647
7648	/*******************************************************************************
7649	 * Private functions and variables
7650	 */
7651
7652	var _assert = $.ui.fancytree.assert;
7653
7654	/* Return first occurrence of member from array. */
7655	function _removeArrayMember(arr, elem) {
7656		// TODO: use Array.indexOf for IE >= 9
7657		var i;
7658		for (i = arr.length - 1; i >= 0; i--) {
7659			if (arr[i] === elem) {
7660				arr.splice(i, 1);
7661				return true;
7662			}
7663		}
7664		return false;
7665	}
7666
7667	/**
7668	 * JS Implementation of MurmurHash3 (r136) (as of May 20, 2011)
7669	 *
7670	 * @author <a href="mailto:gary.court@gmail.com">Gary Court</a>
7671	 * @see http://github.com/garycourt/murmurhash-js
7672	 * @author <a href="mailto:aappleby@gmail.com">Austin Appleby</a>
7673	 * @see http://sites.google.com/site/murmurhash/
7674	 *
7675	 * @param {string} key ASCII only
7676	 * @param {boolean} [asString=false]
7677	 * @param {number} seed Positive integer only
7678	 * @return {number} 32-bit positive integer hash
7679	 */
7680	function hashMurmur3(key, asString, seed) {
7681		/*eslint-disable no-bitwise */
7682		var h1b,
7683			k1,
7684			remainder = key.length & 3,
7685			bytes = key.length - remainder,
7686			h1 = seed,
7687			c1 = 0xcc9e2d51,
7688			c2 = 0x1b873593,
7689			i = 0;
7690
7691		while (i < bytes) {
7692			k1 =
7693				(key.charCodeAt(i) & 0xff) |
7694				((key.charCodeAt(++i) & 0xff) << 8) |
7695				((key.charCodeAt(++i) & 0xff) << 16) |
7696				((key.charCodeAt(++i) & 0xff) << 24);
7697			++i;
7698
7699			k1 =
7700				((k1 & 0xffff) * c1 + ((((k1 >>> 16) * c1) & 0xffff) << 16)) &
7701				0xffffffff;
7702			k1 = (k1 << 15) | (k1 >>> 17);
7703			k1 =
7704				((k1 & 0xffff) * c2 + ((((k1 >>> 16) * c2) & 0xffff) << 16)) &
7705				0xffffffff;
7706
7707			h1 ^= k1;
7708			h1 = (h1 << 13) | (h1 >>> 19);
7709			h1b =
7710				((h1 & 0xffff) * 5 + ((((h1 >>> 16) * 5) & 0xffff) << 16)) &
7711				0xffffffff;
7712			h1 =
7713				(h1b & 0xffff) +
7714				0x6b64 +
7715				((((h1b >>> 16) + 0xe654) & 0xffff) << 16);
7716		}
7717
7718		k1 = 0;
7719
7720		switch (remainder) {
7721			case 3:
7722				k1 ^= (key.charCodeAt(i + 2) & 0xff) << 16;
7723			// fall through
7724			case 2:
7725				k1 ^= (key.charCodeAt(i + 1) & 0xff) << 8;
7726			// fall through
7727			case 1:
7728				k1 ^= key.charCodeAt(i) & 0xff;
7729
7730				k1 =
7731					((k1 & 0xffff) * c1 +
7732						((((k1 >>> 16) * c1) & 0xffff) << 16)) &
7733					0xffffffff;
7734				k1 = (k1 << 15) | (k1 >>> 17);
7735				k1 =
7736					((k1 & 0xffff) * c2 +
7737						((((k1 >>> 16) * c2) & 0xffff) << 16)) &
7738					0xffffffff;
7739				h1 ^= k1;
7740		}
7741
7742		h1 ^= key.length;
7743
7744		h1 ^= h1 >>> 16;
7745		h1 =
7746			((h1 & 0xffff) * 0x85ebca6b +
7747				((((h1 >>> 16) * 0x85ebca6b) & 0xffff) << 16)) &
7748			0xffffffff;
7749		h1 ^= h1 >>> 13;
7750		h1 =
7751			((h1 & 0xffff) * 0xc2b2ae35 +
7752				((((h1 >>> 16) * 0xc2b2ae35) & 0xffff) << 16)) &
7753			0xffffffff;
7754		h1 ^= h1 >>> 16;
7755
7756		if (asString) {
7757			// Convert to 8 digit hex string
7758			return ("0000000" + (h1 >>> 0).toString(16)).substr(-8);
7759		}
7760		return h1 >>> 0;
7761		/*eslint-enable no-bitwise */
7762	}
7763
7764	/*
7765	 * Return a unique key for node by calculating the hash of the parents refKey-list.
7766	 */
7767	function calcUniqueKey(node) {
7768		var key,
7769			h1,
7770			path = $.map(node.getParentList(false, true), function (e) {
7771				return e.refKey || e.key;
7772			});
7773
7774		path = path.join("/");
7775		// 32-bit has a high probability of collisions, so we pump up to 64-bit
7776		// https://security.stackexchange.com/q/209882/207588
7777
7778		h1 = hashMurmur3(path, true);
7779		key = "id_" + h1 + hashMurmur3(h1 + path, true);
7780
7781		return key;
7782	}
7783
7784	/**
7785	 * [ext-clones] Return a list of clone-nodes (i.e. same refKey) or null.
7786	 * @param {boolean} [includeSelf=false]
7787	 * @returns {FancytreeNode[] | null}
7788	 *
7789	 * @alias FancytreeNode#getCloneList
7790	 * @requires jquery.fancytree.clones.js
7791	 */
7792	$.ui.fancytree._FancytreeNodeClass.prototype.getCloneList = function (
7793		includeSelf
7794	) {
7795		var key,
7796			tree = this.tree,
7797			refList = tree.refMap[this.refKey] || null,
7798			keyMap = tree.keyMap;
7799
7800		if (refList) {
7801			key = this.key;
7802			// Convert key list to node list
7803			if (includeSelf) {
7804				refList = $.map(refList, function (val) {
7805					return keyMap[val];
7806				});
7807			} else {
7808				refList = $.map(refList, function (val) {
7809					return val === key ? null : keyMap[val];
7810				});
7811				if (refList.length < 1) {
7812					refList = null;
7813				}
7814			}
7815		}
7816		return refList;
7817	};
7818
7819	/**
7820	 * [ext-clones] Return true if this node has at least another clone with same refKey.
7821	 * @returns {boolean}
7822	 *
7823	 * @alias FancytreeNode#isClone
7824	 * @requires jquery.fancytree.clones.js
7825	 */
7826	$.ui.fancytree._FancytreeNodeClass.prototype.isClone = function () {
7827		var refKey = this.refKey || null,
7828			refList = (refKey && this.tree.refMap[refKey]) || null;
7829		return !!(refList && refList.length > 1);
7830	};
7831
7832	/**
7833	 * [ext-clones] Update key and/or refKey for an existing node.
7834	 * @param {string} key
7835	 * @param {string} refKey
7836	 * @returns {boolean}
7837	 *
7838	 * @alias FancytreeNode#reRegister
7839	 * @requires jquery.fancytree.clones.js
7840	 */
7841	$.ui.fancytree._FancytreeNodeClass.prototype.reRegister = function (
7842		key,
7843		refKey
7844	) {
7845		key = key == null ? null : "" + key;
7846		refKey = refKey == null ? null : "" + refKey;
7847		// this.debug("reRegister", key, refKey);
7848
7849		var tree = this.tree,
7850			prevKey = this.key,
7851			prevRefKey = this.refKey,
7852			keyMap = tree.keyMap,
7853			refMap = tree.refMap,
7854			refList = refMap[prevRefKey] || null,
7855			//		curCloneKeys = refList ? node.getCloneList(true),
7856			modified = false;
7857
7858		// Key has changed: update all references
7859		if (key != null && key !== this.key) {
7860			if (keyMap[key]) {
7861				$.error(
7862					"[ext-clones] reRegister(" +
7863						key +
7864						"): already exists: " +
7865						this
7866				);
7867			}
7868			// Update keyMap
7869			delete keyMap[prevKey];
7870			keyMap[key] = this;
7871			// Update refMap
7872			if (refList) {
7873				refMap[prevRefKey] = $.map(refList, function (e) {
7874					return e === prevKey ? key : e;
7875				});
7876			}
7877			this.key = key;
7878			modified = true;
7879		}
7880
7881		// refKey has changed
7882		if (refKey != null && refKey !== this.refKey) {
7883			// Remove previous refKeys
7884			if (refList) {
7885				if (refList.length === 1) {
7886					delete refMap[prevRefKey];
7887				} else {
7888					refMap[prevRefKey] = $.map(refList, function (e) {
7889						return e === prevKey ? null : e;
7890					});
7891				}
7892			}
7893			// Add refKey
7894			if (refMap[refKey]) {
7895				refMap[refKey].append(key);
7896			} else {
7897				refMap[refKey] = [this.key];
7898			}
7899			this.refKey = refKey;
7900			modified = true;
7901		}
7902		return modified;
7903	};
7904
7905	/**
7906	 * [ext-clones] Define a refKey for an existing node.
7907	 * @param {string} refKey
7908	 * @returns {boolean}
7909	 *
7910	 * @alias FancytreeNode#setRefKey
7911	 * @requires jquery.fancytree.clones.js
7912	 * @since 2.16
7913	 */
7914	$.ui.fancytree._FancytreeNodeClass.prototype.setRefKey = function (refKey) {
7915		return this.reRegister(null, refKey);
7916	};
7917
7918	/**
7919	 * [ext-clones] Return all nodes with a given refKey (null if not found).
7920	 * @param {string} refKey
7921	 * @param {FancytreeNode} [rootNode] optionally restrict results to descendants of this node
7922	 * @returns {FancytreeNode[] | null}
7923	 * @alias Fancytree#getNodesByRef
7924	 * @requires jquery.fancytree.clones.js
7925	 */
7926	$.ui.fancytree._FancytreeClass.prototype.getNodesByRef = function (
7927		refKey,
7928		rootNode
7929	) {
7930		var keyMap = this.keyMap,
7931			refList = this.refMap[refKey] || null;
7932
7933		if (refList) {
7934			// Convert key list to node list
7935			if (rootNode) {
7936				refList = $.map(refList, function (val) {
7937					var node = keyMap[val];
7938					return node.isDescendantOf(rootNode) ? node : null;
7939				});
7940			} else {
7941				refList = $.map(refList, function (val) {
7942					return keyMap[val];
7943				});
7944			}
7945			if (refList.length < 1) {
7946				refList = null;
7947			}
7948		}
7949		return refList;
7950	};
7951
7952	/**
7953	 * [ext-clones] Replace a refKey with a new one.
7954	 * @param {string} oldRefKey
7955	 * @param {string} newRefKey
7956	 * @alias Fancytree#changeRefKey
7957	 * @requires jquery.fancytree.clones.js
7958	 */
7959	$.ui.fancytree._FancytreeClass.prototype.changeRefKey = function (
7960		oldRefKey,
7961		newRefKey
7962	) {
7963		var i,
7964			node,
7965			keyMap = this.keyMap,
7966			refList = this.refMap[oldRefKey] || null;
7967
7968		if (refList) {
7969			for (i = 0; i < refList.length; i++) {
7970				node = keyMap[refList[i]];
7971				node.refKey = newRefKey;
7972			}
7973			delete this.refMap[oldRefKey];
7974			this.refMap[newRefKey] = refList;
7975		}
7976	};
7977
7978	/*******************************************************************************
7979	 * Extension code
7980	 */
7981	$.ui.fancytree.registerExtension({
7982		name: "clones",
7983		version: "2.38.3",
7984		// Default options for this extension.
7985		options: {
7986			highlightActiveClones: true, // set 'fancytree-active-clone' on active clones and all peers
7987			highlightClones: false, // set 'fancytree-clone' class on any node that has at least one clone
7988		},
7989
7990		treeCreate: function (ctx) {
7991			this._superApply(arguments);
7992			ctx.tree.refMap = {};
7993			ctx.tree.keyMap = {};
7994		},
7995		treeInit: function (ctx) {
7996			this.$container.addClass("fancytree-ext-clones");
7997			_assert(ctx.options.defaultKey == null);
7998			// Generate unique / reproducible default keys
7999			ctx.options.defaultKey = function (node) {
8000				return calcUniqueKey(node);
8001			};
8002			// The default implementation loads initial data
8003			this._superApply(arguments);
8004		},
8005		treeClear: function (ctx) {
8006			ctx.tree.refMap = {};
8007			ctx.tree.keyMap = {};
8008			return this._superApply(arguments);
8009		},
8010		treeRegisterNode: function (ctx, add, node) {
8011			var refList,
8012				len,
8013				tree = ctx.tree,
8014				keyMap = tree.keyMap,
8015				refMap = tree.refMap,
8016				key = node.key,
8017				refKey = node && node.refKey != null ? "" + node.refKey : null;
8018
8019			//		ctx.tree.debug("clones.treeRegisterNode", add, node);
8020
8021			if (node.isStatusNode()) {
8022				return this._super(ctx, add, node);
8023			}
8024
8025			if (add) {
8026				if (keyMap[node.key] != null) {
8027					var other = keyMap[node.key],
8028						msg =
8029							"clones.treeRegisterNode: duplicate key '" +
8030							node.key +
8031							"': /" +
8032							node.getPath(true) +
8033							" => " +
8034							other.getPath(true);
8035					// Sometimes this exception is not visible in the console,
8036					// so we also write it:
8037					tree.error(msg);
8038					$.error(msg);
8039				}
8040				keyMap[key] = node;
8041
8042				if (refKey) {
8043					refList = refMap[refKey];
8044					if (refList) {
8045						refList.push(key);
8046						if (
8047							refList.length === 2 &&
8048							ctx.options.clones.highlightClones
8049						) {
8050							// Mark peer node, if it just became a clone (no need to
8051							// mark current node, since it will be rendered later anyway)
8052							keyMap[refList[0]].renderStatus();
8053						}
8054					} else {
8055						refMap[refKey] = [key];
8056					}
8057					// node.debug("clones.treeRegisterNode: add clone =>", refMap[refKey]);
8058				}
8059			} else {
8060				if (keyMap[key] == null) {
8061					$.error(
8062						"clones.treeRegisterNode: node.key not registered: " +
8063							node.key
8064					);
8065				}
8066				delete keyMap[key];
8067				if (refKey) {
8068					refList = refMap[refKey];
8069					// node.debug("clones.treeRegisterNode: remove clone BEFORE =>", refMap[refKey]);
8070					if (refList) {
8071						len = refList.length;
8072						if (len <= 1) {
8073							_assert(len === 1);
8074							_assert(refList[0] === key);
8075							delete refMap[refKey];
8076						} else {
8077							_removeArrayMember(refList, key);
8078							// Unmark peer node, if this was the only clone
8079							if (
8080								len === 2 &&
8081								ctx.options.clones.highlightClones
8082							) {
8083								//							node.debug("clones.treeRegisterNode: last =>", node.getCloneList());
8084								keyMap[refList[0]].renderStatus();
8085							}
8086						}
8087						// node.debug("clones.treeRegisterNode: remove clone =>", refMap[refKey]);
8088					}
8089				}
8090			}
8091			return this._super(ctx, add, node);
8092		},
8093		nodeRenderStatus: function (ctx) {
8094			var $span,
8095				res,
8096				node = ctx.node;
8097
8098			res = this._super(ctx);
8099
8100			if (ctx.options.clones.highlightClones) {
8101				$span = $(node[ctx.tree.statusClassPropName]);
8102				// Only if span already exists
8103				if ($span.length && node.isClone()) {
8104					//				node.debug("clones.nodeRenderStatus: ", ctx.options.clones.highlightClones);
8105					$span.addClass("fancytree-clone");
8106				}
8107			}
8108			return res;
8109		},
8110		nodeSetActive: function (ctx, flag, callOpts) {
8111			var res,
8112				scpn = ctx.tree.statusClassPropName,
8113				node = ctx.node;
8114
8115			res = this._superApply(arguments);
8116
8117			if (ctx.options.clones.highlightActiveClones && node.isClone()) {
8118				$.each(node.getCloneList(true), function (idx, n) {
8119					// n.debug("clones.nodeSetActive: ", flag !== false);
8120					$(n[scpn]).toggleClass(
8121						"fancytree-active-clone",
8122						flag !== false
8123					);
8124				});
8125			}
8126			return res;
8127		},
8128	});
8129	// Value returned by `require('jquery.fancytree..')`
8130	return $.ui.fancytree;
8131}); // End of closure
8132
8133/*!
8134 * jquery.fancytree.dnd.js
8135 *
8136 * Drag-and-drop support (jQuery UI draggable/droppable).
8137 * (Extension module for jquery.fancytree.js: https://github.com/mar10/fancytree/)
8138 *
8139 * Copyright (c) 2008-2023, Martin Wendt (https://wwWendt.de)
8140 *
8141 * Released under the MIT license
8142 * https://github.com/mar10/fancytree/wiki/LicenseInfo
8143 *
8144 * @version 2.38.3
8145 * @date 2023-02-01T20:52:50Z
8146 */
8147
8148(function (factory) {
8149	if (typeof define === "function" && define.amd) {
8150		// AMD. Register as an anonymous module.
8151		define([
8152			"jquery",
8153			"jquery-ui/ui/widgets/draggable",
8154			"jquery-ui/ui/widgets/droppable",
8155			"./jquery.fancytree",
8156		], factory);
8157	} else if (typeof module === "object" && module.exports) {
8158		// Node/CommonJS
8159		require("./jquery.fancytree");
8160		module.exports = factory(require("jquery"));
8161	} else {
8162		// Browser globals
8163		factory(jQuery);
8164	}
8165})(function ($) {
8166	"use strict";
8167
8168	/******************************************************************************
8169	 * Private functions and variables
8170	 */
8171	var didRegisterDnd = false,
8172		classDropAccept = "fancytree-drop-accept",
8173		classDropAfter = "fancytree-drop-after",
8174		classDropBefore = "fancytree-drop-before",
8175		classDropOver = "fancytree-drop-over",
8176		classDropReject = "fancytree-drop-reject",
8177		classDropTarget = "fancytree-drop-target";
8178
8179	/* Convert number to string and prepend +/-; return empty string for 0.*/
8180	function offsetString(n) {
8181		// eslint-disable-next-line no-nested-ternary
8182		return n === 0 ? "" : n > 0 ? "+" + n : "" + n;
8183	}
8184
8185	//--- Extend ui.draggable event handling --------------------------------------
8186
8187	function _registerDnd() {
8188		if (didRegisterDnd) {
8189			return;
8190		}
8191
8192		// Register proxy-functions for draggable.start/drag/stop
8193
8194		$.ui.plugin.add("draggable", "connectToFancytree", {
8195			start: function (event, ui) {
8196				// 'draggable' was renamed to 'ui-draggable' since jQueryUI 1.10
8197				var draggable =
8198						$(this).data("ui-draggable") ||
8199						$(this).data("draggable"),
8200					sourceNode = ui.helper.data("ftSourceNode") || null;
8201
8202				if (sourceNode) {
8203					// Adjust helper offset, so cursor is slightly outside top/left corner
8204					draggable.offset.click.top = -2;
8205					draggable.offset.click.left = +16;
8206					// Trigger dragStart event
8207					// TODO: when called as connectTo..., the return value is ignored(?)
8208					return sourceNode.tree.ext.dnd._onDragEvent(
8209						"start",
8210						sourceNode,
8211						null,
8212						event,
8213						ui,
8214						draggable
8215					);
8216				}
8217			},
8218			drag: function (event, ui) {
8219				var ctx,
8220					isHelper,
8221					logObject,
8222					// 'draggable' was renamed to 'ui-draggable' since jQueryUI 1.10
8223					draggable =
8224						$(this).data("ui-draggable") ||
8225						$(this).data("draggable"),
8226					sourceNode = ui.helper.data("ftSourceNode") || null,
8227					prevTargetNode = ui.helper.data("ftTargetNode") || null,
8228					targetNode = $.ui.fancytree.getNode(event.target),
8229					dndOpts = sourceNode && sourceNode.tree.options.dnd;
8230
8231				// logObject = sourceNode || prevTargetNode || $.ui.fancytree;
8232				// logObject.debug("Drag event:", event, event.shiftKey);
8233				if (event.target && !targetNode) {
8234					// We got a drag event, but the targetNode could not be found
8235					// at the event location. This may happen,
8236					// 1. if the mouse jumped over the drag helper,
8237					// 2. or if a non-fancytree element is dragged
8238					// We ignore it:
8239					isHelper =
8240						$(event.target).closest(
8241							"div.fancytree-drag-helper,#fancytree-drop-marker"
8242						).length > 0;
8243					if (isHelper) {
8244						logObject =
8245							sourceNode || prevTargetNode || $.ui.fancytree;
8246						logObject.debug("Drag event over helper: ignored.");
8247						return;
8248					}
8249				}
8250				ui.helper.data("ftTargetNode", targetNode);
8251
8252				if (dndOpts && dndOpts.updateHelper) {
8253					ctx = sourceNode.tree._makeHookContext(sourceNode, event, {
8254						otherNode: targetNode,
8255						ui: ui,
8256						draggable: draggable,
8257						dropMarker: $("#fancytree-drop-marker"),
8258					});
8259					dndOpts.updateHelper.call(sourceNode.tree, sourceNode, ctx);
8260				}
8261
8262				// Leaving a tree node
8263				if (prevTargetNode && prevTargetNode !== targetNode) {
8264					prevTargetNode.tree.ext.dnd._onDragEvent(
8265						"leave",
8266						prevTargetNode,
8267						sourceNode,
8268						event,
8269						ui,
8270						draggable
8271					);
8272				}
8273				if (targetNode) {
8274					if (!targetNode.tree.options.dnd.dragDrop) {
8275						// not enabled as drop target
8276					} else if (targetNode === prevTargetNode) {
8277						// Moving over same node
8278						targetNode.tree.ext.dnd._onDragEvent(
8279							"over",
8280							targetNode,
8281							sourceNode,
8282							event,
8283							ui,
8284							draggable
8285						);
8286					} else {
8287						// Entering this node first time
8288						targetNode.tree.ext.dnd._onDragEvent(
8289							"enter",
8290							targetNode,
8291							sourceNode,
8292							event,
8293							ui,
8294							draggable
8295						);
8296						targetNode.tree.ext.dnd._onDragEvent(
8297							"over",
8298							targetNode,
8299							sourceNode,
8300							event,
8301							ui,
8302							draggable
8303						);
8304					}
8305				}
8306				// else go ahead with standard event handling
8307			},
8308			stop: function (event, ui) {
8309				var logObject,
8310					// 'draggable' was renamed to 'ui-draggable' since jQueryUI 1.10:
8311					draggable =
8312						$(this).data("ui-draggable") ||
8313						$(this).data("draggable"),
8314					sourceNode = ui.helper.data("ftSourceNode") || null,
8315					targetNode = ui.helper.data("ftTargetNode") || null,
8316					dropped = event.type === "mouseup" && event.which === 1;
8317
8318				if (!dropped) {
8319					logObject = sourceNode || targetNode || $.ui.fancytree;
8320					logObject.debug("Drag was cancelled");
8321				}
8322				if (targetNode) {
8323					if (dropped) {
8324						targetNode.tree.ext.dnd._onDragEvent(
8325							"drop",
8326							targetNode,
8327							sourceNode,
8328							event,
8329							ui,
8330							draggable
8331						);
8332					}
8333					targetNode.tree.ext.dnd._onDragEvent(
8334						"leave",
8335						targetNode,
8336						sourceNode,
8337						event,
8338						ui,
8339						draggable
8340					);
8341				}
8342				if (sourceNode) {
8343					sourceNode.tree.ext.dnd._onDragEvent(
8344						"stop",
8345						sourceNode,
8346						null,
8347						event,
8348						ui,
8349						draggable
8350					);
8351				}
8352			},
8353		});
8354
8355		didRegisterDnd = true;
8356	}
8357
8358	/******************************************************************************
8359	 * Drag and drop support
8360	 */
8361	function _initDragAndDrop(tree) {
8362		var dnd = tree.options.dnd || null,
8363			glyph = tree.options.glyph || null;
8364
8365		// Register 'connectToFancytree' option with ui.draggable
8366		if (dnd) {
8367			_registerDnd();
8368		}
8369		// Attach ui.draggable to this Fancytree instance
8370		if (dnd && dnd.dragStart) {
8371			tree.widget.element.draggable(
8372				$.extend(
8373					{
8374						addClasses: false,
8375						// DT issue 244: helper should be child of scrollParent:
8376						appendTo: tree.$container,
8377						//			appendTo: "body",
8378						containment: false,
8379						//			containment: "parent",
8380						delay: 0,
8381						distance: 4,
8382						revert: false,
8383						scroll: true, // to disable, also set css 'position: inherit' on ul.fancytree-container
8384						scrollSpeed: 7,
8385						scrollSensitivity: 10,
8386						// Delegate draggable.start, drag, and stop events to our handler
8387						connectToFancytree: true,
8388						// Let source tree create the helper element
8389						helper: function (event) {
8390							var $helper,
8391								$nodeTag,
8392								opts,
8393								sourceNode = $.ui.fancytree.getNode(
8394									event.target
8395								);
8396
8397							if (!sourceNode) {
8398								// #405, DT issue 211: might happen, if dragging a table *header*
8399								return "<div>ERROR?: helper requested but sourceNode not found</div>";
8400							}
8401							opts = sourceNode.tree.options.dnd;
8402							$nodeTag = $(sourceNode.span);
8403							// Only event and node argument is available
8404							$helper = $(
8405								"<div class='fancytree-drag-helper'><span class='fancytree-drag-helper-img' /></div>"
8406							)
8407								.css({ zIndex: 3, position: "relative" }) // so it appears above ext-wide selection bar
8408								.append(
8409									$nodeTag
8410										.find("span.fancytree-title")
8411										.clone()
8412								);
8413
8414							// Attach node reference to helper object
8415							$helper.data("ftSourceNode", sourceNode);
8416
8417							// Support glyph symbols instead of icons
8418							if (glyph) {
8419								$helper
8420									.find(".fancytree-drag-helper-img")
8421									.addClass(
8422										glyph.map._addClass +
8423											" " +
8424											glyph.map.dragHelper
8425									);
8426							}
8427							// Allow to modify the helper, e.g. to add multi-node-drag feedback
8428							if (opts.initHelper) {
8429								opts.initHelper.call(
8430									sourceNode.tree,
8431									sourceNode,
8432									{
8433										node: sourceNode,
8434										tree: sourceNode.tree,
8435										originalEvent: event,
8436										ui: { helper: $helper },
8437									}
8438								);
8439							}
8440							// We return an unconnected element, so `draggable` will add this
8441							// to the parent specified as `appendTo` option
8442							return $helper;
8443						},
8444						start: function (event, ui) {
8445							var sourceNode = ui.helper.data("ftSourceNode");
8446							return !!sourceNode; // Abort dragging if no node could be found
8447						},
8448					},
8449					tree.options.dnd.draggable
8450				)
8451			);
8452		}
8453		// Attach ui.droppable to this Fancytree instance
8454		if (dnd && dnd.dragDrop) {
8455			tree.widget.element.droppable(
8456				$.extend(
8457					{
8458						addClasses: false,
8459						tolerance: "intersect",
8460						greedy: false,
8461						/*
8462			activate: function(event, ui) {
8463				tree.debug("droppable - activate", event, ui, this);
8464			},
8465			create: function(event, ui) {
8466				tree.debug("droppable - create", event, ui);
8467			},
8468			deactivate: function(event, ui) {
8469				tree.debug("droppable - deactivate", event, ui);
8470			},
8471			drop: function(event, ui) {
8472				tree.debug("droppable - drop", event, ui);
8473			},
8474			out: function(event, ui) {
8475				tree.debug("droppable - out", event, ui);
8476			},
8477			over: function(event, ui) {
8478				tree.debug("droppable - over", event, ui);
8479			}
8480*/
8481					},
8482					tree.options.dnd.droppable
8483				)
8484			);
8485		}
8486	}
8487
8488	/******************************************************************************
8489	 *
8490	 */
8491
8492	$.ui.fancytree.registerExtension({
8493		name: "dnd",
8494		version: "2.38.3",
8495		// Default options for this extension.
8496		options: {
8497			// Make tree nodes accept draggables
8498			autoExpandMS: 1000, // Expand nodes after n milliseconds of hovering.
8499			draggable: null, // Additional options passed to jQuery draggable
8500			droppable: null, // Additional options passed to jQuery droppable
8501			focusOnClick: false, // Focus, although draggable cancels mousedown event (#270)
8502			preventVoidMoves: true, // Prevent dropping nodes 'before self', etc.
8503			preventRecursiveMoves: true, // Prevent dropping nodes on own descendants
8504			smartRevert: true, // set draggable.revert = true if drop was rejected
8505			dropMarkerOffsetX: -24, // absolute position offset for .fancytree-drop-marker relatively to ..fancytree-title (icon/img near a node accepting drop)
8506			dropMarkerInsertOffsetX: -16, // additional offset for drop-marker with hitMode = "before"/"after"
8507			// Events (drag support)
8508			dragStart: null, // Callback(sourceNode, data), return true, to enable dnd
8509			dragStop: null, // Callback(sourceNode, data)
8510			initHelper: null, // Callback(sourceNode, data)
8511			updateHelper: null, // Callback(sourceNode, data)
8512			// Events (drop support)
8513			dragEnter: null, // Callback(targetNode, data)
8514			dragOver: null, // Callback(targetNode, data)
8515			dragExpand: null, // Callback(targetNode, data), return false to prevent autoExpand
8516			dragDrop: null, // Callback(targetNode, data)
8517			dragLeave: null, // Callback(targetNode, data)
8518		},
8519
8520		treeInit: function (ctx) {
8521			var tree = ctx.tree;
8522			this._superApply(arguments);
8523			// issue #270: draggable eats mousedown events
8524			if (tree.options.dnd.dragStart) {
8525				tree.$container.on("mousedown", function (event) {
8526					//				if( !tree.hasFocus() && ctx.options.dnd.focusOnClick ) {
8527					if (ctx.options.dnd.focusOnClick) {
8528						// #270
8529						var node = $.ui.fancytree.getNode(event);
8530						if (node) {
8531							node.debug(
8532								"Re-enable focus that was prevented by jQuery UI draggable."
8533							);
8534							// node.setFocus();
8535							// $(node.span).closest(":tabbable").focus();
8536							// $(event.target).trigger("focus");
8537							// $(event.target).closest(":tabbable").trigger("focus");
8538						}
8539						setTimeout(function () {
8540							// #300
8541							$(event.target).closest(":tabbable").focus();
8542						}, 10);
8543					}
8544				});
8545			}
8546			_initDragAndDrop(tree);
8547		},
8548		/* Display drop marker according to hitMode ('after', 'before', 'over'). */
8549		_setDndStatus: function (
8550			sourceNode,
8551			targetNode,
8552			helper,
8553			hitMode,
8554			accept
8555		) {
8556			var markerOffsetX,
8557				pos,
8558				markerAt = "center",
8559				instData = this._local,
8560				dndOpt = this.options.dnd,
8561				glyphOpt = this.options.glyph,
8562				$source = sourceNode ? $(sourceNode.span) : null,
8563				$target = $(targetNode.span),
8564				$targetTitle = $target.find("span.fancytree-title");
8565
8566			if (!instData.$dropMarker) {
8567				instData.$dropMarker = $(
8568					"<div id='fancytree-drop-marker'></div>"
8569				)
8570					.hide()
8571					.css({ "z-index": 1000 })
8572					.prependTo($(this.$div).parent());
8573				//                .prependTo("body");
8574
8575				if (glyphOpt) {
8576					instData.$dropMarker.addClass(
8577						glyphOpt.map._addClass + " " + glyphOpt.map.dropMarker
8578					);
8579				}
8580			}
8581			if (
8582				hitMode === "after" ||
8583				hitMode === "before" ||
8584				hitMode === "over"
8585			) {
8586				markerOffsetX = dndOpt.dropMarkerOffsetX || 0;
8587				switch (hitMode) {
8588					case "before":
8589						markerAt = "top";
8590						markerOffsetX += dndOpt.dropMarkerInsertOffsetX || 0;
8591						break;
8592					case "after":
8593						markerAt = "bottom";
8594						markerOffsetX += dndOpt.dropMarkerInsertOffsetX || 0;
8595						break;
8596				}
8597
8598				pos = {
8599					my: "left" + offsetString(markerOffsetX) + " center",
8600					at: "left " + markerAt,
8601					of: $targetTitle,
8602				};
8603				if (this.options.rtl) {
8604					pos.my = "right" + offsetString(-markerOffsetX) + " center";
8605					pos.at = "right " + markerAt;
8606				}
8607				instData.$dropMarker
8608					.toggleClass(classDropAfter, hitMode === "after")
8609					.toggleClass(classDropOver, hitMode === "over")
8610					.toggleClass(classDropBefore, hitMode === "before")
8611					.toggleClass("fancytree-rtl", !!this.options.rtl)
8612					.show()
8613					.position($.ui.fancytree.fixPositionOptions(pos));
8614			} else {
8615				instData.$dropMarker.hide();
8616			}
8617			if ($source) {
8618				$source
8619					.toggleClass(classDropAccept, accept === true)
8620					.toggleClass(classDropReject, accept === false);
8621			}
8622			$target
8623				.toggleClass(
8624					classDropTarget,
8625					hitMode === "after" ||
8626						hitMode === "before" ||
8627						hitMode === "over"
8628				)
8629				.toggleClass(classDropAfter, hitMode === "after")
8630				.toggleClass(classDropBefore, hitMode === "before")
8631				.toggleClass(classDropAccept, accept === true)
8632				.toggleClass(classDropReject, accept === false);
8633
8634			helper
8635				.toggleClass(classDropAccept, accept === true)
8636				.toggleClass(classDropReject, accept === false);
8637		},
8638
8639		/*
8640		 * Handles drag'n'drop functionality.
8641		 *
8642		 * A standard jQuery drag-and-drop process may generate these calls:
8643		 *
8644		 * start:
8645		 *     _onDragEvent("start", sourceNode, null, event, ui, draggable);
8646		 * drag:
8647		 *     _onDragEvent("leave", prevTargetNode, sourceNode, event, ui, draggable);
8648		 *     _onDragEvent("over", targetNode, sourceNode, event, ui, draggable);
8649		 *     _onDragEvent("enter", targetNode, sourceNode, event, ui, draggable);
8650		 * stop:
8651		 *     _onDragEvent("drop", targetNode, sourceNode, event, ui, draggable);
8652		 *     _onDragEvent("leave", targetNode, sourceNode, event, ui, draggable);
8653		 *     _onDragEvent("stop", sourceNode, null, event, ui, draggable);
8654		 */
8655		_onDragEvent: function (
8656			eventName,
8657			node,
8658			otherNode,
8659			event,
8660			ui,
8661			draggable
8662		) {
8663			// if(eventName !== "over"){
8664			// 	this.debug("tree.ext.dnd._onDragEvent(%s, %o, %o) - %o", eventName, node, otherNode, this);
8665			// }
8666			var accept,
8667				nodeOfs,
8668				parentRect,
8669				rect,
8670				relPos,
8671				relPos2,
8672				enterResponse,
8673				hitMode,
8674				r,
8675				opts = this.options,
8676				dnd = opts.dnd,
8677				ctx = this._makeHookContext(node, event, {
8678					otherNode: otherNode,
8679					ui: ui,
8680					draggable: draggable,
8681				}),
8682				res = null,
8683				self = this,
8684				$nodeTag = $(node.span);
8685
8686			if (dnd.smartRevert) {
8687				draggable.options.revert = "invalid";
8688			}
8689
8690			switch (eventName) {
8691				case "start":
8692					if (node.isStatusNode()) {
8693						res = false;
8694					} else if (dnd.dragStart) {
8695						res = dnd.dragStart(node, ctx);
8696					}
8697					if (res === false) {
8698						this.debug("tree.dragStart() cancelled");
8699						//draggable._clear();
8700						// NOTE: the return value seems to be ignored (drag is not cancelled, when false is returned)
8701						// TODO: call this._cancelDrag()?
8702						ui.helper.trigger("mouseup").hide();
8703					} else {
8704						if (dnd.smartRevert) {
8705							// #567, #593: fix revert position
8706							// rect = node.li.getBoundingClientRect();
8707							rect =
8708								node[
8709									ctx.tree.nodeContainerAttrName
8710								].getBoundingClientRect();
8711							parentRect = $(
8712								draggable.options.appendTo
8713							)[0].getBoundingClientRect();
8714							draggable.originalPosition.left = Math.max(
8715								0,
8716								rect.left - parentRect.left
8717							);
8718							draggable.originalPosition.top = Math.max(
8719								0,
8720								rect.top - parentRect.top
8721							);
8722						}
8723						$nodeTag.addClass("fancytree-drag-source");
8724						// Register global handlers to allow cancel
8725						$(document).on(
8726							"keydown.fancytree-dnd,mousedown.fancytree-dnd",
8727							function (event) {
8728								// node.tree.debug("dnd global event", event.type, event.which);
8729								if (
8730									event.type === "keydown" &&
8731									event.which === $.ui.keyCode.ESCAPE
8732								) {
8733									self.ext.dnd._cancelDrag();
8734								} else if (event.type === "mousedown") {
8735									self.ext.dnd._cancelDrag();
8736								}
8737							}
8738						);
8739					}
8740					break;
8741
8742				case "enter":
8743					if (
8744						dnd.preventRecursiveMoves &&
8745						node.isDescendantOf(otherNode)
8746					) {
8747						r = false;
8748					} else {
8749						r = dnd.dragEnter ? dnd.dragEnter(node, ctx) : null;
8750					}
8751					if (!r) {
8752						// convert null, undefined, false to false
8753						res = false;
8754					} else if (Array.isArray(r)) {
8755						// TODO: also accept passing an object of this format directly
8756						res = {
8757							over: $.inArray("over", r) >= 0,
8758							before: $.inArray("before", r) >= 0,
8759							after: $.inArray("after", r) >= 0,
8760						};
8761					} else {
8762						res = {
8763							over: r === true || r === "over",
8764							before: r === true || r === "before",
8765							after: r === true || r === "after",
8766						};
8767					}
8768					ui.helper.data("enterResponse", res);
8769					// this.debug("helper.enterResponse: %o", res);
8770					break;
8771
8772				case "over":
8773					enterResponse = ui.helper.data("enterResponse");
8774					hitMode = null;
8775					if (enterResponse === false) {
8776						// Don't call dragOver if onEnter returned false.
8777						//                break;
8778					} else if (typeof enterResponse === "string") {
8779						// Use hitMode from onEnter if provided.
8780						hitMode = enterResponse;
8781					} else {
8782						// Calculate hitMode from relative cursor position.
8783						nodeOfs = $nodeTag.offset();
8784						relPos = {
8785							x: event.pageX - nodeOfs.left,
8786							y: event.pageY - nodeOfs.top,
8787						};
8788						relPos2 = {
8789							x: relPos.x / $nodeTag.width(),
8790							y: relPos.y / $nodeTag.height(),
8791						};
8792
8793						if (enterResponse.after && relPos2.y > 0.75) {
8794							hitMode = "after";
8795						} else if (
8796							!enterResponse.over &&
8797							enterResponse.after &&
8798							relPos2.y > 0.5
8799						) {
8800							hitMode = "after";
8801						} else if (enterResponse.before && relPos2.y <= 0.25) {
8802							hitMode = "before";
8803						} else if (
8804							!enterResponse.over &&
8805							enterResponse.before &&
8806							relPos2.y <= 0.5
8807						) {
8808							hitMode = "before";
8809						} else if (enterResponse.over) {
8810							hitMode = "over";
8811						}
8812						// Prevent no-ops like 'before source node'
8813						// TODO: these are no-ops when moving nodes, but not in copy mode
8814						if (dnd.preventVoidMoves) {
8815							if (node === otherNode) {
8816								this.debug(
8817									"    drop over source node prevented"
8818								);
8819								hitMode = null;
8820							} else if (
8821								hitMode === "before" &&
8822								otherNode &&
8823								node === otherNode.getNextSibling()
8824							) {
8825								this.debug(
8826									"    drop after source node prevented"
8827								);
8828								hitMode = null;
8829							} else if (
8830								hitMode === "after" &&
8831								otherNode &&
8832								node === otherNode.getPrevSibling()
8833							) {
8834								this.debug(
8835									"    drop before source node prevented"
8836								);
8837								hitMode = null;
8838							} else if (
8839								hitMode === "over" &&
8840								otherNode &&
8841								otherNode.parent === node &&
8842								otherNode.isLastSibling()
8843							) {
8844								this.debug(
8845									"    drop last child over own parent prevented"
8846								);
8847								hitMode = null;
8848							}
8849						}
8850						//                this.debug("hitMode: %s - %s - %s", hitMode, (node.parent === otherNode), node.isLastSibling());
8851						ui.helper.data("hitMode", hitMode);
8852					}
8853					// Auto-expand node (only when 'over' the node, not 'before', or 'after')
8854					if (
8855						hitMode !== "before" &&
8856						hitMode !== "after" &&
8857						dnd.autoExpandMS &&
8858						node.hasChildren() !== false &&
8859						!node.expanded &&
8860						(!dnd.dragExpand || dnd.dragExpand(node, ctx) !== false)
8861					) {
8862						node.scheduleAction("expand", dnd.autoExpandMS);
8863					}
8864					if (hitMode && dnd.dragOver) {
8865						// TODO: http://code.google.com/p/dynatree/source/detail?r=625
8866						ctx.hitMode = hitMode;
8867						res = dnd.dragOver(node, ctx);
8868					}
8869					accept = res !== false && hitMode !== null;
8870					if (dnd.smartRevert) {
8871						draggable.options.revert = !accept;
8872					}
8873					this._local._setDndStatus(
8874						otherNode,
8875						node,
8876						ui.helper,
8877						hitMode,
8878						accept
8879					);
8880					break;
8881
8882				case "drop":
8883					hitMode = ui.helper.data("hitMode");
8884					if (hitMode && dnd.dragDrop) {
8885						ctx.hitMode = hitMode;
8886						dnd.dragDrop(node, ctx);
8887					}
8888					break;
8889
8890				case "leave":
8891					// Cancel pending expand request
8892					node.scheduleAction("cancel");
8893					ui.helper.data("enterResponse", null);
8894					ui.helper.data("hitMode", null);
8895					this._local._setDndStatus(
8896						otherNode,
8897						node,
8898						ui.helper,
8899						"out",
8900						undefined
8901					);
8902					if (dnd.dragLeave) {
8903						dnd.dragLeave(node, ctx);
8904					}
8905					break;
8906
8907				case "stop":
8908					$nodeTag.removeClass("fancytree-drag-source");
8909					$(document).off(".fancytree-dnd");
8910					if (dnd.dragStop) {
8911						dnd.dragStop(node, ctx);
8912					}
8913					break;
8914
8915				default:
8916					$.error("Unsupported drag event: " + eventName);
8917			}
8918			return res;
8919		},
8920
8921		_cancelDrag: function () {
8922			var dd = $.ui.ddmanager.current;
8923			if (dd) {
8924				dd.cancel();
8925			}
8926		},
8927	});
8928	// Value returned by `require('jquery.fancytree..')`
8929	return $.ui.fancytree;
8930}); // End of closure
8931
8932/*!
8933 * jquery.fancytree.dnd5.js
8934 *
8935 * Drag-and-drop support (native HTML5).
8936 * (Extension module for jquery.fancytree.js: https://github.com/mar10/fancytree/)
8937 *
8938 * Copyright (c) 2008-2023, Martin Wendt (https://wwWendt.de)
8939 *
8940 * Released under the MIT license
8941 * https://github.com/mar10/fancytree/wiki/LicenseInfo
8942 *
8943 * @version 2.38.3
8944 * @date 2023-02-01T20:52:50Z
8945 */
8946
8947/*
8948 #TODO
8949	Compatiblity when dragging between *separate* windows:
8950
8951		   Drag from Chrome   Edge    FF    IE11    Safari
8952	  To Chrome      ok       ok      ok    NO      ?
8953		 Edge        ok       ok      ok    NO      ?
8954		 FF          ok       ok      ok    NO      ?
8955		 IE 11       ok       ok      ok    ok      ?
8956		 Safari      ?        ?       ?     ?       ok
8957
8958 */
8959
8960(function (factory) {
8961	if (typeof define === "function" && define.amd) {
8962		// AMD. Register as an anonymous module.
8963		define(["jquery", "./jquery.fancytree"], factory);
8964	} else if (typeof module === "object" && module.exports) {
8965		// Node/CommonJS
8966		require("./jquery.fancytree");
8967		module.exports = factory(require("jquery"));
8968	} else {
8969		// Browser globals
8970		factory(jQuery);
8971	}
8972})(function ($) {
8973	"use strict";
8974
8975	/******************************************************************************
8976	 * Private functions and variables
8977	 */
8978	var FT = $.ui.fancytree,
8979		isMac = /Mac/.test(navigator.platform),
8980		classDragSource = "fancytree-drag-source",
8981		classDragRemove = "fancytree-drag-remove",
8982		classDropAccept = "fancytree-drop-accept",
8983		classDropAfter = "fancytree-drop-after",
8984		classDropBefore = "fancytree-drop-before",
8985		classDropOver = "fancytree-drop-over",
8986		classDropReject = "fancytree-drop-reject",
8987		classDropTarget = "fancytree-drop-target",
8988		nodeMimeType = "application/x-fancytree-node",
8989		$dropMarker = null,
8990		$dragImage,
8991		$extraHelper,
8992		SOURCE_NODE = null,
8993		SOURCE_NODE_LIST = null,
8994		$sourceList = null,
8995		DRAG_ENTER_RESPONSE = null,
8996		// SESSION_DATA = null, // plain object passed to events as `data`
8997		SUGGESTED_DROP_EFFECT = null,
8998		REQUESTED_DROP_EFFECT = null,
8999		REQUESTED_EFFECT_ALLOWED = null,
9000		LAST_HIT_MODE = null,
9001		DRAG_OVER_STAMP = null; // Time when a node entered the 'over' hitmode
9002
9003	/* */
9004	function _clearGlobals() {
9005		DRAG_ENTER_RESPONSE = null;
9006		DRAG_OVER_STAMP = null;
9007		REQUESTED_DROP_EFFECT = null;
9008		REQUESTED_EFFECT_ALLOWED = null;
9009		SUGGESTED_DROP_EFFECT = null;
9010		SOURCE_NODE = null;
9011		SOURCE_NODE_LIST = null;
9012		if ($sourceList) {
9013			$sourceList.removeClass(classDragSource + " " + classDragRemove);
9014		}
9015		$sourceList = null;
9016		if ($dropMarker) {
9017			$dropMarker.hide();
9018		}
9019		// Take this badge off of me - I can't use it anymore:
9020		if ($extraHelper) {
9021			$extraHelper.remove();
9022			$extraHelper = null;
9023		}
9024	}
9025
9026	/* Convert number to string and prepend +/-; return empty string for 0.*/
9027	function offsetString(n) {
9028		// eslint-disable-next-line no-nested-ternary
9029		return n === 0 ? "" : n > 0 ? "+" + n : "" + n;
9030	}
9031
9032	/* Convert a dragEnter() or dragOver() response to a canonical form.
9033	 * Return false or plain object
9034	 * @param {string|object|boolean} r
9035	 * @return {object|false}
9036	 */
9037	function normalizeDragEnterResponse(r) {
9038		var res;
9039
9040		if (!r) {
9041			return false;
9042		}
9043		if ($.isPlainObject(r)) {
9044			res = {
9045				over: !!r.over,
9046				before: !!r.before,
9047				after: !!r.after,
9048			};
9049		} else if (Array.isArray(r)) {
9050			res = {
9051				over: $.inArray("over", r) >= 0,
9052				before: $.inArray("before", r) >= 0,
9053				after: $.inArray("after", r) >= 0,
9054			};
9055		} else {
9056			res = {
9057				over: r === true || r === "over",
9058				before: r === true || r === "before",
9059				after: r === true || r === "after",
9060			};
9061		}
9062		if (Object.keys(res).length === 0) {
9063			return false;
9064		}
9065		// if( Object.keys(res).length === 1 ) {
9066		// 	res.unique = res[0];
9067		// }
9068		return res;
9069	}
9070
9071	/* Convert a dataTransfer.effectAllowed to a canonical form.
9072	 * Return false or plain object
9073	 * @param {string|boolean} r
9074	 * @return {object|false}
9075	 */
9076	// function normalizeEffectAllowed(r) {
9077	// 	if (!r || r === "none") {
9078	// 		return false;
9079	// 	}
9080	// 	var all = r === "all",
9081	// 		res = {
9082	// 			copy: all || /copy/i.test(r),
9083	// 			link: all || /link/i.test(r),
9084	// 			move: all || /move/i.test(r),
9085	// 		};
9086
9087	// 	return res;
9088	// }
9089
9090	/* Implement auto scrolling when drag cursor is in top/bottom area of scroll parent. */
9091	function autoScroll(tree, event) {
9092		var spOfs,
9093			scrollTop,
9094			delta,
9095			dndOpts = tree.options.dnd5,
9096			sp = tree.$scrollParent[0],
9097			sensitivity = dndOpts.scrollSensitivity,
9098			speed = dndOpts.scrollSpeed,
9099			scrolled = 0;
9100
9101		if (sp !== document && sp.tagName !== "HTML") {
9102			spOfs = tree.$scrollParent.offset();
9103			scrollTop = sp.scrollTop;
9104			if (spOfs.top + sp.offsetHeight - event.pageY < sensitivity) {
9105				delta =
9106					sp.scrollHeight -
9107					tree.$scrollParent.innerHeight() -
9108					scrollTop;
9109				// console.log ("sp.offsetHeight: " + sp.offsetHeight
9110				// 	+ ", spOfs.top: " + spOfs.top
9111				// 	+ ", scrollTop: " + scrollTop
9112				// 	+ ", innerHeight: " + tree.$scrollParent.innerHeight()
9113				// 	+ ", scrollHeight: " + sp.scrollHeight
9114				// 	+ ", delta: " + delta
9115				// 	);
9116				if (delta > 0) {
9117					sp.scrollTop = scrolled = scrollTop + speed;
9118				}
9119			} else if (scrollTop > 0 && event.pageY - spOfs.top < sensitivity) {
9120				sp.scrollTop = scrolled = scrollTop - speed;
9121			}
9122		} else {
9123			scrollTop = $(document).scrollTop();
9124			if (scrollTop > 0 && event.pageY - scrollTop < sensitivity) {
9125				scrolled = scrollTop - speed;
9126				$(document).scrollTop(scrolled);
9127			} else if (
9128				$(window).height() - (event.pageY - scrollTop) <
9129				sensitivity
9130			) {
9131				scrolled = scrollTop + speed;
9132				$(document).scrollTop(scrolled);
9133			}
9134		}
9135		if (scrolled) {
9136			tree.debug("autoScroll: " + scrolled + "px");
9137		}
9138		return scrolled;
9139	}
9140
9141	/* Guess dropEffect from modifier keys.
9142	 * Using rules suggested here:
9143	 *     https://ux.stackexchange.com/a/83769
9144	 * @returns
9145	 *     'copy', 'link', 'move', or 'none'
9146	 */
9147	function evalEffectModifiers(tree, event, effectDefault) {
9148		var res = effectDefault;
9149
9150		if (isMac) {
9151			if (event.metaKey && event.altKey) {
9152				// Mac: [Control] + [Option]
9153				res = "link";
9154			} else if (event.ctrlKey) {
9155				// Chrome on Mac: [Control]
9156				res = "link";
9157			} else if (event.metaKey) {
9158				// Mac: [Command]
9159				res = "move";
9160			} else if (event.altKey) {
9161				// Mac: [Option]
9162				res = "copy";
9163			}
9164		} else {
9165			if (event.ctrlKey) {
9166				// Windows: [Ctrl]
9167				res = "copy";
9168			} else if (event.shiftKey) {
9169				// Windows: [Shift]
9170				res = "move";
9171			} else if (event.altKey) {
9172				// Windows: [Alt]
9173				res = "link";
9174			}
9175		}
9176		if (res !== SUGGESTED_DROP_EFFECT) {
9177			tree.info(
9178				"evalEffectModifiers: " +
9179					event.type +
9180					" - evalEffectModifiers(): " +
9181					SUGGESTED_DROP_EFFECT +
9182					" -> " +
9183					res
9184			);
9185		}
9186		SUGGESTED_DROP_EFFECT = res;
9187		// tree.debug("evalEffectModifiers: " + res);
9188		return res;
9189	}
9190	/*
9191	 * Check if the previous callback (dragEnter, dragOver, ...) has changed
9192	 * the `data` object and apply those settings.
9193	 *
9194	 * Safari:
9195	 *     It seems that `dataTransfer.dropEffect` can only be set on dragStart, and will remain
9196	 *     even if the cursor changes when [Alt] or [Ctrl] are pressed (?)
9197	 * Using rules suggested here:
9198	 *     https://ux.stackexchange.com/a/83769
9199	 * @returns
9200	 *     'copy', 'link', 'move', or 'none'
9201	 */
9202	function prepareDropEffectCallback(event, data) {
9203		var tree = data.tree,
9204			dataTransfer = data.dataTransfer;
9205
9206		if (event.type === "dragstart") {
9207			data.effectAllowed = tree.options.dnd5.effectAllowed;
9208			data.dropEffect = tree.options.dnd5.dropEffectDefault;
9209		} else {
9210			data.effectAllowed = REQUESTED_EFFECT_ALLOWED;
9211			data.dropEffect = REQUESTED_DROP_EFFECT;
9212		}
9213		data.dropEffectSuggested = evalEffectModifiers(
9214			tree,
9215			event,
9216			tree.options.dnd5.dropEffectDefault
9217		);
9218		data.isMove = data.dropEffect === "move";
9219		data.files = dataTransfer.files || [];
9220
9221		// if (REQUESTED_EFFECT_ALLOWED !== dataTransfer.effectAllowed) {
9222		// 	tree.warn(
9223		// 		"prepareDropEffectCallback(" +
9224		// 			event.type +
9225		// 			"): dataTransfer.effectAllowed changed from " +
9226		// 			REQUESTED_EFFECT_ALLOWED +
9227		// 			" -> " +
9228		// 			dataTransfer.effectAllowed
9229		// 	);
9230		// }
9231		// if (REQUESTED_DROP_EFFECT !== dataTransfer.dropEffect) {
9232		// 	tree.warn(
9233		// 		"prepareDropEffectCallback(" +
9234		// 			event.type +
9235		// 			"): dataTransfer.dropEffect changed from requested " +
9236		// 			REQUESTED_DROP_EFFECT +
9237		// 			" to " +
9238		// 			dataTransfer.dropEffect
9239		// 	);
9240		// }
9241	}
9242
9243	function applyDropEffectCallback(event, data, allowDrop) {
9244		var tree = data.tree,
9245			dataTransfer = data.dataTransfer;
9246
9247		if (
9248			event.type !== "dragstart" &&
9249			REQUESTED_EFFECT_ALLOWED !== data.effectAllowed
9250		) {
9251			tree.warn(
9252				"effectAllowed should only be changed in dragstart event: " +
9253					event.type +
9254					": data.effectAllowed changed from " +
9255					REQUESTED_EFFECT_ALLOWED +
9256					" -> " +
9257					data.effectAllowed
9258			);
9259		}
9260
9261		if (allowDrop === false) {
9262			tree.info("applyDropEffectCallback: allowDrop === false");
9263			data.effectAllowed = "none";
9264			data.dropEffect = "none";
9265		}
9266		// if (REQUESTED_DROP_EFFECT !== data.dropEffect) {
9267		// 	tree.debug(
9268		// 		"applyDropEffectCallback(" +
9269		// 			event.type +
9270		// 			"): data.dropEffect changed from previous " +
9271		// 			REQUESTED_DROP_EFFECT +
9272		// 			" to " +
9273		// 			data.dropEffect
9274		// 	);
9275		// }
9276
9277		data.isMove = data.dropEffect === "move";
9278		// data.isMove = data.dropEffectSuggested === "move";
9279
9280		// `effectAllowed` must only be defined in dragstart event, so we
9281		// store it in a global variable for reference
9282		if (event.type === "dragstart") {
9283			REQUESTED_EFFECT_ALLOWED = data.effectAllowed;
9284			REQUESTED_DROP_EFFECT = data.dropEffect;
9285		}
9286
9287		// if (REQUESTED_DROP_EFFECT !== dataTransfer.dropEffect) {
9288		// 	data.tree.info(
9289		// 		"applyDropEffectCallback(" +
9290		// 			event.type +
9291		// 			"): dataTransfer.dropEffect changed from " +
9292		// 			REQUESTED_DROP_EFFECT +
9293		// 			" -> " +
9294		// 			dataTransfer.dropEffect
9295		// 	);
9296		// }
9297		dataTransfer.effectAllowed = REQUESTED_EFFECT_ALLOWED;
9298		dataTransfer.dropEffect = REQUESTED_DROP_EFFECT;
9299
9300		// tree.debug(
9301		// 	"applyDropEffectCallback(" +
9302		// 		event.type +
9303		// 		"): set " +
9304		// 		dataTransfer.dropEffect +
9305		// 		"/" +
9306		// 		dataTransfer.effectAllowed
9307		// );
9308		// if (REQUESTED_DROP_EFFECT !== dataTransfer.dropEffect) {
9309		// 	data.tree.warn(
9310		// 		"applyDropEffectCallback(" +
9311		// 			event.type +
9312		// 			"): could not set dataTransfer.dropEffect to " +
9313		// 			REQUESTED_DROP_EFFECT +
9314		// 			": got " +
9315		// 			dataTransfer.dropEffect
9316		// 	);
9317		// }
9318		return REQUESTED_DROP_EFFECT;
9319	}
9320
9321	/* Handle dragover event (fired every x ms) on valid drop targets.
9322	 *
9323	 * - Auto-scroll when cursor is in border regions
9324	 * - Apply restrictioan like 'preventVoidMoves'
9325	 * - Calculate hit mode
9326	 * - Calculate drop effect
9327	 * - Trigger dragOver() callback to let user modify hit mode and drop effect
9328	 * - Adjust the drop marker accordingly
9329	 *
9330	 * @returns hitMode
9331	 */
9332	function handleDragOver(event, data) {
9333		// Implement auto-scrolling
9334		if (data.options.dnd5.scroll) {
9335			autoScroll(data.tree, event);
9336		}
9337		// Bail out with previous response if we get an invalid dragover
9338		if (!data.node) {
9339			data.tree.warn("Ignored dragover for non-node"); //, event, data);
9340			return LAST_HIT_MODE;
9341		}
9342
9343		var markerOffsetX,
9344			nodeOfs,
9345			pos,
9346			relPosY,
9347			hitMode = null,
9348			tree = data.tree,
9349			options = tree.options,
9350			dndOpts = options.dnd5,
9351			targetNode = data.node,
9352			sourceNode = data.otherNode,
9353			markerAt = "center",
9354			$target = $(targetNode.span),
9355			$targetTitle = $target.find("span.fancytree-title");
9356
9357		if (DRAG_ENTER_RESPONSE === false) {
9358			tree.debug("Ignored dragover, since dragenter returned false.");
9359			return false;
9360		} else if (typeof DRAG_ENTER_RESPONSE === "string") {
9361			$.error("assert failed: dragenter returned string");
9362		}
9363		// Calculate hitMode from relative cursor position.
9364		nodeOfs = $target.offset();
9365		relPosY = (event.pageY - nodeOfs.top) / $target.height();
9366		if (event.pageY === undefined) {
9367			tree.warn("event.pageY is undefined: see issue #1013.");
9368		}
9369
9370		if (DRAG_ENTER_RESPONSE.after && relPosY > 0.75) {
9371			hitMode = "after";
9372		} else if (
9373			!DRAG_ENTER_RESPONSE.over &&
9374			DRAG_ENTER_RESPONSE.after &&
9375			relPosY > 0.5
9376		) {
9377			hitMode = "after";
9378		} else if (DRAG_ENTER_RESPONSE.before && relPosY <= 0.25) {
9379			hitMode = "before";
9380		} else if (
9381			!DRAG_ENTER_RESPONSE.over &&
9382			DRAG_ENTER_RESPONSE.before &&
9383			relPosY <= 0.5
9384		) {
9385			hitMode = "before";
9386		} else if (DRAG_ENTER_RESPONSE.over) {
9387			hitMode = "over";
9388		}
9389		// Prevent no-ops like 'before source node'
9390		// TODO: these are no-ops when moving nodes, but not in copy mode
9391		if (dndOpts.preventVoidMoves && data.dropEffect === "move") {
9392			if (targetNode === sourceNode) {
9393				targetNode.debug("Drop over source node prevented.");
9394				hitMode = null;
9395			} else if (
9396				hitMode === "before" &&
9397				sourceNode &&
9398				targetNode === sourceNode.getNextSibling()
9399			) {
9400				targetNode.debug("Drop after source node prevented.");
9401				hitMode = null;
9402			} else if (
9403				hitMode === "after" &&
9404				sourceNode &&
9405				targetNode === sourceNode.getPrevSibling()
9406			) {
9407				targetNode.debug("Drop before source node prevented.");
9408				hitMode = null;
9409			} else if (
9410				hitMode === "over" &&
9411				sourceNode &&
9412				sourceNode.parent === targetNode &&
9413				sourceNode.isLastSibling()
9414			) {
9415				targetNode.debug("Drop last child over own parent prevented.");
9416				hitMode = null;
9417			}
9418		}
9419		// Let callback modify the calculated hitMode
9420		data.hitMode = hitMode;
9421		if (hitMode && dndOpts.dragOver) {
9422			prepareDropEffectCallback(event, data);
9423			dndOpts.dragOver(targetNode, data);
9424			var allowDrop = !!hitMode;
9425			applyDropEffectCallback(event, data, allowDrop);
9426			hitMode = data.hitMode;
9427		}
9428		LAST_HIT_MODE = hitMode;
9429		//
9430		if (hitMode === "after" || hitMode === "before" || hitMode === "over") {
9431			markerOffsetX = dndOpts.dropMarkerOffsetX || 0;
9432			switch (hitMode) {
9433				case "before":
9434					markerAt = "top";
9435					markerOffsetX += dndOpts.dropMarkerInsertOffsetX || 0;
9436					break;
9437				case "after":
9438					markerAt = "bottom";
9439					markerOffsetX += dndOpts.dropMarkerInsertOffsetX || 0;
9440					break;
9441			}
9442
9443			pos = {
9444				my: "left" + offsetString(markerOffsetX) + " center",
9445				at: "left " + markerAt,
9446				of: $targetTitle,
9447			};
9448			if (options.rtl) {
9449				pos.my = "right" + offsetString(-markerOffsetX) + " center";
9450				pos.at = "right " + markerAt;
9451				// console.log("rtl", pos);
9452			}
9453			$dropMarker
9454				.toggleClass(classDropAfter, hitMode === "after")
9455				.toggleClass(classDropOver, hitMode === "over")
9456				.toggleClass(classDropBefore, hitMode === "before")
9457				.show()
9458				.position(FT.fixPositionOptions(pos));
9459		} else {
9460			$dropMarker.hide();
9461			// console.log("hide dropmarker")
9462		}
9463
9464		$(targetNode.span)
9465			.toggleClass(
9466				classDropTarget,
9467				hitMode === "after" ||
9468					hitMode === "before" ||
9469					hitMode === "over"
9470			)
9471			.toggleClass(classDropAfter, hitMode === "after")
9472			.toggleClass(classDropBefore, hitMode === "before")
9473			.toggleClass(classDropAccept, hitMode === "over")
9474			.toggleClass(classDropReject, hitMode === false);
9475
9476		return hitMode;
9477	}
9478
9479	/*
9480	 * Handle dragstart drag dragend events on the container
9481	 */
9482	function onDragEvent(event) {
9483		var json,
9484			tree = this,
9485			dndOpts = tree.options.dnd5,
9486			node = FT.getNode(event),
9487			dataTransfer =
9488				event.dataTransfer || event.originalEvent.dataTransfer,
9489			data = {
9490				tree: tree,
9491				node: node,
9492				options: tree.options,
9493				originalEvent: event.originalEvent,
9494				widget: tree.widget,
9495				dataTransfer: dataTransfer,
9496				useDefaultImage: true,
9497				dropEffect: undefined,
9498				dropEffectSuggested: undefined,
9499				effectAllowed: undefined, // set by dragstart
9500				files: undefined, // only for drop events
9501				isCancelled: undefined, // set by dragend
9502				isMove: undefined,
9503			};
9504
9505		switch (event.type) {
9506			case "dragstart":
9507				if (!node) {
9508					tree.info("Ignored dragstart on a non-node.");
9509					return false;
9510				}
9511				// Store current source node in different formats
9512				SOURCE_NODE = node;
9513
9514				// Also optionally store selected nodes
9515				if (dndOpts.multiSource === false) {
9516					SOURCE_NODE_LIST = [node];
9517				} else if (dndOpts.multiSource === true) {
9518					if (node.isSelected()) {
9519						SOURCE_NODE_LIST = tree.getSelectedNodes();
9520					} else {
9521						SOURCE_NODE_LIST = [node];
9522					}
9523				} else {
9524					SOURCE_NODE_LIST = dndOpts.multiSource(node, data);
9525				}
9526				// Cache as array of jQuery objects for faster access:
9527				$sourceList = $(
9528					$.map(SOURCE_NODE_LIST, function (n) {
9529						return n.span;
9530					})
9531				);
9532				// Set visual feedback
9533				$sourceList.addClass(classDragSource);
9534
9535				// Set payload
9536				// Note:
9537				// Transfer data is only accessible on dragstart and drop!
9538				// For all other events the formats and kinds in the drag
9539				// data store list of items representing dragged data can be
9540				// enumerated, but the data itself is unavailable and no new
9541				// data can be added.
9542				var nodeData = node.toDict(true, dndOpts.sourceCopyHook);
9543				nodeData.treeId = node.tree._id;
9544				json = JSON.stringify(nodeData);
9545				try {
9546					dataTransfer.setData(nodeMimeType, json);
9547					dataTransfer.setData("text/html", $(node.span).html());
9548					dataTransfer.setData("text/plain", node.title);
9549				} catch (ex) {
9550					// IE only accepts 'text' type
9551					tree.warn(
9552						"Could not set data (IE only accepts 'text') - " + ex
9553					);
9554				}
9555				// We always need to set the 'text' type if we want to drag
9556				// Because IE 11 only accepts this single type.
9557				// If we pass JSON here, IE can can access all node properties,
9558				// even when the source lives in another window. (D'n'd inside
9559				// the same window will always work.)
9560				// The drawback is, that in this case ALL browsers will see
9561				// the JSON representation as 'text', so dragging
9562				// to a text field will insert the JSON string instead of
9563				// the node title.
9564				if (dndOpts.setTextTypeJson) {
9565					dataTransfer.setData("text", json);
9566				} else {
9567					dataTransfer.setData("text", node.title);
9568				}
9569
9570				// Set the allowed drag modes (combinations of move, copy, and link)
9571				// (effectAllowed can only be set in the dragstart event.)
9572				// This can be overridden in the dragStart() callback
9573				prepareDropEffectCallback(event, data);
9574
9575				// Let user cancel or modify above settings
9576				// Realize potential changes by previous callback
9577				if (dndOpts.dragStart(node, data) === false) {
9578					// Cancel dragging
9579					// dataTransfer.dropEffect = "none";
9580					_clearGlobals();
9581					return false;
9582				}
9583				applyDropEffectCallback(event, data);
9584
9585				// Unless user set `data.useDefaultImage` to false in dragStart,
9586				// generata a default drag image now:
9587				$extraHelper = null;
9588
9589				if (data.useDefaultImage) {
9590					// Set the title as drag image (otherwise it would contain the expander)
9591					$dragImage = $(node.span).find(".fancytree-title");
9592
9593					if (SOURCE_NODE_LIST && SOURCE_NODE_LIST.length > 1) {
9594						// Add a counter badge to node title if dragging more than one node.
9595						// We want this, because the element that is used as drag image
9596						// must be *visible* in the DOM, so we cannot create some hidden
9597						// custom markup.
9598						// See https://kryogenix.org/code/browser/custom-drag-image.html
9599						// Also, since IE 11 and Edge don't support setDragImage() alltogether,
9600						// it gives som feedback to the user.
9601						// The badge will be removed later on drag end.
9602						$extraHelper = $(
9603							"<span class='fancytree-childcounter'/>"
9604						)
9605							.text("+" + (SOURCE_NODE_LIST.length - 1))
9606							.appendTo($dragImage);
9607					}
9608					if (dataTransfer.setDragImage) {
9609						// IE 11 and Edge do not support this
9610						dataTransfer.setDragImage($dragImage[0], -10, -10);
9611					}
9612				}
9613				return true;
9614
9615			case "drag":
9616				// Called every few milliseconds (no matter if the
9617				// cursor is over a valid drop target)
9618				// data.tree.info("drag", SOURCE_NODE)
9619				prepareDropEffectCallback(event, data);
9620				dndOpts.dragDrag(node, data);
9621				applyDropEffectCallback(event, data);
9622
9623				$sourceList.toggleClass(classDragRemove, data.isMove);
9624				break;
9625
9626			case "dragend":
9627				// Called at the end of a d'n'd process (after drop)
9628				// Note caveat: If drop removed the dragged source element,
9629				// we may not get this event, since the target does not exist
9630				// anymore
9631				prepareDropEffectCallback(event, data);
9632
9633				_clearGlobals();
9634
9635				data.isCancelled = !LAST_HIT_MODE;
9636				dndOpts.dragEnd(node, data, !LAST_HIT_MODE);
9637				// applyDropEffectCallback(event, data);
9638				break;
9639		}
9640	}
9641	/*
9642	 * Handle dragenter dragover dragleave drop events on the container
9643	 */
9644	function onDropEvent(event) {
9645		var json,
9646			allowAutoExpand,
9647			nodeData,
9648			isSourceFtNode,
9649			r,
9650			res,
9651			tree = this,
9652			dndOpts = tree.options.dnd5,
9653			allowDrop = null,
9654			node = FT.getNode(event),
9655			dataTransfer =
9656				event.dataTransfer || event.originalEvent.dataTransfer,
9657			data = {
9658				tree: tree,
9659				node: node,
9660				options: tree.options,
9661				originalEvent: event.originalEvent,
9662				widget: tree.widget,
9663				hitMode: DRAG_ENTER_RESPONSE,
9664				dataTransfer: dataTransfer,
9665				otherNode: SOURCE_NODE || null,
9666				otherNodeList: SOURCE_NODE_LIST || null,
9667				otherNodeData: null, // set by drop event
9668				useDefaultImage: true,
9669				dropEffect: undefined,
9670				dropEffectSuggested: undefined,
9671				effectAllowed: undefined, // set by dragstart
9672				files: null, // list of File objects (may be [])
9673				isCancelled: undefined, // set by drop event
9674				isMove: undefined,
9675			};
9676
9677		// data.isMove = dropEffect === "move";
9678
9679		switch (event.type) {
9680			case "dragenter":
9681				// The dragenter event is fired when a dragged element or
9682				// text selection enters a valid drop target.
9683
9684				DRAG_OVER_STAMP = null;
9685				if (!node) {
9686					// Sometimes we get dragenter for the container element
9687					tree.debug(
9688						"Ignore non-node " +
9689							event.type +
9690							": " +
9691							event.target.tagName +
9692							"." +
9693							event.target.className
9694					);
9695					DRAG_ENTER_RESPONSE = false;
9696					break;
9697				}
9698
9699				$(node.span)
9700					.addClass(classDropOver)
9701					.removeClass(classDropAccept + " " + classDropReject);
9702
9703				// Data is only readable in the dragstart and drop event,
9704				// but we can check for the type:
9705				isSourceFtNode =
9706					$.inArray(nodeMimeType, dataTransfer.types) >= 0;
9707
9708				if (dndOpts.preventNonNodes && !isSourceFtNode) {
9709					node.debug("Reject dropping a non-node.");
9710					DRAG_ENTER_RESPONSE = false;
9711					break;
9712				} else if (
9713					dndOpts.preventForeignNodes &&
9714					(!SOURCE_NODE || SOURCE_NODE.tree !== node.tree)
9715				) {
9716					node.debug("Reject dropping a foreign node.");
9717					DRAG_ENTER_RESPONSE = false;
9718					break;
9719				} else if (
9720					dndOpts.preventSameParent &&
9721					data.otherNode &&
9722					data.otherNode.tree === node.tree &&
9723					node.parent === data.otherNode.parent
9724				) {
9725					node.debug("Reject dropping as sibling (same parent).");
9726					DRAG_ENTER_RESPONSE = false;
9727					break;
9728				} else if (
9729					dndOpts.preventRecursion &&
9730					data.otherNode &&
9731					data.otherNode.tree === node.tree &&
9732					node.isDescendantOf(data.otherNode)
9733				) {
9734					node.debug("Reject dropping below own ancestor.");
9735					DRAG_ENTER_RESPONSE = false;
9736					break;
9737				} else if (dndOpts.preventLazyParents && !node.isLoaded()) {
9738					node.warn("Drop over unloaded target node prevented.");
9739					DRAG_ENTER_RESPONSE = false;
9740					break;
9741				}
9742				$dropMarker.show();
9743
9744				// Call dragEnter() to figure out if (and where) dropping is allowed
9745				prepareDropEffectCallback(event, data);
9746				r = dndOpts.dragEnter(node, data);
9747
9748				res = normalizeDragEnterResponse(r);
9749				// alert("res:" + JSON.stringify(res))
9750				DRAG_ENTER_RESPONSE = res;
9751
9752				allowDrop = res && (res.over || res.before || res.after);
9753
9754				applyDropEffectCallback(event, data, allowDrop);
9755				break;
9756
9757			case "dragover":
9758				if (!node) {
9759					tree.debug(
9760						"Ignore non-node " +
9761							event.type +
9762							": " +
9763							event.target.tagName +
9764							"." +
9765							event.target.className
9766					);
9767					break;
9768				}
9769				// The dragover event is fired when an element or text
9770				// selection is being dragged over a valid drop target
9771				// (every few hundred milliseconds).
9772				// tree.debug(
9773				// 	event.type +
9774				// 		": dropEffect: " +
9775				// 		dataTransfer.dropEffect
9776				// );
9777				prepareDropEffectCallback(event, data);
9778				LAST_HIT_MODE = handleDragOver(event, data);
9779
9780				// The flag controls the preventDefault() below:
9781				allowDrop = !!LAST_HIT_MODE;
9782				allowAutoExpand =
9783					LAST_HIT_MODE === "over" || LAST_HIT_MODE === false;
9784
9785				if (
9786					allowAutoExpand &&
9787					!node.expanded &&
9788					node.hasChildren() !== false
9789				) {
9790					if (!DRAG_OVER_STAMP) {
9791						DRAG_OVER_STAMP = Date.now();
9792					} else if (
9793						dndOpts.autoExpandMS &&
9794						Date.now() - DRAG_OVER_STAMP > dndOpts.autoExpandMS &&
9795						!node.isLoading() &&
9796						(!dndOpts.dragExpand ||
9797							dndOpts.dragExpand(node, data) !== false)
9798					) {
9799						node.setExpanded();
9800					}
9801				} else {
9802					DRAG_OVER_STAMP = null;
9803				}
9804				break;
9805
9806			case "dragleave":
9807				// NOTE: dragleave is fired AFTER the dragenter event of the
9808				// FOLLOWING element.
9809				if (!node) {
9810					tree.debug(
9811						"Ignore non-node " +
9812							event.type +
9813							": " +
9814							event.target.tagName +
9815							"." +
9816							event.target.className
9817					);
9818					break;
9819				}
9820				if (!$(node.span).hasClass(classDropOver)) {
9821					node.debug("Ignore dragleave (multi).");
9822					break;
9823				}
9824				$(node.span).removeClass(
9825					classDropOver +
9826						" " +
9827						classDropAccept +
9828						" " +
9829						classDropReject
9830				);
9831				node.scheduleAction("cancel");
9832				dndOpts.dragLeave(node, data);
9833				$dropMarker.hide();
9834				break;
9835
9836			case "drop":
9837				// Data is only readable in the (dragstart and) drop event:
9838
9839				if ($.inArray(nodeMimeType, dataTransfer.types) >= 0) {
9840					nodeData = dataTransfer.getData(nodeMimeType);
9841					tree.info(
9842						event.type +
9843							": getData('application/x-fancytree-node'): '" +
9844							nodeData +
9845							"'"
9846					);
9847				}
9848				if (!nodeData) {
9849					// 1. Source is not a Fancytree node, or
9850					// 2. If the FT mime type was set, but returns '', this
9851					//    is probably IE 11 (which only supports 'text')
9852					nodeData = dataTransfer.getData("text");
9853					tree.info(
9854						event.type + ": getData('text'): '" + nodeData + "'"
9855					);
9856				}
9857				if (nodeData) {
9858					try {
9859						// 'text' type may contain JSON if IE is involved
9860						// and setTextTypeJson option was set
9861						json = JSON.parse(nodeData);
9862						if (json.title !== undefined) {
9863							data.otherNodeData = json;
9864						}
9865					} catch (ex) {
9866						// assume 'text' type contains plain text, so `otherNodeData`
9867						// should not be set
9868					}
9869				}
9870				tree.debug(
9871					event.type +
9872						": nodeData: '" +
9873						nodeData +
9874						"', otherNodeData: ",
9875					data.otherNodeData
9876				);
9877
9878				$(node.span).removeClass(
9879					classDropOver +
9880						" " +
9881						classDropAccept +
9882						" " +
9883						classDropReject
9884				);
9885
9886				// Let user implement the actual drop operation
9887				data.hitMode = LAST_HIT_MODE;
9888				prepareDropEffectCallback(event, data, !LAST_HIT_MODE);
9889				data.isCancelled = !LAST_HIT_MODE;
9890
9891				var orgSourceElem = SOURCE_NODE && SOURCE_NODE.span,
9892					orgSourceTree = SOURCE_NODE && SOURCE_NODE.tree;
9893
9894				dndOpts.dragDrop(node, data);
9895				// applyDropEffectCallback(event, data);
9896
9897				// Prevent browser's default drop handling, i.e. open as link, ...
9898				event.preventDefault();
9899
9900				if (orgSourceElem && !document.body.contains(orgSourceElem)) {
9901					// The drop handler removed the original drag source from
9902					// the DOM, so the dragend event will probaly not fire.
9903					if (orgSourceTree === tree) {
9904						tree.debug(
9905							"Drop handler removed source element: generating dragEnd."
9906						);
9907						dndOpts.dragEnd(SOURCE_NODE, data);
9908					} else {
9909						tree.warn(
9910							"Drop handler removed source element: dragend event may be lost."
9911						);
9912					}
9913				}
9914
9915				_clearGlobals();
9916
9917				break;
9918		}
9919		// Dnd API madness: we must PREVENT default handling to enable dropping
9920		if (allowDrop) {
9921			event.preventDefault();
9922			return false;
9923		}
9924	}
9925
9926	/** [ext-dnd5] Return a Fancytree instance, from element, index, event, or jQueryObject.
9927	 *
9928	 * @returns {FancytreeNode[]} List of nodes (empty if no drag operation)
9929	 * @example
9930	 * $.ui.fancytree.getDragNodeList();
9931	 *
9932	 * @alias Fancytree_Static#getDragNodeList
9933	 * @requires jquery.fancytree.dnd5.js
9934	 * @since 2.31
9935	 */
9936	$.ui.fancytree.getDragNodeList = function () {
9937		return SOURCE_NODE_LIST || [];
9938	};
9939
9940	/** [ext-dnd5] Return the FancytreeNode that is currently being dragged.
9941	 *
9942	 * If multiple nodes are dragged, only the first is returned.
9943	 *
9944	 * @returns {FancytreeNode | null} dragged nodes or null if no drag operation
9945	 * @example
9946	 * $.ui.fancytree.getDragNode();
9947	 *
9948	 * @alias Fancytree_Static#getDragNode
9949	 * @requires jquery.fancytree.dnd5.js
9950	 * @since 2.31
9951	 */
9952	$.ui.fancytree.getDragNode = function () {
9953		return SOURCE_NODE;
9954	};
9955
9956	/******************************************************************************
9957	 *
9958	 */
9959
9960	$.ui.fancytree.registerExtension({
9961		name: "dnd5",
9962		version: "2.38.3",
9963		// Default options for this extension.
9964		options: {
9965			autoExpandMS: 1500, // Expand nodes after n milliseconds of hovering
9966			dropMarkerInsertOffsetX: -16, // Additional offset for drop-marker with hitMode = "before"/"after"
9967			dropMarkerOffsetX: -24, // Absolute position offset for .fancytree-drop-marker relatively to ..fancytree-title (icon/img near a node accepting drop)
9968			// #1021 `document.body` is not available yet
9969			dropMarkerParent: "body", // Root Container used for drop marker (could be a shadow root)
9970			multiSource: false, // true: Drag multiple (i.e. selected) nodes. Also a callback() is allowed
9971			effectAllowed: "all", // Restrict the possible cursor shapes and modifier operations (can also be set in the dragStart event)
9972			// dropEffect: "auto", // 'copy'|'link'|'move'|'auto'(calculate from `effectAllowed`+modifier keys) or callback(node, data) that returns such string.
9973			dropEffectDefault: "move", // Default dropEffect ('copy', 'link', or 'move') when no modifier is pressed (overide in dragDrag, dragOver).
9974			preventForeignNodes: false, // Prevent dropping nodes from different Fancytrees
9975			preventLazyParents: true, // Prevent dropping items on unloaded lazy Fancytree nodes
9976			preventNonNodes: false, // Prevent dropping items other than Fancytree nodes
9977			preventRecursion: true, // Prevent dropping nodes on own descendants
9978			preventSameParent: false, // Prevent dropping nodes under same direct parent
9979			preventVoidMoves: true, // Prevent dropping nodes 'before self', etc.
9980			scroll: true, // Enable auto-scrolling while dragging
9981			scrollSensitivity: 20, // Active top/bottom margin in pixel
9982			scrollSpeed: 5, // Pixel per event
9983			setTextTypeJson: false, // Allow dragging of nodes to different IE windows
9984			sourceCopyHook: null, // Optional callback passed to `toDict` on dragStart @since 2.38
9985			// Events (drag support)
9986			dragStart: null, // Callback(sourceNode, data), return true, to enable dnd drag
9987			dragDrag: $.noop, // Callback(sourceNode, data)
9988			dragEnd: $.noop, // Callback(sourceNode, data)
9989			// Events (drop support)
9990			dragEnter: null, // Callback(targetNode, data), return true, to enable dnd drop
9991			dragOver: $.noop, // Callback(targetNode, data)
9992			dragExpand: $.noop, // Callback(targetNode, data), return false to prevent autoExpand
9993			dragDrop: $.noop, // Callback(targetNode, data)
9994			dragLeave: $.noop, // Callback(targetNode, data)
9995		},
9996
9997		treeInit: function (ctx) {
9998			var $temp,
9999				tree = ctx.tree,
10000				opts = ctx.options,
10001				glyph = opts.glyph || null,
10002				dndOpts = opts.dnd5;
10003
10004			if ($.inArray("dnd", opts.extensions) >= 0) {
10005				$.error("Extensions 'dnd' and 'dnd5' are mutually exclusive.");
10006			}
10007			if (dndOpts.dragStop) {
10008				$.error(
10009					"dragStop is not used by ext-dnd5. Use dragEnd instead."
10010				);
10011			}
10012			if (dndOpts.preventRecursiveMoves != null) {
10013				$.error(
10014					"preventRecursiveMoves was renamed to preventRecursion."
10015				);
10016			}
10017
10018			// Implement `opts.createNode` event to add the 'draggable' attribute
10019			// #680: this must happen before calling super.treeInit()
10020			if (dndOpts.dragStart) {
10021				FT.overrideMethod(
10022					ctx.options,
10023					"createNode",
10024					function (event, data) {
10025						// Default processing if any
10026						this._super.apply(this, arguments);
10027						if (data.node.span) {
10028							data.node.span.draggable = true;
10029						} else {
10030							data.node.warn(
10031								"Cannot add `draggable`: no span tag"
10032							);
10033						}
10034					}
10035				);
10036			}
10037			this._superApply(arguments);
10038
10039			this.$container.addClass("fancytree-ext-dnd5");
10040
10041			// Store the current scroll parent, which may be the tree
10042			// container, any enclosing div, or the document.
10043			// #761: scrollParent() always needs a container child
10044			$temp = $("<span>").appendTo(this.$container);
10045			this.$scrollParent = $temp.scrollParent();
10046			$temp.remove();
10047
10048			$dropMarker = $("#fancytree-drop-marker");
10049			if (!$dropMarker.length) {
10050				$dropMarker = $("<div id='fancytree-drop-marker'></div>")
10051					.hide()
10052					.css({
10053						"z-index": 1000,
10054						// Drop marker should not steal dragenter/dragover events:
10055						"pointer-events": "none",
10056					})
10057					.prependTo(dndOpts.dropMarkerParent);
10058				if (glyph) {
10059					FT.setSpanIcon(
10060						$dropMarker[0],
10061						glyph.map._addClass,
10062						glyph.map.dropMarker
10063					);
10064				}
10065			}
10066			$dropMarker.toggleClass("fancytree-rtl", !!opts.rtl);
10067
10068			// Enable drag support if dragStart() is specified:
10069			if (dndOpts.dragStart) {
10070				// Bind drag event handlers
10071				tree.$container.on(
10072					"dragstart drag dragend",
10073					onDragEvent.bind(tree)
10074				);
10075			}
10076			// Enable drop support if dragEnter() is specified:
10077			if (dndOpts.dragEnter) {
10078				// Bind drop event handlers
10079				tree.$container.on(
10080					"dragenter dragover dragleave drop",
10081					onDropEvent.bind(tree)
10082				);
10083			}
10084		},
10085	});
10086	// Value returned by `require('jquery.fancytree..')`
10087	return $.ui.fancytree;
10088}); // End of closure
10089
10090/*!
10091 * jquery.fancytree.edit.js
10092 *
10093 * Make node titles editable.
10094 * (Extension module for jquery.fancytree.js: https://github.com/mar10/fancytree/)
10095 *
10096 * Copyright (c) 2008-2023, Martin Wendt (https://wwWendt.de)
10097 *
10098 * Released under the MIT license
10099 * https://github.com/mar10/fancytree/wiki/LicenseInfo
10100 *
10101 * @version 2.38.3
10102 * @date 2023-02-01T20:52:50Z
10103 */
10104
10105(function (factory) {
10106	if (typeof define === "function" && define.amd) {
10107		// AMD. Register as an anonymous module.
10108		define(["jquery", "./jquery.fancytree"], factory);
10109	} else if (typeof module === "object" && module.exports) {
10110		// Node/CommonJS
10111		require("./jquery.fancytree");
10112		module.exports = factory(require("jquery"));
10113	} else {
10114		// Browser globals
10115		factory(jQuery);
10116	}
10117})(function ($) {
10118	"use strict";
10119
10120	/*******************************************************************************
10121	 * Private functions and variables
10122	 */
10123
10124	var isMac = /Mac/.test(navigator.platform),
10125		escapeHtml = $.ui.fancytree.escapeHtml,
10126		trim = $.ui.fancytree.trim,
10127		unescapeHtml = $.ui.fancytree.unescapeHtml;
10128
10129	/**
10130	 * [ext-edit] Start inline editing of current node title.
10131	 *
10132	 * @alias FancytreeNode#editStart
10133	 * @requires Fancytree
10134	 */
10135	$.ui.fancytree._FancytreeNodeClass.prototype.editStart = function () {
10136		var $input,
10137			node = this,
10138			tree = this.tree,
10139			local = tree.ext.edit,
10140			instOpts = tree.options.edit,
10141			$title = $(".fancytree-title", node.span),
10142			eventData = {
10143				node: node,
10144				tree: tree,
10145				options: tree.options,
10146				isNew: $(node[tree.statusClassPropName]).hasClass(
10147					"fancytree-edit-new"
10148				),
10149				orgTitle: node.title,
10150				input: null,
10151				dirty: false,
10152			};
10153
10154		// beforeEdit may want to modify the title before editing
10155		if (
10156			instOpts.beforeEdit.call(
10157				node,
10158				{ type: "beforeEdit" },
10159				eventData
10160			) === false
10161		) {
10162			return false;
10163		}
10164		$.ui.fancytree.assert(!local.currentNode, "recursive edit");
10165		local.currentNode = this;
10166		local.eventData = eventData;
10167
10168		// Disable standard Fancytree mouse- and key handling
10169		tree.widget._unbind();
10170
10171		local.lastDraggableAttrValue = node.span.draggable;
10172		if (local.lastDraggableAttrValue) {
10173			node.span.draggable = false;
10174		}
10175
10176		// #116: ext-dnd prevents the blur event, so we have to catch outer clicks
10177		$(document).on("mousedown.fancytree-edit", function (event) {
10178			if (!$(event.target).hasClass("fancytree-edit-input")) {
10179				node.editEnd(true, event);
10180			}
10181		});
10182
10183		// Replace node with <input>
10184		$input = $("<input />", {
10185			class: "fancytree-edit-input",
10186			type: "text",
10187			value: tree.options.escapeTitles
10188				? eventData.orgTitle
10189				: unescapeHtml(eventData.orgTitle),
10190		});
10191		local.eventData.input = $input;
10192		if (instOpts.adjustWidthOfs != null) {
10193			$input.width($title.width() + instOpts.adjustWidthOfs);
10194		}
10195		if (instOpts.inputCss != null) {
10196			$input.css(instOpts.inputCss);
10197		}
10198
10199		$title.html($input);
10200
10201		// Focus <input> and bind keyboard handler
10202		$input
10203			.focus()
10204			.change(function (event) {
10205				$input.addClass("fancytree-edit-dirty");
10206			})
10207			.on("keydown", function (event) {
10208				switch (event.which) {
10209					case $.ui.keyCode.ESCAPE:
10210						node.editEnd(false, event);
10211						break;
10212					case $.ui.keyCode.ENTER:
10213						node.editEnd(true, event);
10214						return false; // so we don't start editmode on Mac
10215				}
10216				event.stopPropagation();
10217			})
10218			.blur(function (event) {
10219				return node.editEnd(true, event);
10220			});
10221
10222		instOpts.edit.call(node, { type: "edit" }, eventData);
10223	};
10224
10225	/**
10226	 * [ext-edit] Stop inline editing.
10227	 * @param {Boolean} [applyChanges=false] false: cancel edit, true: save (if modified)
10228	 * @alias FancytreeNode#editEnd
10229	 * @requires jquery.fancytree.edit.js
10230	 */
10231	$.ui.fancytree._FancytreeNodeClass.prototype.editEnd = function (
10232		applyChanges,
10233		_event
10234	) {
10235		var newVal,
10236			node = this,
10237			tree = this.tree,
10238			local = tree.ext.edit,
10239			eventData = local.eventData,
10240			instOpts = tree.options.edit,
10241			$title = $(".fancytree-title", node.span),
10242			$input = $title.find("input.fancytree-edit-input");
10243
10244		if (instOpts.trim) {
10245			$input.val(trim($input.val()));
10246		}
10247		newVal = $input.val();
10248
10249		eventData.dirty = newVal !== node.title;
10250		eventData.originalEvent = _event;
10251
10252		// Find out, if saving is required
10253		if (applyChanges === false) {
10254			// If true/false was passed, honor this (except in rename mode, if unchanged)
10255			eventData.save = false;
10256		} else if (eventData.isNew) {
10257			// In create mode, we save everything, except for empty text
10258			eventData.save = newVal !== "";
10259		} else {
10260			// In rename mode, we save everyting, except for empty or unchanged text
10261			eventData.save = eventData.dirty && newVal !== "";
10262		}
10263		// Allow to break (keep editor open), modify input, or re-define data.save
10264		if (
10265			instOpts.beforeClose.call(
10266				node,
10267				{ type: "beforeClose" },
10268				eventData
10269			) === false
10270		) {
10271			return false;
10272		}
10273		if (
10274			eventData.save &&
10275			instOpts.save.call(node, { type: "save" }, eventData) === false
10276		) {
10277			return false;
10278		}
10279		$input.removeClass("fancytree-edit-dirty").off();
10280		// Unbind outer-click handler
10281		$(document).off(".fancytree-edit");
10282
10283		if (eventData.save) {
10284			// # 171: escape user input (not required if global escaping is on)
10285			node.setTitle(
10286				tree.options.escapeTitles ? newVal : escapeHtml(newVal)
10287			);
10288			node.setFocus();
10289		} else {
10290			if (eventData.isNew) {
10291				node.remove();
10292				node = eventData.node = null;
10293				local.relatedNode.setFocus();
10294			} else {
10295				node.renderTitle();
10296				node.setFocus();
10297			}
10298		}
10299		local.eventData = null;
10300		local.currentNode = null;
10301		local.relatedNode = null;
10302		// Re-enable mouse and keyboard handling
10303		tree.widget._bind();
10304
10305		if (node && local.lastDraggableAttrValue) {
10306			node.span.draggable = true;
10307		}
10308
10309		// Set keyboard focus, even if setFocus() claims 'nothing to do'
10310		tree.$container.get(0).focus({ preventScroll: true });
10311		eventData.input = null;
10312		instOpts.close.call(node, { type: "close" }, eventData);
10313		return true;
10314	};
10315
10316	/**
10317	 * [ext-edit] Create a new child or sibling node and start edit mode.
10318	 *
10319	 * @param {String} [mode='child'] 'before', 'after', or 'child'
10320	 * @param {Object} [init] NodeData (or simple title string)
10321	 * @alias FancytreeNode#editCreateNode
10322	 * @requires jquery.fancytree.edit.js
10323	 * @since 2.4
10324	 */
10325	$.ui.fancytree._FancytreeNodeClass.prototype.editCreateNode = function (
10326		mode,
10327		init
10328	) {
10329		var newNode,
10330			tree = this.tree,
10331			self = this;
10332
10333		mode = mode || "child";
10334		if (init == null) {
10335			init = { title: "" };
10336		} else if (typeof init === "string") {
10337			init = { title: init };
10338		} else {
10339			$.ui.fancytree.assert($.isPlainObject(init));
10340		}
10341		// Make sure node is expanded (and loaded) in 'child' mode
10342		if (
10343			mode === "child" &&
10344			!this.isExpanded() &&
10345			this.hasChildren() !== false
10346		) {
10347			this.setExpanded().done(function () {
10348				self.editCreateNode(mode, init);
10349			});
10350			return;
10351		}
10352		newNode = this.addNode(init, mode);
10353
10354		// #644: Don't filter new nodes.
10355		newNode.match = true;
10356		$(newNode[tree.statusClassPropName])
10357			.removeClass("fancytree-hide")
10358			.addClass("fancytree-match");
10359
10360		newNode.makeVisible(/*{noAnimation: true}*/).done(function () {
10361			$(newNode[tree.statusClassPropName]).addClass("fancytree-edit-new");
10362			self.tree.ext.edit.relatedNode = self;
10363			newNode.editStart();
10364		});
10365	};
10366
10367	/**
10368	 * [ext-edit] Check if any node in this tree  in edit mode.
10369	 *
10370	 * @returns {FancytreeNode | null}
10371	 * @alias Fancytree#isEditing
10372	 * @requires jquery.fancytree.edit.js
10373	 */
10374	$.ui.fancytree._FancytreeClass.prototype.isEditing = function () {
10375		return this.ext.edit ? this.ext.edit.currentNode : null;
10376	};
10377
10378	/**
10379	 * [ext-edit] Check if this node is in edit mode.
10380	 * @returns {Boolean} true if node is currently beeing edited
10381	 * @alias FancytreeNode#isEditing
10382	 * @requires jquery.fancytree.edit.js
10383	 */
10384	$.ui.fancytree._FancytreeNodeClass.prototype.isEditing = function () {
10385		return this.tree.ext.edit
10386			? this.tree.ext.edit.currentNode === this
10387			: false;
10388	};
10389
10390	/*******************************************************************************
10391	 * Extension code
10392	 */
10393	$.ui.fancytree.registerExtension({
10394		name: "edit",
10395		version: "2.38.3",
10396		// Default options for this extension.
10397		options: {
10398			adjustWidthOfs: 4, // null: don't adjust input size to content
10399			allowEmpty: false, // Prevent empty input
10400			inputCss: { minWidth: "3em" },
10401			// triggerCancel: ["esc", "tab", "click"],
10402			triggerStart: ["f2", "mac+enter", "shift+click"],
10403			trim: true, // Trim whitespace before save
10404			// Events:
10405			beforeClose: $.noop, // Return false to prevent cancel/save (data.input is available)
10406			beforeEdit: $.noop, // Return false to prevent edit mode
10407			close: $.noop, // Editor was removed
10408			edit: $.noop, // Editor was opened (available as data.input)
10409			//		keypress: $.noop,    // Not yet implemented
10410			save: $.noop, // Save data.input.val() or return false to keep editor open
10411		},
10412		// Local attributes
10413		currentNode: null,
10414
10415		treeInit: function (ctx) {
10416			var tree = ctx.tree;
10417
10418			this._superApply(arguments);
10419
10420			this.$container
10421				.addClass("fancytree-ext-edit")
10422				.on("fancytreebeforeupdateviewport", function (event, data) {
10423					var editNode = tree.isEditing();
10424					// When scrolling, the TR may be re-used by another node, so the
10425					// active cell marker an
10426					if (editNode) {
10427						editNode.info("Cancel edit due to scroll event.");
10428						editNode.editEnd(false, event);
10429					}
10430				});
10431		},
10432		nodeClick: function (ctx) {
10433			var eventStr = $.ui.fancytree.eventToString(ctx.originalEvent),
10434				triggerStart = ctx.options.edit.triggerStart;
10435
10436			if (
10437				eventStr === "shift+click" &&
10438				$.inArray("shift+click", triggerStart) >= 0
10439			) {
10440				if (ctx.originalEvent.shiftKey) {
10441					ctx.node.editStart();
10442					return false;
10443				}
10444			}
10445			if (
10446				eventStr === "click" &&
10447				$.inArray("clickActive", triggerStart) >= 0
10448			) {
10449				// Only when click was inside title text (not aynwhere else in the row)
10450				if (
10451					ctx.node.isActive() &&
10452					!ctx.node.isEditing() &&
10453					$(ctx.originalEvent.target).hasClass("fancytree-title")
10454				) {
10455					ctx.node.editStart();
10456					return false;
10457				}
10458			}
10459			return this._superApply(arguments);
10460		},
10461		nodeDblclick: function (ctx) {
10462			if ($.inArray("dblclick", ctx.options.edit.triggerStart) >= 0) {
10463				ctx.node.editStart();
10464				return false;
10465			}
10466			return this._superApply(arguments);
10467		},
10468		nodeKeydown: function (ctx) {
10469			switch (ctx.originalEvent.which) {
10470				case 113: // [F2]
10471					if ($.inArray("f2", ctx.options.edit.triggerStart) >= 0) {
10472						ctx.node.editStart();
10473						return false;
10474					}
10475					break;
10476				case $.ui.keyCode.ENTER:
10477					if (
10478						$.inArray("mac+enter", ctx.options.edit.triggerStart) >=
10479							0 &&
10480						isMac
10481					) {
10482						ctx.node.editStart();
10483						return false;
10484					}
10485					break;
10486			}
10487			return this._superApply(arguments);
10488		},
10489	});
10490	// Value returned by `require('jquery.fancytree..')`
10491	return $.ui.fancytree;
10492}); // End of closure
10493
10494/*!
10495 * jquery.fancytree.filter.js
10496 *
10497 * Remove or highlight tree nodes, based on a filter.
10498 * (Extension module for jquery.fancytree.js: https://github.com/mar10/fancytree/)
10499 *
10500 * Copyright (c) 2008-2023, Martin Wendt (https://wwWendt.de)
10501 *
10502 * Released under the MIT license
10503 * https://github.com/mar10/fancytree/wiki/LicenseInfo
10504 *
10505 * @version 2.38.3
10506 * @date 2023-02-01T20:52:50Z
10507 */
10508
10509(function (factory) {
10510	if (typeof define === "function" && define.amd) {
10511		// AMD. Register as an anonymous module.
10512		define(["jquery", "./jquery.fancytree"], factory);
10513	} else if (typeof module === "object" && module.exports) {
10514		// Node/CommonJS
10515		require("./jquery.fancytree");
10516		module.exports = factory(require("jquery"));
10517	} else {
10518		// Browser globals
10519		factory(jQuery);
10520	}
10521})(function ($) {
10522	"use strict";
10523
10524	/*******************************************************************************
10525	 * Private functions and variables
10526	 */
10527
10528	var KeyNoData = "__not_found__",
10529		escapeHtml = $.ui.fancytree.escapeHtml,
10530		exoticStartChar = "\uFFF7",
10531		exoticEndChar = "\uFFF8";
10532	function _escapeRegex(str) {
10533		return (str + "").replace(/([.?*+^$[\]\\(){}|-])/g, "\\$1");
10534	}
10535
10536	function extractHtmlText(s) {
10537		if (s.indexOf(">") >= 0) {
10538			return $("<div/>").html(s).text();
10539		}
10540		return s;
10541	}
10542
10543	/**
10544	 * @description Marks the matching charecters of `text` either by `mark` or
10545	 * by exotic*Chars (if `escapeTitles` is `true`) based on `regexMatchArray`
10546	 * which is an array of matching groups.
10547	 * @param {string} text
10548	 * @param {RegExpMatchArray} regexMatchArray
10549	 */
10550	function _markFuzzyMatchedChars(text, regexMatchArray, escapeTitles) {
10551		// It is extremely infuriating that we can not use `let` or `const` or arrow functions.
10552		// Damn you IE!!!
10553		var matchingIndices = [];
10554		// get the indices of matched characters (Iterate through `RegExpMatchArray`)
10555		for (
10556			var _matchingArrIdx = 1;
10557			_matchingArrIdx < regexMatchArray.length;
10558			_matchingArrIdx++
10559		) {
10560			var _mIdx =
10561				// get matching char index by cumulatively adding
10562				// the matched group length
10563				regexMatchArray[_matchingArrIdx].length +
10564				(_matchingArrIdx === 1 ? 0 : 1) +
10565				(matchingIndices[matchingIndices.length - 1] || 0);
10566			matchingIndices.push(_mIdx);
10567		}
10568		// Map each `text` char to its position and store in `textPoses`.
10569		var textPoses = text.split("");
10570		if (escapeTitles) {
10571			// If escaping the title, then wrap the matchng char within exotic chars
10572			matchingIndices.forEach(function (v) {
10573				textPoses[v] = exoticStartChar + textPoses[v] + exoticEndChar;
10574			});
10575		} else {
10576			// Otherwise, Wrap the matching chars within `mark`.
10577			matchingIndices.forEach(function (v) {
10578				textPoses[v] = "<mark>" + textPoses[v] + "</mark>";
10579			});
10580		}
10581		// Join back the modified `textPoses` to create final highlight markup.
10582		return textPoses.join("");
10583	}
10584	$.ui.fancytree._FancytreeClass.prototype._applyFilterImpl = function (
10585		filter,
10586		branchMode,
10587		_opts
10588	) {
10589		var match,
10590			statusNode,
10591			re,
10592			reHighlight,
10593			reExoticStartChar,
10594			reExoticEndChar,
10595			temp,
10596			prevEnableUpdate,
10597			count = 0,
10598			treeOpts = this.options,
10599			escapeTitles = treeOpts.escapeTitles,
10600			prevAutoCollapse = treeOpts.autoCollapse,
10601			opts = $.extend({}, treeOpts.filter, _opts),
10602			hideMode = opts.mode === "hide",
10603			leavesOnly = !!opts.leavesOnly && !branchMode;
10604
10605		// Default to 'match title substring (not case sensitive)'
10606		if (typeof filter === "string") {
10607			if (filter === "") {
10608				this.warn(
10609					"Fancytree passing an empty string as a filter is handled as clearFilter()."
10610				);
10611				this.clearFilter();
10612				return;
10613			}
10614			if (opts.fuzzy) {
10615				// See https://codereview.stackexchange.com/questions/23899/faster-javascript-fuzzy-string-matching-function/23905#23905
10616				// and http://www.quora.com/How-is-the-fuzzy-search-algorithm-in-Sublime-Text-designed
10617				// and http://www.dustindiaz.com/autocomplete-fuzzy-matching
10618				match = filter
10619					.split("")
10620					// Escaping the `filter` will not work because,
10621					// it gets further split into individual characters. So,
10622					// escape each character after splitting
10623					.map(_escapeRegex)
10624					.reduce(function (a, b) {
10625						// create capture groups for parts that comes before
10626						// the character
10627						return a + "([^" + b + "]*)" + b;
10628					}, "");
10629			} else {
10630				match = _escapeRegex(filter); // make sure a '.' is treated literally
10631			}
10632			re = new RegExp(match, "i");
10633			reHighlight = new RegExp(_escapeRegex(filter), "gi");
10634			if (escapeTitles) {
10635				reExoticStartChar = new RegExp(
10636					_escapeRegex(exoticStartChar),
10637					"g"
10638				);
10639				reExoticEndChar = new RegExp(_escapeRegex(exoticEndChar), "g");
10640			}
10641			filter = function (node) {
10642				if (!node.title) {
10643					return false;
10644				}
10645				var text = escapeTitles
10646						? node.title
10647						: extractHtmlText(node.title),
10648					// `.match` instead of `.test` to get the capture groups
10649					res = text.match(re);
10650				if (res && opts.highlight) {
10651					if (escapeTitles) {
10652						if (opts.fuzzy) {
10653							temp = _markFuzzyMatchedChars(
10654								text,
10655								res,
10656								escapeTitles
10657							);
10658						} else {
10659							// #740: we must not apply the marks to escaped entity names, e.g. `&quot;`
10660							// Use some exotic characters to mark matches:
10661							temp = text.replace(reHighlight, function (s) {
10662								return exoticStartChar + s + exoticEndChar;
10663							});
10664						}
10665						// now we can escape the title...
10666						node.titleWithHighlight = escapeHtml(temp)
10667							// ... and finally insert the desired `<mark>` tags
10668							.replace(reExoticStartChar, "<mark>")
10669							.replace(reExoticEndChar, "</mark>");
10670					} else {
10671						if (opts.fuzzy) {
10672							node.titleWithHighlight = _markFuzzyMatchedChars(
10673								text,
10674								res
10675							);
10676						} else {
10677							node.titleWithHighlight = text.replace(
10678								reHighlight,
10679								function (s) {
10680									return "<mark>" + s + "</mark>";
10681								}
10682							);
10683						}
10684					}
10685					// node.debug("filter", escapeTitles, text, node.titleWithHighlight);
10686				}
10687				return !!res;
10688			};
10689		}
10690
10691		this.enableFilter = true;
10692		this.lastFilterArgs = arguments;
10693
10694		prevEnableUpdate = this.enableUpdate(false);
10695
10696		this.$div.addClass("fancytree-ext-filter");
10697		if (hideMode) {
10698			this.$div.addClass("fancytree-ext-filter-hide");
10699		} else {
10700			this.$div.addClass("fancytree-ext-filter-dimm");
10701		}
10702		this.$div.toggleClass(
10703			"fancytree-ext-filter-hide-expanders",
10704			!!opts.hideExpanders
10705		);
10706		// Reset current filter
10707		this.rootNode.subMatchCount = 0;
10708		this.visit(function (node) {
10709			delete node.match;
10710			delete node.titleWithHighlight;
10711			node.subMatchCount = 0;
10712		});
10713		statusNode = this.getRootNode()._findDirectChild(KeyNoData);
10714		if (statusNode) {
10715			statusNode.remove();
10716		}
10717
10718		// Adjust node.hide, .match, and .subMatchCount properties
10719		treeOpts.autoCollapse = false; // #528
10720
10721		this.visit(function (node) {
10722			if (leavesOnly && node.children != null) {
10723				return;
10724			}
10725			var res = filter(node),
10726				matchedByBranch = false;
10727
10728			if (res === "skip") {
10729				node.visit(function (c) {
10730					c.match = false;
10731				}, true);
10732				return "skip";
10733			}
10734			if (!res && (branchMode || res === "branch") && node.parent.match) {
10735				res = true;
10736				matchedByBranch = true;
10737			}
10738			if (res) {
10739				count++;
10740				node.match = true;
10741				node.visitParents(function (p) {
10742					if (p !== node) {
10743						p.subMatchCount += 1;
10744					}
10745					// Expand match (unless this is no real match, but only a node in a matched branch)
10746					if (opts.autoExpand && !matchedByBranch && !p.expanded) {
10747						p.setExpanded(true, {
10748							noAnimation: true,
10749							noEvents: true,
10750							scrollIntoView: false,
10751						});
10752						p._filterAutoExpanded = true;
10753					}
10754				}, true);
10755			}
10756		});
10757		treeOpts.autoCollapse = prevAutoCollapse;
10758
10759		if (count === 0 && opts.nodata && hideMode) {
10760			statusNode = opts.nodata;
10761			if (typeof statusNode === "function") {
10762				statusNode = statusNode();
10763			}
10764			if (statusNode === true) {
10765				statusNode = {};
10766			} else if (typeof statusNode === "string") {
10767				statusNode = { title: statusNode };
10768			}
10769			statusNode = $.extend(
10770				{
10771					statusNodeType: "nodata",
10772					key: KeyNoData,
10773					title: this.options.strings.noData,
10774				},
10775				statusNode
10776			);
10777
10778			this.getRootNode().addNode(statusNode).match = true;
10779		}
10780		// Redraw whole tree
10781		this._callHook("treeStructureChanged", this, "applyFilter");
10782		// this.render();
10783		this.enableUpdate(prevEnableUpdate);
10784		return count;
10785	};
10786
10787	/**
10788	 * [ext-filter] Dimm or hide nodes.
10789	 *
10790	 * @param {function | string} filter
10791	 * @param {boolean} [opts={autoExpand: false, leavesOnly: false}]
10792	 * @returns {integer} count
10793	 * @alias Fancytree#filterNodes
10794	 * @requires jquery.fancytree.filter.js
10795	 */
10796	$.ui.fancytree._FancytreeClass.prototype.filterNodes = function (
10797		filter,
10798		opts
10799	) {
10800		if (typeof opts === "boolean") {
10801			opts = { leavesOnly: opts };
10802			this.warn(
10803				"Fancytree.filterNodes() leavesOnly option is deprecated since 2.9.0 / 2015-04-19. Use opts.leavesOnly instead."
10804			);
10805		}
10806		return this._applyFilterImpl(filter, false, opts);
10807	};
10808
10809	/**
10810	 * [ext-filter] Dimm or hide whole branches.
10811	 *
10812	 * @param {function | string} filter
10813	 * @param {boolean} [opts={autoExpand: false}]
10814	 * @returns {integer} count
10815	 * @alias Fancytree#filterBranches
10816	 * @requires jquery.fancytree.filter.js
10817	 */
10818	$.ui.fancytree._FancytreeClass.prototype.filterBranches = function (
10819		filter,
10820		opts
10821	) {
10822		return this._applyFilterImpl(filter, true, opts);
10823	};
10824
10825	/**
10826	 * [ext-filter] Re-apply current filter.
10827	 *
10828	 * @returns {integer} count
10829	 * @alias Fancytree#updateFilter
10830	 * @requires jquery.fancytree.filter.js
10831	 * @since 2.38
10832	 */
10833	$.ui.fancytree._FancytreeClass.prototype.updateFilter = function () {
10834		if (
10835			this.enableFilter &&
10836			this.lastFilterArgs &&
10837			this.options.filter.autoApply
10838		) {
10839			this._applyFilterImpl.apply(this, this.lastFilterArgs);
10840		} else {
10841			this.warn("updateFilter(): no filter active.");
10842		}
10843	};
10844
10845	/**
10846	 * [ext-filter] Reset the filter.
10847	 *
10848	 * @alias Fancytree#clearFilter
10849	 * @requires jquery.fancytree.filter.js
10850	 */
10851	$.ui.fancytree._FancytreeClass.prototype.clearFilter = function () {
10852		var $title,
10853			statusNode = this.getRootNode()._findDirectChild(KeyNoData),
10854			escapeTitles = this.options.escapeTitles,
10855			enhanceTitle = this.options.enhanceTitle,
10856			prevEnableUpdate = this.enableUpdate(false);
10857
10858		if (statusNode) {
10859			statusNode.remove();
10860		}
10861		// we also counted root node's subMatchCount
10862		delete this.rootNode.match;
10863		delete this.rootNode.subMatchCount;
10864
10865		this.visit(function (node) {
10866			if (node.match && node.span) {
10867				// #491, #601
10868				$title = $(node.span).find(">span.fancytree-title");
10869				if (escapeTitles) {
10870					$title.text(node.title);
10871				} else {
10872					$title.html(node.title);
10873				}
10874				if (enhanceTitle) {
10875					enhanceTitle(
10876						{ type: "enhanceTitle" },
10877						{ node: node, $title: $title }
10878					);
10879				}
10880			}
10881			delete node.match;
10882			delete node.subMatchCount;
10883			delete node.titleWithHighlight;
10884			if (node.$subMatchBadge) {
10885				node.$subMatchBadge.remove();
10886				delete node.$subMatchBadge;
10887			}
10888			if (node._filterAutoExpanded && node.expanded) {
10889				node.setExpanded(false, {
10890					noAnimation: true,
10891					noEvents: true,
10892					scrollIntoView: false,
10893				});
10894			}
10895			delete node._filterAutoExpanded;
10896		});
10897		this.enableFilter = false;
10898		this.lastFilterArgs = null;
10899		this.$div.removeClass(
10900			"fancytree-ext-filter fancytree-ext-filter-dimm fancytree-ext-filter-hide"
10901		);
10902		this._callHook("treeStructureChanged", this, "clearFilter");
10903		// this.render();
10904		this.enableUpdate(prevEnableUpdate);
10905	};
10906
10907	/**
10908	 * [ext-filter] Return true if a filter is currently applied.
10909	 *
10910	 * @returns {Boolean}
10911	 * @alias Fancytree#isFilterActive
10912	 * @requires jquery.fancytree.filter.js
10913	 * @since 2.13
10914	 */
10915	$.ui.fancytree._FancytreeClass.prototype.isFilterActive = function () {
10916		return !!this.enableFilter;
10917	};
10918
10919	/**
10920	 * [ext-filter] Return true if this node is matched by current filter (or no filter is active).
10921	 *
10922	 * @returns {Boolean}
10923	 * @alias FancytreeNode#isMatched
10924	 * @requires jquery.fancytree.filter.js
10925	 * @since 2.13
10926	 */
10927	$.ui.fancytree._FancytreeNodeClass.prototype.isMatched = function () {
10928		return !(this.tree.enableFilter && !this.match);
10929	};
10930
10931	/*******************************************************************************
10932	 * Extension code
10933	 */
10934	$.ui.fancytree.registerExtension({
10935		name: "filter",
10936		version: "2.38.3",
10937		// Default options for this extension.
10938		options: {
10939			autoApply: true, // Re-apply last filter if lazy data is loaded
10940			autoExpand: false, // Expand all branches that contain matches while filtered
10941			counter: true, // Show a badge with number of matching child nodes near parent icons
10942			fuzzy: false, // Match single characters in order, e.g. 'fb' will match 'FooBar'
10943			hideExpandedCounter: true, // Hide counter badge if parent is expanded
10944			hideExpanders: false, // Hide expanders if all child nodes are hidden by filter
10945			highlight: true, // Highlight matches by wrapping inside <mark> tags
10946			leavesOnly: false, // Match end nodes only
10947			nodata: true, // Display a 'no data' status node if result is empty
10948			mode: "dimm", // Grayout unmatched nodes (pass "hide" to remove unmatched node instead)
10949		},
10950		nodeLoadChildren: function (ctx, source) {
10951			var tree = ctx.tree;
10952
10953			return this._superApply(arguments).done(function () {
10954				if (
10955					tree.enableFilter &&
10956					tree.lastFilterArgs &&
10957					ctx.options.filter.autoApply
10958				) {
10959					tree._applyFilterImpl.apply(tree, tree.lastFilterArgs);
10960				}
10961			});
10962		},
10963		nodeSetExpanded: function (ctx, flag, callOpts) {
10964			var node = ctx.node;
10965
10966			delete node._filterAutoExpanded;
10967			// Make sure counter badge is displayed again, when node is beeing collapsed
10968			if (
10969				!flag &&
10970				ctx.options.filter.hideExpandedCounter &&
10971				node.$subMatchBadge
10972			) {
10973				node.$subMatchBadge.show();
10974			}
10975			return this._superApply(arguments);
10976		},
10977		nodeRenderStatus: function (ctx) {
10978			// Set classes for current status
10979			var res,
10980				node = ctx.node,
10981				tree = ctx.tree,
10982				opts = ctx.options.filter,
10983				$title = $(node.span).find("span.fancytree-title"),
10984				$span = $(node[tree.statusClassPropName]),
10985				enhanceTitle = ctx.options.enhanceTitle,
10986				escapeTitles = ctx.options.escapeTitles;
10987
10988			res = this._super(ctx);
10989			// nothing to do, if node was not yet rendered
10990			if (!$span.length || !tree.enableFilter) {
10991				return res;
10992			}
10993			$span
10994				.toggleClass("fancytree-match", !!node.match)
10995				.toggleClass("fancytree-submatch", !!node.subMatchCount)
10996				.toggleClass(
10997					"fancytree-hide",
10998					!(node.match || node.subMatchCount)
10999				);
11000			// Add/update counter badge
11001			if (
11002				opts.counter &&
11003				node.subMatchCount &&
11004				(!node.isExpanded() || !opts.hideExpandedCounter)
11005			) {
11006				if (!node.$subMatchBadge) {
11007					node.$subMatchBadge = $(
11008						"<span class='fancytree-childcounter'/>"
11009					);
11010					$(
11011						"span.fancytree-icon, span.fancytree-custom-icon",
11012						node.span
11013					).append(node.$subMatchBadge);
11014				}
11015				node.$subMatchBadge.show().text(node.subMatchCount);
11016			} else if (node.$subMatchBadge) {
11017				node.$subMatchBadge.hide();
11018			}
11019			// node.debug("nodeRenderStatus", node.titleWithHighlight, node.title)
11020			// #601: also check for $title.length, because we don't need to render
11021			// if node.span is null (i.e. not rendered)
11022			if (node.span && (!node.isEditing || !node.isEditing.call(node))) {
11023				if (node.titleWithHighlight) {
11024					$title.html(node.titleWithHighlight);
11025				} else if (escapeTitles) {
11026					$title.text(node.title);
11027				} else {
11028					$title.html(node.title);
11029				}
11030				if (enhanceTitle) {
11031					enhanceTitle(
11032						{ type: "enhanceTitle" },
11033						{ node: node, $title: $title }
11034					);
11035				}
11036			}
11037			return res;
11038		},
11039	});
11040	// Value returned by `require('jquery.fancytree..')`
11041	return $.ui.fancytree;
11042}); // End of closure
11043
11044/*!
11045 * jquery.fancytree.glyph.js
11046 *
11047 * Use glyph-fonts, ligature-fonts, or SVG icons instead of icon sprites.
11048 * (Extension module for jquery.fancytree.js: https://github.com/mar10/fancytree/)
11049 *
11050 * Copyright (c) 2008-2023, Martin Wendt (https://wwWendt.de)
11051 *
11052 * Released under the MIT license
11053 * https://github.com/mar10/fancytree/wiki/LicenseInfo
11054 *
11055 * @version 2.38.3
11056 * @date 2023-02-01T20:52:50Z
11057 */
11058
11059(function (factory) {
11060	if (typeof define === "function" && define.amd) {
11061		// AMD. Register as an anonymous module.
11062		define(["jquery", "./jquery.fancytree"], factory);
11063	} else if (typeof module === "object" && module.exports) {
11064		// Node/CommonJS
11065		require("./jquery.fancytree");
11066		module.exports = factory(require("jquery"));
11067	} else {
11068		// Browser globals
11069		factory(jQuery);
11070	}
11071})(function ($) {
11072	"use strict";
11073
11074	/******************************************************************************
11075	 * Private functions and variables
11076	 */
11077
11078	var FT = $.ui.fancytree,
11079		PRESETS = {
11080			awesome3: {
11081				// Outdated!
11082				_addClass: "",
11083				checkbox: "icon-check-empty",
11084				checkboxSelected: "icon-check",
11085				checkboxUnknown: "icon-check icon-muted",
11086				dragHelper: "icon-caret-right",
11087				dropMarker: "icon-caret-right",
11088				error: "icon-exclamation-sign",
11089				expanderClosed: "icon-caret-right",
11090				expanderLazy: "icon-angle-right",
11091				expanderOpen: "icon-caret-down",
11092				loading: "icon-refresh icon-spin",
11093				nodata: "icon-meh",
11094				noExpander: "",
11095				radio: "icon-circle-blank",
11096				radioSelected: "icon-circle",
11097				// radioUnknown: "icon-circle icon-muted",
11098				// Default node icons.
11099				// (Use tree.options.icon callback to define custom icons based on node data)
11100				doc: "icon-file-alt",
11101				docOpen: "icon-file-alt",
11102				folder: "icon-folder-close-alt",
11103				folderOpen: "icon-folder-open-alt",
11104			},
11105			awesome4: {
11106				_addClass: "fa",
11107				checkbox: "fa-square-o",
11108				checkboxSelected: "fa-check-square-o",
11109				checkboxUnknown: "fa-square fancytree-helper-indeterminate-cb",
11110				dragHelper: "fa-arrow-right",
11111				dropMarker: "fa-long-arrow-right",
11112				error: "fa-warning",
11113				expanderClosed: "fa-caret-right",
11114				expanderLazy: "fa-angle-right",
11115				expanderOpen: "fa-caret-down",
11116				// We may prevent wobbling rotations on FF by creating a separate sub element:
11117				loading: { html: "<span class='fa fa-spinner fa-pulse' />" },
11118				nodata: "fa-meh-o",
11119				noExpander: "",
11120				radio: "fa-circle-thin", // "fa-circle-o"
11121				radioSelected: "fa-circle",
11122				// radioUnknown: "fa-dot-circle-o",
11123				// Default node icons.
11124				// (Use tree.options.icon callback to define custom icons based on node data)
11125				doc: "fa-file-o",
11126				docOpen: "fa-file-o",
11127				folder: "fa-folder-o",
11128				folderOpen: "fa-folder-open-o",
11129			},
11130			awesome5: {
11131				// fontawesome 5 have several different base classes
11132				// "far, fas, fal and fab" The rendered svg puts that prefix
11133				// in a different location so we have to keep them separate here
11134				_addClass: "",
11135				checkbox: "far fa-square",
11136				checkboxSelected: "far fa-check-square",
11137				// checkboxUnknown: "far fa-window-close",
11138				checkboxUnknown:
11139					"fas fa-square fancytree-helper-indeterminate-cb",
11140				radio: "far fa-circle",
11141				radioSelected: "fas fa-circle",
11142				radioUnknown: "far fa-dot-circle",
11143				dragHelper: "fas fa-arrow-right",
11144				dropMarker: "fas fa-long-arrow-alt-right",
11145				error: "fas fa-exclamation-triangle",
11146				expanderClosed: "fas fa-caret-right",
11147				expanderLazy: "fas fa-angle-right",
11148				expanderOpen: "fas fa-caret-down",
11149				loading: "fas fa-spinner fa-pulse",
11150				nodata: "far fa-meh",
11151				noExpander: "",
11152				// Default node icons.
11153				// (Use tree.options.icon callback to define custom icons based on node data)
11154				doc: "far fa-file",
11155				docOpen: "far fa-file",
11156				folder: "far fa-folder",
11157				folderOpen: "far fa-folder-open",
11158			},
11159			bootstrap3: {
11160				_addClass: "glyphicon",
11161				checkbox: "glyphicon-unchecked",
11162				checkboxSelected: "glyphicon-check",
11163				checkboxUnknown:
11164					"glyphicon-expand fancytree-helper-indeterminate-cb", // "glyphicon-share",
11165				dragHelper: "glyphicon-play",
11166				dropMarker: "glyphicon-arrow-right",
11167				error: "glyphicon-warning-sign",
11168				expanderClosed: "glyphicon-menu-right", // glyphicon-plus-sign
11169				expanderLazy: "glyphicon-menu-right", // glyphicon-plus-sign
11170				expanderOpen: "glyphicon-menu-down", // glyphicon-minus-sign
11171				loading: "glyphicon-refresh fancytree-helper-spin",
11172				nodata: "glyphicon-info-sign",
11173				noExpander: "",
11174				radio: "glyphicon-remove-circle", // "glyphicon-unchecked",
11175				radioSelected: "glyphicon-ok-circle", // "glyphicon-check",
11176				// radioUnknown: "glyphicon-ban-circle",
11177				// Default node icons.
11178				// (Use tree.options.icon callback to define custom icons based on node data)
11179				doc: "glyphicon-file",
11180				docOpen: "glyphicon-file",
11181				folder: "glyphicon-folder-close",
11182				folderOpen: "glyphicon-folder-open",
11183			},
11184			material: {
11185				_addClass: "material-icons",
11186				checkbox: { text: "check_box_outline_blank" },
11187				checkboxSelected: { text: "check_box" },
11188				checkboxUnknown: { text: "indeterminate_check_box" },
11189				dragHelper: { text: "play_arrow" },
11190				dropMarker: { text: "arrow-forward" },
11191				error: { text: "warning" },
11192				expanderClosed: { text: "chevron_right" },
11193				expanderLazy: { text: "last_page" },
11194				expanderOpen: { text: "expand_more" },
11195				loading: {
11196					text: "autorenew",
11197					addClass: "fancytree-helper-spin",
11198				},
11199				nodata: { text: "info" },
11200				noExpander: { text: "" },
11201				radio: { text: "radio_button_unchecked" },
11202				radioSelected: { text: "radio_button_checked" },
11203				// Default node icons.
11204				// (Use tree.options.icon callback to define custom icons based on node data)
11205				doc: { text: "insert_drive_file" },
11206				docOpen: { text: "insert_drive_file" },
11207				folder: { text: "folder" },
11208				folderOpen: { text: "folder_open" },
11209			},
11210		};
11211
11212	function setIcon(node, span, baseClass, opts, type) {
11213		var map = opts.map,
11214			icon = map[type],
11215			$span = $(span),
11216			$counter = $span.find(".fancytree-childcounter"),
11217			setClass = baseClass + " " + (map._addClass || "");
11218
11219		// #871 Allow a callback
11220		if (typeof icon === "function") {
11221			icon = icon.call(this, node, span, type);
11222		}
11223		// node.debug( "setIcon(" + baseClass + ", " + type + "): " + "oldIcon" + " -> " + icon );
11224		// #871: propsed this, but I am not sure how robust this is, e.g.
11225		// the prefix (fas, far) class changes are not considered?
11226		// if (span.tagName === "svg" && opts.preset === "awesome5") {
11227		// 	// fa5 script converts <i> to <svg> so call a specific handler.
11228		// 	var oldIcon = "fa-" + $span.data("icon");
11229		// 	// node.debug( "setIcon(" + baseClass + ", " + type + "): " + oldIcon + " -> " + icon );
11230		// 	if (typeof oldIcon === "string") {
11231		// 		$span.removeClass(oldIcon);
11232		// 	}
11233		// 	if (typeof icon === "string") {
11234		// 		$span.addClass(icon);
11235		// 	}
11236		// 	return;
11237		// }
11238		if (typeof icon === "string") {
11239			// #883: remove inner html that may be added by prev. mode
11240			span.innerHTML = "";
11241			$span.attr("class", setClass + " " + icon).append($counter);
11242		} else if (icon) {
11243			if (icon.text) {
11244				span.textContent = "" + icon.text;
11245			} else if (icon.html) {
11246				span.innerHTML = icon.html;
11247			} else {
11248				span.innerHTML = "";
11249			}
11250			$span
11251				.attr("class", setClass + " " + (icon.addClass || ""))
11252				.append($counter);
11253		}
11254	}
11255
11256	$.ui.fancytree.registerExtension({
11257		name: "glyph",
11258		version: "2.38.3",
11259		// Default options for this extension.
11260		options: {
11261			preset: null, // 'awesome3', 'awesome4', 'bootstrap3', 'material'
11262			map: {},
11263		},
11264
11265		treeInit: function (ctx) {
11266			var tree = ctx.tree,
11267				opts = ctx.options.glyph;
11268
11269			if (opts.preset) {
11270				FT.assert(
11271					!!PRESETS[opts.preset],
11272					"Invalid value for `options.glyph.preset`: " + opts.preset
11273				);
11274				opts.map = $.extend({}, PRESETS[opts.preset], opts.map);
11275			} else {
11276				tree.warn("ext-glyph: missing `preset` option.");
11277			}
11278			this._superApply(arguments);
11279			tree.$container.addClass("fancytree-ext-glyph");
11280		},
11281		nodeRenderStatus: function (ctx) {
11282			var checkbox,
11283				icon,
11284				res,
11285				span,
11286				node = ctx.node,
11287				$span = $(node.span),
11288				opts = ctx.options.glyph;
11289
11290			res = this._super(ctx);
11291
11292			if (node.isRootNode()) {
11293				return res;
11294			}
11295			span = $span.children(".fancytree-expander").get(0);
11296			if (span) {
11297				// if( node.isLoading() ){
11298				// icon = "loading";
11299				if (node.expanded && node.hasChildren()) {
11300					icon = "expanderOpen";
11301				} else if (node.isUndefined()) {
11302					icon = "expanderLazy";
11303				} else if (node.hasChildren()) {
11304					icon = "expanderClosed";
11305				} else {
11306					icon = "noExpander";
11307				}
11308				// span.className = "fancytree-expander " + map[icon];
11309				setIcon(node, span, "fancytree-expander", opts, icon);
11310			}
11311
11312			if (node.tr) {
11313				span = $("td", node.tr).find(".fancytree-checkbox").get(0);
11314			} else {
11315				span = $span.children(".fancytree-checkbox").get(0);
11316			}
11317			if (span) {
11318				checkbox = FT.evalOption("checkbox", node, node, opts, false);
11319				if (
11320					(node.parent && node.parent.radiogroup) ||
11321					checkbox === "radio"
11322				) {
11323					icon = node.selected ? "radioSelected" : "radio";
11324					setIcon(
11325						node,
11326						span,
11327						"fancytree-checkbox fancytree-radio",
11328						opts,
11329						icon
11330					);
11331				} else {
11332					// eslint-disable-next-line no-nested-ternary
11333					icon = node.selected
11334						? "checkboxSelected"
11335						: node.partsel
11336						? "checkboxUnknown"
11337						: "checkbox";
11338					// span.className = "fancytree-checkbox " + map[icon];
11339					setIcon(node, span, "fancytree-checkbox", opts, icon);
11340				}
11341			}
11342
11343			// Standard icon (note that this does not match .fancytree-custom-icon,
11344			// that might be set by opts.icon callbacks)
11345			span = $span.children(".fancytree-icon").get(0);
11346			if (span) {
11347				if (node.statusNodeType) {
11348					icon = node.statusNodeType; // loading, error
11349				} else if (node.folder) {
11350					icon =
11351						node.expanded && node.hasChildren()
11352							? "folderOpen"
11353							: "folder";
11354				} else {
11355					icon = node.expanded ? "docOpen" : "doc";
11356				}
11357				setIcon(node, span, "fancytree-icon", opts, icon);
11358			}
11359			return res;
11360		},
11361		nodeSetStatus: function (ctx, status, message, details) {
11362			var res,
11363				span,
11364				opts = ctx.options.glyph,
11365				node = ctx.node;
11366
11367			res = this._superApply(arguments);
11368
11369			if (
11370				status === "error" ||
11371				status === "loading" ||
11372				status === "nodata"
11373			) {
11374				if (node.parent) {
11375					span = $(".fancytree-expander", node.span).get(0);
11376					if (span) {
11377						setIcon(node, span, "fancytree-expander", opts, status);
11378					}
11379				} else {
11380					//
11381					span = $(
11382						".fancytree-statusnode-" + status,
11383						node[this.nodeContainerAttrName]
11384					)
11385						.find(".fancytree-icon")
11386						.get(0);
11387					if (span) {
11388						setIcon(node, span, "fancytree-icon", opts, status);
11389					}
11390				}
11391			}
11392			return res;
11393		},
11394	});
11395	// Value returned by `require('jquery.fancytree..')`
11396	return $.ui.fancytree;
11397}); // End of closure
11398
11399/*!
11400 * jquery.fancytree.gridnav.js
11401 *
11402 * Support keyboard navigation for trees with embedded input controls.
11403 * (Extension module for jquery.fancytree.js: https://github.com/mar10/fancytree/)
11404 *
11405 * Copyright (c) 2008-2023, Martin Wendt (https://wwWendt.de)
11406 *
11407 * Released under the MIT license
11408 * https://github.com/mar10/fancytree/wiki/LicenseInfo
11409 *
11410 * @version 2.38.3
11411 * @date 2023-02-01T20:52:50Z
11412 */
11413
11414(function (factory) {
11415	if (typeof define === "function" && define.amd) {
11416		// AMD. Register as an anonymous module.
11417		define([
11418			"jquery",
11419			"./jquery.fancytree",
11420			"./jquery.fancytree.table",
11421		], factory);
11422	} else if (typeof module === "object" && module.exports) {
11423		// Node/CommonJS
11424		require("./jquery.fancytree.table"); // core + table
11425		module.exports = factory(require("jquery"));
11426	} else {
11427		// Browser globals
11428		factory(jQuery);
11429	}
11430})(function ($) {
11431	"use strict";
11432
11433	/*******************************************************************************
11434	 * Private functions and variables
11435	 */
11436
11437	// Allow these navigation keys even when input controls are focused
11438
11439	var KC = $.ui.keyCode,
11440		// which keys are *not* handled by embedded control, but passed to tree
11441		// navigation handler:
11442		NAV_KEYS = {
11443			text: [KC.UP, KC.DOWN],
11444			checkbox: [KC.UP, KC.DOWN, KC.LEFT, KC.RIGHT],
11445			link: [KC.UP, KC.DOWN, KC.LEFT, KC.RIGHT],
11446			radiobutton: [KC.UP, KC.DOWN, KC.LEFT, KC.RIGHT],
11447			"select-one": [KC.LEFT, KC.RIGHT],
11448			"select-multiple": [KC.LEFT, KC.RIGHT],
11449		};
11450
11451	/* Calculate TD column index (considering colspans).*/
11452	function getColIdx($tr, $td) {
11453		var colspan,
11454			td = $td.get(0),
11455			idx = 0;
11456
11457		$tr.children().each(function () {
11458			if (this === td) {
11459				return false;
11460			}
11461			colspan = $(this).prop("colspan");
11462			idx += colspan ? colspan : 1;
11463		});
11464		return idx;
11465	}
11466
11467	/* Find TD at given column index (considering colspans).*/
11468	function findTdAtColIdx($tr, colIdx) {
11469		var colspan,
11470			res = null,
11471			idx = 0;
11472
11473		$tr.children().each(function () {
11474			if (idx >= colIdx) {
11475				res = $(this);
11476				return false;
11477			}
11478			colspan = $(this).prop("colspan");
11479			idx += colspan ? colspan : 1;
11480		});
11481		return res;
11482	}
11483
11484	/* Find adjacent cell for a given direction. Skip empty cells and consider merged cells */
11485	function findNeighbourTd($target, keyCode) {
11486		var $tr,
11487			colIdx,
11488			$td = $target.closest("td"),
11489			$tdNext = null;
11490
11491		switch (keyCode) {
11492			case KC.LEFT:
11493				$tdNext = $td.prev();
11494				break;
11495			case KC.RIGHT:
11496				$tdNext = $td.next();
11497				break;
11498			case KC.UP:
11499			case KC.DOWN:
11500				$tr = $td.parent();
11501				colIdx = getColIdx($tr, $td);
11502				while (true) {
11503					$tr = keyCode === KC.UP ? $tr.prev() : $tr.next();
11504					if (!$tr.length) {
11505						break;
11506					}
11507					// Skip hidden rows
11508					if ($tr.is(":hidden")) {
11509						continue;
11510					}
11511					// Find adjacent cell in the same column
11512					$tdNext = findTdAtColIdx($tr, colIdx);
11513					// Skip cells that don't conatain a focusable element
11514					if ($tdNext && $tdNext.find(":input,a").length) {
11515						break;
11516					}
11517				}
11518				break;
11519		}
11520		return $tdNext;
11521	}
11522
11523	/*******************************************************************************
11524	 * Extension code
11525	 */
11526	$.ui.fancytree.registerExtension({
11527		name: "gridnav",
11528		version: "2.38.3",
11529		// Default options for this extension.
11530		options: {
11531			autofocusInput: false, // Focus first embedded input if node gets activated
11532			handleCursorKeys: true, // Allow UP/DOWN in inputs to move to prev/next node
11533		},
11534
11535		treeInit: function (ctx) {
11536			// gridnav requires the table extension to be loaded before itself
11537			this._requireExtension("table", true, true);
11538			this._superApply(arguments);
11539
11540			this.$container.addClass("fancytree-ext-gridnav");
11541
11542			// Activate node if embedded input gets focus (due to a click)
11543			this.$container.on("focusin", function (event) {
11544				var ctx2,
11545					node = $.ui.fancytree.getNode(event.target);
11546
11547				if (node && !node.isActive()) {
11548					// Call node.setActive(), but also pass the event
11549					ctx2 = ctx.tree._makeHookContext(node, event);
11550					ctx.tree._callHook("nodeSetActive", ctx2, true);
11551				}
11552			});
11553		},
11554		nodeSetActive: function (ctx, flag, callOpts) {
11555			var $outer,
11556				opts = ctx.options.gridnav,
11557				node = ctx.node,
11558				event = ctx.originalEvent || {},
11559				triggeredByInput = $(event.target).is(":input");
11560
11561			flag = flag !== false;
11562
11563			this._superApply(arguments);
11564
11565			if (flag) {
11566				if (ctx.options.titlesTabbable) {
11567					if (!triggeredByInput) {
11568						$(node.span).find("span.fancytree-title").focus();
11569						node.setFocus();
11570					}
11571					// If one node is tabbable, the container no longer needs to be
11572					ctx.tree.$container.attr("tabindex", "-1");
11573					// ctx.tree.$container.removeAttr("tabindex");
11574				} else if (opts.autofocusInput && !triggeredByInput) {
11575					// Set focus to input sub input (if node was clicked, but not
11576					// when TAB was pressed )
11577					$outer = $(node.tr || node.span);
11578					$outer.find(":input:enabled").first().focus();
11579				}
11580			}
11581		},
11582		nodeKeydown: function (ctx) {
11583			var inputType,
11584				handleKeys,
11585				$td,
11586				opts = ctx.options.gridnav,
11587				event = ctx.originalEvent,
11588				$target = $(event.target);
11589
11590			if ($target.is(":input:enabled")) {
11591				inputType = $target.prop("type");
11592			} else if ($target.is("a")) {
11593				inputType = "link";
11594			}
11595			// ctx.tree.debug("ext-gridnav nodeKeydown", event, inputType);
11596
11597			if (inputType && opts.handleCursorKeys) {
11598				handleKeys = NAV_KEYS[inputType];
11599				if (handleKeys && $.inArray(event.which, handleKeys) >= 0) {
11600					$td = findNeighbourTd($target, event.which);
11601					if ($td && $td.length) {
11602						// ctx.node.debug("ignore keydown in input", event.which, handleKeys);
11603						$td.find(":input:enabled,a").focus();
11604						// Prevent Fancytree default navigation
11605						return false;
11606					}
11607				}
11608				return true;
11609			}
11610			// ctx.tree.debug("ext-gridnav NOT HANDLED", event, inputType);
11611			return this._superApply(arguments);
11612		},
11613	});
11614	// Value returned by `require('jquery.fancytree..')`
11615	return $.ui.fancytree;
11616}); // End of closure
11617
11618/*!
11619 * jquery.fancytree.multi.js
11620 *
11621 * Allow multiple selection of nodes  by mouse or keyboard.
11622 * (Extension module for jquery.fancytree.js: https://github.com/mar10/fancytree/)
11623 *
11624 * Copyright (c) 2008-2023, Martin Wendt (https://wwWendt.de)
11625 *
11626 * Released under the MIT license
11627 * https://github.com/mar10/fancytree/wiki/LicenseInfo
11628 *
11629 * @version 2.38.3
11630 * @date 2023-02-01T20:52:50Z
11631 */
11632
11633(function (factory) {
11634	if (typeof define === "function" && define.amd) {
11635		// AMD. Register as an anonymous module.
11636		define(["jquery", "./jquery.fancytree"], factory);
11637	} else if (typeof module === "object" && module.exports) {
11638		// Node/CommonJS
11639		require("./jquery.fancytree");
11640		module.exports = factory(require("jquery"));
11641	} else {
11642		// Browser globals
11643		factory(jQuery);
11644	}
11645})(function ($) {
11646	"use strict";
11647
11648	/*******************************************************************************
11649	 * Private functions and variables
11650	 */
11651
11652	// var isMac = /Mac/.test(navigator.platform);
11653
11654	/*******************************************************************************
11655	 * Extension code
11656	 */
11657	$.ui.fancytree.registerExtension({
11658		name: "multi",
11659		version: "2.38.3",
11660		// Default options for this extension.
11661		options: {
11662			allowNoSelect: false, //
11663			mode: "sameParent", //
11664			// Events:
11665			// beforeSelect: $.noop  // Return false to prevent cancel/save (data.input is available)
11666		},
11667
11668		treeInit: function (ctx) {
11669			this._superApply(arguments);
11670			this.$container.addClass("fancytree-ext-multi");
11671			if (ctx.options.selectMode === 1) {
11672				$.error(
11673					"Fancytree ext-multi: selectMode: 1 (single) is not compatible."
11674				);
11675			}
11676		},
11677		nodeClick: function (ctx) {
11678			var //pluginOpts = ctx.options.multi,
11679				tree = ctx.tree,
11680				node = ctx.node,
11681				activeNode = tree.getActiveNode() || tree.getFirstChild(),
11682				isCbClick = ctx.targetType === "checkbox",
11683				isExpanderClick = ctx.targetType === "expander",
11684				eventStr = $.ui.fancytree.eventToString(ctx.originalEvent);
11685
11686			switch (eventStr) {
11687				case "click":
11688					if (isExpanderClick) {
11689						break;
11690					} // Default handler will expand/collapse
11691					if (!isCbClick) {
11692						tree.selectAll(false);
11693						// Select clicked node (radio-button  mode)
11694						node.setSelected();
11695					}
11696					// Default handler will toggle checkbox clicks and activate
11697					break;
11698				case "shift+click":
11699					// node.debug("click")
11700					tree.visitRows(
11701						function (n) {
11702							// n.debug("click2", n===node, node)
11703							n.setSelected();
11704							if (n === node) {
11705								return false;
11706							}
11707						},
11708						{
11709							start: activeNode,
11710							reverse: activeNode.isBelowOf(node),
11711						}
11712					);
11713					break;
11714				case "ctrl+click":
11715				case "meta+click": // Mac: [Command]
11716					node.toggleSelected();
11717					return;
11718			}
11719			return this._superApply(arguments);
11720		},
11721		nodeKeydown: function (ctx) {
11722			var tree = ctx.tree,
11723				node = ctx.node,
11724				event = ctx.originalEvent,
11725				eventStr = $.ui.fancytree.eventToString(event);
11726
11727			switch (eventStr) {
11728				case "up":
11729				case "down":
11730					tree.selectAll(false);
11731					node.navigate(event.which, true);
11732					tree.getActiveNode().setSelected();
11733					break;
11734				case "shift+up":
11735				case "shift+down":
11736					node.navigate(event.which, true);
11737					tree.getActiveNode().setSelected();
11738					break;
11739			}
11740			return this._superApply(arguments);
11741		},
11742	});
11743	// Value returned by `require('jquery.fancytree..')`
11744	return $.ui.fancytree;
11745}); // End of closure
11746
11747/*!
11748 * jquery.fancytree.persist.js
11749 *
11750 * Persist tree status in cookiesRemove or highlight tree nodes, based on a filter.
11751 * (Extension module for jquery.fancytree.js: https://github.com/mar10/fancytree/)
11752 *
11753 * @depends: js-cookie or jquery-cookie
11754 *
11755 * Copyright (c) 2008-2023, Martin Wendt (https://wwWendt.de)
11756 *
11757 * Released under the MIT license
11758 * https://github.com/mar10/fancytree/wiki/LicenseInfo
11759 *
11760 * @version 2.38.3
11761 * @date 2023-02-01T20:52:50Z
11762 */
11763
11764(function (factory) {
11765	if (typeof define === "function" && define.amd) {
11766		// AMD. Register as an anonymous module.
11767		define(["jquery", "./jquery.fancytree"], factory);
11768	} else if (typeof module === "object" && module.exports) {
11769		// Node/CommonJS
11770		require("./jquery.fancytree");
11771		module.exports = factory(require("jquery"));
11772	} else {
11773		// Browser globals
11774		factory(jQuery);
11775	}
11776})(function ($) {
11777	"use strict";
11778	/* global Cookies:false */
11779
11780	/*******************************************************************************
11781	 * Private functions and variables
11782	 */
11783	var cookieStore = null,
11784		localStorageStore = null,
11785		sessionStorageStore = null,
11786		_assert = $.ui.fancytree.assert,
11787		ACTIVE = "active",
11788		EXPANDED = "expanded",
11789		FOCUS = "focus",
11790		SELECTED = "selected";
11791
11792	// Accessing window.xxxStorage may raise security exceptions (see #1022)
11793	try {
11794		_assert(window.localStorage && window.localStorage.getItem);
11795		localStorageStore = {
11796			get: function (key) {
11797				return window.localStorage.getItem(key);
11798			},
11799			set: function (key, value) {
11800				window.localStorage.setItem(key, value);
11801			},
11802			remove: function (key) {
11803				window.localStorage.removeItem(key);
11804			},
11805		};
11806	} catch (e) {
11807		$.ui.fancytree.warn("Could not access window.localStorage", e);
11808	}
11809
11810	try {
11811		_assert(window.sessionStorage && window.sessionStorage.getItem);
11812		sessionStorageStore = {
11813			get: function (key) {
11814				return window.sessionStorage.getItem(key);
11815			},
11816			set: function (key, value) {
11817				window.sessionStorage.setItem(key, value);
11818			},
11819			remove: function (key) {
11820				window.sessionStorage.removeItem(key);
11821			},
11822		};
11823	} catch (e) {
11824		$.ui.fancytree.warn("Could not access window.sessionStorage", e);
11825	}
11826
11827	if (typeof Cookies === "function") {
11828		// Assume https://github.com/js-cookie/js-cookie
11829		cookieStore = {
11830			get: Cookies.get,
11831			set: function (key, value) {
11832				Cookies.set(key, value, this.options.persist.cookie);
11833			},
11834			remove: Cookies.remove,
11835		};
11836	} else if ($ && typeof $.cookie === "function") {
11837		// Fall back to https://github.com/carhartl/jquery-cookie
11838		cookieStore = {
11839			get: $.cookie,
11840			set: function (key, value) {
11841				$.cookie(key, value, this.options.persist.cookie);
11842			},
11843			remove: $.removeCookie,
11844		};
11845	}
11846
11847	/* Recursively load lazy nodes
11848	 * @param {string} mode 'load', 'expand', false
11849	 */
11850	function _loadLazyNodes(tree, local, keyList, mode, dfd) {
11851		var i,
11852			key,
11853			l,
11854			node,
11855			foundOne = false,
11856			expandOpts = tree.options.persist.expandOpts,
11857			deferredList = [],
11858			missingKeyList = [];
11859
11860		keyList = keyList || [];
11861		dfd = dfd || $.Deferred();
11862
11863		for (i = 0, l = keyList.length; i < l; i++) {
11864			key = keyList[i];
11865			node = tree.getNodeByKey(key);
11866			if (node) {
11867				if (mode && node.isUndefined()) {
11868					foundOne = true;
11869					tree.debug(
11870						"_loadLazyNodes: " + node + " is lazy: loading..."
11871					);
11872					if (mode === "expand") {
11873						deferredList.push(node.setExpanded(true, expandOpts));
11874					} else {
11875						deferredList.push(node.load());
11876					}
11877				} else {
11878					tree.debug("_loadLazyNodes: " + node + " already loaded.");
11879					node.setExpanded(true, expandOpts);
11880				}
11881			} else {
11882				missingKeyList.push(key);
11883				tree.debug("_loadLazyNodes: " + node + " was not yet found.");
11884			}
11885		}
11886
11887		$.when.apply($, deferredList).always(function () {
11888			// All lazy-expands have finished
11889			if (foundOne && missingKeyList.length > 0) {
11890				// If we read new nodes from server, try to resolve yet-missing keys
11891				_loadLazyNodes(tree, local, missingKeyList, mode, dfd);
11892			} else {
11893				if (missingKeyList.length) {
11894					tree.warn(
11895						"_loadLazyNodes: could not load those keys: ",
11896						missingKeyList
11897					);
11898					for (i = 0, l = missingKeyList.length; i < l; i++) {
11899						key = keyList[i];
11900						local._appendKey(EXPANDED, keyList[i], false);
11901					}
11902				}
11903				dfd.resolve();
11904			}
11905		});
11906		return dfd;
11907	}
11908
11909	/**
11910	 * [ext-persist] Remove persistence data of the given type(s).
11911	 * Called like
11912	 *     $.ui.fancytree.getTree("#tree").clearCookies("active expanded focus selected");
11913	 *
11914	 * @alias Fancytree#clearPersistData
11915	 * @requires jquery.fancytree.persist.js
11916	 */
11917	$.ui.fancytree._FancytreeClass.prototype.clearPersistData = function (
11918		types
11919	) {
11920		var local = this.ext.persist,
11921			prefix = local.cookiePrefix;
11922
11923		types = types || "active expanded focus selected";
11924		if (types.indexOf(ACTIVE) >= 0) {
11925			local._data(prefix + ACTIVE, null);
11926		}
11927		if (types.indexOf(EXPANDED) >= 0) {
11928			local._data(prefix + EXPANDED, null);
11929		}
11930		if (types.indexOf(FOCUS) >= 0) {
11931			local._data(prefix + FOCUS, null);
11932		}
11933		if (types.indexOf(SELECTED) >= 0) {
11934			local._data(prefix + SELECTED, null);
11935		}
11936	};
11937
11938	$.ui.fancytree._FancytreeClass.prototype.clearCookies = function (types) {
11939		this.warn(
11940			"'tree.clearCookies()' is deprecated since v2.27.0: use 'clearPersistData()' instead."
11941		);
11942		return this.clearPersistData(types);
11943	};
11944
11945	/**
11946	 * [ext-persist] Return persistence information from cookies
11947	 *
11948	 * Called like
11949	 *     $.ui.fancytree.getTree("#tree").getPersistData();
11950	 *
11951	 * @alias Fancytree#getPersistData
11952	 * @requires jquery.fancytree.persist.js
11953	 */
11954	$.ui.fancytree._FancytreeClass.prototype.getPersistData = function () {
11955		var local = this.ext.persist,
11956			prefix = local.cookiePrefix,
11957			delim = local.cookieDelimiter,
11958			res = {};
11959
11960		res[ACTIVE] = local._data(prefix + ACTIVE);
11961		res[EXPANDED] = (local._data(prefix + EXPANDED) || "").split(delim);
11962		res[SELECTED] = (local._data(prefix + SELECTED) || "").split(delim);
11963		res[FOCUS] = local._data(prefix + FOCUS);
11964		return res;
11965	};
11966
11967	/******************************************************************************
11968	 * Extension code
11969	 */
11970	$.ui.fancytree.registerExtension({
11971		name: "persist",
11972		version: "2.38.3",
11973		// Default options for this extension.
11974		options: {
11975			cookieDelimiter: "~",
11976			cookiePrefix: undefined, // 'fancytree-<treeId>-' by default
11977			cookie: {
11978				raw: false,
11979				expires: "",
11980				path: "",
11981				domain: "",
11982				secure: false,
11983			},
11984			expandLazy: false, // true: recursively expand and load lazy nodes
11985			expandOpts: undefined, // optional `opts` argument passed to setExpanded()
11986			fireActivate: true, // false: suppress `activate` event after active node was restored
11987			overrideSource: true, // true: cookie takes precedence over `source` data attributes.
11988			store: "auto", // 'cookie': force cookie, 'local': force localStore, 'session': force sessionStore
11989			types: "active expanded focus selected",
11990		},
11991
11992		/* Generic read/write string data to cookie, sessionStorage or localStorage. */
11993		_data: function (key, value) {
11994			var store = this._local.store;
11995
11996			if (value === undefined) {
11997				return store.get.call(this, key);
11998			} else if (value === null) {
11999				store.remove.call(this, key);
12000			} else {
12001				store.set.call(this, key, value);
12002			}
12003		},
12004
12005		/* Append `key` to a cookie. */
12006		_appendKey: function (type, key, flag) {
12007			key = "" + key; // #90
12008			var local = this._local,
12009				instOpts = this.options.persist,
12010				delim = instOpts.cookieDelimiter,
12011				cookieName = local.cookiePrefix + type,
12012				data = local._data(cookieName),
12013				keyList = data ? data.split(delim) : [],
12014				idx = $.inArray(key, keyList);
12015			// Remove, even if we add a key,  so the key is always the last entry
12016			if (idx >= 0) {
12017				keyList.splice(idx, 1);
12018			}
12019			// Append key to cookie
12020			if (flag) {
12021				keyList.push(key);
12022			}
12023			local._data(cookieName, keyList.join(delim));
12024		},
12025
12026		treeInit: function (ctx) {
12027			var tree = ctx.tree,
12028				opts = ctx.options,
12029				local = this._local,
12030				instOpts = this.options.persist;
12031
12032			// // For 'auto' or 'cookie' mode, the cookie plugin must be available
12033			// _assert((instOpts.store !== "auto" && instOpts.store !== "cookie") || cookieStore,
12034			// 	"Missing required plugin for 'persist' extension: js.cookie.js or jquery.cookie.js");
12035
12036			local.cookiePrefix =
12037				instOpts.cookiePrefix || "fancytree-" + tree._id + "-";
12038			local.storeActive = instOpts.types.indexOf(ACTIVE) >= 0;
12039			local.storeExpanded = instOpts.types.indexOf(EXPANDED) >= 0;
12040			local.storeSelected = instOpts.types.indexOf(SELECTED) >= 0;
12041			local.storeFocus = instOpts.types.indexOf(FOCUS) >= 0;
12042			local.store = null;
12043
12044			if (instOpts.store === "auto") {
12045				instOpts.store = localStorageStore ? "local" : "cookie";
12046			}
12047			if ($.isPlainObject(instOpts.store)) {
12048				local.store = instOpts.store;
12049			} else if (instOpts.store === "cookie") {
12050				local.store = cookieStore;
12051			} else if (instOpts.store === "local") {
12052				local.store =
12053					instOpts.store === "local"
12054						? localStorageStore
12055						: sessionStorageStore;
12056			} else if (instOpts.store === "session") {
12057				local.store =
12058					instOpts.store === "local"
12059						? localStorageStore
12060						: sessionStorageStore;
12061			}
12062			_assert(local.store, "Need a valid store.");
12063
12064			// Bind init-handler to apply cookie state
12065			tree.$div.on("fancytreeinit", function (event) {
12066				if (
12067					tree._triggerTreeEvent("beforeRestore", null, {}) === false
12068				) {
12069					return;
12070				}
12071
12072				var cookie,
12073					dfd,
12074					i,
12075					keyList,
12076					node,
12077					prevFocus = local._data(local.cookiePrefix + FOCUS), // record this before node.setActive() overrides it;
12078					noEvents = instOpts.fireActivate === false;
12079
12080				// tree.debug("document.cookie:", document.cookie);
12081
12082				cookie = local._data(local.cookiePrefix + EXPANDED);
12083				keyList = cookie && cookie.split(instOpts.cookieDelimiter);
12084
12085				if (local.storeExpanded) {
12086					// Recursively load nested lazy nodes if expandLazy is 'expand' or 'load'
12087					// Also remove expand-cookies for unmatched nodes
12088					dfd = _loadLazyNodes(
12089						tree,
12090						local,
12091						keyList,
12092						instOpts.expandLazy ? "expand" : false,
12093						null
12094					);
12095				} else {
12096					// nothing to do
12097					dfd = new $.Deferred().resolve();
12098				}
12099
12100				dfd.done(function () {
12101					if (local.storeSelected) {
12102						cookie = local._data(local.cookiePrefix + SELECTED);
12103						if (cookie) {
12104							keyList = cookie.split(instOpts.cookieDelimiter);
12105							for (i = 0; i < keyList.length; i++) {
12106								node = tree.getNodeByKey(keyList[i]);
12107								if (node) {
12108									if (
12109										node.selected === undefined ||
12110										(instOpts.overrideSource &&
12111											node.selected === false)
12112									) {
12113										//									node.setSelected();
12114										node.selected = true;
12115										node.renderStatus();
12116									}
12117								} else {
12118									// node is no longer member of the tree: remove from cookie also
12119									local._appendKey(
12120										SELECTED,
12121										keyList[i],
12122										false
12123									);
12124								}
12125							}
12126						}
12127						// In selectMode 3 we have to fix the child nodes, since we
12128						// only stored the selected *top* nodes
12129						if (tree.options.selectMode === 3) {
12130							tree.visit(function (n) {
12131								if (n.selected) {
12132									n.fixSelection3AfterClick();
12133									return "skip";
12134								}
12135							});
12136						}
12137					}
12138					if (local.storeActive) {
12139						cookie = local._data(local.cookiePrefix + ACTIVE);
12140						if (
12141							cookie &&
12142							(opts.persist.overrideSource || !tree.activeNode)
12143						) {
12144							node = tree.getNodeByKey(cookie);
12145							if (node) {
12146								node.debug("persist: set active", cookie);
12147								// We only want to set the focus if the container
12148								// had the keyboard focus before
12149								node.setActive(true, {
12150									noFocus: true,
12151									noEvents: noEvents,
12152								});
12153							}
12154						}
12155					}
12156					if (local.storeFocus && prevFocus) {
12157						node = tree.getNodeByKey(prevFocus);
12158						if (node) {
12159							// node.debug("persist: set focus", cookie);
12160							if (tree.options.titlesTabbable) {
12161								$(node.span).find(".fancytree-title").focus();
12162							} else {
12163								$(tree.$container).focus();
12164							}
12165							// node.setFocus();
12166						}
12167					}
12168					tree._triggerTreeEvent("restore", null, {});
12169				});
12170			});
12171			// Init the tree
12172			return this._superApply(arguments);
12173		},
12174		nodeSetActive: function (ctx, flag, callOpts) {
12175			var res,
12176				local = this._local;
12177
12178			flag = flag !== false;
12179			res = this._superApply(arguments);
12180
12181			if (local.storeActive) {
12182				local._data(
12183					local.cookiePrefix + ACTIVE,
12184					this.activeNode ? this.activeNode.key : null
12185				);
12186			}
12187			return res;
12188		},
12189		nodeSetExpanded: function (ctx, flag, callOpts) {
12190			var res,
12191				node = ctx.node,
12192				local = this._local;
12193
12194			flag = flag !== false;
12195			res = this._superApply(arguments);
12196
12197			if (local.storeExpanded) {
12198				local._appendKey(EXPANDED, node.key, flag);
12199			}
12200			return res;
12201		},
12202		nodeSetFocus: function (ctx, flag) {
12203			var res,
12204				local = this._local;
12205
12206			flag = flag !== false;
12207			res = this._superApply(arguments);
12208
12209			if (local.storeFocus) {
12210				local._data(
12211					local.cookiePrefix + FOCUS,
12212					this.focusNode ? this.focusNode.key : null
12213				);
12214			}
12215			return res;
12216		},
12217		nodeSetSelected: function (ctx, flag, callOpts) {
12218			var res,
12219				selNodes,
12220				tree = ctx.tree,
12221				node = ctx.node,
12222				local = this._local;
12223
12224			flag = flag !== false;
12225			res = this._superApply(arguments);
12226
12227			if (local.storeSelected) {
12228				if (tree.options.selectMode === 3) {
12229					// In selectMode 3 we only store the the selected *top* nodes.
12230					// De-selecting a node may also de-select some parents, so we
12231					// calculate the current status again
12232					selNodes = $.map(tree.getSelectedNodes(true), function (n) {
12233						return n.key;
12234					});
12235					selNodes = selNodes.join(
12236						ctx.options.persist.cookieDelimiter
12237					);
12238					local._data(local.cookiePrefix + SELECTED, selNodes);
12239				} else {
12240					// beforeSelect can prevent the change - flag doesn't reflect the node.selected state
12241					local._appendKey(SELECTED, node.key, node.selected);
12242				}
12243			}
12244			return res;
12245		},
12246	});
12247	// Value returned by `require('jquery.fancytree..')`
12248	return $.ui.fancytree;
12249}); // End of closure
12250
12251/*!
12252 * jquery.fancytree.table.js
12253 *
12254 * Render tree as table (aka 'tree grid', 'table tree').
12255 * (Extension module for jquery.fancytree.js: https://github.com/mar10/fancytree/)
12256 *
12257 * Copyright (c) 2008-2023, Martin Wendt (https://wwWendt.de)
12258 *
12259 * Released under the MIT license
12260 * https://github.com/mar10/fancytree/wiki/LicenseInfo
12261 *
12262 * @version 2.38.3
12263 * @date 2023-02-01T20:52:50Z
12264 */
12265
12266(function (factory) {
12267	if (typeof define === "function" && define.amd) {
12268		// AMD. Register as an anonymous module.
12269		define(["jquery", "./jquery.fancytree"], factory);
12270	} else if (typeof module === "object" && module.exports) {
12271		// Node/CommonJS
12272		require("./jquery.fancytree");
12273		module.exports = factory(require("jquery"));
12274	} else {
12275		// Browser globals
12276		factory(jQuery);
12277	}
12278})(function ($) {
12279	"use strict";
12280
12281	/******************************************************************************
12282	 * Private functions and variables
12283	 */
12284	var _assert = $.ui.fancytree.assert;
12285
12286	function insertFirstChild(referenceNode, newNode) {
12287		referenceNode.insertBefore(newNode, referenceNode.firstChild);
12288	}
12289
12290	function insertSiblingAfter(referenceNode, newNode) {
12291		referenceNode.parentNode.insertBefore(
12292			newNode,
12293			referenceNode.nextSibling
12294		);
12295	}
12296
12297	/* Show/hide all rows that are structural descendants of `parent`. */
12298	function setChildRowVisibility(parent, flag) {
12299		parent.visit(function (node) {
12300			var tr = node.tr;
12301			// currentFlag = node.hide ? false : flag; // fix for ext-filter
12302			if (tr) {
12303				tr.style.display = node.hide || !flag ? "none" : "";
12304			}
12305			if (!node.expanded) {
12306				return "skip";
12307			}
12308		});
12309	}
12310
12311	/* Find node that is rendered in previous row. */
12312	function findPrevRowNode(node) {
12313		var i,
12314			last,
12315			prev,
12316			parent = node.parent,
12317			siblings = parent ? parent.children : null;
12318
12319		if (siblings && siblings.length > 1 && siblings[0] !== node) {
12320			// use the lowest descendant of the preceeding sibling
12321			i = $.inArray(node, siblings);
12322			prev = siblings[i - 1];
12323			_assert(prev.tr);
12324			// descend to lowest child (with a <tr> tag)
12325			while (prev.children && prev.children.length) {
12326				last = prev.children[prev.children.length - 1];
12327				if (!last.tr) {
12328					break;
12329				}
12330				prev = last;
12331			}
12332		} else {
12333			// if there is no preceding sibling, use the direct parent
12334			prev = parent;
12335		}
12336		return prev;
12337	}
12338
12339	$.ui.fancytree.registerExtension({
12340		name: "table",
12341		version: "2.38.3",
12342		// Default options for this extension.
12343		options: {
12344			checkboxColumnIdx: null, // render the checkboxes into the this column index (default: nodeColumnIdx)
12345			indentation: 16, // indent every node level by 16px
12346			mergeStatusColumns: true, // display 'nodata', 'loading', 'error' centered in a single, merged TR
12347			nodeColumnIdx: 0, // render node expander, icon, and title to this column (default: #0)
12348		},
12349		// Overide virtual methods for this extension.
12350		// `this`       : is this extension object
12351		// `this._super`: the virtual function that was overriden (member of prev. extension or Fancytree)
12352		treeInit: function (ctx) {
12353			var i,
12354				n,
12355				$row,
12356				$tbody,
12357				tree = ctx.tree,
12358				opts = ctx.options,
12359				tableOpts = opts.table,
12360				$table = tree.widget.element;
12361
12362			if (tableOpts.customStatus != null) {
12363				if (opts.renderStatusColumns == null) {
12364					tree.warn(
12365						"The 'customStatus' option is deprecated since v2.15.0. Use 'renderStatusColumns' instead."
12366					);
12367					opts.renderStatusColumns = tableOpts.customStatus;
12368				} else {
12369					$.error(
12370						"The 'customStatus' option is deprecated since v2.15.0. Use 'renderStatusColumns' only instead."
12371					);
12372				}
12373			}
12374			if (opts.renderStatusColumns) {
12375				if (opts.renderStatusColumns === true) {
12376					opts.renderStatusColumns = opts.renderColumns;
12377					// } else if( opts.renderStatusColumns === "wide" ) {
12378					// 	opts.renderStatusColumns = _renderStatusNodeWide;
12379				}
12380			}
12381
12382			$table.addClass("fancytree-container fancytree-ext-table");
12383			$tbody = $table.find(">tbody");
12384			if (!$tbody.length) {
12385				// TODO: not sure if we can rely on browsers to insert missing <tbody> before <tr>s:
12386				if ($table.find(">tr").length) {
12387					$.error(
12388						"Expected table > tbody > tr. If you see this please open an issue."
12389					);
12390				}
12391				$tbody = $("<tbody>").appendTo($table);
12392			}
12393
12394			tree.tbody = $tbody[0];
12395
12396			// Prepare row templates:
12397			// Determine column count from table header if any
12398			tree.columnCount = $("thead >tr", $table)
12399				.last()
12400				.find(">th", $table).length;
12401			// Read TR templates from tbody if any
12402			$row = $tbody.children("tr").first();
12403			if ($row.length) {
12404				n = $row.children("td").length;
12405				if (tree.columnCount && n !== tree.columnCount) {
12406					tree.warn(
12407						"Column count mismatch between thead (" +
12408							tree.columnCount +
12409							") and tbody (" +
12410							n +
12411							"): using tbody."
12412					);
12413					tree.columnCount = n;
12414				}
12415				$row = $row.clone();
12416			} else {
12417				// Only thead is defined: create default row markup
12418				_assert(
12419					tree.columnCount >= 1,
12420					"Need either <thead> or <tbody> with <td> elements to determine column count."
12421				);
12422				$row = $("<tr />");
12423				for (i = 0; i < tree.columnCount; i++) {
12424					$row.append("<td />");
12425				}
12426			}
12427			$row.find(">td")
12428				.eq(tableOpts.nodeColumnIdx)
12429				.html("<span class='fancytree-node' />");
12430			if (opts.aria) {
12431				$row.attr("role", "row");
12432				$row.find("td").attr("role", "gridcell");
12433			}
12434			tree.rowFragment = document.createDocumentFragment();
12435			tree.rowFragment.appendChild($row.get(0));
12436
12437			// // If tbody contains a second row, use this as status node template
12438			// $row = $tbody.children("tr").eq(1);
12439			// if( $row.length === 0 ) {
12440			// 	tree.statusRowFragment = tree.rowFragment;
12441			// } else {
12442			// 	$row = $row.clone();
12443			// 	tree.statusRowFragment = document.createDocumentFragment();
12444			// 	tree.statusRowFragment.appendChild($row.get(0));
12445			// }
12446			//
12447			$tbody.empty();
12448
12449			// Make sure that status classes are set on the node's <tr> elements
12450			tree.statusClassPropName = "tr";
12451			tree.ariaPropName = "tr";
12452			this.nodeContainerAttrName = "tr";
12453
12454			// #489: make sure $container is set to <table>, even if ext-dnd is listed before ext-table
12455			tree.$container = $table;
12456
12457			this._superApply(arguments);
12458
12459			// standard Fancytree created a root UL
12460			$(tree.rootNode.ul).remove();
12461			tree.rootNode.ul = null;
12462
12463			// Add container to the TAB chain
12464			// #577: Allow to set tabindex to "0", "-1" and ""
12465			this.$container.attr("tabindex", opts.tabindex);
12466			// this.$container.attr("tabindex", opts.tabbable ? "0" : "-1");
12467			if (opts.aria) {
12468				tree.$container
12469					.attr("role", "treegrid")
12470					.attr("aria-readonly", true);
12471			}
12472		},
12473		nodeRemoveChildMarkup: function (ctx) {
12474			var node = ctx.node;
12475			//		node.debug("nodeRemoveChildMarkup()");
12476			node.visit(function (n) {
12477				if (n.tr) {
12478					$(n.tr).remove();
12479					n.tr = null;
12480				}
12481			});
12482		},
12483		nodeRemoveMarkup: function (ctx) {
12484			var node = ctx.node;
12485			//		node.debug("nodeRemoveMarkup()");
12486			if (node.tr) {
12487				$(node.tr).remove();
12488				node.tr = null;
12489			}
12490			this.nodeRemoveChildMarkup(ctx);
12491		},
12492		/* Override standard render. */
12493		nodeRender: function (ctx, force, deep, collapsed, _recursive) {
12494			var children,
12495				firstTr,
12496				i,
12497				l,
12498				newRow,
12499				prevNode,
12500				prevTr,
12501				subCtx,
12502				tree = ctx.tree,
12503				node = ctx.node,
12504				opts = ctx.options,
12505				isRootNode = !node.parent;
12506
12507			if (tree._enableUpdate === false) {
12508				// $.ui.fancytree.debug("*** nodeRender _enableUpdate: false");
12509				return;
12510			}
12511			if (!_recursive) {
12512				ctx.hasCollapsedParents = node.parent && !node.parent.expanded;
12513			}
12514			// $.ui.fancytree.debug("*** nodeRender " + node + ", isRoot=" + isRootNode, "tr=" + node.tr, "hcp=" + ctx.hasCollapsedParents, "parent.tr=" + (node.parent && node.parent.tr));
12515			if (!isRootNode) {
12516				if (node.tr && force) {
12517					this.nodeRemoveMarkup(ctx);
12518				}
12519				if (node.tr) {
12520					if (force) {
12521						// Set icon, link, and title (normally this is only required on initial render)
12522						this.nodeRenderTitle(ctx); // triggers renderColumns()
12523					} else {
12524						// Update element classes according to node state
12525						this.nodeRenderStatus(ctx);
12526					}
12527				} else {
12528					if (ctx.hasCollapsedParents && !deep) {
12529						// #166: we assume that the parent will be (recursively) rendered
12530						// later anyway.
12531						// node.debug("nodeRender ignored due to unrendered parent");
12532						return;
12533					}
12534					// Create new <tr> after previous row
12535					// if( node.isStatusNode() ) {
12536					// 	newRow = tree.statusRowFragment.firstChild.cloneNode(true);
12537					// } else {
12538					newRow = tree.rowFragment.firstChild.cloneNode(true);
12539					// }
12540					prevNode = findPrevRowNode(node);
12541					// $.ui.fancytree.debug("*** nodeRender " + node + ": prev: " + prevNode.key);
12542					_assert(prevNode);
12543					if (collapsed === true && _recursive) {
12544						// hide all child rows, so we can use an animation to show it later
12545						newRow.style.display = "none";
12546					} else if (deep && ctx.hasCollapsedParents) {
12547						// also hide this row if deep === true but any parent is collapsed
12548						newRow.style.display = "none";
12549						//					newRow.style.color = "red";
12550					}
12551					if (prevNode.tr) {
12552						insertSiblingAfter(prevNode.tr, newRow);
12553					} else {
12554						_assert(
12555							!prevNode.parent,
12556							"prev. row must have a tr, or be system root"
12557						);
12558						// tree.tbody.appendChild(newRow);
12559						insertFirstChild(tree.tbody, newRow); // #675
12560					}
12561					node.tr = newRow;
12562					if (node.key && opts.generateIds) {
12563						node.tr.id = opts.idPrefix + node.key;
12564					}
12565					node.tr.ftnode = node;
12566					// if(opts.aria){
12567					// 	$(node.tr).attr("aria-labelledby", "ftal_" + opts.idPrefix + node.key);
12568					// }
12569					node.span = $("span.fancytree-node", node.tr).get(0);
12570					// Set icon, link, and title (normally this is only required on initial render)
12571					this.nodeRenderTitle(ctx);
12572					// Allow tweaking, binding, after node was created for the first time
12573					//				tree._triggerNodeEvent("createNode", ctx);
12574					if (opts.createNode) {
12575						opts.createNode.call(tree, { type: "createNode" }, ctx);
12576					}
12577				}
12578			}
12579			// Allow tweaking after node state was rendered
12580			//		tree._triggerNodeEvent("renderNode", ctx);
12581			if (opts.renderNode) {
12582				opts.renderNode.call(tree, { type: "renderNode" }, ctx);
12583			}
12584			// Visit child nodes
12585			// Add child markup
12586			children = node.children;
12587			if (children && (isRootNode || deep || node.expanded)) {
12588				for (i = 0, l = children.length; i < l; i++) {
12589					subCtx = $.extend({}, ctx, { node: children[i] });
12590					subCtx.hasCollapsedParents =
12591						subCtx.hasCollapsedParents || !node.expanded;
12592					this.nodeRender(subCtx, force, deep, collapsed, true);
12593				}
12594			}
12595			// Make sure, that <tr> order matches node.children order.
12596			if (children && !_recursive) {
12597				// we only have to do it once, for the root branch
12598				prevTr = node.tr || null;
12599				firstTr = tree.tbody.firstChild;
12600				// Iterate over all descendants
12601				node.visit(function (n) {
12602					if (n.tr) {
12603						if (
12604							!n.parent.expanded &&
12605							n.tr.style.display !== "none"
12606						) {
12607							// fix after a node was dropped over a collapsed
12608							n.tr.style.display = "none";
12609							setChildRowVisibility(n, false);
12610						}
12611						if (n.tr.previousSibling !== prevTr) {
12612							node.debug("_fixOrder: mismatch at node: " + n);
12613							var nextTr = prevTr ? prevTr.nextSibling : firstTr;
12614							tree.tbody.insertBefore(n.tr, nextTr);
12615						}
12616						prevTr = n.tr;
12617					}
12618				});
12619			}
12620			// Update element classes according to node state
12621			// if(!isRootNode){
12622			// 	this.nodeRenderStatus(ctx);
12623			// }
12624		},
12625		nodeRenderTitle: function (ctx, title) {
12626			var $cb,
12627				res,
12628				tree = ctx.tree,
12629				node = ctx.node,
12630				opts = ctx.options,
12631				isStatusNode = node.isStatusNode();
12632
12633			res = this._super(ctx, title);
12634
12635			if (node.isRootNode()) {
12636				return res;
12637			}
12638			// Move checkbox to custom column
12639			if (
12640				opts.checkbox &&
12641				!isStatusNode &&
12642				opts.table.checkboxColumnIdx != null
12643			) {
12644				$cb = $("span.fancytree-checkbox", node.span); //.detach();
12645				$(node.tr)
12646					.find("td")
12647					.eq(+opts.table.checkboxColumnIdx)
12648					.html($cb);
12649			}
12650			// Update element classes according to node state
12651			this.nodeRenderStatus(ctx);
12652
12653			if (isStatusNode) {
12654				if (opts.renderStatusColumns) {
12655					// Let user code write column content
12656					opts.renderStatusColumns.call(
12657						tree,
12658						{ type: "renderStatusColumns" },
12659						ctx
12660					);
12661				} else if (opts.table.mergeStatusColumns && node.isTopLevel()) {
12662					$(node.tr)
12663						.find(">td")
12664						.eq(0)
12665						.prop("colspan", tree.columnCount)
12666						.text(node.title)
12667						.addClass("fancytree-status-merged")
12668						.nextAll()
12669						.remove();
12670				} // else: default rendering for status node: leave other cells empty
12671			} else if (opts.renderColumns) {
12672				opts.renderColumns.call(tree, { type: "renderColumns" }, ctx);
12673			}
12674			return res;
12675		},
12676		nodeRenderStatus: function (ctx) {
12677			var indent,
12678				node = ctx.node,
12679				opts = ctx.options;
12680
12681			this._super(ctx);
12682
12683			$(node.tr).removeClass("fancytree-node");
12684			// indent
12685			indent = (node.getLevel() - 1) * opts.table.indentation;
12686			if (opts.rtl) {
12687				$(node.span).css({ paddingRight: indent + "px" });
12688			} else {
12689				$(node.span).css({ paddingLeft: indent + "px" });
12690			}
12691		},
12692		/* Expand node, return Deferred.promise. */
12693		nodeSetExpanded: function (ctx, flag, callOpts) {
12694			// flag defaults to true
12695			flag = flag !== false;
12696
12697			if ((ctx.node.expanded && flag) || (!ctx.node.expanded && !flag)) {
12698				// Expanded state isn't changed - just call base implementation
12699				return this._superApply(arguments);
12700			}
12701
12702			var dfd = new $.Deferred(),
12703				subOpts = $.extend({}, callOpts, {
12704					noEvents: true,
12705					noAnimation: true,
12706				});
12707
12708			callOpts = callOpts || {};
12709
12710			function _afterExpand(ok, args) {
12711				// ctx.tree.info("ok:" + ok, args);
12712				if (ok) {
12713					// #1108 minExpandLevel: 2 together with table extension does not work
12714					// don't call when 'ok' is false:
12715					setChildRowVisibility(ctx.node, flag);
12716					if (
12717						flag &&
12718						ctx.options.autoScroll &&
12719						!callOpts.noAnimation &&
12720						ctx.node.hasChildren()
12721					) {
12722						// Scroll down to last child, but keep current node visible
12723						ctx.node
12724							.getLastChild()
12725							.scrollIntoView(true, { topNode: ctx.node })
12726							.always(function () {
12727								if (!callOpts.noEvents) {
12728									ctx.tree._triggerNodeEvent(
12729										flag ? "expand" : "collapse",
12730										ctx
12731									);
12732								}
12733								dfd.resolveWith(ctx.node);
12734							});
12735					} else {
12736						if (!callOpts.noEvents) {
12737							ctx.tree._triggerNodeEvent(
12738								flag ? "expand" : "collapse",
12739								ctx
12740							);
12741						}
12742						dfd.resolveWith(ctx.node);
12743					}
12744				} else {
12745					if (!callOpts.noEvents) {
12746						ctx.tree._triggerNodeEvent(
12747							flag ? "expand" : "collapse",
12748							ctx
12749						);
12750					}
12751					dfd.rejectWith(ctx.node);
12752				}
12753			}
12754			// Call base-expand with disabled events and animation
12755			this._super(ctx, flag, subOpts)
12756				.done(function () {
12757					_afterExpand(true, arguments);
12758				})
12759				.fail(function () {
12760					_afterExpand(false, arguments);
12761				});
12762			return dfd.promise();
12763		},
12764		nodeSetStatus: function (ctx, status, message, details) {
12765			if (status === "ok") {
12766				var node = ctx.node,
12767					firstChild = node.children ? node.children[0] : null;
12768				if (firstChild && firstChild.isStatusNode()) {
12769					$(firstChild.tr).remove();
12770				}
12771			}
12772			return this._superApply(arguments);
12773		},
12774		treeClear: function (ctx) {
12775			this.nodeRemoveChildMarkup(this._makeHookContext(this.rootNode));
12776			return this._superApply(arguments);
12777		},
12778		treeDestroy: function (ctx) {
12779			this.$container.find("tbody").empty();
12780			if (this.$source) {
12781				this.$source.removeClass("fancytree-helper-hidden");
12782			}
12783			return this._superApply(arguments);
12784		},
12785		/*,
12786	treeSetFocus: function(ctx, flag) {
12787//	        alert("treeSetFocus" + ctx.tree.$container);
12788		ctx.tree.$container.focus();
12789		$.ui.fancytree.focusTree = ctx.tree;
12790	}*/
12791	});
12792	// Value returned by `require('jquery.fancytree..')`
12793	return $.ui.fancytree;
12794}); // End of closure
12795
12796/*!
12797 * jquery.fancytree.themeroller.js
12798 *
12799 * Enable jQuery UI ThemeRoller styles.
12800 * (Extension module for jquery.fancytree.js: https://github.com/mar10/fancytree/)
12801 *
12802 * @see http://jqueryui.com/themeroller/
12803 *
12804 * Copyright (c) 2008-2023, Martin Wendt (https://wwWendt.de)
12805 *
12806 * Released under the MIT license
12807 * https://github.com/mar10/fancytree/wiki/LicenseInfo
12808 *
12809 * @version 2.38.3
12810 * @date 2023-02-01T20:52:50Z
12811 */
12812
12813(function (factory) {
12814	if (typeof define === "function" && define.amd) {
12815		// AMD. Register as an anonymous module.
12816		define(["jquery", "./jquery.fancytree"], factory);
12817	} else if (typeof module === "object" && module.exports) {
12818		// Node/CommonJS
12819		require("./jquery.fancytree");
12820		module.exports = factory(require("jquery"));
12821	} else {
12822		// Browser globals
12823		factory(jQuery);
12824	}
12825})(function ($) {
12826	"use strict";
12827
12828	/*******************************************************************************
12829	 * Extension code
12830	 */
12831	$.ui.fancytree.registerExtension({
12832		name: "themeroller",
12833		version: "2.38.3",
12834		// Default options for this extension.
12835		options: {
12836			activeClass: "ui-state-active", // Class added to active node
12837			// activeClass: "ui-state-highlight",
12838			addClass: "ui-corner-all", // Class added to all nodes
12839			focusClass: "ui-state-focus", // Class added to focused node
12840			hoverClass: "ui-state-hover", // Class added to hovered node
12841			selectedClass: "ui-state-highlight", // Class added to selected nodes
12842			// selectedClass: "ui-state-active"
12843		},
12844
12845		treeInit: function (ctx) {
12846			var $el = ctx.widget.element,
12847				opts = ctx.options.themeroller;
12848
12849			this._superApply(arguments);
12850
12851			if ($el[0].nodeName === "TABLE") {
12852				$el.addClass("ui-widget ui-corner-all");
12853				$el.find(">thead tr").addClass("ui-widget-header");
12854				$el.find(">tbody").addClass("ui-widget-conent");
12855			} else {
12856				$el.addClass("ui-widget ui-widget-content ui-corner-all");
12857			}
12858
12859			$el.on(
12860				"mouseenter mouseleave",
12861				".fancytree-node",
12862				function (event) {
12863					var node = $.ui.fancytree.getNode(event.target),
12864						flag = event.type === "mouseenter";
12865
12866					$(node.tr ? node.tr : node.span).toggleClass(
12867						opts.hoverClass + " " + opts.addClass,
12868						flag
12869					);
12870				}
12871			);
12872		},
12873		treeDestroy: function (ctx) {
12874			this._superApply(arguments);
12875			ctx.widget.element.removeClass(
12876				"ui-widget ui-widget-content ui-corner-all"
12877			);
12878		},
12879		nodeRenderStatus: function (ctx) {
12880			var classes = {},
12881				node = ctx.node,
12882				$el = $(node.tr ? node.tr : node.span),
12883				opts = ctx.options.themeroller;
12884
12885			this._super(ctx);
12886			/*
12887		.ui-state-highlight: Class to be applied to highlighted or selected elements. Applies "highlight" container styles to an element and its child text, links, and icons.
12888		.ui-state-error: Class to be applied to error messaging container elements. Applies "error" container styles to an element and its child text, links, and icons.
12889		.ui-state-error-text: An additional class that applies just the error text color without background. Can be used on form labels for instance. Also applies error icon color to child icons.
12890
12891		.ui-state-default: Class to be applied to clickable button-like elements. Applies "clickable default" container styles to an element and its child text, links, and icons.
12892		.ui-state-hover: Class to be applied on mouseover to clickable button-like elements. Applies "clickable hover" container styles to an element and its child text, links, and icons.
12893		.ui-state-focus: Class to be applied on keyboard focus to clickable button-like elements. Applies "clickable hover" container styles to an element and its child text, links, and icons.
12894		.ui-state-active: Class to be applied on mousedown to clickable button-like elements. Applies "clickable active" container styles to an element and its child text, links, and icons.
12895*/
12896			// Set ui-state-* class (handle the case that the same class is assigned
12897			// to different states)
12898			classes[opts.activeClass] = false;
12899			classes[opts.focusClass] = false;
12900			classes[opts.selectedClass] = false;
12901			if (node.isActive()) {
12902				classes[opts.activeClass] = true;
12903			}
12904			if (node.hasFocus()) {
12905				classes[opts.focusClass] = true;
12906			}
12907			// activeClass takes precedence before selectedClass:
12908			if (node.isSelected() && !node.isActive()) {
12909				classes[opts.selectedClass] = true;
12910			}
12911			$el.toggleClass(opts.activeClass, classes[opts.activeClass]);
12912			$el.toggleClass(opts.focusClass, classes[opts.focusClass]);
12913			$el.toggleClass(opts.selectedClass, classes[opts.selectedClass]);
12914			// Additional classes (e.g. 'ui-corner-all')
12915			$el.addClass(opts.addClass);
12916		},
12917	});
12918	// Value returned by `require('jquery.fancytree..')`
12919	return $.ui.fancytree;
12920}); // End of closure
12921
12922/*!
12923 * jquery.fancytree.wide.js
12924 * Support for 100% wide selection bars.
12925 * (Extension module for jquery.fancytree.js: https://github.com/mar10/fancytree/)
12926 *
12927 * Copyright (c) 2008-2023, Martin Wendt (https://wwWendt.de)
12928 *
12929 * Released under the MIT license
12930 * https://github.com/mar10/fancytree/wiki/LicenseInfo
12931 *
12932 * @version 2.38.3
12933 * @date 2023-02-01T20:52:50Z
12934 */
12935
12936(function (factory) {
12937	if (typeof define === "function" && define.amd) {
12938		// AMD. Register as an anonymous module.
12939		define(["jquery", "./jquery.fancytree"], factory);
12940	} else if (typeof module === "object" && module.exports) {
12941		// Node/CommonJS
12942		require("./jquery.fancytree");
12943		module.exports = factory(require("jquery"));
12944	} else {
12945		// Browser globals
12946		factory(jQuery);
12947	}
12948})(function ($) {
12949	"use strict";
12950
12951	var reNumUnit = /^([+-]?(?:\d+|\d*\.\d+))([a-z]*|%)$/; // split "1.5em" to ["1.5", "em"]
12952
12953	/*******************************************************************************
12954	 * Private functions and variables
12955	 */
12956	// var _assert = $.ui.fancytree.assert;
12957
12958	/* Calculate inner width without scrollbar */
12959	// function realInnerWidth($el) {
12960	// 	// http://blog.jquery.com/2012/08/16/jquery-1-8-box-sizing-width-csswidth-and-outerwidth/
12961	// //	inst.contWidth = parseFloat(this.$container.css("width"), 10);
12962	// 	// 'Client width without scrollbar' - 'padding'
12963	// 	return $el[0].clientWidth - ($el.innerWidth() -  parseFloat($el.css("width"), 10));
12964	// }
12965
12966	/* Create a global embedded CSS style for the tree. */
12967	function defineHeadStyleElement(id, cssText) {
12968		id = "fancytree-style-" + id;
12969		var $headStyle = $("#" + id);
12970
12971		if (!cssText) {
12972			$headStyle.remove();
12973			return null;
12974		}
12975		if (!$headStyle.length) {
12976			$headStyle = $("<style />")
12977				.attr("id", id)
12978				.addClass("fancytree-style")
12979				.prop("type", "text/css")
12980				.appendTo("head");
12981		}
12982		try {
12983			$headStyle.html(cssText);
12984		} catch (e) {
12985			// fix for IE 6-8
12986			$headStyle[0].styleSheet.cssText = cssText;
12987		}
12988		return $headStyle;
12989	}
12990
12991	/* Calculate the CSS rules that indent title spans. */
12992	function renderLevelCss(
12993		containerId,
12994		depth,
12995		levelOfs,
12996		lineOfs,
12997		labelOfs,
12998		measureUnit
12999	) {
13000		var i,
13001			prefix = "#" + containerId + " span.fancytree-level-",
13002			rules = [];
13003
13004		for (i = 0; i < depth; i++) {
13005			rules.push(
13006				prefix +
13007					(i + 1) +
13008					" span.fancytree-title { padding-left: " +
13009					(i * levelOfs + lineOfs) +
13010					measureUnit +
13011					"; }"
13012			);
13013		}
13014		// Some UI animations wrap the UL inside a DIV and set position:relative on both.
13015		// This breaks the left:0 and padding-left:nn settings of the title
13016		rules.push(
13017			"#" +
13018				containerId +
13019				" div.ui-effects-wrapper ul li span.fancytree-title, " +
13020				"#" +
13021				containerId +
13022				" li.fancytree-animating span.fancytree-title " + // #716
13023				"{ padding-left: " +
13024				labelOfs +
13025				measureUnit +
13026				"; position: static; width: auto; }"
13027		);
13028		return rules.join("\n");
13029	}
13030
13031	// /**
13032	//  * [ext-wide] Recalculate the width of the selection bar after the tree container
13033	//  * was resized.<br>
13034	//  * May be called explicitly on container resize, since there is no resize event
13035	//  * for DIV tags.
13036	//  *
13037	//  * @alias Fancytree#wideUpdate
13038	//  * @requires jquery.fancytree.wide.js
13039	//  */
13040	// $.ui.fancytree._FancytreeClass.prototype.wideUpdate = function(){
13041	// 	var inst = this.ext.wide,
13042	// 		prevCw = inst.contWidth,
13043	// 		prevLo = inst.lineOfs;
13044
13045	// 	inst.contWidth = realInnerWidth(this.$container);
13046	// 	// Each title is precceeded by 2 or 3 icons (16px + 3 margin)
13047	// 	//     + 1px title border and 3px title padding
13048	// 	// TODO: use code from treeInit() below
13049	// 	inst.lineOfs = (this.options.checkbox ? 3 : 2) * 19;
13050	// 	if( prevCw !== inst.contWidth || prevLo !== inst.lineOfs ) {
13051	// 		this.debug("wideUpdate: " + inst.contWidth);
13052	// 		this.visit(function(node){
13053	// 			node.tree._callHook("nodeRenderTitle", node);
13054	// 		});
13055	// 	}
13056	// };
13057
13058	/*******************************************************************************
13059	 * Extension code
13060	 */
13061	$.ui.fancytree.registerExtension({
13062		name: "wide",
13063		version: "2.38.3",
13064		// Default options for this extension.
13065		options: {
13066			iconWidth: null, // Adjust this if @fancy-icon-width != "16px"
13067			iconSpacing: null, // Adjust this if @fancy-icon-spacing != "3px"
13068			labelSpacing: null, // Adjust this if padding between icon and label != "3px"
13069			levelOfs: null, // Adjust this if ul padding != "16px"
13070		},
13071
13072		treeCreate: function (ctx) {
13073			this._superApply(arguments);
13074			this.$container.addClass("fancytree-ext-wide");
13075
13076			var containerId,
13077				cssText,
13078				iconSpacingUnit,
13079				labelSpacingUnit,
13080				iconWidthUnit,
13081				levelOfsUnit,
13082				instOpts = ctx.options.wide,
13083				// css sniffing
13084				$dummyLI = $(
13085					"<li id='fancytreeTemp'><span class='fancytree-node'><span class='fancytree-icon' /><span class='fancytree-title' /></span><ul />"
13086				).appendTo(ctx.tree.$container),
13087				$dummyIcon = $dummyLI.find(".fancytree-icon"),
13088				$dummyUL = $dummyLI.find("ul"),
13089				// $dummyTitle = $dummyLI.find(".fancytree-title"),
13090				iconSpacing =
13091					instOpts.iconSpacing || $dummyIcon.css("margin-left"),
13092				iconWidth = instOpts.iconWidth || $dummyIcon.css("width"),
13093				labelSpacing = instOpts.labelSpacing || "3px",
13094				levelOfs = instOpts.levelOfs || $dummyUL.css("padding-left");
13095
13096			$dummyLI.remove();
13097
13098			iconSpacingUnit = iconSpacing.match(reNumUnit)[2];
13099			iconSpacing = parseFloat(iconSpacing, 10);
13100			labelSpacingUnit = labelSpacing.match(reNumUnit)[2];
13101			labelSpacing = parseFloat(labelSpacing, 10);
13102			iconWidthUnit = iconWidth.match(reNumUnit)[2];
13103			iconWidth = parseFloat(iconWidth, 10);
13104			levelOfsUnit = levelOfs.match(reNumUnit)[2];
13105			if (
13106				iconSpacingUnit !== iconWidthUnit ||
13107				levelOfsUnit !== iconWidthUnit ||
13108				labelSpacingUnit !== iconWidthUnit
13109			) {
13110				$.error(
13111					"iconWidth, iconSpacing, and levelOfs must have the same css measure unit"
13112				);
13113			}
13114			this._local.measureUnit = iconWidthUnit;
13115			this._local.levelOfs = parseFloat(levelOfs);
13116			this._local.lineOfs =
13117				(1 +
13118					(ctx.options.checkbox ? 1 : 0) +
13119					(ctx.options.icon === false ? 0 : 1)) *
13120					(iconWidth + iconSpacing) +
13121				iconSpacing;
13122			this._local.labelOfs = labelSpacing;
13123			this._local.maxDepth = 10;
13124
13125			// Get/Set a unique Id on the container (if not already exists)
13126			containerId = this.$container.uniqueId().attr("id");
13127			// Generated css rules for some levels (extended on demand)
13128			cssText = renderLevelCss(
13129				containerId,
13130				this._local.maxDepth,
13131				this._local.levelOfs,
13132				this._local.lineOfs,
13133				this._local.labelOfs,
13134				this._local.measureUnit
13135			);
13136			defineHeadStyleElement(containerId, cssText);
13137		},
13138		treeDestroy: function (ctx) {
13139			// Remove generated css rules
13140			defineHeadStyleElement(this.$container.attr("id"), null);
13141			return this._superApply(arguments);
13142		},
13143		nodeRenderStatus: function (ctx) {
13144			var containerId,
13145				cssText,
13146				res,
13147				node = ctx.node,
13148				level = node.getLevel();
13149
13150			res = this._super(ctx);
13151			// Generate some more level-n rules if required
13152			if (level > this._local.maxDepth) {
13153				containerId = this.$container.attr("id");
13154				this._local.maxDepth *= 2;
13155				node.debug(
13156					"Define global ext-wide css up to level " +
13157						this._local.maxDepth
13158				);
13159				cssText = renderLevelCss(
13160					containerId,
13161					this._local.maxDepth,
13162					this._local.levelOfs,
13163					this._local.lineOfs,
13164					this._local.labelSpacing,
13165					this._local.measureUnit
13166				);
13167				defineHeadStyleElement(containerId, cssText);
13168			}
13169			// Add level-n class to apply indentation padding.
13170			// (Setting element style would not work, since it cannot easily be
13171			// overriden while animations run)
13172			$(node.span).addClass("fancytree-level-" + level);
13173			return res;
13174		},
13175	});
13176	// Value returned by `require('jquery.fancytree..')`
13177	return $.ui.fancytree;
13178}); // End of closure
13179