1/*! jQuery Fancytree Plugin - 2.38.3 - 2023-02-01T20:52:50Z
2  * https://github.com/mar10/fancytree
3  * Copyright (c) 2023 Martin Wendt; Licensed MIT
4 */
5/*! jQuery UI - v1.13.0 - 2021-11-09
6* http://jqueryui.com
7* Includes: widget.js, position.js, jquery-patch.js, keycode.js, scroll-parent.js, unique-id.js
8* Copyright jQuery Foundation and other contributors; Licensed MIT */
9
10/*
11	NOTE: Original jQuery UI wrapper was replaced with a simple IIFE.
12	See README-Fancytree.md
13*/
14(function( $ ) {
15
16	$.ui = $.ui || {};
17
18	var version = $.ui.version = "1.13.2";
19
20
21	/*!
22	 * jQuery UI Widget 1.13.2
23	 * http://jqueryui.com
24	 *
25	 * Copyright jQuery Foundation and other contributors
26	 * Released under the MIT license.
27	 * http://jquery.org/license
28	 */
29
30	//>>label: Widget
31	//>>group: Core
32	//>>description: Provides a factory for creating stateful widgets with a common API.
33	//>>docs: http://api.jqueryui.com/jQuery.widget/
34	//>>demos: http://jqueryui.com/widget/
35
36
37	var widgetUuid = 0;
38	var widgetHasOwnProperty = Array.prototype.hasOwnProperty;
39	var widgetSlice = Array.prototype.slice;
40
41	$.cleanData = ( function( orig ) {
42		return function( elems ) {
43			var events, elem, i;
44			for ( i = 0; ( elem = elems[ i ] ) != null; i++ ) {
45
46				// Only trigger remove when necessary to save time
47				events = $._data( elem, "events" );
48				if ( events && events.remove ) {
49					$( elem ).triggerHandler( "remove" );
50				}
51			}
52			orig( elems );
53		};
54	} )( $.cleanData );
55
56	$.widget = function( name, base, prototype ) {
57		var existingConstructor, constructor, basePrototype;
58
59		// ProxiedPrototype allows the provided prototype to remain unmodified
60		// so that it can be used as a mixin for multiple widgets (#8876)
61		var proxiedPrototype = {};
62
63		var namespace = name.split( "." )[ 0 ];
64		name = name.split( "." )[ 1 ];
65		var fullName = namespace + "-" + name;
66
67		if ( !prototype ) {
68			prototype = base;
69			base = $.Widget;
70		}
71
72		if ( Array.isArray( prototype ) ) {
73			prototype = $.extend.apply( null, [ {} ].concat( prototype ) );
74		}
75
76		// Create selector for plugin
77		$.expr.pseudos[ fullName.toLowerCase() ] = function( elem ) {
78			return !!$.data( elem, fullName );
79		};
80
81		$[ namespace ] = $[ namespace ] || {};
82		existingConstructor = $[ namespace ][ name ];
83		constructor = $[ namespace ][ name ] = function( options, element ) {
84
85			// Allow instantiation without "new" keyword
86			if ( !this || !this._createWidget ) {
87				return new constructor( options, element );
88			}
89
90			// Allow instantiation without initializing for simple inheritance
91			// must use "new" keyword (the code above always passes args)
92			if ( arguments.length ) {
93				this._createWidget( options, element );
94			}
95		};
96
97		// Extend with the existing constructor to carry over any static properties
98		$.extend( constructor, existingConstructor, {
99			version: prototype.version,
100
101			// Copy the object used to create the prototype in case we need to
102			// redefine the widget later
103			_proto: $.extend( {}, prototype ),
104
105			// Track widgets that inherit from this widget in case this widget is
106			// redefined after a widget inherits from it
107			_childConstructors: []
108		} );
109
110		basePrototype = new base();
111
112		// We need to make the options hash a property directly on the new instance
113		// otherwise we'll modify the options hash on the prototype that we're
114		// inheriting from
115		basePrototype.options = $.widget.extend( {}, basePrototype.options );
116		$.each( prototype, function( prop, value ) {
117			if ( typeof value !== "function" ) {
118				proxiedPrototype[ prop ] = value;
119				return;
120			}
121			proxiedPrototype[ prop ] = ( function() {
122				function _super() {
123					return base.prototype[ prop ].apply( this, arguments );
124				}
125
126				function _superApply( args ) {
127					return base.prototype[ prop ].apply( this, args );
128				}
129
130				return function() {
131					var __super = this._super;
132					var __superApply = this._superApply;
133					var returnValue;
134
135					this._super = _super;
136					this._superApply = _superApply;
137
138					returnValue = value.apply( this, arguments );
139
140					this._super = __super;
141					this._superApply = __superApply;
142
143					return returnValue;
144				};
145			} )();
146		} );
147		constructor.prototype = $.widget.extend( basePrototype, {
148
149			// TODO: remove support for widgetEventPrefix
150			// always use the name + a colon as the prefix, e.g., draggable:start
151			// don't prefix for widgets that aren't DOM-based
152			widgetEventPrefix: existingConstructor ? ( basePrototype.widgetEventPrefix || name ) : name
153		}, proxiedPrototype, {
154			constructor: constructor,
155			namespace: namespace,
156			widgetName: name,
157			widgetFullName: fullName
158		} );
159
160		// If this widget is being redefined then we need to find all widgets that
161		// are inheriting from it and redefine all of them so that they inherit from
162		// the new version of this widget. We're essentially trying to replace one
163		// level in the prototype chain.
164		if ( existingConstructor ) {
165			$.each( existingConstructor._childConstructors, function( i, child ) {
166				var childPrototype = child.prototype;
167
168				// Redefine the child widget using the same prototype that was
169				// originally used, but inherit from the new version of the base
170				$.widget( childPrototype.namespace + "." + childPrototype.widgetName, constructor,
171					child._proto );
172			} );
173
174			// Remove the list of existing child constructors from the old constructor
175			// so the old child constructors can be garbage collected
176			delete existingConstructor._childConstructors;
177		} else {
178			base._childConstructors.push( constructor );
179		}
180
181		$.widget.bridge( name, constructor );
182
183		return constructor;
184	};
185
186	$.widget.extend = function( target ) {
187		var input = widgetSlice.call( arguments, 1 );
188		var inputIndex = 0;
189		var inputLength = input.length;
190		var key;
191		var value;
192
193		for ( ; inputIndex < inputLength; inputIndex++ ) {
194			for ( key in input[ inputIndex ] ) {
195				value = input[ inputIndex ][ key ];
196				if ( widgetHasOwnProperty.call( input[ inputIndex ], key ) && value !== undefined ) {
197
198					// Clone objects
199					if ( $.isPlainObject( value ) ) {
200						target[ key ] = $.isPlainObject( target[ key ] ) ?
201							$.widget.extend( {}, target[ key ], value ) :
202
203							// Don't extend strings, arrays, etc. with objects
204							$.widget.extend( {}, value );
205
206					// Copy everything else by reference
207					} else {
208						target[ key ] = value;
209					}
210				}
211			}
212		}
213		return target;
214	};
215
216	$.widget.bridge = function( name, object ) {
217		var fullName = object.prototype.widgetFullName || name;
218		$.fn[ name ] = function( options ) {
219			var isMethodCall = typeof options === "string";
220			var args = widgetSlice.call( arguments, 1 );
221			var returnValue = this;
222
223			if ( isMethodCall ) {
224
225				// If this is an empty collection, we need to have the instance method
226				// return undefined instead of the jQuery instance
227				if ( !this.length && options === "instance" ) {
228					returnValue = undefined;
229				} else {
230					this.each( function() {
231						var methodValue;
232						var instance = $.data( this, fullName );
233
234						if ( options === "instance" ) {
235							returnValue = instance;
236							return false;
237						}
238
239						if ( !instance ) {
240							return $.error( "cannot call methods on " + name +
241								" prior to initialization; " +
242								"attempted to call method '" + options + "'" );
243						}
244
245						if ( typeof instance[ options ] !== "function" ||
246							options.charAt( 0 ) === "_" ) {
247							return $.error( "no such method '" + options + "' for " + name +
248								" widget instance" );
249						}
250
251						methodValue = instance[ options ].apply( instance, args );
252
253						if ( methodValue !== instance && methodValue !== undefined ) {
254							returnValue = methodValue && methodValue.jquery ?
255								returnValue.pushStack( methodValue.get() ) :
256								methodValue;
257							return false;
258						}
259					} );
260				}
261			} else {
262
263				// Allow multiple hashes to be passed on init
264				if ( args.length ) {
265					options = $.widget.extend.apply( null, [ options ].concat( args ) );
266				}
267
268				this.each( function() {
269					var instance = $.data( this, fullName );
270					if ( instance ) {
271						instance.option( options || {} );
272						if ( instance._init ) {
273							instance._init();
274						}
275					} else {
276						$.data( this, fullName, new object( options, this ) );
277					}
278				} );
279			}
280
281			return returnValue;
282		};
283	};
284
285	$.Widget = function( /* options, element */ ) {};
286	$.Widget._childConstructors = [];
287
288	$.Widget.prototype = {
289		widgetName: "widget",
290		widgetEventPrefix: "",
291		defaultElement: "<div>",
292
293		options: {
294			classes: {},
295			disabled: false,
296
297			// Callbacks
298			create: null
299		},
300
301		_createWidget: function( options, element ) {
302			element = $( element || this.defaultElement || this )[ 0 ];
303			this.element = $( element );
304			this.uuid = widgetUuid++;
305			this.eventNamespace = "." + this.widgetName + this.uuid;
306
307			this.bindings = $();
308			this.hoverable = $();
309			this.focusable = $();
310			this.classesElementLookup = {};
311
312			if ( element !== this ) {
313				$.data( element, this.widgetFullName, this );
314				this._on( true, this.element, {
315					remove: function( event ) {
316						if ( event.target === element ) {
317							this.destroy();
318						}
319					}
320				} );
321				this.document = $( element.style ?
322
323					// Element within the document
324					element.ownerDocument :
325
326					// Element is window or document
327					element.document || element );
328				this.window = $( this.document[ 0 ].defaultView || this.document[ 0 ].parentWindow );
329			}
330
331			this.options = $.widget.extend( {},
332				this.options,
333				this._getCreateOptions(),
334				options );
335
336			this._create();
337
338			if ( this.options.disabled ) {
339				this._setOptionDisabled( this.options.disabled );
340			}
341
342			this._trigger( "create", null, this._getCreateEventData() );
343			this._init();
344		},
345
346		_getCreateOptions: function() {
347			return {};
348		},
349
350		_getCreateEventData: $.noop,
351
352		_create: $.noop,
353
354		_init: $.noop,
355
356		destroy: function() {
357			var that = this;
358
359			this._destroy();
360			$.each( this.classesElementLookup, function( key, value ) {
361				that._removeClass( value, key );
362			} );
363
364			// We can probably remove the unbind calls in 2.0
365			// all event bindings should go through this._on()
366			this.element
367				.off( this.eventNamespace )
368				.removeData( this.widgetFullName );
369			this.widget()
370				.off( this.eventNamespace )
371				.removeAttr( "aria-disabled" );
372
373			// Clean up events and states
374			this.bindings.off( this.eventNamespace );
375		},
376
377		_destroy: $.noop,
378
379		widget: function() {
380			return this.element;
381		},
382
383		option: function( key, value ) {
384			var options = key;
385			var parts;
386			var curOption;
387			var i;
388
389			if ( arguments.length === 0 ) {
390
391				// Don't return a reference to the internal hash
392				return $.widget.extend( {}, this.options );
393			}
394
395			if ( typeof key === "string" ) {
396
397				// Handle nested keys, e.g., "foo.bar" => { foo: { bar: ___ } }
398				options = {};
399				parts = key.split( "." );
400				key = parts.shift();
401				if ( parts.length ) {
402					curOption = options[ key ] = $.widget.extend( {}, this.options[ key ] );
403					for ( i = 0; i < parts.length - 1; i++ ) {
404						curOption[ parts[ i ] ] = curOption[ parts[ i ] ] || {};
405						curOption = curOption[ parts[ i ] ];
406					}
407					key = parts.pop();
408					if ( arguments.length === 1 ) {
409						return curOption[ key ] === undefined ? null : curOption[ key ];
410					}
411					curOption[ key ] = value;
412				} else {
413					if ( arguments.length === 1 ) {
414						return this.options[ key ] === undefined ? null : this.options[ key ];
415					}
416					options[ key ] = value;
417				}
418			}
419
420			this._setOptions( options );
421
422			return this;
423		},
424
425		_setOptions: function( options ) {
426			var key;
427
428			for ( key in options ) {
429				this._setOption( key, options[ key ] );
430			}
431
432			return this;
433		},
434
435		_setOption: function( key, value ) {
436			if ( key === "classes" ) {
437				this._setOptionClasses( value );
438			}
439
440			this.options[ key ] = value;
441
442			if ( key === "disabled" ) {
443				this._setOptionDisabled( value );
444			}
445
446			return this;
447		},
448
449		_setOptionClasses: function( value ) {
450			var classKey, elements, currentElements;
451
452			for ( classKey in value ) {
453				currentElements = this.classesElementLookup[ classKey ];
454				if ( value[ classKey ] === this.options.classes[ classKey ] ||
455						!currentElements ||
456						!currentElements.length ) {
457					continue;
458				}
459
460				// We are doing this to create a new jQuery object because the _removeClass() call
461				// on the next line is going to destroy the reference to the current elements being
462				// tracked. We need to save a copy of this collection so that we can add the new classes
463				// below.
464				elements = $( currentElements.get() );
465				this._removeClass( currentElements, classKey );
466
467				// We don't use _addClass() here, because that uses this.options.classes
468				// for generating the string of classes. We want to use the value passed in from
469				// _setOption(), this is the new value of the classes option which was passed to
470				// _setOption(). We pass this value directly to _classes().
471				elements.addClass( this._classes( {
472					element: elements,
473					keys: classKey,
474					classes: value,
475					add: true
476				} ) );
477			}
478		},
479
480		_setOptionDisabled: function( value ) {
481			this._toggleClass( this.widget(), this.widgetFullName + "-disabled", null, !!value );
482
483			// If the widget is becoming disabled, then nothing is interactive
484			if ( value ) {
485				this._removeClass( this.hoverable, null, "ui-state-hover" );
486				this._removeClass( this.focusable, null, "ui-state-focus" );
487			}
488		},
489
490		enable: function() {
491			return this._setOptions( { disabled: false } );
492		},
493
494		disable: function() {
495			return this._setOptions( { disabled: true } );
496		},
497
498		_classes: function( options ) {
499			var full = [];
500			var that = this;
501
502			options = $.extend( {
503				element: this.element,
504				classes: this.options.classes || {}
505			}, options );
506
507			function bindRemoveEvent() {
508				var nodesToBind = [];
509
510				options.element.each( function( _, element ) {
511					var isTracked = $.map( that.classesElementLookup, function( elements ) {
512						return elements;
513					} )
514						.some( function( elements ) {
515							return elements.is( element );
516						} );
517
518					if ( !isTracked ) {
519						nodesToBind.push( element );
520					}
521				} );
522
523				that._on( $( nodesToBind ), {
524					remove: "_untrackClassesElement"
525				} );
526			}
527
528			function processClassString( classes, checkOption ) {
529				var current, i;
530				for ( i = 0; i < classes.length; i++ ) {
531					current = that.classesElementLookup[ classes[ i ] ] || $();
532					if ( options.add ) {
533						bindRemoveEvent();
534						current = $( $.uniqueSort( current.get().concat( options.element.get() ) ) );
535					} else {
536						current = $( current.not( options.element ).get() );
537					}
538					that.classesElementLookup[ classes[ i ] ] = current;
539					full.push( classes[ i ] );
540					if ( checkOption && options.classes[ classes[ i ] ] ) {
541						full.push( options.classes[ classes[ i ] ] );
542					}
543				}
544			}
545
546			if ( options.keys ) {
547				processClassString( options.keys.match( /\S+/g ) || [], true );
548			}
549			if ( options.extra ) {
550				processClassString( options.extra.match( /\S+/g ) || [] );
551			}
552
553			return full.join( " " );
554		},
555
556		_untrackClassesElement: function( event ) {
557			var that = this;
558			$.each( that.classesElementLookup, function( key, value ) {
559				if ( $.inArray( event.target, value ) !== -1 ) {
560					that.classesElementLookup[ key ] = $( value.not( event.target ).get() );
561				}
562			} );
563
564			this._off( $( event.target ) );
565		},
566
567		_removeClass: function( element, keys, extra ) {
568			return this._toggleClass( element, keys, extra, false );
569		},
570
571		_addClass: function( element, keys, extra ) {
572			return this._toggleClass( element, keys, extra, true );
573		},
574
575		_toggleClass: function( element, keys, extra, add ) {
576			add = ( typeof add === "boolean" ) ? add : extra;
577			var shift = ( typeof element === "string" || element === null ),
578				options = {
579					extra: shift ? keys : extra,
580					keys: shift ? element : keys,
581					element: shift ? this.element : element,
582					add: add
583				};
584			options.element.toggleClass( this._classes( options ), add );
585			return this;
586		},
587
588		_on: function( suppressDisabledCheck, element, handlers ) {
589			var delegateElement;
590			var instance = this;
591
592			// No suppressDisabledCheck flag, shuffle arguments
593			if ( typeof suppressDisabledCheck !== "boolean" ) {
594				handlers = element;
595				element = suppressDisabledCheck;
596				suppressDisabledCheck = false;
597			}
598
599			// No element argument, shuffle and use this.element
600			if ( !handlers ) {
601				handlers = element;
602				element = this.element;
603				delegateElement = this.widget();
604			} else {
605				element = delegateElement = $( element );
606				this.bindings = this.bindings.add( element );
607			}
608
609			$.each( handlers, function( event, handler ) {
610				function handlerProxy() {
611
612					// Allow widgets to customize the disabled handling
613					// - disabled as an array instead of boolean
614					// - disabled class as method for disabling individual parts
615					if ( !suppressDisabledCheck &&
616							( instance.options.disabled === true ||
617							$( this ).hasClass( "ui-state-disabled" ) ) ) {
618						return;
619					}
620					return ( typeof handler === "string" ? instance[ handler ] : handler )
621						.apply( instance, arguments );
622				}
623
624				// Copy the guid so direct unbinding works
625				if ( typeof handler !== "string" ) {
626					handlerProxy.guid = handler.guid =
627						handler.guid || handlerProxy.guid || $.guid++;
628				}
629
630				var match = event.match( /^([\w:-]*)\s*(.*)$/ );
631				var eventName = match[ 1 ] + instance.eventNamespace;
632				var selector = match[ 2 ];
633
634				if ( selector ) {
635					delegateElement.on( eventName, selector, handlerProxy );
636				} else {
637					element.on( eventName, handlerProxy );
638				}
639			} );
640		},
641
642		_off: function( element, eventName ) {
643			eventName = ( eventName || "" ).split( " " ).join( this.eventNamespace + " " ) +
644				this.eventNamespace;
645			element.off( eventName );
646
647			// Clear the stack to avoid memory leaks (#10056)
648			this.bindings = $( this.bindings.not( element ).get() );
649			this.focusable = $( this.focusable.not( element ).get() );
650			this.hoverable = $( this.hoverable.not( element ).get() );
651		},
652
653		_delay: function( handler, delay ) {
654			function handlerProxy() {
655				return ( typeof handler === "string" ? instance[ handler ] : handler )
656					.apply( instance, arguments );
657			}
658			var instance = this;
659			return setTimeout( handlerProxy, delay || 0 );
660		},
661
662		_hoverable: function( element ) {
663			this.hoverable = this.hoverable.add( element );
664			this._on( element, {
665				mouseenter: function( event ) {
666					this._addClass( $( event.currentTarget ), null, "ui-state-hover" );
667				},
668				mouseleave: function( event ) {
669					this._removeClass( $( event.currentTarget ), null, "ui-state-hover" );
670				}
671			} );
672		},
673
674		_focusable: function( element ) {
675			this.focusable = this.focusable.add( element );
676			this._on( element, {
677				focusin: function( event ) {
678					this._addClass( $( event.currentTarget ), null, "ui-state-focus" );
679				},
680				focusout: function( event ) {
681					this._removeClass( $( event.currentTarget ), null, "ui-state-focus" );
682				}
683			} );
684		},
685
686		_trigger: function( type, event, data ) {
687			var prop, orig;
688			var callback = this.options[ type ];
689
690			data = data || {};
691			event = $.Event( event );
692			event.type = ( type === this.widgetEventPrefix ?
693				type :
694				this.widgetEventPrefix + type ).toLowerCase();
695
696			// The original event may come from any element
697			// so we need to reset the target on the new event
698			event.target = this.element[ 0 ];
699
700			// Copy original event properties over to the new event
701			orig = event.originalEvent;
702			if ( orig ) {
703				for ( prop in orig ) {
704					if ( !( prop in event ) ) {
705						event[ prop ] = orig[ prop ];
706					}
707				}
708			}
709
710			this.element.trigger( event, data );
711			return !( typeof callback === "function" &&
712				callback.apply( this.element[ 0 ], [ event ].concat( data ) ) === false ||
713				event.isDefaultPrevented() );
714		}
715	};
716
717	$.each( { show: "fadeIn", hide: "fadeOut" }, function( method, defaultEffect ) {
718		$.Widget.prototype[ "_" + method ] = function( element, options, callback ) {
719			if ( typeof options === "string" ) {
720				options = { effect: options };
721			}
722
723			var hasOptions;
724			var effectName = !options ?
725				method :
726				options === true || typeof options === "number" ?
727					defaultEffect :
728					options.effect || defaultEffect;
729
730			options = options || {};
731			if ( typeof options === "number" ) {
732				options = { duration: options };
733			} else if ( options === true ) {
734				options = {};
735			}
736
737			hasOptions = !$.isEmptyObject( options );
738			options.complete = callback;
739
740			if ( options.delay ) {
741				element.delay( options.delay );
742			}
743
744			if ( hasOptions && $.effects && $.effects.effect[ effectName ] ) {
745				element[ method ]( options );
746			} else if ( effectName !== method && element[ effectName ] ) {
747				element[ effectName ]( options.duration, options.easing, callback );
748			} else {
749				element.queue( function( next ) {
750					$( this )[ method ]();
751					if ( callback ) {
752						callback.call( element[ 0 ] );
753					}
754					next();
755				} );
756			}
757		};
758	} );
759
760	var widget = $.widget;
761
762
763	/*!
764	 * jQuery UI Position 1.13.2
765	 * http://jqueryui.com
766	 *
767	 * Copyright jQuery Foundation and other contributors
768	 * Released under the MIT license.
769	 * http://jquery.org/license
770	 *
771	 * http://api.jqueryui.com/position/
772	 */
773
774	//>>label: Position
775	//>>group: Core
776	//>>description: Positions elements relative to other elements.
777	//>>docs: http://api.jqueryui.com/position/
778	//>>demos: http://jqueryui.com/position/
779
780
781	( function() {
782	var cachedScrollbarWidth,
783		max = Math.max,
784		abs = Math.abs,
785		rhorizontal = /left|center|right/,
786		rvertical = /top|center|bottom/,
787		roffset = /[\+\-]\d+(\.[\d]+)?%?/,
788		rposition = /^\w+/,
789		rpercent = /%$/,
790		_position = $.fn.position;
791
792	function getOffsets( offsets, width, height ) {
793		return [
794			parseFloat( offsets[ 0 ] ) * ( rpercent.test( offsets[ 0 ] ) ? width / 100 : 1 ),
795			parseFloat( offsets[ 1 ] ) * ( rpercent.test( offsets[ 1 ] ) ? height / 100 : 1 )
796		];
797	}
798
799	function parseCss( element, property ) {
800		return parseInt( $.css( element, property ), 10 ) || 0;
801	}
802
803	function isWindow( obj ) {
804		return obj != null && obj === obj.window;
805	}
806
807	function getDimensions( elem ) {
808		var raw = elem[ 0 ];
809		if ( raw.nodeType === 9 ) {
810			return {
811				width: elem.width(),
812				height: elem.height(),
813				offset: { top: 0, left: 0 }
814			};
815		}
816		if ( isWindow( raw ) ) {
817			return {
818				width: elem.width(),
819				height: elem.height(),
820				offset: { top: elem.scrollTop(), left: elem.scrollLeft() }
821			};
822		}
823		if ( raw.preventDefault ) {
824			return {
825				width: 0,
826				height: 0,
827				offset: { top: raw.pageY, left: raw.pageX }
828			};
829		}
830		return {
831			width: elem.outerWidth(),
832			height: elem.outerHeight(),
833			offset: elem.offset()
834		};
835	}
836
837	$.position = {
838		scrollbarWidth: function() {
839			if ( cachedScrollbarWidth !== undefined ) {
840				return cachedScrollbarWidth;
841			}
842			var w1, w2,
843				div = $( "<div style=" +
844					"'display:block;position:absolute;width:200px;height:200px;overflow:hidden;'>" +
845					"<div style='height:300px;width:auto;'></div></div>" ),
846				innerDiv = div.children()[ 0 ];
847
848			$( "body" ).append( div );
849			w1 = innerDiv.offsetWidth;
850			div.css( "overflow", "scroll" );
851
852			w2 = innerDiv.offsetWidth;
853
854			if ( w1 === w2 ) {
855				w2 = div[ 0 ].clientWidth;
856			}
857
858			div.remove();
859
860			return ( cachedScrollbarWidth = w1 - w2 );
861		},
862		getScrollInfo: function( within ) {
863			var overflowX = within.isWindow || within.isDocument ? "" :
864					within.element.css( "overflow-x" ),
865				overflowY = within.isWindow || within.isDocument ? "" :
866					within.element.css( "overflow-y" ),
867				hasOverflowX = overflowX === "scroll" ||
868					( overflowX === "auto" && within.width < within.element[ 0 ].scrollWidth ),
869				hasOverflowY = overflowY === "scroll" ||
870					( overflowY === "auto" && within.height < within.element[ 0 ].scrollHeight );
871			return {
872				width: hasOverflowY ? $.position.scrollbarWidth() : 0,
873				height: hasOverflowX ? $.position.scrollbarWidth() : 0
874			};
875		},
876		getWithinInfo: function( element ) {
877			var withinElement = $( element || window ),
878				isElemWindow = isWindow( withinElement[ 0 ] ),
879				isDocument = !!withinElement[ 0 ] && withinElement[ 0 ].nodeType === 9,
880				hasOffset = !isElemWindow && !isDocument;
881			return {
882				element: withinElement,
883				isWindow: isElemWindow,
884				isDocument: isDocument,
885				offset: hasOffset ? $( element ).offset() : { left: 0, top: 0 },
886				scrollLeft: withinElement.scrollLeft(),
887				scrollTop: withinElement.scrollTop(),
888				width: withinElement.outerWidth(),
889				height: withinElement.outerHeight()
890			};
891		}
892	};
893
894	$.fn.position = function( options ) {
895		if ( !options || !options.of ) {
896			return _position.apply( this, arguments );
897		}
898
899		// Make a copy, we don't want to modify arguments
900		options = $.extend( {}, options );
901
902		var atOffset, targetWidth, targetHeight, targetOffset, basePosition, dimensions,
903
904			// Make sure string options are treated as CSS selectors
905			target = typeof options.of === "string" ?
906				$( document ).find( options.of ) :
907				$( options.of ),
908
909			within = $.position.getWithinInfo( options.within ),
910			scrollInfo = $.position.getScrollInfo( within ),
911			collision = ( options.collision || "flip" ).split( " " ),
912			offsets = {};
913
914		dimensions = getDimensions( target );
915		if ( target[ 0 ].preventDefault ) {
916
917			// Force left top to allow flipping
918			options.at = "left top";
919		}
920		targetWidth = dimensions.width;
921		targetHeight = dimensions.height;
922		targetOffset = dimensions.offset;
923
924		// Clone to reuse original targetOffset later
925		basePosition = $.extend( {}, targetOffset );
926
927		// Force my and at to have valid horizontal and vertical positions
928		// if a value is missing or invalid, it will be converted to center
929		$.each( [ "my", "at" ], function() {
930			var pos = ( options[ this ] || "" ).split( " " ),
931				horizontalOffset,
932				verticalOffset;
933
934			if ( pos.length === 1 ) {
935				pos = rhorizontal.test( pos[ 0 ] ) ?
936					pos.concat( [ "center" ] ) :
937					rvertical.test( pos[ 0 ] ) ?
938						[ "center" ].concat( pos ) :
939						[ "center", "center" ];
940			}
941			pos[ 0 ] = rhorizontal.test( pos[ 0 ] ) ? pos[ 0 ] : "center";
942			pos[ 1 ] = rvertical.test( pos[ 1 ] ) ? pos[ 1 ] : "center";
943
944			// Calculate offsets
945			horizontalOffset = roffset.exec( pos[ 0 ] );
946			verticalOffset = roffset.exec( pos[ 1 ] );
947			offsets[ this ] = [
948				horizontalOffset ? horizontalOffset[ 0 ] : 0,
949				verticalOffset ? verticalOffset[ 0 ] : 0
950			];
951
952			// Reduce to just the positions without the offsets
953			options[ this ] = [
954				rposition.exec( pos[ 0 ] )[ 0 ],
955				rposition.exec( pos[ 1 ] )[ 0 ]
956			];
957		} );
958
959		// Normalize collision option
960		if ( collision.length === 1 ) {
961			collision[ 1 ] = collision[ 0 ];
962		}
963
964		if ( options.at[ 0 ] === "right" ) {
965			basePosition.left += targetWidth;
966		} else if ( options.at[ 0 ] === "center" ) {
967			basePosition.left += targetWidth / 2;
968		}
969
970		if ( options.at[ 1 ] === "bottom" ) {
971			basePosition.top += targetHeight;
972		} else if ( options.at[ 1 ] === "center" ) {
973			basePosition.top += targetHeight / 2;
974		}
975
976		atOffset = getOffsets( offsets.at, targetWidth, targetHeight );
977		basePosition.left += atOffset[ 0 ];
978		basePosition.top += atOffset[ 1 ];
979
980		return this.each( function() {
981			var collisionPosition, using,
982				elem = $( this ),
983				elemWidth = elem.outerWidth(),
984				elemHeight = elem.outerHeight(),
985				marginLeft = parseCss( this, "marginLeft" ),
986				marginTop = parseCss( this, "marginTop" ),
987				collisionWidth = elemWidth + marginLeft + parseCss( this, "marginRight" ) +
988					scrollInfo.width,
989				collisionHeight = elemHeight + marginTop + parseCss( this, "marginBottom" ) +
990					scrollInfo.height,
991				position = $.extend( {}, basePosition ),
992				myOffset = getOffsets( offsets.my, elem.outerWidth(), elem.outerHeight() );
993
994			if ( options.my[ 0 ] === "right" ) {
995				position.left -= elemWidth;
996			} else if ( options.my[ 0 ] === "center" ) {
997				position.left -= elemWidth / 2;
998			}
999
1000			if ( options.my[ 1 ] === "bottom" ) {
1001				position.top -= elemHeight;
1002			} else if ( options.my[ 1 ] === "center" ) {
1003				position.top -= elemHeight / 2;
1004			}
1005
1006			position.left += myOffset[ 0 ];
1007			position.top += myOffset[ 1 ];
1008
1009			collisionPosition = {
1010				marginLeft: marginLeft,
1011				marginTop: marginTop
1012			};
1013
1014			$.each( [ "left", "top" ], function( i, dir ) {
1015				if ( $.ui.position[ collision[ i ] ] ) {
1016					$.ui.position[ collision[ i ] ][ dir ]( position, {
1017						targetWidth: targetWidth,
1018						targetHeight: targetHeight,
1019						elemWidth: elemWidth,
1020						elemHeight: elemHeight,
1021						collisionPosition: collisionPosition,
1022						collisionWidth: collisionWidth,
1023						collisionHeight: collisionHeight,
1024						offset: [ atOffset[ 0 ] + myOffset[ 0 ], atOffset [ 1 ] + myOffset[ 1 ] ],
1025						my: options.my,
1026						at: options.at,
1027						within: within,
1028						elem: elem
1029					} );
1030				}
1031			} );
1032
1033			if ( options.using ) {
1034
1035				// Adds feedback as second argument to using callback, if present
1036				using = function( props ) {
1037					var left = targetOffset.left - position.left,
1038						right = left + targetWidth - elemWidth,
1039						top = targetOffset.top - position.top,
1040						bottom = top + targetHeight - elemHeight,
1041						feedback = {
1042							target: {
1043								element: target,
1044								left: targetOffset.left,
1045								top: targetOffset.top,
1046								width: targetWidth,
1047								height: targetHeight
1048							},
1049							element: {
1050								element: elem,
1051								left: position.left,
1052								top: position.top,
1053								width: elemWidth,
1054								height: elemHeight
1055							},
1056							horizontal: right < 0 ? "left" : left > 0 ? "right" : "center",
1057							vertical: bottom < 0 ? "top" : top > 0 ? "bottom" : "middle"
1058						};
1059					if ( targetWidth < elemWidth && abs( left + right ) < targetWidth ) {
1060						feedback.horizontal = "center";
1061					}
1062					if ( targetHeight < elemHeight && abs( top + bottom ) < targetHeight ) {
1063						feedback.vertical = "middle";
1064					}
1065					if ( max( abs( left ), abs( right ) ) > max( abs( top ), abs( bottom ) ) ) {
1066						feedback.important = "horizontal";
1067					} else {
1068						feedback.important = "vertical";
1069					}
1070					options.using.call( this, props, feedback );
1071				};
1072			}
1073
1074			elem.offset( $.extend( position, { using: using } ) );
1075		} );
1076	};
1077
1078	$.ui.position = {
1079		fit: {
1080			left: function( position, data ) {
1081				var within = data.within,
1082					withinOffset = within.isWindow ? within.scrollLeft : within.offset.left,
1083					outerWidth = within.width,
1084					collisionPosLeft = position.left - data.collisionPosition.marginLeft,
1085					overLeft = withinOffset - collisionPosLeft,
1086					overRight = collisionPosLeft + data.collisionWidth - outerWidth - withinOffset,
1087					newOverRight;
1088
1089				// Element is wider than within
1090				if ( data.collisionWidth > outerWidth ) {
1091
1092					// Element is initially over the left side of within
1093					if ( overLeft > 0 && overRight <= 0 ) {
1094						newOverRight = position.left + overLeft + data.collisionWidth - outerWidth -
1095							withinOffset;
1096						position.left += overLeft - newOverRight;
1097
1098					// Element is initially over right side of within
1099					} else if ( overRight > 0 && overLeft <= 0 ) {
1100						position.left = withinOffset;
1101
1102					// Element is initially over both left and right sides of within
1103					} else {
1104						if ( overLeft > overRight ) {
1105							position.left = withinOffset + outerWidth - data.collisionWidth;
1106						} else {
1107							position.left = withinOffset;
1108						}
1109					}
1110
1111				// Too far left -> align with left edge
1112				} else if ( overLeft > 0 ) {
1113					position.left += overLeft;
1114
1115				// Too far right -> align with right edge
1116				} else if ( overRight > 0 ) {
1117					position.left -= overRight;
1118
1119				// Adjust based on position and margin
1120				} else {
1121					position.left = max( position.left - collisionPosLeft, position.left );
1122				}
1123			},
1124			top: function( position, data ) {
1125				var within = data.within,
1126					withinOffset = within.isWindow ? within.scrollTop : within.offset.top,
1127					outerHeight = data.within.height,
1128					collisionPosTop = position.top - data.collisionPosition.marginTop,
1129					overTop = withinOffset - collisionPosTop,
1130					overBottom = collisionPosTop + data.collisionHeight - outerHeight - withinOffset,
1131					newOverBottom;
1132
1133				// Element is taller than within
1134				if ( data.collisionHeight > outerHeight ) {
1135
1136					// Element is initially over the top of within
1137					if ( overTop > 0 && overBottom <= 0 ) {
1138						newOverBottom = position.top + overTop + data.collisionHeight - outerHeight -
1139							withinOffset;
1140						position.top += overTop - newOverBottom;
1141
1142					// Element is initially over bottom of within
1143					} else if ( overBottom > 0 && overTop <= 0 ) {
1144						position.top = withinOffset;
1145
1146					// Element is initially over both top and bottom of within
1147					} else {
1148						if ( overTop > overBottom ) {
1149							position.top = withinOffset + outerHeight - data.collisionHeight;
1150						} else {
1151							position.top = withinOffset;
1152						}
1153					}
1154
1155				// Too far up -> align with top
1156				} else if ( overTop > 0 ) {
1157					position.top += overTop;
1158
1159				// Too far down -> align with bottom edge
1160				} else if ( overBottom > 0 ) {
1161					position.top -= overBottom;
1162
1163				// Adjust based on position and margin
1164				} else {
1165					position.top = max( position.top - collisionPosTop, position.top );
1166				}
1167			}
1168		},
1169		flip: {
1170			left: function( position, data ) {
1171				var within = data.within,
1172					withinOffset = within.offset.left + within.scrollLeft,
1173					outerWidth = within.width,
1174					offsetLeft = within.isWindow ? within.scrollLeft : within.offset.left,
1175					collisionPosLeft = position.left - data.collisionPosition.marginLeft,
1176					overLeft = collisionPosLeft - offsetLeft,
1177					overRight = collisionPosLeft + data.collisionWidth - outerWidth - offsetLeft,
1178					myOffset = data.my[ 0 ] === "left" ?
1179						-data.elemWidth :
1180						data.my[ 0 ] === "right" ?
1181							data.elemWidth :
1182							0,
1183					atOffset = data.at[ 0 ] === "left" ?
1184						data.targetWidth :
1185						data.at[ 0 ] === "right" ?
1186							-data.targetWidth :
1187							0,
1188					offset = -2 * data.offset[ 0 ],
1189					newOverRight,
1190					newOverLeft;
1191
1192				if ( overLeft < 0 ) {
1193					newOverRight = position.left + myOffset + atOffset + offset + data.collisionWidth -
1194						outerWidth - withinOffset;
1195					if ( newOverRight < 0 || newOverRight < abs( overLeft ) ) {
1196						position.left += myOffset + atOffset + offset;
1197					}
1198				} else if ( overRight > 0 ) {
1199					newOverLeft = position.left - data.collisionPosition.marginLeft + myOffset +
1200						atOffset + offset - offsetLeft;
1201					if ( newOverLeft > 0 || abs( newOverLeft ) < overRight ) {
1202						position.left += myOffset + atOffset + offset;
1203					}
1204				}
1205			},
1206			top: function( position, data ) {
1207				var within = data.within,
1208					withinOffset = within.offset.top + within.scrollTop,
1209					outerHeight = within.height,
1210					offsetTop = within.isWindow ? within.scrollTop : within.offset.top,
1211					collisionPosTop = position.top - data.collisionPosition.marginTop,
1212					overTop = collisionPosTop - offsetTop,
1213					overBottom = collisionPosTop + data.collisionHeight - outerHeight - offsetTop,
1214					top = data.my[ 1 ] === "top",
1215					myOffset = top ?
1216						-data.elemHeight :
1217						data.my[ 1 ] === "bottom" ?
1218							data.elemHeight :
1219							0,
1220					atOffset = data.at[ 1 ] === "top" ?
1221						data.targetHeight :
1222						data.at[ 1 ] === "bottom" ?
1223							-data.targetHeight :
1224							0,
1225					offset = -2 * data.offset[ 1 ],
1226					newOverTop,
1227					newOverBottom;
1228				if ( overTop < 0 ) {
1229					newOverBottom = position.top + myOffset + atOffset + offset + data.collisionHeight -
1230						outerHeight - withinOffset;
1231					if ( newOverBottom < 0 || newOverBottom < abs( overTop ) ) {
1232						position.top += myOffset + atOffset + offset;
1233					}
1234				} else if ( overBottom > 0 ) {
1235					newOverTop = position.top - data.collisionPosition.marginTop + myOffset + atOffset +
1236						offset - offsetTop;
1237					if ( newOverTop > 0 || abs( newOverTop ) < overBottom ) {
1238						position.top += myOffset + atOffset + offset;
1239					}
1240				}
1241			}
1242		},
1243		flipfit: {
1244			left: function() {
1245				$.ui.position.flip.left.apply( this, arguments );
1246				$.ui.position.fit.left.apply( this, arguments );
1247			},
1248			top: function() {
1249				$.ui.position.flip.top.apply( this, arguments );
1250				$.ui.position.fit.top.apply( this, arguments );
1251			}
1252		}
1253	};
1254
1255	} )();
1256
1257	var position = $.ui.position;
1258
1259
1260	/*!
1261	 * jQuery UI Support for jQuery core 1.8.x and newer 1.13.2
1262	 * http://jqueryui.com
1263	 *
1264	 * Copyright jQuery Foundation and other contributors
1265	 * Released under the MIT license.
1266	 * http://jquery.org/license
1267	 *
1268	 */
1269
1270	//>>label: jQuery 1.8+ Support
1271	//>>group: Core
1272	//>>description: Support version 1.8.x and newer of jQuery core
1273
1274
1275	// Support: jQuery 1.9.x or older
1276	// $.expr[ ":" ] is deprecated.
1277	if ( !$.expr.pseudos ) {
1278		$.expr.pseudos = $.expr[ ":" ];
1279	}
1280
1281	// Support: jQuery 1.11.x or older
1282	// $.unique has been renamed to $.uniqueSort
1283	if ( !$.uniqueSort ) {
1284		$.uniqueSort = $.unique;
1285	}
1286
1287	// Support: jQuery 2.2.x or older.
1288	// This method has been defined in jQuery 3.0.0.
1289	// Code from https://github.com/jquery/jquery/blob/e539bac79e666bba95bba86d690b4e609dca2286/src/selector/escapeSelector.js
1290	if ( !$.escapeSelector ) {
1291
1292		// CSS string/identifier serialization
1293		// https://drafts.csswg.org/cssom/#common-serializing-idioms
1294		var rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\x80-\uFFFF\w-]/g;
1295
1296		var fcssescape = function( ch, asCodePoint ) {
1297			if ( asCodePoint ) {
1298
1299				// U+0000 NULL becomes U+FFFD REPLACEMENT CHARACTER
1300				if ( ch === "\0" ) {
1301					return "\uFFFD";
1302				}
1303
1304				// Control characters and (dependent upon position) numbers get escaped as code points
1305				return ch.slice( 0, -1 ) + "\\" + ch.charCodeAt( ch.length - 1 ).toString( 16 ) + " ";
1306			}
1307
1308			// Other potentially-special ASCII characters get backslash-escaped
1309			return "\\" + ch;
1310		};
1311
1312		$.escapeSelector = function( sel ) {
1313			return ( sel + "" ).replace( rcssescape, fcssescape );
1314		};
1315	}
1316
1317	// Support: jQuery 3.4.x or older
1318	// These methods have been defined in jQuery 3.5.0.
1319	if ( !$.fn.even || !$.fn.odd ) {
1320		$.fn.extend( {
1321			even: function() {
1322				return this.filter( function( i ) {
1323					return i % 2 === 0;
1324				} );
1325			},
1326			odd: function() {
1327				return this.filter( function( i ) {
1328					return i % 2 === 1;
1329				} );
1330			}
1331		} );
1332	}
1333
1334	;
1335	/*!
1336	 * jQuery UI Keycode 1.13.2
1337	 * http://jqueryui.com
1338	 *
1339	 * Copyright jQuery Foundation and other contributors
1340	 * Released under the MIT license.
1341	 * http://jquery.org/license
1342	 */
1343
1344	//>>label: Keycode
1345	//>>group: Core
1346	//>>description: Provide keycodes as keynames
1347	//>>docs: http://api.jqueryui.com/jQuery.ui.keyCode/
1348
1349
1350	var keycode = $.ui.keyCode = {
1351		BACKSPACE: 8,
1352		COMMA: 188,
1353		DELETE: 46,
1354		DOWN: 40,
1355		END: 35,
1356		ENTER: 13,
1357		ESCAPE: 27,
1358		HOME: 36,
1359		LEFT: 37,
1360		PAGE_DOWN: 34,
1361		PAGE_UP: 33,
1362		PERIOD: 190,
1363		RIGHT: 39,
1364		SPACE: 32,
1365		TAB: 9,
1366		UP: 38
1367	};
1368
1369
1370	/*!
1371	 * jQuery UI Scroll Parent 1.13.2
1372	 * http://jqueryui.com
1373	 *
1374	 * Copyright jQuery Foundation and other contributors
1375	 * Released under the MIT license.
1376	 * http://jquery.org/license
1377	 */
1378
1379	//>>label: scrollParent
1380	//>>group: Core
1381	//>>description: Get the closest ancestor element that is scrollable.
1382	//>>docs: http://api.jqueryui.com/scrollParent/
1383
1384
1385	var scrollParent = $.fn.scrollParent = function( includeHidden ) {
1386		var position = this.css( "position" ),
1387			excludeStaticParent = position === "absolute",
1388			overflowRegex = includeHidden ? /(auto|scroll|hidden)/ : /(auto|scroll)/,
1389			scrollParent = this.parents().filter( function() {
1390				var parent = $( this );
1391				if ( excludeStaticParent && parent.css( "position" ) === "static" ) {
1392					return false;
1393				}
1394				return overflowRegex.test( parent.css( "overflow" ) + parent.css( "overflow-y" ) +
1395					parent.css( "overflow-x" ) );
1396			} ).eq( 0 );
1397
1398		return position === "fixed" || !scrollParent.length ?
1399			$( this[ 0 ].ownerDocument || document ) :
1400			scrollParent;
1401	};
1402
1403
1404	/*!
1405	 * jQuery UI Unique ID 1.13.2
1406	 * http://jqueryui.com
1407	 *
1408	 * Copyright jQuery Foundation and other contributors
1409	 * Released under the MIT license.
1410	 * http://jquery.org/license
1411	 */
1412
1413	//>>label: uniqueId
1414	//>>group: Core
1415	//>>description: Functions to generate and remove uniqueId's
1416	//>>docs: http://api.jqueryui.com/uniqueId/
1417
1418
1419	var uniqueId = $.fn.extend( {
1420		uniqueId: ( function() {
1421			var uuid = 0;
1422
1423			return function() {
1424				return this.each( function() {
1425					if ( !this.id ) {
1426						this.id = "ui-id-" + ( ++uuid );
1427					}
1428				} );
1429			};
1430		} )(),
1431
1432		removeUniqueId: function() {
1433			return this.each( function() {
1434				if ( /^ui-id-\d+$/.test( this.id ) ) {
1435					$( this ).removeAttr( "id" );
1436				}
1437			} );
1438		}
1439	} );
1440
1441
1442
1443
1444// NOTE: Original jQuery UI wrapper was replaced. See README-Fancytree.md
1445// }));
1446})(jQuery);
1447
1448(function( factory ) {
1449	if ( typeof define === "function" && define.amd ) {
1450		// AMD. Register as an anonymous module.
1451		define( [ "jquery" ], factory );
1452	} else if ( typeof module === "object" && module.exports ) {
1453		// Node/CommonJS
1454		module.exports = factory(require("jquery"));
1455	} else {
1456		// Browser globals
1457		factory( jQuery );
1458	}
1459}(function( $ ) {
1460
1461
1462/*! Fancytree Core *//*!
1463 * jquery.fancytree.js
1464 * Tree view control with support for lazy loading and much more.
1465 * https://github.com/mar10/fancytree/
1466 *
1467 * Copyright (c) 2008-2023, Martin Wendt (https://wwWendt.de)
1468 * Released under the MIT license
1469 * https://github.com/mar10/fancytree/wiki/LicenseInfo
1470 *
1471 * @version 2.38.3
1472 * @date 2023-02-01T20:52:50Z
1473 */
1474
1475/** Core Fancytree module.
1476 */
1477
1478// UMD wrapper for the Fancytree core module
1479(function (factory) {
1480	if (typeof define === "function" && define.amd) {
1481		// AMD. Register as an anonymous module.
1482		define(["jquery", "./jquery.fancytree.ui-deps"], factory);
1483	} else if (typeof module === "object" && module.exports) {
1484		// Node/CommonJS
1485		require("./jquery.fancytree.ui-deps");
1486		module.exports = factory(require("jquery"));
1487	} else {
1488		// Browser globals
1489		factory(jQuery);
1490	}
1491})(function ($) {
1492	"use strict";
1493
1494	// prevent duplicate loading
1495	if ($.ui && $.ui.fancytree) {
1496		$.ui.fancytree.warn("Fancytree: ignored duplicate include");
1497		return;
1498	}
1499
1500	/******************************************************************************
1501	 * Private functions and variables
1502	 */
1503
1504	var i,
1505		attr,
1506		FT = null, // initialized below
1507		TEST_IMG = new RegExp(/\.|\//), // strings are considered image urls if they contain '.' or '/'
1508		REX_HTML = /[&<>"'/]/g, // Escape those characters
1509		REX_TOOLTIP = /[<>"'/]/g, // Don't escape `&` in tooltips
1510		RECURSIVE_REQUEST_ERROR = "$recursive_request",
1511		INVALID_REQUEST_TARGET_ERROR = "$request_target_invalid",
1512		ENTITY_MAP = {
1513			"&": "&amp;",
1514			"<": "&lt;",
1515			">": "&gt;",
1516			'"': "&quot;",
1517			"'": "&#39;",
1518			"/": "&#x2F;",
1519		},
1520		IGNORE_KEYCODES = { 16: true, 17: true, 18: true },
1521		SPECIAL_KEYCODES = {
1522			8: "backspace",
1523			9: "tab",
1524			10: "return",
1525			13: "return",
1526			// 16: null, 17: null, 18: null,  // ignore shift, ctrl, alt
1527			19: "pause",
1528			20: "capslock",
1529			27: "esc",
1530			32: "space",
1531			33: "pageup",
1532			34: "pagedown",
1533			35: "end",
1534			36: "home",
1535			37: "left",
1536			38: "up",
1537			39: "right",
1538			40: "down",
1539			45: "insert",
1540			46: "del",
1541			59: ";",
1542			61: "=",
1543			// 91: null, 93: null,  // ignore left and right meta
1544			96: "0",
1545			97: "1",
1546			98: "2",
1547			99: "3",
1548			100: "4",
1549			101: "5",
1550			102: "6",
1551			103: "7",
1552			104: "8",
1553			105: "9",
1554			106: "*",
1555			107: "+",
1556			109: "-",
1557			110: ".",
1558			111: "/",
1559			112: "f1",
1560			113: "f2",
1561			114: "f3",
1562			115: "f4",
1563			116: "f5",
1564			117: "f6",
1565			118: "f7",
1566			119: "f8",
1567			120: "f9",
1568			121: "f10",
1569			122: "f11",
1570			123: "f12",
1571			144: "numlock",
1572			145: "scroll",
1573			173: "-",
1574			186: ";",
1575			187: "=",
1576			188: ",",
1577			189: "-",
1578			190: ".",
1579			191: "/",
1580			192: "`",
1581			219: "[",
1582			220: "\\",
1583			221: "]",
1584			222: "'",
1585		},
1586		MODIFIERS = {
1587			16: "shift",
1588			17: "ctrl",
1589			18: "alt",
1590			91: "meta",
1591			93: "meta",
1592		},
1593		MOUSE_BUTTONS = { 0: "", 1: "left", 2: "middle", 3: "right" },
1594		// Boolean attributes that can be set with equivalent class names in the LI tags
1595		// Note: v2.23: checkbox and hideCheckbox are *not* in this list
1596		CLASS_ATTRS =
1597			"active expanded focus folder lazy radiogroup selected unselectable unselectableIgnore".split(
1598				" "
1599			),
1600		CLASS_ATTR_MAP = {},
1601		// Top-level Fancytree attributes, that can be set by dict
1602		TREE_ATTRS = "columns types".split(" "),
1603		// TREE_ATTR_MAP = {},
1604		// Top-level FancytreeNode attributes, that can be set by dict
1605		NODE_ATTRS =
1606			"checkbox expanded extraClasses folder icon iconTooltip key lazy partsel radiogroup refKey selected statusNodeType title tooltip type unselectable unselectableIgnore unselectableStatus".split(
1607				" "
1608			),
1609		NODE_ATTR_MAP = {},
1610		// Mapping of lowercase -> real name (because HTML5 data-... attribute only supports lowercase)
1611		NODE_ATTR_LOWERCASE_MAP = {},
1612		// Attribute names that should NOT be added to node.data
1613		NONE_NODE_DATA_MAP = {
1614			active: true,
1615			children: true,
1616			data: true,
1617			focus: true,
1618		};
1619
1620	for (i = 0; i < CLASS_ATTRS.length; i++) {
1621		CLASS_ATTR_MAP[CLASS_ATTRS[i]] = true;
1622	}
1623	for (i = 0; i < NODE_ATTRS.length; i++) {
1624		attr = NODE_ATTRS[i];
1625		NODE_ATTR_MAP[attr] = true;
1626		if (attr !== attr.toLowerCase()) {
1627			NODE_ATTR_LOWERCASE_MAP[attr.toLowerCase()] = attr;
1628		}
1629	}
1630	// for(i=0; i<TREE_ATTRS.length; i++) {
1631	// 	TREE_ATTR_MAP[TREE_ATTRS[i]] = true;
1632	// }
1633
1634	function _assert(cond, msg) {
1635		// TODO: see qunit.js extractStacktrace()
1636		if (!cond) {
1637			msg = msg ? ": " + msg : "";
1638			msg = "Fancytree assertion failed" + msg;
1639
1640			// consoleApply("assert", [!!cond, msg]);
1641
1642			// #1041: Raised exceptions may not be visible in the browser
1643			// console if inside promise chains, so we also print directly:
1644			$.ui.fancytree.error(msg);
1645
1646			// Throw exception:
1647			$.error(msg);
1648		}
1649	}
1650
1651	function _hasProp(object, property) {
1652		return Object.prototype.hasOwnProperty.call(object, property);
1653	}
1654
1655	/* Replacement for the deprecated `jQuery.isFunction()`. */
1656	function _isFunction(obj) {
1657		return typeof obj === "function";
1658	}
1659
1660	/* Replacement for the deprecated `jQuery.trim()`. */
1661	function _trim(text) {
1662		return text == null ? "" : text.trim();
1663	}
1664
1665	/* Replacement for the deprecated `jQuery.isArray()`. */
1666	var _isArray = Array.isArray;
1667
1668	_assert($.ui, "Fancytree requires jQuery UI (http://jqueryui.com)");
1669
1670	function consoleApply(method, args) {
1671		var i,
1672			s,
1673			fn = window.console ? window.console[method] : null;
1674
1675		if (fn) {
1676			try {
1677				fn.apply(window.console, args);
1678			} catch (e) {
1679				// IE 8?
1680				s = "";
1681				for (i = 0; i < args.length; i++) {
1682					s += args[i];
1683				}
1684				fn(s);
1685			}
1686		}
1687	}
1688
1689	/* support: IE8 Polyfil for Date.now() */
1690	if (!Date.now) {
1691		Date.now = function now() {
1692			return new Date().getTime();
1693		};
1694	}
1695
1696	/*Return true if x is a FancytreeNode.*/
1697	function _isNode(x) {
1698		return !!(x.tree && x.statusNodeType !== undefined);
1699	}
1700
1701	/** Return true if dotted version string is equal or higher than requested version.
1702	 *
1703	 * See http://jsfiddle.net/mar10/FjSAN/
1704	 */
1705	function isVersionAtLeast(dottedVersion, major, minor, patch) {
1706		var i,
1707			v,
1708			t,
1709			verParts = $.map(_trim(dottedVersion).split("."), function (e) {
1710				return parseInt(e, 10);
1711			}),
1712			testParts = $.map(
1713				Array.prototype.slice.call(arguments, 1),
1714				function (e) {
1715					return parseInt(e, 10);
1716				}
1717			);
1718
1719		for (i = 0; i < testParts.length; i++) {
1720			v = verParts[i] || 0;
1721			t = testParts[i] || 0;
1722			if (v !== t) {
1723				return v > t;
1724			}
1725		}
1726		return true;
1727	}
1728
1729	/**
1730	 * Deep-merge a list of objects (but replace array-type options).
1731	 *
1732	 * jQuery's $.extend(true, ...) method does a deep merge, that also merges Arrays.
1733	 * This variant is used to merge extension defaults with user options, and should
1734	 * merge objects, but override arrays (for example the `triggerStart: [...]` option
1735	 * of ext-edit). Also `null` values are copied over and not skipped.
1736	 *
1737	 * See issue #876
1738	 *
1739	 * Example:
1740	 * _simpleDeepMerge({}, o1, o2);
1741	 */
1742	function _simpleDeepMerge() {
1743		var options,
1744			name,
1745			src,
1746			copy,
1747			clone,
1748			target = arguments[0] || {},
1749			i = 1,
1750			length = arguments.length;
1751
1752		// Handle case when target is a string or something (possible in deep copy)
1753		if (typeof target !== "object" && !_isFunction(target)) {
1754			target = {};
1755		}
1756		if (i === length) {
1757			throw Error("need at least two args");
1758		}
1759		for (; i < length; i++) {
1760			// Only deal with non-null/undefined values
1761			if ((options = arguments[i]) != null) {
1762				// Extend the base object
1763				for (name in options) {
1764					if (_hasProp(options, name)) {
1765						src = target[name];
1766						copy = options[name];
1767						// Prevent never-ending loop
1768						if (target === copy) {
1769							continue;
1770						}
1771						// Recurse if we're merging plain objects
1772						// (NOTE: unlike $.extend, we don't merge arrays, but replace them)
1773						if (copy && $.isPlainObject(copy)) {
1774							clone = src && $.isPlainObject(src) ? src : {};
1775							// Never move original objects, clone them
1776							target[name] = _simpleDeepMerge(clone, copy);
1777							// Don't bring in undefined values
1778						} else if (copy !== undefined) {
1779							target[name] = copy;
1780						}
1781					}
1782				}
1783			}
1784		}
1785		// Return the modified object
1786		return target;
1787	}
1788
1789	/** Return a wrapper that calls sub.methodName() and exposes
1790	 *  this             : tree
1791	 *  this._local      : tree.ext.EXTNAME
1792	 *  this._super      : base.methodName.call()
1793	 *  this._superApply : base.methodName.apply()
1794	 */
1795	function _makeVirtualFunction(methodName, tree, base, extension, extName) {
1796		// $.ui.fancytree.debug("_makeVirtualFunction", methodName, tree, base, extension, extName);
1797		// if(rexTestSuper && !rexTestSuper.test(func)){
1798		//     // extension.methodName() doesn't call _super(), so no wrapper required
1799		//     return func;
1800		// }
1801		// Use an immediate function as closure
1802		var proxy = (function () {
1803			var prevFunc = tree[methodName], // org. tree method or prev. proxy
1804				baseFunc = extension[methodName], //
1805				_local = tree.ext[extName],
1806				_super = function () {
1807					return prevFunc.apply(tree, arguments);
1808				},
1809				_superApply = function (args) {
1810					return prevFunc.apply(tree, args);
1811				};
1812
1813			// Return the wrapper function
1814			return function () {
1815				var prevLocal = tree._local,
1816					prevSuper = tree._super,
1817					prevSuperApply = tree._superApply;
1818
1819				try {
1820					tree._local = _local;
1821					tree._super = _super;
1822					tree._superApply = _superApply;
1823					return baseFunc.apply(tree, arguments);
1824				} finally {
1825					tree._local = prevLocal;
1826					tree._super = prevSuper;
1827					tree._superApply = prevSuperApply;
1828				}
1829			};
1830		})(); // end of Immediate Function
1831		return proxy;
1832	}
1833
1834	/**
1835	 * Subclass `base` by creating proxy functions
1836	 */
1837	function _subclassObject(tree, base, extension, extName) {
1838		// $.ui.fancytree.debug("_subclassObject", tree, base, extension, extName);
1839		for (var attrName in extension) {
1840			if (typeof extension[attrName] === "function") {
1841				if (typeof tree[attrName] === "function") {
1842					// override existing method
1843					tree[attrName] = _makeVirtualFunction(
1844						attrName,
1845						tree,
1846						base,
1847						extension,
1848						extName
1849					);
1850				} else if (attrName.charAt(0) === "_") {
1851					// Create private methods in tree.ext.EXTENSION namespace
1852					tree.ext[extName][attrName] = _makeVirtualFunction(
1853						attrName,
1854						tree,
1855						base,
1856						extension,
1857						extName
1858					);
1859				} else {
1860					$.error(
1861						"Could not override tree." +
1862							attrName +
1863							". Use prefix '_' to create tree." +
1864							extName +
1865							"._" +
1866							attrName
1867					);
1868				}
1869			} else {
1870				// Create member variables in tree.ext.EXTENSION namespace
1871				if (attrName !== "options") {
1872					tree.ext[extName][attrName] = extension[attrName];
1873				}
1874			}
1875		}
1876	}
1877
1878	function _getResolvedPromise(context, argArray) {
1879		if (context === undefined) {
1880			return $.Deferred(function () {
1881				this.resolve();
1882			}).promise();
1883		}
1884		return $.Deferred(function () {
1885			this.resolveWith(context, argArray);
1886		}).promise();
1887	}
1888
1889	function _getRejectedPromise(context, argArray) {
1890		if (context === undefined) {
1891			return $.Deferred(function () {
1892				this.reject();
1893			}).promise();
1894		}
1895		return $.Deferred(function () {
1896			this.rejectWith(context, argArray);
1897		}).promise();
1898	}
1899
1900	function _makeResolveFunc(deferred, context) {
1901		return function () {
1902			deferred.resolveWith(context);
1903		};
1904	}
1905
1906	function _getElementDataAsDict($el) {
1907		// Evaluate 'data-NAME' attributes with special treatment for 'data-json'.
1908		var d = $.extend({}, $el.data()),
1909			json = d.json;
1910
1911		delete d.fancytree; // added to container by widget factory (old jQuery UI)
1912		delete d.uiFancytree; // added to container by widget factory
1913
1914		if (json) {
1915			delete d.json;
1916			// <li data-json='...'> is already returned as object (http://api.jquery.com/data/#data-html5)
1917			d = $.extend(d, json);
1918		}
1919		return d;
1920	}
1921
1922	function _escapeTooltip(s) {
1923		return ("" + s).replace(REX_TOOLTIP, function (s) {
1924			return ENTITY_MAP[s];
1925		});
1926	}
1927
1928	// TODO: use currying
1929	function _makeNodeTitleMatcher(s) {
1930		s = s.toLowerCase();
1931		return function (node) {
1932			return node.title.toLowerCase().indexOf(s) >= 0;
1933		};
1934	}
1935
1936	function _makeNodeTitleStartMatcher(s) {
1937		var reMatch = new RegExp("^" + s, "i");
1938		return function (node) {
1939			return reMatch.test(node.title);
1940		};
1941	}
1942
1943	/******************************************************************************
1944	 * FancytreeNode
1945	 */
1946
1947	/**
1948	 * Creates a new FancytreeNode
1949	 *
1950	 * @class FancytreeNode
1951	 * @classdesc A FancytreeNode represents the hierarchical data model and operations.
1952	 *
1953	 * @param {FancytreeNode} parent
1954	 * @param {NodeData} obj
1955	 *
1956	 * @property {Fancytree} tree The tree instance
1957	 * @property {FancytreeNode} parent The parent node
1958	 * @property {string} key Node id (must be unique inside the tree)
1959	 * @property {string} title Display name (may contain HTML)
1960	 * @property {object} data Contains all extra data that was passed on node creation
1961	 * @property {FancytreeNode[] | null | undefined} children Array of child nodes.<br>
1962	 *     For lazy nodes, null or undefined means 'not yet loaded'. Use an empty array
1963	 *     to define a node that has no children.
1964	 * @property {boolean} expanded Use isExpanded(), setExpanded() to access this property.
1965	 * @property {string} extraClasses Additional CSS classes, added to the node's `<span>`.<br>
1966	 *     Note: use `node.add/remove/toggleClass()` to modify.
1967	 * @property {boolean} folder Folder nodes have different default icons and click behavior.<br>
1968	 *     Note: Also non-folders may have children.
1969	 * @property {string} statusNodeType null for standard nodes. Otherwise type of special system node: 'error', 'loading', 'nodata', or 'paging'.
1970	 * @property {boolean} lazy True if this node is loaded on demand, i.e. on first expansion.
1971	 * @property {boolean} selected Use isSelected(), setSelected() to access this property.
1972	 * @property {string} tooltip Alternative description used as hover popup
1973	 * @property {string} iconTooltip Description used as hover popup for icon. @since 2.27
1974	 * @property {string} type Node type, used with tree.types map. @since 2.27
1975	 */
1976	function FancytreeNode(parent, obj) {
1977		var i, l, name, cl;
1978
1979		this.parent = parent;
1980		this.tree = parent.tree;
1981		this.ul = null;
1982		this.li = null; // <li id='key' ftnode=this> tag
1983		this.statusNodeType = null; // if this is a temp. node to display the status of its parent
1984		this._isLoading = false; // if this node itself is loading
1985		this._error = null; // {message: '...'} if a load error occurred
1986		this.data = {};
1987
1988		// TODO: merge this code with node.toDict()
1989		// copy attributes from obj object
1990		for (i = 0, l = NODE_ATTRS.length; i < l; i++) {
1991			name = NODE_ATTRS[i];
1992			this[name] = obj[name];
1993		}
1994		// unselectableIgnore and unselectableStatus imply unselectable
1995		if (
1996			this.unselectableIgnore != null ||
1997			this.unselectableStatus != null
1998		) {
1999			this.unselectable = true;
2000		}
2001		if (obj.hideCheckbox) {
2002			$.error(
2003				"'hideCheckbox' node option was removed in v2.23.0: use 'checkbox: false'"
2004			);
2005		}
2006		// node.data += obj.data
2007		if (obj.data) {
2008			$.extend(this.data, obj.data);
2009		}
2010		// Copy all other attributes to this.data.NAME
2011		for (name in obj) {
2012			if (
2013				!NODE_ATTR_MAP[name] &&
2014				(this.tree.options.copyFunctionsToData ||
2015					!_isFunction(obj[name])) &&
2016				!NONE_NODE_DATA_MAP[name]
2017			) {
2018				// node.data.NAME = obj.NAME
2019				this.data[name] = obj[name];
2020			}
2021		}
2022
2023		// Fix missing key
2024		if (this.key == null) {
2025			// test for null OR undefined
2026			if (this.tree.options.defaultKey) {
2027				this.key = "" + this.tree.options.defaultKey(this);
2028				_assert(this.key, "defaultKey() must return a unique key");
2029			} else {
2030				this.key = "_" + FT._nextNodeKey++;
2031			}
2032		} else {
2033			this.key = "" + this.key; // Convert to string (#217)
2034		}
2035
2036		// Fix tree.activeNode
2037		// TODO: not elegant: we use obj.active as marker to set tree.activeNode
2038		// when loading from a dictionary.
2039		if (obj.active) {
2040			_assert(
2041				this.tree.activeNode === null,
2042				"only one active node allowed"
2043			);
2044			this.tree.activeNode = this;
2045		}
2046		if (obj.selected) {
2047			// #186
2048			this.tree.lastSelectedNode = this;
2049		}
2050		// TODO: handle obj.focus = true
2051
2052		// Create child nodes
2053		cl = obj.children;
2054		if (cl) {
2055			if (cl.length) {
2056				this._setChildren(cl);
2057			} else {
2058				// if an empty array was passed for a lazy node, keep it, in order to mark it 'loaded'
2059				this.children = this.lazy ? [] : null;
2060			}
2061		} else {
2062			this.children = null;
2063		}
2064		// Add to key/ref map (except for root node)
2065		//	if( parent ) {
2066		this.tree._callHook("treeRegisterNode", this.tree, true, this);
2067		//	}
2068	}
2069
2070	FancytreeNode.prototype = /** @lends FancytreeNode# */ {
2071		/* Return the direct child FancytreeNode with a given key, index. */
2072		_findDirectChild: function (ptr) {
2073			var i,
2074				l,
2075				cl = this.children;
2076
2077			if (cl) {
2078				if (typeof ptr === "string") {
2079					for (i = 0, l = cl.length; i < l; i++) {
2080						if (cl[i].key === ptr) {
2081							return cl[i];
2082						}
2083					}
2084				} else if (typeof ptr === "number") {
2085					return this.children[ptr];
2086				} else if (ptr.parent === this) {
2087					return ptr;
2088				}
2089			}
2090			return null;
2091		},
2092		// TODO: activate()
2093		// TODO: activateSilently()
2094		/* Internal helper called in recursive addChildren sequence.*/
2095		_setChildren: function (children) {
2096			_assert(
2097				children && (!this.children || this.children.length === 0),
2098				"only init supported"
2099			);
2100			this.children = [];
2101			for (var i = 0, l = children.length; i < l; i++) {
2102				this.children.push(new FancytreeNode(this, children[i]));
2103			}
2104			this.tree._callHook(
2105				"treeStructureChanged",
2106				this.tree,
2107				"setChildren"
2108			);
2109		},
2110		/**
2111		 * Append (or insert) a list of child nodes.
2112		 *
2113		 * @param {NodeData[]} children array of child node definitions (also single child accepted)
2114		 * @param {FancytreeNode | string | Integer} [insertBefore] child node (or key or index of such).
2115		 *     If omitted, the new children are appended.
2116		 * @returns {FancytreeNode} first child added
2117		 *
2118		 * @see FancytreeNode#applyPatch
2119		 */
2120		addChildren: function (children, insertBefore) {
2121			var i,
2122				l,
2123				pos,
2124				origFirstChild = this.getFirstChild(),
2125				origLastChild = this.getLastChild(),
2126				firstNode = null,
2127				nodeList = [];
2128
2129			if ($.isPlainObject(children)) {
2130				children = [children];
2131			}
2132			if (!this.children) {
2133				this.children = [];
2134			}
2135			for (i = 0, l = children.length; i < l; i++) {
2136				nodeList.push(new FancytreeNode(this, children[i]));
2137			}
2138			firstNode = nodeList[0];
2139			if (insertBefore == null) {
2140				this.children = this.children.concat(nodeList);
2141			} else {
2142				// Returns null if insertBefore is not a direct child:
2143				insertBefore = this._findDirectChild(insertBefore);
2144				pos = $.inArray(insertBefore, this.children);
2145				_assert(pos >= 0, "insertBefore must be an existing child");
2146				// insert nodeList after children[pos]
2147				this.children.splice.apply(
2148					this.children,
2149					[pos, 0].concat(nodeList)
2150				);
2151			}
2152			if (origFirstChild && !insertBefore) {
2153				// #708: Fast path -- don't render every child of root, just the new ones!
2154				// #723, #729: but only if it's appended to an existing child list
2155				for (i = 0, l = nodeList.length; i < l; i++) {
2156					nodeList[i].render(); // New nodes were never rendered before
2157				}
2158				// Adjust classes where status may have changed
2159				// Has a first child
2160				if (origFirstChild !== this.getFirstChild()) {
2161					// Different first child -- recompute classes
2162					origFirstChild.renderStatus();
2163				}
2164				if (origLastChild !== this.getLastChild()) {
2165					// Different last child -- recompute classes
2166					origLastChild.renderStatus();
2167				}
2168			} else if (!this.parent || this.parent.ul || this.tr) {
2169				// render if the parent was rendered (or this is a root node)
2170				this.render();
2171			}
2172			if (this.tree.options.selectMode === 3) {
2173				this.fixSelection3FromEndNodes();
2174			}
2175			this.triggerModifyChild(
2176				"add",
2177				nodeList.length === 1 ? nodeList[0] : null
2178			);
2179			return firstNode;
2180		},
2181		/**
2182		 * Add class to node's span tag and to .extraClasses.
2183		 *
2184		 * @param {string} className class name
2185		 *
2186		 * @since 2.17
2187		 */
2188		addClass: function (className) {
2189			return this.toggleClass(className, true);
2190		},
2191		/**
2192		 * Append or prepend a node, or append a child node.
2193		 *
2194		 * This a convenience function that calls addChildren()
2195		 *
2196		 * @param {NodeData} node node definition
2197		 * @param {string} [mode=child] 'before', 'after', 'firstChild', or 'child' ('over' is a synonym for 'child')
2198		 * @returns {FancytreeNode} new node
2199		 */
2200		addNode: function (node, mode) {
2201			if (mode === undefined || mode === "over") {
2202				mode = "child";
2203			}
2204			switch (mode) {
2205				case "after":
2206					return this.getParent().addChildren(
2207						node,
2208						this.getNextSibling()
2209					);
2210				case "before":
2211					return this.getParent().addChildren(node, this);
2212				case "firstChild":
2213					// Insert before the first child if any
2214					var insertBefore = this.children ? this.children[0] : null;
2215					return this.addChildren(node, insertBefore);
2216				case "child":
2217				case "over":
2218					return this.addChildren(node);
2219			}
2220			_assert(false, "Invalid mode: " + mode);
2221		},
2222		/**Add child status nodes that indicate 'More...', etc.
2223		 *
2224		 * This also maintains the node's `partload` property.
2225		 * @param {boolean|object} node optional node definition. Pass `false` to remove all paging nodes.
2226		 * @param {string} [mode='child'] 'child'|firstChild'
2227		 * @since 2.15
2228		 */
2229		addPagingNode: function (node, mode) {
2230			var i, n;
2231
2232			mode = mode || "child";
2233			if (node === false) {
2234				for (i = this.children.length - 1; i >= 0; i--) {
2235					n = this.children[i];
2236					if (n.statusNodeType === "paging") {
2237						this.removeChild(n);
2238					}
2239				}
2240				this.partload = false;
2241				return;
2242			}
2243			node = $.extend(
2244				{
2245					title: this.tree.options.strings.moreData,
2246					statusNodeType: "paging",
2247					icon: false,
2248				},
2249				node
2250			);
2251			this.partload = true;
2252			return this.addNode(node, mode);
2253		},
2254		/**
2255		 * Append new node after this.
2256		 *
2257		 * This a convenience function that calls addNode(node, 'after')
2258		 *
2259		 * @param {NodeData} node node definition
2260		 * @returns {FancytreeNode} new node
2261		 */
2262		appendSibling: function (node) {
2263			return this.addNode(node, "after");
2264		},
2265		/**
2266		 * (experimental) Apply a modification (or navigation) operation.
2267		 *
2268		 * @param {string} cmd
2269		 * @param {object} [opts]
2270		 * @see Fancytree#applyCommand
2271		 * @since 2.32
2272		 */
2273		applyCommand: function (cmd, opts) {
2274			return this.tree.applyCommand(cmd, this, opts);
2275		},
2276		/**
2277		 * Modify existing child nodes.
2278		 *
2279		 * @param {NodePatch} patch
2280		 * @returns {$.Promise}
2281		 * @see FancytreeNode#addChildren
2282		 */
2283		applyPatch: function (patch) {
2284			// patch [key, null] means 'remove'
2285			if (patch === null) {
2286				this.remove();
2287				return _getResolvedPromise(this);
2288			}
2289			// TODO: make sure that root node is not collapsed or modified
2290			// copy (most) attributes to node.ATTR or node.data.ATTR
2291			var name,
2292				promise,
2293				v,
2294				IGNORE_MAP = { children: true, expanded: true, parent: true }; // TODO: should be global
2295
2296			for (name in patch) {
2297				if (_hasProp(patch, name)) {
2298					v = patch[name];
2299					if (!IGNORE_MAP[name] && !_isFunction(v)) {
2300						if (NODE_ATTR_MAP[name]) {
2301							this[name] = v;
2302						} else {
2303							this.data[name] = v;
2304						}
2305					}
2306				}
2307			}
2308			// Remove and/or create children
2309			if (_hasProp(patch, "children")) {
2310				this.removeChildren();
2311				if (patch.children) {
2312					// only if not null and not empty list
2313					// TODO: addChildren instead?
2314					this._setChildren(patch.children);
2315				}
2316				// TODO: how can we APPEND or INSERT child nodes?
2317			}
2318			if (this.isVisible()) {
2319				this.renderTitle();
2320				this.renderStatus();
2321			}
2322			// Expand collapse (final step, since this may be async)
2323			if (_hasProp(patch, "expanded")) {
2324				promise = this.setExpanded(patch.expanded);
2325			} else {
2326				promise = _getResolvedPromise(this);
2327			}
2328			return promise;
2329		},
2330		/** Collapse all sibling nodes.
2331		 * @returns {$.Promise}
2332		 */
2333		collapseSiblings: function () {
2334			return this.tree._callHook("nodeCollapseSiblings", this);
2335		},
2336		/** Copy this node as sibling or child of `node`.
2337		 *
2338		 * @param {FancytreeNode} node source node
2339		 * @param {string} [mode=child] 'before' | 'after' | 'child'
2340		 * @param {Function} [map] callback function(NodeData, FancytreeNode) that could modify the new node
2341		 * @returns {FancytreeNode} new
2342		 */
2343		copyTo: function (node, mode, map) {
2344			return node.addNode(this.toDict(true, map), mode);
2345		},
2346		/** Count direct and indirect children.
2347		 *
2348		 * @param {boolean} [deep=true] pass 'false' to only count direct children
2349		 * @returns {int} number of child nodes
2350		 */
2351		countChildren: function (deep) {
2352			var cl = this.children,
2353				i,
2354				l,
2355				n;
2356			if (!cl) {
2357				return 0;
2358			}
2359			n = cl.length;
2360			if (deep !== false) {
2361				for (i = 0, l = n; i < l; i++) {
2362					n += cl[i].countChildren();
2363				}
2364			}
2365			return n;
2366		},
2367		// TODO: deactivate()
2368		/** Write to browser console if debugLevel >= 4 (prepending node info)
2369		 *
2370		 * @param {*} msg string or object or array of such
2371		 */
2372		debug: function (msg) {
2373			if (this.tree.options.debugLevel >= 4) {
2374				Array.prototype.unshift.call(arguments, this.toString());
2375				consoleApply("log", arguments);
2376			}
2377		},
2378		/** Deprecated.
2379		 * @deprecated since 2014-02-16. Use resetLazy() instead.
2380		 */
2381		discard: function () {
2382			this.warn(
2383				"FancytreeNode.discard() is deprecated since 2014-02-16. Use .resetLazy() instead."
2384			);
2385			return this.resetLazy();
2386		},
2387		/** Remove DOM elements for all descendents. May be called on .collapse event
2388		 * to keep the DOM small.
2389		 * @param {boolean} [includeSelf=false]
2390		 */
2391		discardMarkup: function (includeSelf) {
2392			var fn = includeSelf ? "nodeRemoveMarkup" : "nodeRemoveChildMarkup";
2393			this.tree._callHook(fn, this);
2394		},
2395		/** Write error to browser console if debugLevel >= 1 (prepending tree info)
2396		 *
2397		 * @param {*} msg string or object or array of such
2398		 */
2399		error: function (msg) {
2400			if (this.tree.options.debugLevel >= 1) {
2401				Array.prototype.unshift.call(arguments, this.toString());
2402				consoleApply("error", arguments);
2403			}
2404		},
2405		/**Find all nodes that match condition (excluding self).
2406		 *
2407		 * @param {string | function(node)} match title string to search for, or a
2408		 *     callback function that returns `true` if a node is matched.
2409		 * @returns {FancytreeNode[]} array of nodes (may be empty)
2410		 */
2411		findAll: function (match) {
2412			match = _isFunction(match) ? match : _makeNodeTitleMatcher(match);
2413			var res = [];
2414			this.visit(function (n) {
2415				if (match(n)) {
2416					res.push(n);
2417				}
2418			});
2419			return res;
2420		},
2421		/**Find first node that matches condition (excluding self).
2422		 *
2423		 * @param {string | function(node)} match title string to search for, or a
2424		 *     callback function that returns `true` if a node is matched.
2425		 * @returns {FancytreeNode} matching node or null
2426		 * @see FancytreeNode#findAll
2427		 */
2428		findFirst: function (match) {
2429			match = _isFunction(match) ? match : _makeNodeTitleMatcher(match);
2430			var res = null;
2431			this.visit(function (n) {
2432				if (match(n)) {
2433					res = n;
2434					return false;
2435				}
2436			});
2437			return res;
2438		},
2439		/** Find a node relative to self.
2440		 *
2441		 * @param {number|string} where The keyCode that would normally trigger this move,
2442		 *		or a keyword ('down', 'first', 'last', 'left', 'parent', 'right', 'up').
2443		 * @returns {FancytreeNode}
2444		 * @since v2.31
2445		 */
2446		findRelatedNode: function (where, includeHidden) {
2447			return this.tree.findRelatedNode(this, where, includeHidden);
2448		},
2449		/* Apply selection state (internal use only) */
2450		_changeSelectStatusAttrs: function (state) {
2451			var changed = false,
2452				opts = this.tree.options,
2453				unselectable = FT.evalOption(
2454					"unselectable",
2455					this,
2456					this,
2457					opts,
2458					false
2459				),
2460				unselectableStatus = FT.evalOption(
2461					"unselectableStatus",
2462					this,
2463					this,
2464					opts,
2465					undefined
2466				);
2467
2468			if (unselectable && unselectableStatus != null) {
2469				state = unselectableStatus;
2470			}
2471			switch (state) {
2472				case false:
2473					changed = this.selected || this.partsel;
2474					this.selected = false;
2475					this.partsel = false;
2476					break;
2477				case true:
2478					changed = !this.selected || !this.partsel;
2479					this.selected = true;
2480					this.partsel = true;
2481					break;
2482				case undefined:
2483					changed = this.selected || !this.partsel;
2484					this.selected = false;
2485					this.partsel = true;
2486					break;
2487				default:
2488					_assert(false, "invalid state: " + state);
2489			}
2490			// this.debug("fixSelection3AfterLoad() _changeSelectStatusAttrs()", state, changed);
2491			if (changed) {
2492				this.renderStatus();
2493			}
2494			return changed;
2495		},
2496		/**
2497		 * Fix selection status, after this node was (de)selected in multi-hier mode.
2498		 * This includes (de)selecting all children.
2499		 */
2500		fixSelection3AfterClick: function (callOpts) {
2501			var flag = this.isSelected();
2502
2503			// this.debug("fixSelection3AfterClick()");
2504
2505			this.visit(function (node) {
2506				node._changeSelectStatusAttrs(flag);
2507				if (node.radiogroup) {
2508					// #931: don't (de)select this branch
2509					return "skip";
2510				}
2511			});
2512			this.fixSelection3FromEndNodes(callOpts);
2513		},
2514		/**
2515		 * Fix selection status for multi-hier mode.
2516		 * Only end-nodes are considered to update the descendants branch and parents.
2517		 * Should be called after this node has loaded new children or after
2518		 * children have been modified using the API.
2519		 */
2520		fixSelection3FromEndNodes: function (callOpts) {
2521			var opts = this.tree.options;
2522
2523			// this.debug("fixSelection3FromEndNodes()");
2524			_assert(opts.selectMode === 3, "expected selectMode 3");
2525
2526			// Visit all end nodes and adjust their parent's `selected` and `partsel`
2527			// attributes. Return selection state true, false, or undefined.
2528			function _walk(node) {
2529				var i,
2530					l,
2531					child,
2532					s,
2533					state,
2534					allSelected,
2535					someSelected,
2536					unselIgnore,
2537					unselState,
2538					children = node.children;
2539
2540				if (children && children.length) {
2541					// check all children recursively
2542					allSelected = true;
2543					someSelected = false;
2544
2545					for (i = 0, l = children.length; i < l; i++) {
2546						child = children[i];
2547						// the selection state of a node is not relevant; we need the end-nodes
2548						s = _walk(child);
2549						// if( !child.unselectableIgnore ) {
2550						unselIgnore = FT.evalOption(
2551							"unselectableIgnore",
2552							child,
2553							child,
2554							opts,
2555							false
2556						);
2557						if (!unselIgnore) {
2558							if (s !== false) {
2559								someSelected = true;
2560							}
2561							if (s !== true) {
2562								allSelected = false;
2563							}
2564						}
2565					}
2566					// eslint-disable-next-line no-nested-ternary
2567					state = allSelected
2568						? true
2569						: someSelected
2570						? undefined
2571						: false;
2572				} else {
2573					// This is an end-node: simply report the status
2574					unselState = FT.evalOption(
2575						"unselectableStatus",
2576						node,
2577						node,
2578						opts,
2579						undefined
2580					);
2581					state = unselState == null ? !!node.selected : !!unselState;
2582				}
2583				// #939: Keep a `partsel` flag that was explicitly set on a lazy node
2584				if (
2585					node.partsel &&
2586					!node.selected &&
2587					node.lazy &&
2588					node.children == null
2589				) {
2590					state = undefined;
2591				}
2592				node._changeSelectStatusAttrs(state);
2593				return state;
2594			}
2595			_walk(this);
2596
2597			// Update parent's state
2598			this.visitParents(function (node) {
2599				var i,
2600					l,
2601					child,
2602					state,
2603					unselIgnore,
2604					unselState,
2605					children = node.children,
2606					allSelected = true,
2607					someSelected = false;
2608
2609				for (i = 0, l = children.length; i < l; i++) {
2610					child = children[i];
2611					unselIgnore = FT.evalOption(
2612						"unselectableIgnore",
2613						child,
2614						child,
2615						opts,
2616						false
2617					);
2618					if (!unselIgnore) {
2619						unselState = FT.evalOption(
2620							"unselectableStatus",
2621							child,
2622							child,
2623							opts,
2624							undefined
2625						);
2626						state =
2627							unselState == null
2628								? !!child.selected
2629								: !!unselState;
2630						// When fixing the parents, we trust the sibling status (i.e.
2631						// we don't recurse)
2632						if (state || child.partsel) {
2633							someSelected = true;
2634						}
2635						if (!state) {
2636							allSelected = false;
2637						}
2638					}
2639				}
2640				// eslint-disable-next-line no-nested-ternary
2641				state = allSelected ? true : someSelected ? undefined : false;
2642				node._changeSelectStatusAttrs(state);
2643			});
2644		},
2645		// TODO: focus()
2646		/**
2647		 * Update node data. If dict contains 'children', then also replace
2648		 * the hole sub tree.
2649		 * @param {NodeData} dict
2650		 *
2651		 * @see FancytreeNode#addChildren
2652		 * @see FancytreeNode#applyPatch
2653		 */
2654		fromDict: function (dict) {
2655			// copy all other attributes to this.data.xxx
2656			for (var name in dict) {
2657				if (NODE_ATTR_MAP[name]) {
2658					// node.NAME = dict.NAME
2659					this[name] = dict[name];
2660				} else if (name === "data") {
2661					// node.data += dict.data
2662					$.extend(this.data, dict.data);
2663				} else if (
2664					!_isFunction(dict[name]) &&
2665					!NONE_NODE_DATA_MAP[name]
2666				) {
2667					// node.data.NAME = dict.NAME
2668					this.data[name] = dict[name];
2669				}
2670			}
2671			if (dict.children) {
2672				// recursively set children and render
2673				this.removeChildren();
2674				this.addChildren(dict.children);
2675			}
2676			this.renderTitle();
2677			/*
2678			var children = dict.children;
2679			if(children === undefined){
2680				this.data = $.extend(this.data, dict);
2681				this.render();
2682				return;
2683			}
2684			dict = $.extend({}, dict);
2685			dict.children = undefined;
2686			this.data = $.extend(this.data, dict);
2687			this.removeChildren();
2688			this.addChild(children);
2689			*/
2690		},
2691		/** Return the list of child nodes (undefined for unexpanded lazy nodes).
2692		 * @returns {FancytreeNode[] | undefined}
2693		 */
2694		getChildren: function () {
2695			if (this.hasChildren() === undefined) {
2696				// TODO: only required for lazy nodes?
2697				return undefined; // Lazy node: unloaded, currently loading, or load error
2698			}
2699			return this.children;
2700		},
2701		/** Return the first child node or null.
2702		 * @returns {FancytreeNode | null}
2703		 */
2704		getFirstChild: function () {
2705			return this.children ? this.children[0] : null;
2706		},
2707		/** Return the 0-based child index.
2708		 * @returns {int}
2709		 */
2710		getIndex: function () {
2711			// return this.parent.children.indexOf(this);
2712			return $.inArray(this, this.parent.children); // indexOf doesn't work in IE7
2713		},
2714		/** Return the hierarchical child index (1-based, e.g. '3.2.4').
2715		 * @param {string} [separator="."]
2716		 * @param {int} [digits=1]
2717		 * @returns {string}
2718		 */
2719		getIndexHier: function (separator, digits) {
2720			separator = separator || ".";
2721			var s,
2722				res = [];
2723			$.each(this.getParentList(false, true), function (i, o) {
2724				s = "" + (o.getIndex() + 1);
2725				if (digits) {
2726					// prepend leading zeroes
2727					s = ("0000000" + s).substr(-digits);
2728				}
2729				res.push(s);
2730			});
2731			return res.join(separator);
2732		},
2733		/** Return the parent keys separated by options.keyPathSeparator, e.g. "/id_1/id_17/id_32".
2734		 *
2735		 * (Unlike `node.getPath()`, this method prepends a "/" and inverts the first argument.)
2736		 *
2737		 * @see FancytreeNode#getPath
2738		 * @param {boolean} [excludeSelf=false]
2739		 * @returns {string}
2740		 */
2741		getKeyPath: function (excludeSelf) {
2742			var sep = this.tree.options.keyPathSeparator;
2743
2744			return sep + this.getPath(!excludeSelf, "key", sep);
2745		},
2746		/** Return the last child of this node or null.
2747		 * @returns {FancytreeNode | null}
2748		 */
2749		getLastChild: function () {
2750			return this.children
2751				? this.children[this.children.length - 1]
2752				: null;
2753		},
2754		/** Return node depth. 0: System root node, 1: visible top-level node, 2: first sub-level, ... .
2755		 * @returns {int}
2756		 */
2757		getLevel: function () {
2758			var level = 0,
2759				dtn = this.parent;
2760			while (dtn) {
2761				level++;
2762				dtn = dtn.parent;
2763			}
2764			return level;
2765		},
2766		/** Return the successor node (under the same parent) or null.
2767		 * @returns {FancytreeNode | null}
2768		 */
2769		getNextSibling: function () {
2770			// TODO: use indexOf, if available: (not in IE6)
2771			if (this.parent) {
2772				var i,
2773					l,
2774					ac = this.parent.children;
2775
2776				for (i = 0, l = ac.length - 1; i < l; i++) {
2777					// up to length-2, so next(last) = null
2778					if (ac[i] === this) {
2779						return ac[i + 1];
2780					}
2781				}
2782			}
2783			return null;
2784		},
2785		/** Return the parent node (null for the system root node).
2786		 * @returns {FancytreeNode | null}
2787		 */
2788		getParent: function () {
2789			// TODO: return null for top-level nodes?
2790			return this.parent;
2791		},
2792		/** Return an array of all parent nodes (top-down).
2793		 * @param {boolean} [includeRoot=false] Include the invisible system root node.
2794		 * @param {boolean} [includeSelf=false] Include the node itself.
2795		 * @returns {FancytreeNode[]}
2796		 */
2797		getParentList: function (includeRoot, includeSelf) {
2798			var l = [],
2799				dtn = includeSelf ? this : this.parent;
2800			while (dtn) {
2801				if (includeRoot || dtn.parent) {
2802					l.unshift(dtn);
2803				}
2804				dtn = dtn.parent;
2805			}
2806			return l;
2807		},
2808		/** Return a string representing the hierachical node path, e.g. "a/b/c".
2809		 * @param {boolean} [includeSelf=true]
2810		 * @param {string | function} [part="title"] node property name or callback
2811		 * @param {string} [separator="/"]
2812		 * @returns {string}
2813		 * @since v2.31
2814		 */
2815		getPath: function (includeSelf, part, separator) {
2816			includeSelf = includeSelf !== false;
2817			part = part || "title";
2818			separator = separator || "/";
2819
2820			var val,
2821				path = [],
2822				isFunc = _isFunction(part);
2823
2824			this.visitParents(function (n) {
2825				if (n.parent) {
2826					val = isFunc ? part(n) : n[part];
2827					path.unshift(val);
2828				}
2829			}, includeSelf);
2830			return path.join(separator);
2831		},
2832		/** Return the predecessor node (under the same parent) or null.
2833		 * @returns {FancytreeNode | null}
2834		 */
2835		getPrevSibling: function () {
2836			if (this.parent) {
2837				var i,
2838					l,
2839					ac = this.parent.children;
2840
2841				for (i = 1, l = ac.length; i < l; i++) {
2842					// start with 1, so prev(first) = null
2843					if (ac[i] === this) {
2844						return ac[i - 1];
2845					}
2846				}
2847			}
2848			return null;
2849		},
2850		/**
2851		 * Return an array of selected descendant nodes.
2852		 * @param {boolean} [stopOnParents=false] only return the topmost selected
2853		 *     node (useful with selectMode 3)
2854		 * @returns {FancytreeNode[]}
2855		 */
2856		getSelectedNodes: function (stopOnParents) {
2857			var nodeList = [];
2858			this.visit(function (node) {
2859				if (node.selected) {
2860					nodeList.push(node);
2861					if (stopOnParents === true) {
2862						return "skip"; // stop processing this branch
2863					}
2864				}
2865			});
2866			return nodeList;
2867		},
2868		/** Return true if node has children. Return undefined if not sure, i.e. the node is lazy and not yet loaded).
2869		 * @returns {boolean | undefined}
2870		 */
2871		hasChildren: function () {
2872			if (this.lazy) {
2873				if (this.children == null) {
2874					// null or undefined: Not yet loaded
2875					return undefined;
2876				} else if (this.children.length === 0) {
2877					// Loaded, but response was empty
2878					return false;
2879				} else if (
2880					this.children.length === 1 &&
2881					this.children[0].isStatusNode()
2882				) {
2883					// Currently loading or load error
2884					return undefined;
2885				}
2886				return true;
2887			}
2888			return !!(this.children && this.children.length);
2889		},
2890		/**
2891		 * Return true if node has `className` defined in .extraClasses.
2892		 *
2893		 * @param {string} className class name (separate multiple classes by space)
2894		 * @returns {boolean}
2895		 *
2896		 * @since 2.32
2897		 */
2898		hasClass: function (className) {
2899			return (
2900				(" " + (this.extraClasses || "") + " ").indexOf(
2901					" " + className + " "
2902				) >= 0
2903			);
2904		},
2905		/** Return true if node has keyboard focus.
2906		 * @returns {boolean}
2907		 */
2908		hasFocus: function () {
2909			return this.tree.hasFocus() && this.tree.focusNode === this;
2910		},
2911		/** Write to browser console if debugLevel >= 3 (prepending node info)
2912		 *
2913		 * @param {*} msg string or object or array of such
2914		 */
2915		info: function (msg) {
2916			if (this.tree.options.debugLevel >= 3) {
2917				Array.prototype.unshift.call(arguments, this.toString());
2918				consoleApply("info", arguments);
2919			}
2920		},
2921		/** Return true if node is active (see also FancytreeNode#isSelected).
2922		 * @returns {boolean}
2923		 */
2924		isActive: function () {
2925			return this.tree.activeNode === this;
2926		},
2927		/** Return true if node is vertically below `otherNode`, i.e. rendered in a subsequent row.
2928		 * @param {FancytreeNode} otherNode
2929		 * @returns {boolean}
2930		 * @since 2.28
2931		 */
2932		isBelowOf: function (otherNode) {
2933			return this.getIndexHier(".", 5) > otherNode.getIndexHier(".", 5);
2934		},
2935		/** Return true if node is a direct child of otherNode.
2936		 * @param {FancytreeNode} otherNode
2937		 * @returns {boolean}
2938		 */
2939		isChildOf: function (otherNode) {
2940			return this.parent && this.parent === otherNode;
2941		},
2942		/** Return true, if node is a direct or indirect sub node of otherNode.
2943		 * @param {FancytreeNode} otherNode
2944		 * @returns {boolean}
2945		 */
2946		isDescendantOf: function (otherNode) {
2947			if (!otherNode || otherNode.tree !== this.tree) {
2948				return false;
2949			}
2950			var p = this.parent;
2951			while (p) {
2952				if (p === otherNode) {
2953					return true;
2954				}
2955				if (p === p.parent) {
2956					$.error("Recursive parent link: " + p);
2957				}
2958				p = p.parent;
2959			}
2960			return false;
2961		},
2962		/** Return true if node is expanded.
2963		 * @returns {boolean}
2964		 */
2965		isExpanded: function () {
2966			return !!this.expanded;
2967		},
2968		/** Return true if node is the first node of its parent's children.
2969		 * @returns {boolean}
2970		 */
2971		isFirstSibling: function () {
2972			var p = this.parent;
2973			return !p || p.children[0] === this;
2974		},
2975		/** Return true if node is a folder, i.e. has the node.folder attribute set.
2976		 * @returns {boolean}
2977		 */
2978		isFolder: function () {
2979			return !!this.folder;
2980		},
2981		/** Return true if node is the last node of its parent's children.
2982		 * @returns {boolean}
2983		 */
2984		isLastSibling: function () {
2985			var p = this.parent;
2986			return !p || p.children[p.children.length - 1] === this;
2987		},
2988		/** Return true if node is lazy (even if data was already loaded)
2989		 * @returns {boolean}
2990		 */
2991		isLazy: function () {
2992			return !!this.lazy;
2993		},
2994		/** Return true if node is lazy and loaded. For non-lazy nodes always return true.
2995		 * @returns {boolean}
2996		 */
2997		isLoaded: function () {
2998			return !this.lazy || this.hasChildren() !== undefined; // Also checks if the only child is a status node
2999		},
3000		/** Return true if children are currently beeing loaded, i.e. a Ajax request is pending.
3001		 * @returns {boolean}
3002		 */
3003		isLoading: function () {
3004			return !!this._isLoading;
3005		},
3006		/*
3007		 * @deprecated since v2.4.0:  Use isRootNode() instead
3008		 */
3009		isRoot: function () {
3010			return this.isRootNode();
3011		},
3012		/** Return true if node is partially selected (tri-state).
3013		 * @returns {boolean}
3014		 * @since 2.23
3015		 */
3016		isPartsel: function () {
3017			return !this.selected && !!this.partsel;
3018		},
3019		/** (experimental) Return true if this is partially loaded.
3020		 * @returns {boolean}
3021		 * @since 2.15
3022		 */
3023		isPartload: function () {
3024			return !!this.partload;
3025		},
3026		/** Return true if this is the (invisible) system root node.
3027		 * @returns {boolean}
3028		 * @since 2.4
3029		 */
3030		isRootNode: function () {
3031			return this.tree.rootNode === this;
3032		},
3033		/** Return true if node is selected, i.e. has a checkmark set (see also FancytreeNode#isActive).
3034		 * @returns {boolean}
3035		 */
3036		isSelected: function () {
3037			return !!this.selected;
3038		},
3039		/** Return true if this node is a temporarily generated system node like
3040		 * 'loading', 'paging', or 'error' (node.statusNodeType contains the type).
3041		 * @returns {boolean}
3042		 */
3043		isStatusNode: function () {
3044			return !!this.statusNodeType;
3045		},
3046		/** Return true if this node is a status node of type 'paging'.
3047		 * @returns {boolean}
3048		 * @since 2.15
3049		 */
3050		isPagingNode: function () {
3051			return this.statusNodeType === "paging";
3052		},
3053		/** Return true if this a top level node, i.e. a direct child of the (invisible) system root node.
3054		 * @returns {boolean}
3055		 * @since 2.4
3056		 */
3057		isTopLevel: function () {
3058			return this.tree.rootNode === this.parent;
3059		},
3060		/** Return true if node is lazy and not yet loaded. For non-lazy nodes always return false.
3061		 * @returns {boolean}
3062		 */
3063		isUndefined: function () {
3064			return this.hasChildren() === undefined; // also checks if the only child is a status node
3065		},
3066		/** Return true if all parent nodes are expanded. Note: this does not check
3067		 * whether the node is scrolled into the visible part of the screen.
3068		 * @returns {boolean}
3069		 */
3070		isVisible: function () {
3071			var i,
3072				l,
3073				n,
3074				hasFilter = this.tree.enableFilter,
3075				parents = this.getParentList(false, false);
3076
3077			// TODO: check $(n.span).is(":visible")
3078			// i.e. return false for nodes (but not parents) that are hidden
3079			// by a filter
3080			if (hasFilter && !this.match && !this.subMatchCount) {
3081				// this.debug( "isVisible: HIDDEN (" + hasFilter + ", " + this.match + ", " + this.match + ")" );
3082				return false;
3083			}
3084
3085			for (i = 0, l = parents.length; i < l; i++) {
3086				n = parents[i];
3087
3088				if (!n.expanded) {
3089					// this.debug("isVisible: HIDDEN (parent collapsed)");
3090					return false;
3091				}
3092				// if (hasFilter && !n.match && !n.subMatchCount) {
3093				// 	this.debug("isVisible: HIDDEN (" + hasFilter + ", " + this.match + ", " + this.match + ")");
3094				// 	return false;
3095				// }
3096			}
3097			// this.debug("isVisible: VISIBLE");
3098			return true;
3099		},
3100		/** Deprecated.
3101		 * @deprecated since 2014-02-16: use load() instead.
3102		 */
3103		lazyLoad: function (discard) {
3104			$.error(
3105				"FancytreeNode.lazyLoad() is deprecated since 2014-02-16. Use .load() instead."
3106			);
3107		},
3108		/**
3109		 * Load all children of a lazy node if neccessary. The <i>expanded</i> state is maintained.
3110		 * @param {boolean} [forceReload=false] Pass true to discard any existing nodes before. Otherwise this method does nothing if the node was already loaded.
3111		 * @returns {$.Promise}
3112		 */
3113		load: function (forceReload) {
3114			var res,
3115				source,
3116				self = this,
3117				wasExpanded = this.isExpanded();
3118
3119			_assert(this.isLazy(), "load() requires a lazy node");
3120			// _assert( forceReload || this.isUndefined(), "Pass forceReload=true to re-load a lazy node" );
3121			if (!forceReload && !this.isUndefined()) {
3122				return _getResolvedPromise(this);
3123			}
3124			if (this.isLoaded()) {
3125				this.resetLazy(); // also collapses
3126			}
3127			// This method is also called by setExpanded() and loadKeyPath(), so we
3128			// have to avoid recursion.
3129			source = this.tree._triggerNodeEvent("lazyLoad", this);
3130			if (source === false) {
3131				// #69
3132				return _getResolvedPromise(this);
3133			}
3134			_assert(
3135				typeof source !== "boolean",
3136				"lazyLoad event must return source in data.result"
3137			);
3138			res = this.tree._callHook("nodeLoadChildren", this, source);
3139			if (wasExpanded) {
3140				this.expanded = true;
3141				res.always(function () {
3142					self.render();
3143				});
3144			} else {
3145				res.always(function () {
3146					self.renderStatus(); // fix expander icon to 'loaded'
3147				});
3148			}
3149			return res;
3150		},
3151		/** Expand all parents and optionally scroll into visible area as neccessary.
3152		 * Promise is resolved, when lazy loading and animations are done.
3153		 * @param {object} [opts] passed to `setExpanded()`.
3154		 *     Defaults to {noAnimation: false, noEvents: false, scrollIntoView: true}
3155		 * @returns {$.Promise}
3156		 */
3157		makeVisible: function (opts) {
3158			var i,
3159				self = this,
3160				deferreds = [],
3161				dfd = new $.Deferred(),
3162				parents = this.getParentList(false, false),
3163				len = parents.length,
3164				effects = !(opts && opts.noAnimation === true),
3165				scroll = !(opts && opts.scrollIntoView === false);
3166
3167			// Expand bottom-up, so only the top node is animated
3168			for (i = len - 1; i >= 0; i--) {
3169				// self.debug("pushexpand" + parents[i]);
3170				deferreds.push(parents[i].setExpanded(true, opts));
3171			}
3172			$.when.apply($, deferreds).done(function () {
3173				// All expands have finished
3174				// self.debug("expand DONE", scroll);
3175				if (scroll) {
3176					self.scrollIntoView(effects).done(function () {
3177						// self.debug("scroll DONE");
3178						dfd.resolve();
3179					});
3180				} else {
3181					dfd.resolve();
3182				}
3183			});
3184			return dfd.promise();
3185		},
3186		/** Move this node to targetNode.
3187		 *  @param {FancytreeNode} targetNode
3188		 *  @param {string} mode <pre>
3189		 *      'child': append this node as last child of targetNode.
3190		 *               This is the default. To be compatble with the D'n'd
3191		 *               hitMode, we also accept 'over'.
3192		 *      'firstChild': add this node as first child of targetNode.
3193		 *      'before': add this node as sibling before targetNode.
3194		 *      'after': add this node as sibling after targetNode.</pre>
3195		 *  @param {function} [map] optional callback(FancytreeNode) to allow modifcations
3196		 */
3197		moveTo: function (targetNode, mode, map) {
3198			if (mode === undefined || mode === "over") {
3199				mode = "child";
3200			} else if (mode === "firstChild") {
3201				if (targetNode.children && targetNode.children.length) {
3202					mode = "before";
3203					targetNode = targetNode.children[0];
3204				} else {
3205					mode = "child";
3206				}
3207			}
3208			var pos,
3209				tree = this.tree,
3210				prevParent = this.parent,
3211				targetParent =
3212					mode === "child" ? targetNode : targetNode.parent;
3213
3214			if (this === targetNode) {
3215				return;
3216			} else if (!this.parent) {
3217				$.error("Cannot move system root");
3218			} else if (targetParent.isDescendantOf(this)) {
3219				$.error("Cannot move a node to its own descendant");
3220			}
3221			if (targetParent !== prevParent) {
3222				prevParent.triggerModifyChild("remove", this);
3223			}
3224			// Unlink this node from current parent
3225			if (this.parent.children.length === 1) {
3226				if (this.parent === targetParent) {
3227					return; // #258
3228				}
3229				this.parent.children = this.parent.lazy ? [] : null;
3230				this.parent.expanded = false;
3231			} else {
3232				pos = $.inArray(this, this.parent.children);
3233				_assert(pos >= 0, "invalid source parent");
3234				this.parent.children.splice(pos, 1);
3235			}
3236			// Remove from source DOM parent
3237			// if(this.parent.ul){
3238			// 	this.parent.ul.removeChild(this.li);
3239			// }
3240
3241			// Insert this node to target parent's child list
3242			this.parent = targetParent;
3243			if (targetParent.hasChildren()) {
3244				switch (mode) {
3245					case "child":
3246						// Append to existing target children
3247						targetParent.children.push(this);
3248						break;
3249					case "before":
3250						// Insert this node before target node
3251						pos = $.inArray(targetNode, targetParent.children);
3252						_assert(pos >= 0, "invalid target parent");
3253						targetParent.children.splice(pos, 0, this);
3254						break;
3255					case "after":
3256						// Insert this node after target node
3257						pos = $.inArray(targetNode, targetParent.children);
3258						_assert(pos >= 0, "invalid target parent");
3259						targetParent.children.splice(pos + 1, 0, this);
3260						break;
3261					default:
3262						$.error("Invalid mode " + mode);
3263				}
3264			} else {
3265				targetParent.children = [this];
3266			}
3267			// Parent has no <ul> tag yet:
3268			// if( !targetParent.ul ) {
3269			// 	// This is the parent's first child: create UL tag
3270			// 	// (Hidden, because it will be
3271			// 	targetParent.ul = document.createElement("ul");
3272			// 	targetParent.ul.style.display = "none";
3273			// 	targetParent.li.appendChild(targetParent.ul);
3274			// }
3275			// // Issue 319: Add to target DOM parent (only if node was already rendered(expanded))
3276			// if(this.li){
3277			// 	targetParent.ul.appendChild(this.li);
3278			// }
3279
3280			// Let caller modify the nodes
3281			if (map) {
3282				targetNode.visit(map, true);
3283			}
3284			if (targetParent === prevParent) {
3285				targetParent.triggerModifyChild("move", this);
3286			} else {
3287				// prevParent.triggerModifyChild("remove", this);
3288				targetParent.triggerModifyChild("add", this);
3289			}
3290			// Handle cross-tree moves
3291			if (tree !== targetNode.tree) {
3292				// Fix node.tree for all source nodes
3293				// 	_assert(false, "Cross-tree move is not yet implemented.");
3294				this.warn("Cross-tree moveTo is experimental!");
3295				this.visit(function (n) {
3296					// TODO: fix selection state and activation, ...
3297					n.tree = targetNode.tree;
3298				}, true);
3299			}
3300
3301			// A collaposed node won't re-render children, so we have to remove it manually
3302			// if( !targetParent.expanded ){
3303			//   prevParent.ul.removeChild(this.li);
3304			// }
3305			tree._callHook("treeStructureChanged", tree, "moveTo");
3306
3307			// Update HTML markup
3308			if (!prevParent.isDescendantOf(targetParent)) {
3309				prevParent.render();
3310			}
3311			if (
3312				!targetParent.isDescendantOf(prevParent) &&
3313				targetParent !== prevParent
3314			) {
3315				targetParent.render();
3316			}
3317			// TODO: fix selection state
3318			// TODO: fix active state
3319
3320			/*
3321			var tree = this.tree;
3322			var opts = tree.options;
3323			var pers = tree.persistence;
3324
3325			// Always expand, if it's below minExpandLevel
3326			// tree.logDebug ("%s._addChildNode(%o), l=%o", this, ftnode, ftnode.getLevel());
3327			if ( opts.minExpandLevel >= ftnode.getLevel() ) {
3328				// tree.logDebug ("Force expand for %o", ftnode);
3329				this.bExpanded = true;
3330			}
3331
3332			// In multi-hier mode, update the parents selection state
3333			// DT issue #82: only if not initializing, because the children may not exist yet
3334			// if( !ftnode.data.isStatusNode() && opts.selectMode==3 && !isInitializing )
3335			// 	ftnode._fixSelectionState();
3336
3337			// In multi-hier mode, update the parents selection state
3338			if( ftnode.bSelected && opts.selectMode==3 ) {
3339				var p = this;
3340				while( p ) {
3341					if( !p.hasSubSel )
3342						p._setSubSel(true);
3343					p = p.parent;
3344				}
3345			}
3346			// render this node and the new child
3347			if ( tree.bEnableUpdate )
3348				this.render();
3349			return ftnode;
3350			*/
3351		},
3352		/** Set focus relative to this node and optionally activate.
3353		 *
3354		 * 'left' collapses the node if it is expanded, or move to the parent
3355		 * otherwise.
3356		 * 'right' expands the node if it is collapsed, or move to the first
3357		 * child otherwise.
3358		 *
3359		 * @param {string|number} where 'down', 'first', 'last', 'left', 'parent', 'right', or 'up'.
3360		 *   (Alternatively the keyCode that would normally trigger this move,
3361		 *   e.g. `$.ui.keyCode.LEFT` = 'left'.
3362		 * @param {boolean} [activate=true]
3363		 * @returns {$.Promise}
3364		 */
3365		navigate: function (where, activate) {
3366			var node,
3367				KC = $.ui.keyCode;
3368
3369			// Handle optional expand/collapse action for LEFT/RIGHT
3370			switch (where) {
3371				case "left":
3372				case KC.LEFT:
3373					if (this.expanded) {
3374						return this.setExpanded(false);
3375					}
3376					break;
3377				case "right":
3378				case KC.RIGHT:
3379					if (!this.expanded && (this.children || this.lazy)) {
3380						return this.setExpanded();
3381					}
3382					break;
3383			}
3384			// Otherwise activate or focus the related node
3385			node = this.findRelatedNode(where);
3386			if (node) {
3387				// setFocus/setActive will scroll later (if autoScroll is specified)
3388				try {
3389					node.makeVisible({ scrollIntoView: false });
3390				} catch (e) {} // #272
3391				if (activate === false) {
3392					node.setFocus();
3393					return _getResolvedPromise();
3394				}
3395				return node.setActive();
3396			}
3397			this.warn("Could not find related node '" + where + "'.");
3398			return _getResolvedPromise();
3399		},
3400		/**
3401		 * Remove this node (not allowed for system root).
3402		 */
3403		remove: function () {
3404			return this.parent.removeChild(this);
3405		},
3406		/**
3407		 * Remove childNode from list of direct children.
3408		 * @param {FancytreeNode} childNode
3409		 */
3410		removeChild: function (childNode) {
3411			return this.tree._callHook("nodeRemoveChild", this, childNode);
3412		},
3413		/**
3414		 * Remove all child nodes and descendents. This converts the node into a leaf.<br>
3415		 * If this was a lazy node, it is still considered 'loaded'; call node.resetLazy()
3416		 * in order to trigger lazyLoad on next expand.
3417		 */
3418		removeChildren: function () {
3419			return this.tree._callHook("nodeRemoveChildren", this);
3420		},
3421		/**
3422		 * Remove class from node's span tag and .extraClasses.
3423		 *
3424		 * @param {string} className class name
3425		 *
3426		 * @since 2.17
3427		 */
3428		removeClass: function (className) {
3429			return this.toggleClass(className, false);
3430		},
3431		/**
3432		 * This method renders and updates all HTML markup that is required
3433		 * to display this node in its current state.<br>
3434		 * Note:
3435		 * <ul>
3436		 * <li>It should only be neccessary to call this method after the node object
3437		 *     was modified by direct access to its properties, because the common
3438		 *     API methods (node.setTitle(), moveTo(), addChildren(), remove(), ...)
3439		 *     already handle this.
3440		 * <li> {@link FancytreeNode#renderTitle} and {@link FancytreeNode#renderStatus}
3441		 *     are implied. If changes are more local, calling only renderTitle() or
3442		 *     renderStatus() may be sufficient and faster.
3443		 * </ul>
3444		 *
3445		 * @param {boolean} [force=false] re-render, even if html markup was already created
3446		 * @param {boolean} [deep=false] also render all descendants, even if parent is collapsed
3447		 */
3448		render: function (force, deep) {
3449			return this.tree._callHook("nodeRender", this, force, deep);
3450		},
3451		/** Create HTML markup for the node's outer `<span>` (expander, checkbox, icon, and title).
3452		 * Implies {@link FancytreeNode#renderStatus}.
3453		 * @see Fancytree_Hooks#nodeRenderTitle
3454		 */
3455		renderTitle: function () {
3456			return this.tree._callHook("nodeRenderTitle", this);
3457		},
3458		/** Update element's CSS classes according to node state.
3459		 * @see Fancytree_Hooks#nodeRenderStatus
3460		 */
3461		renderStatus: function () {
3462			return this.tree._callHook("nodeRenderStatus", this);
3463		},
3464		/**
3465		 * (experimental) Replace this node with `source`.
3466		 * (Currently only available for paging nodes.)
3467		 * @param {NodeData[]} source List of child node definitions
3468		 * @since 2.15
3469		 */
3470		replaceWith: function (source) {
3471			var res,
3472				parent = this.parent,
3473				pos = $.inArray(this, parent.children),
3474				self = this;
3475
3476			_assert(
3477				this.isPagingNode(),
3478				"replaceWith() currently requires a paging status node"
3479			);
3480
3481			res = this.tree._callHook("nodeLoadChildren", this, source);
3482			res.done(function (data) {
3483				// New nodes are currently children of `this`.
3484				var children = self.children;
3485				// Prepend newly loaded child nodes to `this`
3486				// Move new children after self
3487				for (i = 0; i < children.length; i++) {
3488					children[i].parent = parent;
3489				}
3490				parent.children.splice.apply(
3491					parent.children,
3492					[pos + 1, 0].concat(children)
3493				);
3494
3495				// Remove self
3496				self.children = null;
3497				self.remove();
3498				// Redraw new nodes
3499				parent.render();
3500				// TODO: set node.partload = false if this was tha last paging node?
3501				// parent.addPagingNode(false);
3502			}).fail(function () {
3503				self.setExpanded();
3504			});
3505			return res;
3506			// $.error("Not implemented: replaceWith()");
3507		},
3508		/**
3509		 * Remove all children, collapse, and set the lazy-flag, so that the lazyLoad
3510		 * event is triggered on next expand.
3511		 */
3512		resetLazy: function () {
3513			this.removeChildren();
3514			this.expanded = false;
3515			this.lazy = true;
3516			this.children = undefined;
3517			this.renderStatus();
3518		},
3519		/** Schedule activity for delayed execution (cancel any pending request).
3520		 *  scheduleAction('cancel') will only cancel a pending request (if any).
3521		 * @param {string} mode
3522		 * @param {number} ms
3523		 */
3524		scheduleAction: function (mode, ms) {
3525			if (this.tree.timer) {
3526				clearTimeout(this.tree.timer);
3527				this.tree.debug("clearTimeout(%o)", this.tree.timer);
3528			}
3529			this.tree.timer = null;
3530			var self = this; // required for closures
3531			switch (mode) {
3532				case "cancel":
3533					// Simply made sure that timer was cleared
3534					break;
3535				case "expand":
3536					this.tree.timer = setTimeout(function () {
3537						self.tree.debug("setTimeout: trigger expand");
3538						self.setExpanded(true);
3539					}, ms);
3540					break;
3541				case "activate":
3542					this.tree.timer = setTimeout(function () {
3543						self.tree.debug("setTimeout: trigger activate");
3544						self.setActive(true);
3545					}, ms);
3546					break;
3547				default:
3548					$.error("Invalid mode " + mode);
3549			}
3550			// this.tree.debug("setTimeout(%s, %s): %s", mode, ms, this.tree.timer);
3551		},
3552		/**
3553		 *
3554		 * @param {boolean | PlainObject} [effects=false] animation options.
3555		 * @param {object} [options=null] {topNode: null, effects: ..., parent: ...} this node will remain visible in
3556		 *     any case, even if `this` is outside the scroll pane.
3557		 * @returns {$.Promise}
3558		 */
3559		scrollIntoView: function (effects, options) {
3560			if (options !== undefined && _isNode(options)) {
3561				throw Error(
3562					"scrollIntoView() with 'topNode' option is deprecated since 2014-05-08. Use 'options.topNode' instead."
3563				);
3564			}
3565			// The scroll parent is typically the plain tree's <UL> container.
3566			// For ext-table, we choose the nearest parent that has `position: relative`
3567			// and `overflow` set.
3568			// (This default can be overridden by the local or global `scrollParent` option.)
3569			var opts = $.extend(
3570					{
3571						effects:
3572							effects === true
3573								? { duration: 200, queue: false }
3574								: effects,
3575						scrollOfs: this.tree.options.scrollOfs,
3576						scrollParent: this.tree.options.scrollParent,
3577						topNode: null,
3578					},
3579					options
3580				),
3581				$scrollParent = opts.scrollParent,
3582				$container = this.tree.$container,
3583				overflowY = $container.css("overflow-y");
3584
3585			if (!$scrollParent) {
3586				if (this.tree.tbody) {
3587					$scrollParent = $container.scrollParent();
3588				} else if (overflowY === "scroll" || overflowY === "auto") {
3589					$scrollParent = $container;
3590				} else {
3591					// #922 plain tree in a non-fixed-sized UL scrolls inside its parent
3592					$scrollParent = $container.scrollParent();
3593				}
3594			} else if (!$scrollParent.jquery) {
3595				// Make sure we have a jQuery object
3596				$scrollParent = $($scrollParent);
3597			}
3598			if (
3599				$scrollParent[0] === document ||
3600				$scrollParent[0] === document.body
3601			) {
3602				// `document` may be returned by $().scrollParent(), if nothing is found,
3603				// but would not work: (see #894)
3604				this.debug(
3605					"scrollIntoView(): normalizing scrollParent to 'window':",
3606					$scrollParent[0]
3607				);
3608				$scrollParent = $(window);
3609			}
3610			// eslint-disable-next-line one-var
3611			var topNodeY,
3612				nodeY,
3613				horzScrollbarHeight,
3614				containerOffsetTop,
3615				dfd = new $.Deferred(),
3616				self = this,
3617				nodeHeight = $(this.span).height(),
3618				topOfs = opts.scrollOfs.top || 0,
3619				bottomOfs = opts.scrollOfs.bottom || 0,
3620				containerHeight = $scrollParent.height(),
3621				scrollTop = $scrollParent.scrollTop(),
3622				$animateTarget = $scrollParent,
3623				isParentWindow = $scrollParent[0] === window,
3624				topNode = opts.topNode || null,
3625				newScrollTop = null;
3626
3627			// this.debug("scrollIntoView(), scrollTop=" + scrollTop, opts.scrollOfs);
3628			// _assert($(this.span).is(":visible"), "scrollIntoView node is invisible"); // otherwise we cannot calc offsets
3629			if (this.isRootNode() || !this.isVisible()) {
3630				// We cannot calc offsets for hidden elements
3631				this.info("scrollIntoView(): node is invisible.");
3632				return _getResolvedPromise();
3633			}
3634			if (isParentWindow) {
3635				nodeY = $(this.span).offset().top;
3636				topNodeY =
3637					topNode && topNode.span ? $(topNode.span).offset().top : 0;
3638				$animateTarget = $("html,body");
3639			} else {
3640				_assert(
3641					$scrollParent[0] !== document &&
3642						$scrollParent[0] !== document.body,
3643					"scrollParent should be a simple element or `window`, not document or body."
3644				);
3645
3646				containerOffsetTop = $scrollParent.offset().top;
3647				nodeY =
3648					$(this.span).offset().top - containerOffsetTop + scrollTop; // relative to scroll parent
3649				topNodeY = topNode
3650					? $(topNode.span).offset().top -
3651					  containerOffsetTop +
3652					  scrollTop
3653					: 0;
3654				horzScrollbarHeight = Math.max(
3655					0,
3656					$scrollParent.innerHeight() - $scrollParent[0].clientHeight
3657				);
3658				containerHeight -= horzScrollbarHeight;
3659			}
3660
3661			// this.debug("    scrollIntoView(), nodeY=" + nodeY + ", containerHeight=" + containerHeight);
3662			if (nodeY < scrollTop + topOfs) {
3663				// Node is above visible container area
3664				newScrollTop = nodeY - topOfs;
3665				// this.debug("    scrollIntoView(), UPPER newScrollTop=" + newScrollTop);
3666			} else if (
3667				nodeY + nodeHeight >
3668				scrollTop + containerHeight - bottomOfs
3669			) {
3670				newScrollTop = nodeY + nodeHeight - containerHeight + bottomOfs;
3671				// this.debug("    scrollIntoView(), LOWER newScrollTop=" + newScrollTop);
3672				// If a topNode was passed, make sure that it is never scrolled
3673				// outside the upper border
3674				if (topNode) {
3675					_assert(
3676						topNode.isRootNode() || topNode.isVisible(),
3677						"topNode must be visible"
3678					);
3679					if (topNodeY < newScrollTop) {
3680						newScrollTop = topNodeY - topOfs;
3681						// this.debug("    scrollIntoView(), TOP newScrollTop=" + newScrollTop);
3682					}
3683				}
3684			}
3685
3686			if (newScrollTop === null) {
3687				dfd.resolveWith(this);
3688			} else {
3689				// this.debug("    scrollIntoView(), SET newScrollTop=" + newScrollTop);
3690				if (opts.effects) {
3691					opts.effects.complete = function () {
3692						dfd.resolveWith(self);
3693					};
3694					$animateTarget.stop(true).animate(
3695						{
3696							scrollTop: newScrollTop,
3697						},
3698						opts.effects
3699					);
3700				} else {
3701					$animateTarget[0].scrollTop = newScrollTop;
3702					dfd.resolveWith(this);
3703				}
3704			}
3705			return dfd.promise();
3706		},
3707
3708		/**Activate this node.
3709		 *
3710		 * The `cell` option requires the ext-table and ext-ariagrid extensions.
3711		 *
3712		 * @param {boolean} [flag=true] pass false to deactivate
3713		 * @param {object} [opts] additional options. Defaults to {noEvents: false, noFocus: false, cell: null}
3714		 * @returns {$.Promise}
3715		 */
3716		setActive: function (flag, opts) {
3717			return this.tree._callHook("nodeSetActive", this, flag, opts);
3718		},
3719		/**Expand or collapse this node. Promise is resolved, when lazy loading and animations are done.
3720		 * @param {boolean} [flag=true] pass false to collapse
3721		 * @param {object} [opts] additional options. Defaults to {noAnimation: false, noEvents: false}
3722		 * @returns {$.Promise}
3723		 */
3724		setExpanded: function (flag, opts) {
3725			return this.tree._callHook("nodeSetExpanded", this, flag, opts);
3726		},
3727		/**Set keyboard focus to this node.
3728		 * @param {boolean} [flag=true] pass false to blur
3729		 * @see Fancytree#setFocus
3730		 */
3731		setFocus: function (flag) {
3732			return this.tree._callHook("nodeSetFocus", this, flag);
3733		},
3734		/**Select this node, i.e. check the checkbox.
3735		 * @param {boolean} [flag=true] pass false to deselect
3736		 * @param {object} [opts] additional options. Defaults to {noEvents: false, p
3737		 *     propagateDown: null, propagateUp: null, callback: null }
3738		 */
3739		setSelected: function (flag, opts) {
3740			return this.tree._callHook("nodeSetSelected", this, flag, opts);
3741		},
3742		/**Mark a lazy node as 'error', 'loading', 'nodata', or 'ok'.
3743		 * @param {string} status 'error'|'loading'|'nodata'|'ok'
3744		 * @param {string} [message]
3745		 * @param {string} [details]
3746		 */
3747		setStatus: function (status, message, details) {
3748			return this.tree._callHook(
3749				"nodeSetStatus",
3750				this,
3751				status,
3752				message,
3753				details
3754			);
3755		},
3756		/**Rename this node.
3757		 * @param {string} title
3758		 */
3759		setTitle: function (title) {
3760			this.title = title;
3761			this.renderTitle();
3762			this.triggerModify("rename");
3763		},
3764		/**Sort child list by title.
3765		 * @param {function} [cmp] custom compare function(a, b) that returns -1, 0, or 1 (defaults to sort by title).
3766		 * @param {boolean} [deep=false] pass true to sort all descendant nodes
3767		 */
3768		sortChildren: function (cmp, deep) {
3769			var i,
3770				l,
3771				cl = this.children;
3772
3773			if (!cl) {
3774				return;
3775			}
3776			cmp =
3777				cmp ||
3778				function (a, b) {
3779					var x = a.title.toLowerCase(),
3780						y = b.title.toLowerCase();
3781
3782					// eslint-disable-next-line no-nested-ternary
3783					return x === y ? 0 : x > y ? 1 : -1;
3784				};
3785			cl.sort(cmp);
3786			if (deep) {
3787				for (i = 0, l = cl.length; i < l; i++) {
3788					if (cl[i].children) {
3789						cl[i].sortChildren(cmp, "$norender$");
3790					}
3791				}
3792			}
3793			if (deep !== "$norender$") {
3794				this.render();
3795			}
3796			this.triggerModifyChild("sort");
3797		},
3798		/** Convert node (or whole branch) into a plain object.
3799		 *
3800		 * The result is compatible with node.addChildren().
3801		 *
3802		 * @param {boolean} [recursive=false] include child nodes
3803		 * @param {function} [callback] callback(dict, node) is called for every node, in order to allow modifications.
3804		 *     Return `false` to ignore this node or `"skip"` to include this node without its children.
3805		 * @returns {NodeData}
3806		 */
3807		toDict: function (recursive, callback) {
3808			var i,
3809				l,
3810				node,
3811				res,
3812				dict = {},
3813				self = this;
3814
3815			$.each(NODE_ATTRS, function (i, a) {
3816				if (self[a] || self[a] === false) {
3817					dict[a] = self[a];
3818				}
3819			});
3820			if (!$.isEmptyObject(this.data)) {
3821				dict.data = $.extend({}, this.data);
3822				if ($.isEmptyObject(dict.data)) {
3823					delete dict.data;
3824				}
3825			}
3826			if (callback) {
3827				res = callback(dict, self);
3828				if (res === false) {
3829					return false; // Don't include this node nor its children
3830				}
3831				if (res === "skip") {
3832					recursive = false; // Include this node, but not the children
3833				}
3834			}
3835			if (recursive) {
3836				if (_isArray(this.children)) {
3837					dict.children = [];
3838					for (i = 0, l = this.children.length; i < l; i++) {
3839						node = this.children[i];
3840						if (!node.isStatusNode()) {
3841							res = node.toDict(true, callback);
3842							if (res !== false) {
3843								dict.children.push(res);
3844							}
3845						}
3846					}
3847				}
3848			}
3849			return dict;
3850		},
3851		/**
3852		 * Set, clear, or toggle class of node's span tag and .extraClasses.
3853		 *
3854		 * @param {string} className class name (separate multiple classes by space)
3855		 * @param {boolean} [flag] true/false to add/remove class. If omitted, class is toggled.
3856		 * @returns {boolean} true if a class was added
3857		 *
3858		 * @since 2.17
3859		 */
3860		toggleClass: function (value, flag) {
3861			var className,
3862				hasClass,
3863				rnotwhite = /\S+/g,
3864				classNames = value.match(rnotwhite) || [],
3865				i = 0,
3866				wasAdded = false,
3867				statusElem = this[this.tree.statusClassPropName],
3868				curClasses = " " + (this.extraClasses || "") + " ";
3869
3870			// this.info("toggleClass('" + value + "', " + flag + ")", curClasses);
3871			// Modify DOM element directly if it already exists
3872			if (statusElem) {
3873				$(statusElem).toggleClass(value, flag);
3874			}
3875			// Modify node.extraClasses to make this change persistent
3876			// Toggle if flag was not passed
3877			while ((className = classNames[i++])) {
3878				hasClass = curClasses.indexOf(" " + className + " ") >= 0;
3879				flag = flag === undefined ? !hasClass : !!flag;
3880				if (flag) {
3881					if (!hasClass) {
3882						curClasses += className + " ";
3883						wasAdded = true;
3884					}
3885				} else {
3886					while (curClasses.indexOf(" " + className + " ") > -1) {
3887						curClasses = curClasses.replace(
3888							" " + className + " ",
3889							" "
3890						);
3891					}
3892				}
3893			}
3894			this.extraClasses = _trim(curClasses);
3895			// this.info("-> toggleClass('" + value + "', " + flag + "): '" + this.extraClasses + "'");
3896			return wasAdded;
3897		},
3898		/** Flip expanded status. */
3899		toggleExpanded: function () {
3900			return this.tree._callHook("nodeToggleExpanded", this);
3901		},
3902		/** Flip selection status. */
3903		toggleSelected: function () {
3904			return this.tree._callHook("nodeToggleSelected", this);
3905		},
3906		toString: function () {
3907			return "FancytreeNode@" + this.key + "[title='" + this.title + "']";
3908			// return "<FancytreeNode(#" + this.key + ", '" + this.title + "')>";
3909		},
3910		/**
3911		 * Trigger `modifyChild` event on a parent to signal that a child was modified.
3912		 * @param {string} operation Type of change: 'add', 'remove', 'rename', 'move', 'data', ...
3913		 * @param {FancytreeNode} [childNode]
3914		 * @param {object} [extra]
3915		 */
3916		triggerModifyChild: function (operation, childNode, extra) {
3917			var data,
3918				modifyChild = this.tree.options.modifyChild;
3919
3920			if (modifyChild) {
3921				if (childNode && childNode.parent !== this) {
3922					$.error(
3923						"childNode " + childNode + " is not a child of " + this
3924					);
3925				}
3926				data = {
3927					node: this,
3928					tree: this.tree,
3929					operation: operation,
3930					childNode: childNode || null,
3931				};
3932				if (extra) {
3933					$.extend(data, extra);
3934				}
3935				modifyChild({ type: "modifyChild" }, data);
3936			}
3937		},
3938		/**
3939		 * Trigger `modifyChild` event on node.parent(!).
3940		 * @param {string} operation Type of change: 'add', 'remove', 'rename', 'move', 'data', ...
3941		 * @param {object} [extra]
3942		 */
3943		triggerModify: function (operation, extra) {
3944			this.parent.triggerModifyChild(operation, this, extra);
3945		},
3946		/** Call fn(node) for all child nodes in hierarchical order (depth-first).<br>
3947		 * Stop iteration, if fn() returns false. Skip current branch, if fn() returns "skip".<br>
3948		 * Return false if iteration was stopped.
3949		 *
3950		 * @param {function} fn the callback function.
3951		 *     Return false to stop iteration, return "skip" to skip this node and
3952		 *     its children only.
3953		 * @param {boolean} [includeSelf=false]
3954		 * @returns {boolean}
3955		 */
3956		visit: function (fn, includeSelf) {
3957			var i,
3958				l,
3959				res = true,
3960				children = this.children;
3961
3962			if (includeSelf === true) {
3963				res = fn(this);
3964				if (res === false || res === "skip") {
3965					return res;
3966				}
3967			}
3968			if (children) {
3969				for (i = 0, l = children.length; i < l; i++) {
3970					res = children[i].visit(fn, true);
3971					if (res === false) {
3972						break;
3973					}
3974				}
3975			}
3976			return res;
3977		},
3978		/** Call fn(node) for all child nodes and recursively load lazy children.<br>
3979		 * <b>Note:</b> If you need this method, you probably should consider to review
3980		 * your architecture! Recursivley loading nodes is a perfect way for lazy
3981		 * programmers to flood the server with requests ;-)
3982		 *
3983		 * @param {function} [fn] optional callback function.
3984		 *     Return false to stop iteration, return "skip" to skip this node and
3985		 *     its children only.
3986		 * @param {boolean} [includeSelf=false]
3987		 * @returns {$.Promise}
3988		 * @since 2.4
3989		 */
3990		visitAndLoad: function (fn, includeSelf, _recursion) {
3991			var dfd,
3992				res,
3993				loaders,
3994				node = this;
3995
3996			// node.debug("visitAndLoad");
3997			if (fn && includeSelf === true) {
3998				res = fn(node);
3999				if (res === false || res === "skip") {
4000					return _recursion ? res : _getResolvedPromise();
4001				}
4002			}
4003			if (!node.children && !node.lazy) {
4004				return _getResolvedPromise();
4005			}
4006			dfd = new $.Deferred();
4007			loaders = [];
4008			// node.debug("load()...");
4009			node.load().done(function () {
4010				// node.debug("load()... done.");
4011				for (var i = 0, l = node.children.length; i < l; i++) {
4012					res = node.children[i].visitAndLoad(fn, true, true);
4013					if (res === false) {
4014						dfd.reject();
4015						break;
4016					} else if (res !== "skip") {
4017						loaders.push(res); // Add promise to the list
4018					}
4019				}
4020				$.when.apply(this, loaders).then(function () {
4021					dfd.resolve();
4022				});
4023			});
4024			return dfd.promise();
4025		},
4026		/** Call fn(node) for all parent nodes, bottom-up, including invisible system root.<br>
4027		 * Stop iteration, if fn() returns false.<br>
4028		 * Return false if iteration was stopped.
4029		 *
4030		 * @param {function} fn the callback function.
4031		 *     Return false to stop iteration, return "skip" to skip this node and children only.
4032		 * @param {boolean} [includeSelf=false]
4033		 * @returns {boolean}
4034		 */
4035		visitParents: function (fn, includeSelf) {
4036			// Visit parent nodes (bottom up)
4037			if (includeSelf && fn(this) === false) {
4038				return false;
4039			}
4040			var p = this.parent;
4041			while (p) {
4042				if (fn(p) === false) {
4043					return false;
4044				}
4045				p = p.parent;
4046			}
4047			return true;
4048		},
4049		/** Call fn(node) for all sibling nodes.<br>
4050		 * Stop iteration, if fn() returns false.<br>
4051		 * Return false if iteration was stopped.
4052		 *
4053		 * @param {function} fn the callback function.
4054		 *     Return false to stop iteration.
4055		 * @param {boolean} [includeSelf=false]
4056		 * @returns {boolean}
4057		 */
4058		visitSiblings: function (fn, includeSelf) {
4059			var i,
4060				l,
4061				n,
4062				ac = this.parent.children;
4063
4064			for (i = 0, l = ac.length; i < l; i++) {
4065				n = ac[i];
4066				if (includeSelf || n !== this) {
4067					if (fn(n) === false) {
4068						return false;
4069					}
4070				}
4071			}
4072			return true;
4073		},
4074		/** Write warning to browser console if debugLevel >= 2 (prepending node info)
4075		 *
4076		 * @param {*} msg string or object or array of such
4077		 */
4078		warn: function (msg) {
4079			if (this.tree.options.debugLevel >= 2) {
4080				Array.prototype.unshift.call(arguments, this.toString());
4081				consoleApply("warn", arguments);
4082			}
4083		},
4084	};
4085
4086	/******************************************************************************
4087	 * Fancytree
4088	 */
4089	/**
4090	 * Construct a new tree object.
4091	 *
4092	 * @class Fancytree
4093	 * @classdesc The controller behind a fancytree.
4094	 * This class also contains 'hook methods': see {@link Fancytree_Hooks}.
4095	 *
4096	 * @param {Widget} widget
4097	 *
4098	 * @property {string} _id Automatically generated unique tree instance ID, e.g. "1".
4099	 * @property {string} _ns Automatically generated unique tree namespace, e.g. ".fancytree-1".
4100	 * @property {FancytreeNode} activeNode Currently active node or null.
4101	 * @property {string} ariaPropName Property name of FancytreeNode that contains the element which will receive the aria attributes.
4102	 *     Typically "li", but "tr" for table extension.
4103	 * @property {jQueryObject} $container Outer `<ul>` element (or `<table>` element for ext-table).
4104	 * @property {jQueryObject} $div A jQuery object containing the element used to instantiate the tree widget (`widget.element`)
4105	 * @property {object|array} columns Recommended place to store shared column meta data. @since 2.27
4106	 * @property {object} data Metadata, i.e. properties that may be passed to `source` in addition to a children array.
4107	 * @property {object} ext Hash of all active plugin instances.
4108	 * @property {FancytreeNode} focusNode Currently focused node or null.
4109	 * @property {FancytreeNode} lastSelectedNode Used to implement selectMode 1 (single select)
4110	 * @property {string} nodeContainerAttrName Property name of FancytreeNode that contains the outer element of single nodes.
4111	 *     Typically "li", but "tr" for table extension.
4112	 * @property {FancytreeOptions} options Current options, i.e. default options + options passed to constructor.
4113	 * @property {FancytreeNode} rootNode Invisible system root node.
4114	 * @property {string} statusClassPropName Property name of FancytreeNode that contains the element which will receive the status classes.
4115	 *     Typically "span", but "tr" for table extension.
4116	 * @property {object} types Map for shared type specific meta data, used with node.type attribute. @since 2.27
4117	 * @property {object} viewport See ext-vieport. @since v2.31
4118	 * @property {object} widget Base widget instance.
4119	 */
4120	function Fancytree(widget) {
4121		this.widget = widget;
4122		this.$div = widget.element;
4123		this.options = widget.options;
4124		if (this.options) {
4125			if (this.options.lazyload !== undefined) {
4126				$.error(
4127					"The 'lazyload' event is deprecated since 2014-02-25. Use 'lazyLoad' (with uppercase L) instead."
4128				);
4129			}
4130			if (this.options.loaderror !== undefined) {
4131				$.error(
4132					"The 'loaderror' event was renamed since 2014-07-03. Use 'loadError' (with uppercase E) instead."
4133				);
4134			}
4135			if (this.options.fx !== undefined) {
4136				$.error(
4137					"The 'fx' option was replaced by 'toggleEffect' since 2014-11-30."
4138				);
4139			}
4140			if (this.options.removeNode !== undefined) {
4141				$.error(
4142					"The 'removeNode' event was replaced by 'modifyChild' since 2.20 (2016-09-10)."
4143				);
4144			}
4145		}
4146		this.ext = {}; // Active extension instances
4147		this.types = {};
4148		this.columns = {};
4149		// allow to init tree.data.foo from <div data-foo=''>
4150		this.data = _getElementDataAsDict(this.$div);
4151		// TODO: use widget.uuid instead?
4152		this._id = "" + (this.options.treeId || $.ui.fancytree._nextId++);
4153		// TODO: use widget.eventNamespace instead?
4154		this._ns = ".fancytree-" + this._id; // append for namespaced events
4155		this.activeNode = null;
4156		this.focusNode = null;
4157		this._hasFocus = null;
4158		this._tempCache = {};
4159		this._lastMousedownNode = null;
4160		this._enableUpdate = true;
4161		this.lastSelectedNode = null;
4162		this.systemFocusElement = null;
4163		this.lastQuicksearchTerm = "";
4164		this.lastQuicksearchTime = 0;
4165		this.viewport = null; // ext-grid
4166
4167		this.statusClassPropName = "span";
4168		this.ariaPropName = "li";
4169		this.nodeContainerAttrName = "li";
4170
4171		// Remove previous markup if any
4172		this.$div.find(">ul.fancytree-container").remove();
4173
4174		// Create a node without parent.
4175		var fakeParent = { tree: this },
4176			$ul;
4177		this.rootNode = new FancytreeNode(fakeParent, {
4178			title: "root",
4179			key: "root_" + this._id,
4180			children: null,
4181			expanded: true,
4182		});
4183		this.rootNode.parent = null;
4184
4185		// Create root markup
4186		$ul = $("<ul>", {
4187			id: "ft-id-" + this._id,
4188			class: "ui-fancytree fancytree-container fancytree-plain",
4189		}).appendTo(this.$div);
4190		this.$container = $ul;
4191		this.rootNode.ul = $ul[0];
4192
4193		if (this.options.debugLevel == null) {
4194			this.options.debugLevel = FT.debugLevel;
4195		}
4196		// // Add container to the TAB chain
4197		// // See http://www.w3.org/TR/wai-aria-practices/#focus_activedescendant
4198		// // #577: Allow to set tabindex to "0", "-1" and ""
4199		// this.$container.attr("tabindex", this.options.tabindex);
4200
4201		// if( this.options.rtl ) {
4202		// 	this.$container.attr("DIR", "RTL").addClass("fancytree-rtl");
4203		// // }else{
4204		// //	this.$container.attr("DIR", null).removeClass("fancytree-rtl");
4205		// }
4206		// if(this.options.aria){
4207		// 	this.$container.attr("role", "tree");
4208		// 	if( this.options.selectMode !== 1 ) {
4209		// 		this.$container.attr("aria-multiselectable", true);
4210		// 	}
4211		// }
4212	}
4213
4214	Fancytree.prototype = /** @lends Fancytree# */ {
4215		/* Return a context object that can be re-used for _callHook().
4216		 * @param {Fancytree | FancytreeNode | EventData} obj
4217		 * @param {Event} originalEvent
4218		 * @param {Object} extra
4219		 * @returns {EventData}
4220		 */
4221		_makeHookContext: function (obj, originalEvent, extra) {
4222			var ctx, tree;
4223			if (obj.node !== undefined) {
4224				// obj is already a context object
4225				if (originalEvent && obj.originalEvent !== originalEvent) {
4226					$.error("invalid args");
4227				}
4228				ctx = obj;
4229			} else if (obj.tree) {
4230				// obj is a FancytreeNode
4231				tree = obj.tree;
4232				ctx = {
4233					node: obj,
4234					tree: tree,
4235					widget: tree.widget,
4236					options: tree.widget.options,
4237					originalEvent: originalEvent,
4238					typeInfo: tree.types[obj.type] || {},
4239				};
4240			} else if (obj.widget) {
4241				// obj is a Fancytree
4242				ctx = {
4243					node: null,
4244					tree: obj,
4245					widget: obj.widget,
4246					options: obj.widget.options,
4247					originalEvent: originalEvent,
4248				};
4249			} else {
4250				$.error("invalid args");
4251			}
4252			if (extra) {
4253				$.extend(ctx, extra);
4254			}
4255			return ctx;
4256		},
4257		/* Trigger a hook function: funcName(ctx, [...]).
4258		 *
4259		 * @param {string} funcName
4260		 * @param {Fancytree|FancytreeNode|EventData} contextObject
4261		 * @param {any}  [_extraArgs] optional additional arguments
4262		 * @returns {any}
4263		 */
4264		_callHook: function (funcName, contextObject, _extraArgs) {
4265			var ctx = this._makeHookContext(contextObject),
4266				fn = this[funcName],
4267				args = Array.prototype.slice.call(arguments, 2);
4268			if (!_isFunction(fn)) {
4269				$.error("_callHook('" + funcName + "') is not a function");
4270			}
4271			args.unshift(ctx);
4272			// this.debug("_hook", funcName, ctx.node && ctx.node.toString() || ctx.tree.toString(), args);
4273			return fn.apply(this, args);
4274		},
4275		_setExpiringValue: function (key, value, ms) {
4276			this._tempCache[key] = {
4277				value: value,
4278				expire: Date.now() + (+ms || 50),
4279			};
4280		},
4281		_getExpiringValue: function (key) {
4282			var entry = this._tempCache[key];
4283			if (entry && entry.expire > Date.now()) {
4284				return entry.value;
4285			}
4286			delete this._tempCache[key];
4287			return null;
4288		},
4289		/* Check if this tree has extension `name` enabled.
4290		 *
4291		 * @param {string} name name of the required extension
4292		 */
4293		_usesExtension: function (name) {
4294			return $.inArray(name, this.options.extensions) >= 0;
4295		},
4296		/* Check if current extensions dependencies are met and throw an error if not.
4297		 *
4298		 * This method may be called inside the `treeInit` hook for custom extensions.
4299		 *
4300		 * @param {string} name name of the required extension
4301		 * @param {boolean} [required=true] pass `false` if the extension is optional, but we want to check for order if it is present
4302		 * @param {boolean} [before] `true` if `name` must be included before this, `false` otherwise (use `null` if order doesn't matter)
4303		 * @param {string} [message] optional error message (defaults to a descriptve error message)
4304		 */
4305		_requireExtension: function (name, required, before, message) {
4306			if (before != null) {
4307				before = !!before;
4308			}
4309			var thisName = this._local.name,
4310				extList = this.options.extensions,
4311				isBefore =
4312					$.inArray(name, extList) < $.inArray(thisName, extList),
4313				isMissing = required && this.ext[name] == null,
4314				badOrder = !isMissing && before != null && before !== isBefore;
4315
4316			_assert(
4317				thisName && thisName !== name,
4318				"invalid or same name '" + thisName + "' (require yourself?)"
4319			);
4320
4321			if (isMissing || badOrder) {
4322				if (!message) {
4323					if (isMissing || required) {
4324						message =
4325							"'" +
4326							thisName +
4327							"' extension requires '" +
4328							name +
4329							"'";
4330						if (badOrder) {
4331							message +=
4332								" to be registered " +
4333								(before ? "before" : "after") +
4334								" itself";
4335						}
4336					} else {
4337						message =
4338							"If used together, `" +
4339							name +
4340							"` must be registered " +
4341							(before ? "before" : "after") +
4342							" `" +
4343							thisName +
4344							"`";
4345					}
4346				}
4347				$.error(message);
4348				return false;
4349			}
4350			return true;
4351		},
4352		/** Activate node with a given key and fire focus and activate events.
4353		 *
4354		 * A previously activated node will be deactivated.
4355		 * If activeVisible option is set, all parents will be expanded as necessary.
4356		 * Pass key = false, to deactivate the current node only.
4357		 * @param {string} key
4358		 * @param {object} [opts] additional options. Defaults to {noEvents: false, noFocus: false}
4359		 * @returns {FancytreeNode} activated node (null, if not found)
4360		 */
4361		activateKey: function (key, opts) {
4362			var node = this.getNodeByKey(key);
4363			if (node) {
4364				node.setActive(true, opts);
4365			} else if (this.activeNode) {
4366				this.activeNode.setActive(false, opts);
4367			}
4368			return node;
4369		},
4370		/** (experimental) Add child status nodes that indicate 'More...', ....
4371		 * @param {boolean|object} node optional node definition. Pass `false` to remove all paging nodes.
4372		 * @param {string} [mode='append'] 'child'|firstChild'
4373		 * @since 2.15
4374		 */
4375		addPagingNode: function (node, mode) {
4376			return this.rootNode.addPagingNode(node, mode);
4377		},
4378		/**
4379		 * (experimental) Apply a modification (or navigation) operation.
4380		 *
4381		 * Valid commands:
4382		 *   - 'moveUp', 'moveDown'
4383		 *   - 'indent', 'outdent'
4384		 *   - 'remove'
4385		 *   - 'edit', 'addChild', 'addSibling': (reqires ext-edit extension)
4386		 *   - 'cut', 'copy', 'paste': (use an internal singleton 'clipboard')
4387		 *   - 'down', 'first', 'last', 'left', 'parent', 'right', 'up': navigate
4388		 *
4389		 * @param {string} cmd
4390		 * @param {FancytreeNode} [node=active_node]
4391		 * @param {object} [opts] Currently unused
4392		 *
4393		 * @since 2.32
4394		 */
4395		applyCommand: function (cmd, node, opts_) {
4396			var // clipboard,
4397				refNode;
4398			// opts = $.extend(
4399			// 	{ setActive: true, clipboard: CLIPBOARD },
4400			// 	opts_
4401			// );
4402
4403			node = node || this.getActiveNode();
4404			// clipboard = opts.clipboard;
4405
4406			switch (cmd) {
4407				// Sorting and indentation:
4408				case "moveUp":
4409					refNode = node.getPrevSibling();
4410					if (refNode) {
4411						node.moveTo(refNode, "before");
4412						node.setActive();
4413					}
4414					break;
4415				case "moveDown":
4416					refNode = node.getNextSibling();
4417					if (refNode) {
4418						node.moveTo(refNode, "after");
4419						node.setActive();
4420					}
4421					break;
4422				case "indent":
4423					refNode = node.getPrevSibling();
4424					if (refNode) {
4425						node.moveTo(refNode, "child");
4426						refNode.setExpanded();
4427						node.setActive();
4428					}
4429					break;
4430				case "outdent":
4431					if (!node.isTopLevel()) {
4432						node.moveTo(node.getParent(), "after");
4433						node.setActive();
4434					}
4435					break;
4436				// Remove:
4437				case "remove":
4438					refNode = node.getPrevSibling() || node.getParent();
4439					node.remove();
4440					if (refNode) {
4441						refNode.setActive();
4442					}
4443					break;
4444				// Add, edit (requires ext-edit):
4445				case "addChild":
4446					node.editCreateNode("child", "");
4447					break;
4448				case "addSibling":
4449					node.editCreateNode("after", "");
4450					break;
4451				case "rename":
4452					node.editStart();
4453					break;
4454				// Simple clipboard simulation:
4455				// case "cut":
4456				// 	clipboard = { mode: cmd, data: node };
4457				// 	break;
4458				// case "copy":
4459				// 	clipboard = {
4460				// 		mode: cmd,
4461				// 		data: node.toDict(function(d, n) {
4462				// 			delete d.key;
4463				// 		}),
4464				// 	};
4465				// 	break;
4466				// case "clear":
4467				// 	clipboard = null;
4468				// 	break;
4469				// case "paste":
4470				// 	if (clipboard.mode === "cut") {
4471				// 		// refNode = node.getPrevSibling();
4472				// 		clipboard.data.moveTo(node, "child");
4473				// 		clipboard.data.setActive();
4474				// 	} else if (clipboard.mode === "copy") {
4475				// 		node.addChildren(clipboard.data).setActive();
4476				// 	}
4477				// 	break;
4478				// Navigation commands:
4479				case "down":
4480				case "first":
4481				case "last":
4482				case "left":
4483				case "parent":
4484				case "right":
4485				case "up":
4486					return node.navigate(cmd);
4487				default:
4488					$.error("Unhandled command: '" + cmd + "'");
4489			}
4490		},
4491		/** (experimental) Modify existing data model.
4492		 *
4493		 * @param {Array} patchList array of [key, NodePatch] arrays
4494		 * @returns {$.Promise} resolved, when all patches have been applied
4495		 * @see TreePatch
4496		 */
4497		applyPatch: function (patchList) {
4498			var dfd,
4499				i,
4500				p2,
4501				key,
4502				patch,
4503				node,
4504				patchCount = patchList.length,
4505				deferredList = [];
4506
4507			for (i = 0; i < patchCount; i++) {
4508				p2 = patchList[i];
4509				_assert(
4510					p2.length === 2,
4511					"patchList must be an array of length-2-arrays"
4512				);
4513				key = p2[0];
4514				patch = p2[1];
4515				node = key === null ? this.rootNode : this.getNodeByKey(key);
4516				if (node) {
4517					dfd = new $.Deferred();
4518					deferredList.push(dfd);
4519					node.applyPatch(patch).always(_makeResolveFunc(dfd, node));
4520				} else {
4521					this.warn("could not find node with key '" + key + "'");
4522				}
4523			}
4524			// Return a promise that is resolved, when ALL patches were applied
4525			return $.when.apply($, deferredList).promise();
4526		},
4527		/* TODO: implement in dnd extension
4528		cancelDrag: function() {
4529				var dd = $.ui.ddmanager.current;
4530				if(dd){
4531					dd.cancel();
4532				}
4533			},
4534		*/
4535		/** Remove all nodes.
4536		 * @since 2.14
4537		 */
4538		clear: function (source) {
4539			this._callHook("treeClear", this);
4540		},
4541		/** Return the number of nodes.
4542		 * @returns {integer}
4543		 */
4544		count: function () {
4545			return this.rootNode.countChildren();
4546		},
4547		/** Write to browser console if debugLevel >= 4 (prepending tree name)
4548		 *
4549		 * @param {*} msg string or object or array of such
4550		 */
4551		debug: function (msg) {
4552			if (this.options.debugLevel >= 4) {
4553				Array.prototype.unshift.call(arguments, this.toString());
4554				consoleApply("log", arguments);
4555			}
4556		},
4557		/** Destroy this widget, restore previous markup and cleanup resources.
4558		 *
4559		 * @since 2.34
4560		 */
4561		destroy: function () {
4562			this.widget.destroy();
4563		},
4564		/** Enable (or disable) the tree control.
4565		 *
4566		 * @param {boolean} [flag=true] pass false to disable
4567		 * @since 2.30
4568		 */
4569		enable: function (flag) {
4570			if (flag === false) {
4571				this.widget.disable();
4572			} else {
4573				this.widget.enable();
4574			}
4575		},
4576		/** Temporarily suppress rendering to improve performance on bulk-updates.
4577		 *
4578		 * @param {boolean} flag
4579		 * @returns {boolean} previous status
4580		 * @since 2.19
4581		 */
4582		enableUpdate: function (flag) {
4583			flag = flag !== false;
4584			if (!!this._enableUpdate === !!flag) {
4585				return flag;
4586			}
4587			this._enableUpdate = flag;
4588			if (flag) {
4589				this.debug("enableUpdate(true): redraw "); //, this._dirtyRoots);
4590				this._callHook("treeStructureChanged", this, "enableUpdate");
4591				this.render();
4592			} else {
4593				// 	this._dirtyRoots = null;
4594				this.debug("enableUpdate(false)...");
4595			}
4596			return !flag; // return previous value
4597		},
4598		/** Write error to browser console if debugLevel >= 1 (prepending tree info)
4599		 *
4600		 * @param {*} msg string or object or array of such
4601		 */
4602		error: function (msg) {
4603			if (this.options.debugLevel >= 1) {
4604				Array.prototype.unshift.call(arguments, this.toString());
4605				consoleApply("error", arguments);
4606			}
4607		},
4608		/** Expand (or collapse) all parent nodes.
4609		 *
4610		 * This convenience method uses `tree.visit()` and `tree.setExpanded()`
4611		 * internally.
4612		 *
4613		 * @param {boolean} [flag=true] pass false to collapse
4614		 * @param {object} [opts] passed to setExpanded()
4615		 * @since 2.30
4616		 */
4617		expandAll: function (flag, opts) {
4618			var prev = this.enableUpdate(false);
4619
4620			flag = flag !== false;
4621			this.visit(function (node) {
4622				if (
4623					node.hasChildren() !== false &&
4624					node.isExpanded() !== flag
4625				) {
4626					node.setExpanded(flag, opts);
4627				}
4628			});
4629			this.enableUpdate(prev);
4630		},
4631		/**Find all nodes that matches condition.
4632		 *
4633		 * @param {string | function(node)} match title string to search for, or a
4634		 *     callback function that returns `true` if a node is matched.
4635		 * @returns {FancytreeNode[]} array of nodes (may be empty)
4636		 * @see FancytreeNode#findAll
4637		 * @since 2.12
4638		 */
4639		findAll: function (match) {
4640			return this.rootNode.findAll(match);
4641		},
4642		/**Find first node that matches condition.
4643		 *
4644		 * @param {string | function(node)} match title string to search for, or a
4645		 *     callback function that returns `true` if a node is matched.
4646		 * @returns {FancytreeNode} matching node or null
4647		 * @see FancytreeNode#findFirst
4648		 * @since 2.12
4649		 */
4650		findFirst: function (match) {
4651			return this.rootNode.findFirst(match);
4652		},
4653		/** Find the next visible node that starts with `match`, starting at `startNode`
4654		 * and wrap-around at the end.
4655		 *
4656		 * @param {string|function} match
4657		 * @param {FancytreeNode} [startNode] defaults to first node
4658		 * @returns {FancytreeNode} matching node or null
4659		 */
4660		findNextNode: function (match, startNode) {
4661			//, visibleOnly) {
4662			var res = null,
4663				firstNode = this.getFirstChild();
4664
4665			match =
4666				typeof match === "string"
4667					? _makeNodeTitleStartMatcher(match)
4668					: match;
4669			startNode = startNode || firstNode;
4670
4671			function _checkNode(n) {
4672				// console.log("_check " + n)
4673				if (match(n)) {
4674					res = n;
4675				}
4676				if (res || n === startNode) {
4677					return false;
4678				}
4679			}
4680			this.visitRows(_checkNode, {
4681				start: startNode,
4682				includeSelf: false,
4683			});
4684			// Wrap around search
4685			if (!res && startNode !== firstNode) {
4686				this.visitRows(_checkNode, {
4687					start: firstNode,
4688					includeSelf: true,
4689				});
4690			}
4691			return res;
4692		},
4693		/** Find a node relative to another node.
4694		 *
4695		 * @param {FancytreeNode} node
4696		 * @param {string|number} where 'down', 'first', 'last', 'left', 'parent', 'right', or 'up'.
4697		 *   (Alternatively the keyCode that would normally trigger this move,
4698		 *   e.g. `$.ui.keyCode.LEFT` = 'left'.
4699		 * @param {boolean} [includeHidden=false] Not yet implemented
4700		 * @returns {FancytreeNode|null}
4701		 * @since v2.31
4702		 */
4703		findRelatedNode: function (node, where, includeHidden) {
4704			var res = null,
4705				KC = $.ui.keyCode;
4706
4707			switch (where) {
4708				case "parent":
4709				case KC.BACKSPACE:
4710					if (node.parent && node.parent.parent) {
4711						res = node.parent;
4712					}
4713					break;
4714				case "first":
4715				case KC.HOME:
4716					// First visible node
4717					this.visit(function (n) {
4718						if (n.isVisible()) {
4719							res = n;
4720							return false;
4721						}
4722					});
4723					break;
4724				case "last":
4725				case KC.END:
4726					this.visit(function (n) {
4727						// last visible node
4728						if (n.isVisible()) {
4729							res = n;
4730						}
4731					});
4732					break;
4733				case "left":
4734				case KC.LEFT:
4735					if (node.expanded) {
4736						node.setExpanded(false);
4737					} else if (node.parent && node.parent.parent) {
4738						res = node.parent;
4739					}
4740					break;
4741				case "right":
4742				case KC.RIGHT:
4743					if (!node.expanded && (node.children || node.lazy)) {
4744						node.setExpanded();
4745						res = node;
4746					} else if (node.children && node.children.length) {
4747						res = node.children[0];
4748					}
4749					break;
4750				case "up":
4751				case KC.UP:
4752					this.visitRows(
4753						function (n) {
4754							res = n;
4755							return false;
4756						},
4757						{ start: node, reverse: true, includeSelf: false }
4758					);
4759					break;
4760				case "down":
4761				case KC.DOWN:
4762					this.visitRows(
4763						function (n) {
4764							res = n;
4765							return false;
4766						},
4767						{ start: node, includeSelf: false }
4768					);
4769					break;
4770				default:
4771					this.tree.warn("Unknown relation '" + where + "'.");
4772			}
4773			return res;
4774		},
4775		// TODO: fromDict
4776		/**
4777		 * Generate INPUT elements that can be submitted with html forms.
4778		 *
4779		 * In selectMode 3 only the topmost selected nodes are considered, unless
4780		 * `opts.stopOnParents: false` is passed.
4781		 *
4782		 * @example
4783		 * // Generate input elements for active and selected nodes
4784		 * tree.generateFormElements();
4785		 * // Generate input elements selected nodes, using a custom `name` attribute
4786		 * tree.generateFormElements("cust_sel", false);
4787		 * // Generate input elements using a custom filter
4788		 * tree.generateFormElements(true, true, { filter: function(node) {
4789		 *     return node.isSelected() && node.data.yes;
4790		 * }});
4791		 *
4792		 * @param {boolean | string} [selected=true] Pass false to disable, pass a string to override the field name (default: 'ft_ID[]')
4793		 * @param {boolean | string} [active=true] Pass false to disable, pass a string to override the field name (default: 'ft_ID_active')
4794		 * @param {object} [opts] default { filter: null, stopOnParents: true }
4795		 */
4796		generateFormElements: function (selected, active, opts) {
4797			opts = opts || {};
4798
4799			var nodeList,
4800				selectedName =
4801					typeof selected === "string"
4802						? selected
4803						: "ft_" + this._id + "[]",
4804				activeName =
4805					typeof active === "string"
4806						? active
4807						: "ft_" + this._id + "_active",
4808				id = "fancytree_result_" + this._id,
4809				$result = $("#" + id),
4810				stopOnParents =
4811					this.options.selectMode === 3 &&
4812					opts.stopOnParents !== false;
4813
4814			if ($result.length) {
4815				$result.empty();
4816			} else {
4817				$result = $("<div>", {
4818					id: id,
4819				})
4820					.hide()
4821					.insertAfter(this.$container);
4822			}
4823			if (active !== false && this.activeNode) {
4824				$result.append(
4825					$("<input>", {
4826						type: "radio",
4827						name: activeName,
4828						value: this.activeNode.key,
4829						checked: true,
4830					})
4831				);
4832			}
4833			function _appender(node) {
4834				$result.append(
4835					$("<input>", {
4836						type: "checkbox",
4837						name: selectedName,
4838						value: node.key,
4839						checked: true,
4840					})
4841				);
4842			}
4843			if (opts.filter) {
4844				this.visit(function (node) {
4845					var res = opts.filter(node);
4846					if (res === "skip") {
4847						return res;
4848					}
4849					if (res !== false) {
4850						_appender(node);
4851					}
4852				});
4853			} else if (selected !== false) {
4854				nodeList = this.getSelectedNodes(stopOnParents);
4855				$.each(nodeList, function (idx, node) {
4856					_appender(node);
4857				});
4858			}
4859		},
4860		/**
4861		 * Return the currently active node or null.
4862		 * @returns {FancytreeNode}
4863		 */
4864		getActiveNode: function () {
4865			return this.activeNode;
4866		},
4867		/** Return the first top level node if any (not the invisible root node).
4868		 * @returns {FancytreeNode | null}
4869		 */
4870		getFirstChild: function () {
4871			return this.rootNode.getFirstChild();
4872		},
4873		/**
4874		 * Return node that has keyboard focus or null.
4875		 * @returns {FancytreeNode}
4876		 */
4877		getFocusNode: function () {
4878			return this.focusNode;
4879		},
4880		/**
4881		 * Return current option value.
4882		 * (Note: this is the preferred variant of `$().fancytree("option", "KEY")`)
4883		 *
4884		 * @param {string} name option name (may contain '.')
4885		 * @returns {any}
4886		 */
4887		getOption: function (optionName) {
4888			return this.widget.option(optionName);
4889		},
4890		/**
4891		 * Return node with a given key or null if not found.
4892		 *
4893		 * @param {string} key
4894		 * @param {FancytreeNode} [searchRoot] only search below this node
4895		 * @returns {FancytreeNode | null}
4896		 */
4897		getNodeByKey: function (key, searchRoot) {
4898			// Search the DOM by element ID (assuming this is faster than traversing all nodes).
4899			var el, match;
4900			// TODO: use tree.keyMap if available
4901			// TODO: check opts.generateIds === true
4902			if (!searchRoot) {
4903				el = document.getElementById(this.options.idPrefix + key);
4904				if (el) {
4905					return el.ftnode ? el.ftnode : null;
4906				}
4907			}
4908			// Not found in the DOM, but still may be in an unrendered part of tree
4909			searchRoot = searchRoot || this.rootNode;
4910			match = null;
4911			key = "" + key; // Convert to string (#1005)
4912			searchRoot.visit(function (node) {
4913				if (node.key === key) {
4914					match = node;
4915					return false; // Stop iteration
4916				}
4917			}, true);
4918			return match;
4919		},
4920		/** Return the invisible system root node.
4921		 * @returns {FancytreeNode}
4922		 */
4923		getRootNode: function () {
4924			return this.rootNode;
4925		},
4926		/**
4927		 * Return an array of selected nodes.
4928		 *
4929		 * Note: you cannot send this result via Ajax directly. Instead the
4930		 * node object need to be converted to plain objects, for example
4931		 * by using `$.map()` and `node.toDict()`.
4932		 * @param {boolean} [stopOnParents=false] only return the topmost selected
4933		 *     node (useful with selectMode 3)
4934		 * @returns {FancytreeNode[]}
4935		 */
4936		getSelectedNodes: function (stopOnParents) {
4937			return this.rootNode.getSelectedNodes(stopOnParents);
4938		},
4939		/** Return true if the tree control has keyboard focus
4940		 * @returns {boolean}
4941		 */
4942		hasFocus: function () {
4943			// var ae = document.activeElement,
4944			// 	hasFocus = !!(
4945			// 		ae && $(ae).closest(".fancytree-container").length
4946			// 	);
4947
4948			// if (hasFocus !== !!this._hasFocus) {
4949			// 	this.warn(
4950			// 		"hasFocus(): fix inconsistent container state, now: " +
4951			// 			hasFocus
4952			// 	);
4953			// 	this._hasFocus = hasFocus;
4954			// 	this.$container.toggleClass("fancytree-treefocus", hasFocus);
4955			// }
4956			// return hasFocus;
4957			return !!this._hasFocus;
4958		},
4959		/** Write to browser console if debugLevel >= 3 (prepending tree name)
4960		 * @param {*} msg string or object or array of such
4961		 */
4962		info: function (msg) {
4963			if (this.options.debugLevel >= 3) {
4964				Array.prototype.unshift.call(arguments, this.toString());
4965				consoleApply("info", arguments);
4966			}
4967		},
4968		/** Return true if any node is currently beeing loaded, i.e. a Ajax request is pending.
4969		 * @returns {boolean}
4970		 * @since 2.32
4971		 */
4972		isLoading: function () {
4973			var res = false;
4974
4975			this.rootNode.visit(function (n) {
4976				// also visit rootNode
4977				if (n._isLoading || n._requestId) {
4978					res = true;
4979					return false;
4980				}
4981			}, true);
4982			return res;
4983		},
4984		/*
4985		TODO: isInitializing: function() {
4986			return ( this.phase=="init" || this.phase=="postInit" );
4987		},
4988		TODO: isReloading: function() {
4989			return ( this.phase=="init" || this.phase=="postInit" ) && this.options.persist && this.persistence.cookiesFound;
4990		},
4991		TODO: isUserEvent: function() {
4992			return ( this.phase=="userEvent" );
4993		},
4994		*/
4995
4996		/**
4997		 * Make sure that a node with a given ID is loaded, by traversing - and
4998		 * loading - its parents. This method is meant for lazy hierarchies.
4999		 * A callback is executed for every node as we go.
5000		 * @example
5001		 * // Resolve using node.key:
5002		 * tree.loadKeyPath("/_3/_23/_26/_27", function(node, status){
5003		 *   if(status === "loaded") {
5004		 *     console.log("loaded intermediate node " + node);
5005		 *   }else if(status === "ok") {
5006		 *     node.activate();
5007		 *   }
5008		 * });
5009		 * // Use deferred promise:
5010		 * tree.loadKeyPath("/_3/_23/_26/_27").progress(function(data){
5011		 *   if(data.status === "loaded") {
5012		 *     console.log("loaded intermediate node " + data.node);
5013		 *   }else if(data.status === "ok") {
5014		 *     node.activate();
5015		 *   }
5016		 * }).done(function(){
5017		 *    ...
5018		 * });
5019		 * // Custom path segment resolver:
5020		 * tree.loadKeyPath("/321/431/21/2", {
5021		 *   matchKey: function(node, key){
5022		 *     return node.data.refKey === key;
5023		 *   },
5024		 *   callback: function(node, status){
5025		 *     if(status === "loaded") {
5026		 *       console.log("loaded intermediate node " + node);
5027		 *     }else if(status === "ok") {
5028		 *       node.activate();
5029		 *     }
5030		 *   }
5031		 * });
5032		 * @param {string | string[]} keyPathList one or more key paths (e.g. '/3/2_1/7')
5033		 * @param {function | object} optsOrCallback callback(node, status) is called for every visited node ('loading', 'loaded', 'ok', 'error').
5034		 *     Pass an object to define custom key matchers for the path segments: {callback: function, matchKey: function}.
5035		 * @returns {$.Promise}
5036		 */
5037		loadKeyPath: function (keyPathList, optsOrCallback) {
5038			var callback,
5039				i,
5040				path,
5041				self = this,
5042				dfd = new $.Deferred(),
5043				parent = this.getRootNode(),
5044				sep = this.options.keyPathSeparator,
5045				pathSegList = [],
5046				opts = $.extend({}, optsOrCallback);
5047
5048			// Prepare options
5049			if (typeof optsOrCallback === "function") {
5050				callback = optsOrCallback;
5051			} else if (optsOrCallback && optsOrCallback.callback) {
5052				callback = optsOrCallback.callback;
5053			}
5054			opts.callback = function (ctx, node, status) {
5055				if (callback) {
5056					callback.call(ctx, node, status);
5057				}
5058				dfd.notifyWith(ctx, [{ node: node, status: status }]);
5059			};
5060			if (opts.matchKey == null) {
5061				opts.matchKey = function (node, key) {
5062					return node.key === key;
5063				};
5064			}
5065			// Convert array of path strings to array of segment arrays
5066			if (!_isArray(keyPathList)) {
5067				keyPathList = [keyPathList];
5068			}
5069			for (i = 0; i < keyPathList.length; i++) {
5070				path = keyPathList[i];
5071				// strip leading slash
5072				if (path.charAt(0) === sep) {
5073					path = path.substr(1);
5074				}
5075				// segListMap[path] = { parent: parent, segList: path.split(sep) };
5076				pathSegList.push(path.split(sep));
5077				// targetList.push({ parent: parent, segList: path.split(sep)/* , path: path*/});
5078			}
5079			// The timeout forces async behavior always (even if nodes are all loaded)
5080			// This way a potential progress() event will fire.
5081			setTimeout(function () {
5082				self._loadKeyPathImpl(dfd, opts, parent, pathSegList).done(
5083					function () {
5084						dfd.resolve();
5085					}
5086				);
5087			}, 0);
5088			return dfd.promise();
5089		},
5090		/*
5091		 * Resolve a list of paths, relative to one parent node.
5092		 */
5093		_loadKeyPathImpl: function (dfd, opts, parent, pathSegList) {
5094			var deferredList,
5095				i,
5096				key,
5097				node,
5098				nodeKey,
5099				remain,
5100				remainMap,
5101				tmpParent,
5102				segList,
5103				subDfd,
5104				self = this;
5105
5106			function __findChild(parent, key) {
5107				// console.log("__findChild", key, parent);
5108				var i,
5109					l,
5110					cl = parent.children;
5111
5112				if (cl) {
5113					for (i = 0, l = cl.length; i < l; i++) {
5114						if (opts.matchKey(cl[i], key)) {
5115							return cl[i];
5116						}
5117					}
5118				}
5119				return null;
5120			}
5121
5122			// console.log("_loadKeyPathImpl, parent=", parent, ", pathSegList=", pathSegList);
5123
5124			// Pass 1:
5125			// Handle all path segments for nodes that are already loaded.
5126			// Collect distinct top-most lazy nodes in a map.
5127			// Note that we can use node.key to de-dupe entries, even if a custom matcher would
5128			// look for other node attributes.
5129			// map[node.key] => {node: node, pathList: [list of remaining rest-paths]}
5130			remainMap = {};
5131
5132			for (i = 0; i < pathSegList.length; i++) {
5133				segList = pathSegList[i];
5134				// target = targetList[i];
5135
5136				// Traverse and pop path segments (i.e. keys), until we hit a lazy, unloaded node
5137				tmpParent = parent;
5138				while (segList.length) {
5139					key = segList.shift();
5140					node = __findChild(tmpParent, key);
5141					if (!node) {
5142						this.warn(
5143							"loadKeyPath: key not found: " +
5144								key +
5145								" (parent: " +
5146								tmpParent +
5147								")"
5148						);
5149						opts.callback(this, key, "error");
5150						break;
5151					} else if (segList.length === 0) {
5152						opts.callback(this, node, "ok");
5153						break;
5154					} else if (!node.lazy || node.hasChildren() !== undefined) {
5155						opts.callback(this, node, "loaded");
5156						tmpParent = node;
5157					} else {
5158						opts.callback(this, node, "loaded");
5159						key = node.key; //target.segList.join(sep);
5160						if (remainMap[key]) {
5161							remainMap[key].pathSegList.push(segList);
5162						} else {
5163							remainMap[key] = {
5164								parent: node,
5165								pathSegList: [segList],
5166							};
5167						}
5168						break;
5169					}
5170				}
5171			}
5172			// console.log("_loadKeyPathImpl AFTER pass 1, remainMap=", remainMap);
5173
5174			// Now load all lazy nodes and continue iteration for remaining paths
5175			deferredList = [];
5176
5177			// Avoid jshint warning 'Don't make functions within a loop.':
5178			function __lazyload(dfd, parent, pathSegList) {
5179				// console.log("__lazyload", parent, "pathSegList=", pathSegList);
5180				opts.callback(self, parent, "loading");
5181				parent
5182					.load()
5183					.done(function () {
5184						self._loadKeyPathImpl
5185							.call(self, dfd, opts, parent, pathSegList)
5186							.always(_makeResolveFunc(dfd, self));
5187					})
5188					.fail(function (errMsg) {
5189						self.warn("loadKeyPath: error loading lazy " + parent);
5190						opts.callback(self, node, "error");
5191						dfd.rejectWith(self);
5192					});
5193			}
5194			// remainMap contains parent nodes, each with a list of relative sub-paths.
5195			// We start loading all of them now, and pass the the list to each loader.
5196			for (nodeKey in remainMap) {
5197				if (_hasProp(remainMap, nodeKey)) {
5198					remain = remainMap[nodeKey];
5199					// console.log("for(): remain=", remain, "remainMap=", remainMap);
5200					// key = remain.segList.shift();
5201					// node = __findChild(remain.parent, key);
5202					// if (node == null) {  // #576
5203					// 	// Issue #576, refactored for v2.27:
5204					// 	// The root cause was, that sometimes the wrong parent was used here
5205					// 	// to find the next segment.
5206					// 	// Falling back to getNodeByKey() was a hack that no longer works if a custom
5207					// 	// matcher is used, because we cannot assume that a single segment-key is unique
5208					// 	// throughout the tree.
5209					// 	self.error("loadKeyPath: error loading child by key '" + key + "' (parent: " + target.parent + ")", target);
5210					// 	// 	node = self.getNodeByKey(key);
5211					// 	continue;
5212					// }
5213					subDfd = new $.Deferred();
5214					deferredList.push(subDfd);
5215					__lazyload(subDfd, remain.parent, remain.pathSegList);
5216				}
5217			}
5218			// Return a promise that is resolved, when ALL paths were loaded
5219			return $.when.apply($, deferredList).promise();
5220		},
5221		/** Re-fire beforeActivate, activate, and (optional) focus events.
5222		 * Calling this method in the `init` event, will activate the node that
5223		 * was marked 'active' in the source data, and optionally set the keyboard
5224		 * focus.
5225		 * @param [setFocus=false]
5226		 */
5227		reactivate: function (setFocus) {
5228			var res,
5229				node = this.activeNode;
5230
5231			if (!node) {
5232				return _getResolvedPromise();
5233			}
5234			this.activeNode = null; // Force re-activating
5235			res = node.setActive(true, { noFocus: true });
5236			if (setFocus) {
5237				node.setFocus();
5238			}
5239			return res;
5240		},
5241		/** Reload tree from source and return a promise.
5242		 * @param [source] optional new source (defaults to initial source data)
5243		 * @returns {$.Promise}
5244		 */
5245		reload: function (source) {
5246			this._callHook("treeClear", this);
5247			return this._callHook("treeLoad", this, source);
5248		},
5249		/**Render tree (i.e. create DOM elements for all top-level nodes).
5250		 * @param {boolean} [force=false] create DOM elemnts, even if parent is collapsed
5251		 * @param {boolean} [deep=false]
5252		 */
5253		render: function (force, deep) {
5254			return this.rootNode.render(force, deep);
5255		},
5256		/**(De)select all nodes.
5257		 * @param {boolean} [flag=true]
5258		 * @since 2.28
5259		 */
5260		selectAll: function (flag) {
5261			this.visit(function (node) {
5262				node.setSelected(flag);
5263			});
5264		},
5265		// TODO: selectKey: function(key, select)
5266		// TODO: serializeArray: function(stopOnParents)
5267		/**
5268		 * @param {boolean} [flag=true]
5269		 */
5270		setFocus: function (flag) {
5271			return this._callHook("treeSetFocus", this, flag);
5272		},
5273		/**
5274		 * Set current option value.
5275		 * (Note: this is the preferred variant of `$().fancytree("option", "KEY", VALUE)`)
5276		 * @param {string} name option name (may contain '.')
5277		 * @param {any} new value
5278		 */
5279		setOption: function (optionName, value) {
5280			return this.widget.option(optionName, value);
5281		},
5282		/**
5283		 * Call console.time() when in debug mode (verbose >= 4).
5284		 *
5285		 * @param {string} label
5286		 */
5287		debugTime: function (label) {
5288			if (this.options.debugLevel >= 4) {
5289				window.console.time(this + " - " + label);
5290			}
5291		},
5292		/**
5293		 * Call console.timeEnd() when in debug mode (verbose >= 4).
5294		 *
5295		 * @param {string} label
5296		 */
5297		debugTimeEnd: function (label) {
5298			if (this.options.debugLevel >= 4) {
5299				window.console.timeEnd(this + " - " + label);
5300			}
5301		},
5302		/**
5303		 * Return all nodes as nested list of {@link NodeData}.
5304		 *
5305		 * @param {boolean} [includeRoot=false] Returns the hidden system root node (and its children)
5306		 * @param {function} [callback] callback(dict, node) is called for every node, in order to allow modifications.
5307		 *     Return `false` to ignore this node or "skip" to include this node without its children.
5308		 * @returns {Array | object}
5309		 * @see FancytreeNode#toDict
5310		 */
5311		toDict: function (includeRoot, callback) {
5312			var res = this.rootNode.toDict(true, callback);
5313			return includeRoot ? res : res.children;
5314		},
5315		/* Implicitly called for string conversions.
5316		 * @returns {string}
5317		 */
5318		toString: function () {
5319			return "Fancytree@" + this._id;
5320			// return "<Fancytree(#" + this._id + ")>";
5321		},
5322		/* _trigger a widget event with additional node ctx.
5323		 * @see EventData
5324		 */
5325		_triggerNodeEvent: function (type, node, originalEvent, extra) {
5326			// this.debug("_trigger(" + type + "): '" + ctx.node.title + "'", ctx);
5327			var ctx = this._makeHookContext(node, originalEvent, extra),
5328				res = this.widget._trigger(type, originalEvent, ctx);
5329			if (res !== false && ctx.result !== undefined) {
5330				return ctx.result;
5331			}
5332			return res;
5333		},
5334		/* _trigger a widget event with additional tree data. */
5335		_triggerTreeEvent: function (type, originalEvent, extra) {
5336			// this.debug("_trigger(" + type + ")", ctx);
5337			var ctx = this._makeHookContext(this, originalEvent, extra),
5338				res = this.widget._trigger(type, originalEvent, ctx);
5339
5340			if (res !== false && ctx.result !== undefined) {
5341				return ctx.result;
5342			}
5343			return res;
5344		},
5345		/** Call fn(node) for all nodes in hierarchical order (depth-first).
5346		 *
5347		 * @param {function} fn the callback function.
5348		 *     Return false to stop iteration, return "skip" to skip this node and children only.
5349		 * @returns {boolean} false, if the iterator was stopped.
5350		 */
5351		visit: function (fn) {
5352			return this.rootNode.visit(fn, false);
5353		},
5354		/** Call fn(node) for all nodes in vertical order, top down (or bottom up).<br>
5355		 * Stop iteration, if fn() returns false.<br>
5356		 * Return false if iteration was stopped.
5357		 *
5358		 * @param {function} fn the callback function.
5359		 *     Return false to stop iteration, return "skip" to skip this node and children only.
5360		 * @param {object} [options]
5361		 *     Defaults:
5362		 *     {start: First top node, reverse: false, includeSelf: true, includeHidden: false}
5363		 * @returns {boolean} false if iteration was cancelled
5364		 * @since 2.28
5365		 */
5366		visitRows: function (fn, opts) {
5367			if (!this.rootNode.hasChildren()) {
5368				return false;
5369			}
5370			if (opts && opts.reverse) {
5371				delete opts.reverse;
5372				return this._visitRowsUp(fn, opts);
5373			}
5374			opts = opts || {};
5375
5376			var i,
5377				nextIdx,
5378				parent,
5379				res,
5380				siblings,
5381				siblingOfs = 0,
5382				skipFirstNode = opts.includeSelf === false,
5383				includeHidden = !!opts.includeHidden,
5384				checkFilter = !includeHidden && this.enableFilter,
5385				node = opts.start || this.rootNode.children[0];
5386
5387			parent = node.parent;
5388			while (parent) {
5389				// visit siblings
5390				siblings = parent.children;
5391				nextIdx = siblings.indexOf(node) + siblingOfs;
5392				_assert(
5393					nextIdx >= 0,
5394					"Could not find " +
5395						node +
5396						" in parent's children: " +
5397						parent
5398				);
5399
5400				for (i = nextIdx; i < siblings.length; i++) {
5401					node = siblings[i];
5402					if (checkFilter && !node.match && !node.subMatchCount) {
5403						continue;
5404					}
5405					if (!skipFirstNode && fn(node) === false) {
5406						return false;
5407					}
5408					skipFirstNode = false;
5409					// Dive into node's child nodes
5410					if (
5411						node.children &&
5412						node.children.length &&
5413						(includeHidden || node.expanded)
5414					) {
5415						// Disable warning: Functions declared within loops referencing an outer
5416						// scoped variable may lead to confusing semantics:
5417						/*jshint -W083 */
5418						res = node.visit(function (n) {
5419							if (checkFilter && !n.match && !n.subMatchCount) {
5420								return "skip";
5421							}
5422							if (fn(n) === false) {
5423								return false;
5424							}
5425							if (!includeHidden && n.children && !n.expanded) {
5426								return "skip";
5427							}
5428						}, false);
5429						/*jshint +W083 */
5430						if (res === false) {
5431							return false;
5432						}
5433					}
5434				}
5435				// Visit parent nodes (bottom up)
5436				node = parent;
5437				parent = parent.parent;
5438				siblingOfs = 1; //
5439			}
5440			return true;
5441		},
5442		/* Call fn(node) for all nodes in vertical order, bottom up.
5443		 */
5444		_visitRowsUp: function (fn, opts) {
5445			var children,
5446				idx,
5447				parent,
5448				includeHidden = !!opts.includeHidden,
5449				node = opts.start || this.rootNode.children[0];
5450
5451			while (true) {
5452				parent = node.parent;
5453				children = parent.children;
5454
5455				if (children[0] === node) {
5456					// If this is already the first sibling, goto parent
5457					node = parent;
5458					if (!node.parent) {
5459						break; // first node of the tree
5460					}
5461					children = parent.children;
5462				} else {
5463					// Otherwise, goto prev. sibling
5464					idx = children.indexOf(node);
5465					node = children[idx - 1];
5466					// If the prev. sibling has children, follow down to last descendant
5467					while (
5468						// See: https://github.com/eslint/eslint/issues/11302
5469						// eslint-disable-next-line no-unmodified-loop-condition
5470						(includeHidden || node.expanded) &&
5471						node.children &&
5472						node.children.length
5473					) {
5474						children = node.children;
5475						parent = node;
5476						node = children[children.length - 1];
5477					}
5478				}
5479				// Skip invisible
5480				if (!includeHidden && !node.isVisible()) {
5481					continue;
5482				}
5483				if (fn(node) === false) {
5484					return false;
5485				}
5486			}
5487		},
5488		/** Write warning to browser console if debugLevel >= 2 (prepending tree info)
5489		 *
5490		 * @param {*} msg string or object or array of such
5491		 */
5492		warn: function (msg) {
5493			if (this.options.debugLevel >= 2) {
5494				Array.prototype.unshift.call(arguments, this.toString());
5495				consoleApply("warn", arguments);
5496			}
5497		},
5498	};
5499
5500	/**
5501	 * These additional methods of the {@link Fancytree} class are 'hook functions'
5502	 * that can be used and overloaded by extensions.
5503	 *
5504	 * @see [writing extensions](https://github.com/mar10/fancytree/wiki/TutorialExtensions)
5505	 * @mixin Fancytree_Hooks
5506	 */
5507	$.extend(
5508		Fancytree.prototype,
5509		/** @lends Fancytree_Hooks# */
5510		{
5511			/** Default handling for mouse click events.
5512			 *
5513			 * @param {EventData} ctx
5514			 */
5515			nodeClick: function (ctx) {
5516				var activate,
5517					expand,
5518					// event = ctx.originalEvent,
5519					targetType = ctx.targetType,
5520					node = ctx.node;
5521
5522				// this.debug("ftnode.onClick(" + event.type + "): ftnode:" + this + ", button:" + event.button + ", which: " + event.which, ctx);
5523				// TODO: use switch
5524				// TODO: make sure clicks on embedded <input> doesn't steal focus (see table sample)
5525				if (targetType === "expander") {
5526					if (node.isLoading()) {
5527						// #495: we probably got a click event while a lazy load is pending.
5528						// The 'expanded' state is not yet set, so 'toggle' would expand
5529						// and trigger lazyLoad again.
5530						// It would be better to allow to collapse/expand the status node
5531						// while loading (instead of ignoring), but that would require some
5532						// more work.
5533						node.debug("Got 2nd click while loading: ignored");
5534						return;
5535					}
5536					// Clicking the expander icon always expands/collapses
5537					this._callHook("nodeToggleExpanded", ctx);
5538				} else if (targetType === "checkbox") {
5539					// Clicking the checkbox always (de)selects
5540					this._callHook("nodeToggleSelected", ctx);
5541					if (ctx.options.focusOnSelect) {
5542						// #358
5543						this._callHook("nodeSetFocus", ctx, true);
5544					}
5545				} else {
5546					// Honor `clickFolderMode` for
5547					expand = false;
5548					activate = true;
5549					if (node.folder) {
5550						switch (ctx.options.clickFolderMode) {
5551							case 2: // expand only
5552								expand = true;
5553								activate = false;
5554								break;
5555							case 3: // expand and activate
5556								activate = true;
5557								expand = true; //!node.isExpanded();
5558								break;
5559							// else 1 or 4: just activate
5560						}
5561					}
5562					if (activate) {
5563						this.nodeSetFocus(ctx);
5564						this._callHook("nodeSetActive", ctx, true);
5565					}
5566					if (expand) {
5567						if (!activate) {
5568							// this._callHook("nodeSetFocus", ctx);
5569						}
5570						// this._callHook("nodeSetExpanded", ctx, true);
5571						this._callHook("nodeToggleExpanded", ctx);
5572					}
5573				}
5574				// Make sure that clicks stop, otherwise <a href='#'> jumps to the top
5575				// if(event.target.localName === "a" && event.target.className === "fancytree-title"){
5576				// 	event.preventDefault();
5577				// }
5578				// TODO: return promise?
5579			},
5580			/** Collapse all other  children of same parent.
5581			 *
5582			 * @param {EventData} ctx
5583			 * @param {object} callOpts
5584			 */
5585			nodeCollapseSiblings: function (ctx, callOpts) {
5586				// TODO: return promise?
5587				var ac,
5588					i,
5589					l,
5590					node = ctx.node;
5591
5592				if (node.parent) {
5593					ac = node.parent.children;
5594					for (i = 0, l = ac.length; i < l; i++) {
5595						if (ac[i] !== node && ac[i].expanded) {
5596							this._callHook(
5597								"nodeSetExpanded",
5598								ac[i],
5599								false,
5600								callOpts
5601							);
5602						}
5603					}
5604				}
5605			},
5606			/** Default handling for mouse douleclick events.
5607			 * @param {EventData} ctx
5608			 */
5609			nodeDblclick: function (ctx) {
5610				// TODO: return promise?
5611				if (
5612					ctx.targetType === "title" &&
5613					ctx.options.clickFolderMode === 4
5614				) {
5615					// this.nodeSetFocus(ctx);
5616					// this._callHook("nodeSetActive", ctx, true);
5617					this._callHook("nodeToggleExpanded", ctx);
5618				}
5619				// TODO: prevent text selection on dblclicks
5620				if (ctx.targetType === "title") {
5621					ctx.originalEvent.preventDefault();
5622				}
5623			},
5624			/** Default handling for mouse keydown events.
5625			 *
5626			 * NOTE: this may be called with node == null if tree (but no node) has focus.
5627			 * @param {EventData} ctx
5628			 */
5629			nodeKeydown: function (ctx) {
5630				// TODO: return promise?
5631				var matchNode,
5632					stamp,
5633					_res,
5634					focusNode,
5635					event = ctx.originalEvent,
5636					node = ctx.node,
5637					tree = ctx.tree,
5638					opts = ctx.options,
5639					which = event.which,
5640					// #909: Use event.key, to get unicode characters.
5641					// We can't use `/\w/.test(key)`, because that would
5642					// only detect plain ascii alpha-numerics. But we still need
5643					// to ignore modifier-only, whitespace, cursor-keys, etc.
5644					key = event.key || String.fromCharCode(which),
5645					specialModifiers = !!(
5646						event.altKey ||
5647						event.ctrlKey ||
5648						event.metaKey
5649					),
5650					isAlnum =
5651						!MODIFIERS[which] &&
5652						!SPECIAL_KEYCODES[which] &&
5653						!specialModifiers,
5654					$target = $(event.target),
5655					handled = true,
5656					activate = !(event.ctrlKey || !opts.autoActivate);
5657
5658				// (node || FT).debug("ftnode.nodeKeydown(" + event.type + "): ftnode:" + this + ", charCode:" + event.charCode + ", keyCode: " + event.keyCode + ", which: " + event.which);
5659				// FT.debug( "eventToString(): " + FT.eventToString(event) + ", key='" + key + "', isAlnum: " + isAlnum );
5660
5661				// Set focus to active (or first node) if no other node has the focus yet
5662				if (!node) {
5663					focusNode = this.getActiveNode() || this.getFirstChild();
5664					if (focusNode) {
5665						focusNode.setFocus();
5666						node = ctx.node = this.focusNode;
5667						node.debug("Keydown force focus on active node");
5668					}
5669				}
5670
5671				if (
5672					opts.quicksearch &&
5673					isAlnum &&
5674					!$target.is(":input:enabled")
5675				) {
5676					// Allow to search for longer streaks if typed in quickly
5677					stamp = Date.now();
5678					if (stamp - tree.lastQuicksearchTime > 500) {
5679						tree.lastQuicksearchTerm = "";
5680					}
5681					tree.lastQuicksearchTime = stamp;
5682					tree.lastQuicksearchTerm += key;
5683					// tree.debug("quicksearch find", tree.lastQuicksearchTerm);
5684					matchNode = tree.findNextNode(
5685						tree.lastQuicksearchTerm,
5686						tree.getActiveNode()
5687					);
5688					if (matchNode) {
5689						matchNode.setActive();
5690					}
5691					event.preventDefault();
5692					return;
5693				}
5694				switch (FT.eventToString(event)) {
5695					case "+":
5696					case "=": // 187: '+' @ Chrome, Safari
5697						tree.nodeSetExpanded(ctx, true);
5698						break;
5699					case "-":
5700						tree.nodeSetExpanded(ctx, false);
5701						break;
5702					case "space":
5703						if (node.isPagingNode()) {
5704							tree._triggerNodeEvent("clickPaging", ctx, event);
5705						} else if (
5706							FT.evalOption("checkbox", node, node, opts, false)
5707						) {
5708							// #768
5709							tree.nodeToggleSelected(ctx);
5710						} else {
5711							tree.nodeSetActive(ctx, true);
5712						}
5713						break;
5714					case "return":
5715						tree.nodeSetActive(ctx, true);
5716						break;
5717					case "home":
5718					case "end":
5719					case "backspace":
5720					case "left":
5721					case "right":
5722					case "up":
5723					case "down":
5724						_res = node.navigate(event.which, activate);
5725						break;
5726					default:
5727						handled = false;
5728				}
5729				if (handled) {
5730					event.preventDefault();
5731				}
5732			},
5733
5734			// /** Default handling for mouse keypress events. */
5735			// nodeKeypress: function(ctx) {
5736			//     var event = ctx.originalEvent;
5737			// },
5738
5739			// /** Trigger lazyLoad event (async). */
5740			// nodeLazyLoad: function(ctx) {
5741			//     var node = ctx.node;
5742			//     if(this._triggerNodeEvent())
5743			// },
5744			/** Load child nodes (async).
5745			 *
5746			 * @param {EventData} ctx
5747			 * @param {object[]|object|string|$.Promise|function} source
5748			 * @returns {$.Promise} The deferred will be resolved as soon as the (ajax)
5749			 *     data was rendered.
5750			 */
5751			nodeLoadChildren: function (ctx, source) {
5752				var ajax,
5753					delay,
5754					ajaxDfd = null,
5755					resultDfd,
5756					isAsync = true,
5757					tree = ctx.tree,
5758					node = ctx.node,
5759					nodePrevParent = node.parent,
5760					tag = "nodeLoadChildren",
5761					requestId = Date.now();
5762
5763				// `source` is a callback: use the returned result instead:
5764				if (_isFunction(source)) {
5765					source = source.call(tree, { type: "source" }, ctx);
5766					_assert(
5767						!_isFunction(source),
5768						"source callback must not return another function"
5769					);
5770				}
5771				// `source` is already a promise:
5772				if (_isFunction(source.then)) {
5773					// _assert(_isFunction(source.always), "Expected jQuery?");
5774					ajaxDfd = source;
5775				} else if (source.url) {
5776					// `source` is an Ajax options object
5777					ajax = $.extend({}, ctx.options.ajax, source);
5778					if (ajax.debugDelay) {
5779						// Simulate a slow server
5780						delay = ajax.debugDelay;
5781						delete ajax.debugDelay; // remove debug option
5782						if (_isArray(delay)) {
5783							// random delay range [min..max]
5784							delay =
5785								delay[0] +
5786								Math.random() * (delay[1] - delay[0]);
5787						}
5788						node.warn(
5789							"nodeLoadChildren waiting debugDelay " +
5790								Math.round(delay) +
5791								" ms ..."
5792						);
5793						ajaxDfd = $.Deferred(function (ajaxDfd) {
5794							setTimeout(function () {
5795								$.ajax(ajax)
5796									.done(function () {
5797										ajaxDfd.resolveWith(this, arguments);
5798									})
5799									.fail(function () {
5800										ajaxDfd.rejectWith(this, arguments);
5801									});
5802							}, delay);
5803						});
5804					} else {
5805						ajaxDfd = $.ajax(ajax);
5806					}
5807				} else if ($.isPlainObject(source) || _isArray(source)) {
5808					// `source` is already a constant dict or list, but we convert
5809					// to a thenable for unified processing.
5810					// 2020-01-03: refactored.
5811					// `ajaxDfd = $.when(source)` would do the trick, but the returned
5812					// promise will resolve async, which broke some tests and
5813					// would probably also break current implementations out there.
5814					// So we mock-up a thenable that resolves synchronously:
5815					ajaxDfd = {
5816						then: function (resolve, reject) {
5817							resolve(source, null, null);
5818						},
5819					};
5820					isAsync = false;
5821				} else {
5822					$.error("Invalid source type: " + source);
5823				}
5824
5825				// Check for overlapping requests
5826				if (node._requestId) {
5827					node.warn(
5828						"Recursive load request #" +
5829							requestId +
5830							" while #" +
5831							node._requestId +
5832							" is pending."
5833					);
5834					node._requestId = requestId;
5835					// 	node.debug("Send load request #" + requestId);
5836				}
5837
5838				if (isAsync) {
5839					tree.debugTime(tag);
5840					tree.nodeSetStatus(ctx, "loading");
5841				}
5842
5843				// The async Ajax request has now started...
5844				// Defer the deferred:
5845				// we want to be able to reject invalid responses, even if
5846				// the raw HTTP Ajax XHR resolved as Ok.
5847				// We use the ajaxDfd.then() syntax here, which is compatible with
5848				// jQuery and ECMA6.
5849				// However resultDfd is a jQuery deferred, which is currently the
5850				// expected result type of nodeLoadChildren()
5851				resultDfd = new $.Deferred();
5852				ajaxDfd.then(
5853					function (data, textStatus, jqXHR) {
5854						// ajaxDfd was resolved, but we reject or resolve resultDfd
5855						// depending on the response data
5856						var errorObj, res;
5857
5858						if (
5859							(source.dataType === "json" ||
5860								source.dataType === "jsonp") &&
5861							typeof data === "string"
5862						) {
5863							$.error(
5864								"Ajax request returned a string (did you get the JSON dataType wrong?)."
5865							);
5866						}
5867						if (node._requestId && node._requestId > requestId) {
5868							// The expected request time stamp is later than `requestId`
5869							// (which was kept as as closure variable to this handler function)
5870							// node.warn("Ignored load response for obsolete request #" + requestId + " (expected #" + node._requestId + ")");
5871							resultDfd.rejectWith(this, [
5872								RECURSIVE_REQUEST_ERROR,
5873							]);
5874							return;
5875							// } else {
5876							// 	node.debug("Response returned for load request #" + requestId);
5877						}
5878						if (node.parent === null && nodePrevParent !== null) {
5879							resultDfd.rejectWith(this, [
5880								INVALID_REQUEST_TARGET_ERROR,
5881							]);
5882							return;
5883						}
5884						// Allow to adjust the received response data in the `postProcess` event.
5885						if (ctx.options.postProcess) {
5886							// The handler may either
5887							//   - modify `ctx.response` in-place (and leave `ctx.result` undefined)
5888							//     => res = undefined
5889							//   - return a replacement in `ctx.result`
5890							//     => res = <new data>
5891							//   If res contains an `error` property, an error status is displayed
5892							try {
5893								res = tree._triggerNodeEvent(
5894									"postProcess",
5895									ctx,
5896									ctx.originalEvent,
5897									{
5898										response: data,
5899										error: null,
5900										dataType: source.dataType,
5901									}
5902								);
5903								if (res.error) {
5904									tree.warn(
5905										"postProcess returned error:",
5906										res
5907									);
5908								}
5909							} catch (e) {
5910								res = {
5911									error: e,
5912									message: "" + e,
5913									details: "postProcess failed",
5914								};
5915							}
5916							if (res.error) {
5917								// Either postProcess failed with an exception, or the returned
5918								// result object has an 'error' property attached:
5919								errorObj = $.isPlainObject(res.error)
5920									? res.error
5921									: { message: res.error };
5922								errorObj = tree._makeHookContext(
5923									node,
5924									null,
5925									errorObj
5926								);
5927								resultDfd.rejectWith(this, [errorObj]);
5928								return;
5929							}
5930							if (
5931								_isArray(res) ||
5932								($.isPlainObject(res) && _isArray(res.children))
5933							) {
5934								// Use `ctx.result` if valid
5935								// (otherwise use existing data, which may have been modified in-place)
5936								data = res;
5937							}
5938						} else if (
5939							data &&
5940							_hasProp(data, "d") &&
5941							ctx.options.enableAspx
5942						) {
5943							// Process ASPX WebMethod JSON object inside "d" property
5944							// (only if no postProcess event was defined)
5945							if (ctx.options.enableAspx === 42) {
5946								tree.warn(
5947									"The default for enableAspx will change to `false` in the fututure. " +
5948										"Pass `enableAspx: true` or implement postProcess to silence this warning."
5949								);
5950							}
5951							data =
5952								typeof data.d === "string"
5953									? $.parseJSON(data.d)
5954									: data.d;
5955						}
5956						resultDfd.resolveWith(this, [data]);
5957					},
5958					function (jqXHR, textStatus, errorThrown) {
5959						// ajaxDfd was rejected, so we reject resultDfd as well
5960						var errorObj = tree._makeHookContext(node, null, {
5961							error: jqXHR,
5962							args: Array.prototype.slice.call(arguments),
5963							message: errorThrown,
5964							details: jqXHR.status + ": " + errorThrown,
5965						});
5966						resultDfd.rejectWith(this, [errorObj]);
5967					}
5968				);
5969
5970				// The async Ajax request has now started.
5971				// resultDfd will be resolved/rejected after the response arrived,
5972				// was postProcessed, and checked.
5973				// Now we implement the UI update and add the data to the tree.
5974				// We also return this promise to the caller.
5975				resultDfd
5976					.done(function (data) {
5977						tree.nodeSetStatus(ctx, "ok");
5978						var children, metaData, noDataRes;
5979
5980						if ($.isPlainObject(data)) {
5981							// We got {foo: 'abc', children: [...]}
5982							// Copy extra properties to tree.data.foo
5983							_assert(
5984								node.isRootNode(),
5985								"source may only be an object for root nodes (expecting an array of child objects otherwise)"
5986							);
5987							_assert(
5988								_isArray(data.children),
5989								"if an object is passed as source, it must contain a 'children' array (all other properties are added to 'tree.data')"
5990							);
5991							metaData = data;
5992							children = data.children;
5993							delete metaData.children;
5994							// Copy some attributes to tree.data
5995							$.each(TREE_ATTRS, function (i, attr) {
5996								if (metaData[attr] !== undefined) {
5997									tree[attr] = metaData[attr];
5998									delete metaData[attr];
5999								}
6000							});
6001							// Copy all other attributes to tree.data.NAME
6002							$.extend(tree.data, metaData);
6003						} else {
6004							children = data;
6005						}
6006						_assert(
6007							_isArray(children),
6008							"expected array of children"
6009						);
6010						node._setChildren(children);
6011
6012						if (tree.options.nodata && children.length === 0) {
6013							if (_isFunction(tree.options.nodata)) {
6014								noDataRes = tree.options.nodata.call(
6015									tree,
6016									{ type: "nodata" },
6017									ctx
6018								);
6019							} else if (
6020								tree.options.nodata === true &&
6021								node.isRootNode()
6022							) {
6023								noDataRes = tree.options.strings.noData;
6024							} else if (
6025								typeof tree.options.nodata === "string" &&
6026								node.isRootNode()
6027							) {
6028								noDataRes = tree.options.nodata;
6029							}
6030							if (noDataRes) {
6031								node.setStatus("nodata", noDataRes);
6032							}
6033						}
6034						// trigger fancytreeloadchildren
6035						tree._triggerNodeEvent("loadChildren", node);
6036					})
6037					.fail(function (error) {
6038						var ctxErr;
6039
6040						if (error === RECURSIVE_REQUEST_ERROR) {
6041							node.warn(
6042								"Ignored response for obsolete load request #" +
6043									requestId +
6044									" (expected #" +
6045									node._requestId +
6046									")"
6047							);
6048							return;
6049						} else if (error === INVALID_REQUEST_TARGET_ERROR) {
6050							node.warn(
6051								"Lazy parent node was removed while loading: discarding response."
6052							);
6053							return;
6054						} else if (error.node && error.error && error.message) {
6055							// error is already a context object
6056							ctxErr = error;
6057						} else {
6058							ctxErr = tree._makeHookContext(node, null, {
6059								error: error, // it can be jqXHR or any custom error
6060								args: Array.prototype.slice.call(arguments),
6061								message: error
6062									? error.message || error.toString()
6063									: "",
6064							});
6065							if (ctxErr.message === "[object Object]") {
6066								ctxErr.message = "";
6067							}
6068						}
6069						node.warn(
6070							"Load children failed (" + ctxErr.message + ")",
6071							ctxErr
6072						);
6073						if (
6074							tree._triggerNodeEvent(
6075								"loadError",
6076								ctxErr,
6077								null
6078							) !== false
6079						) {
6080							tree.nodeSetStatus(
6081								ctx,
6082								"error",
6083								ctxErr.message,
6084								ctxErr.details
6085							);
6086						}
6087					})
6088					.always(function () {
6089						node._requestId = null;
6090						if (isAsync) {
6091							tree.debugTimeEnd(tag);
6092						}
6093					});
6094
6095				return resultDfd.promise();
6096			},
6097			/** [Not Implemented]  */
6098			nodeLoadKeyPath: function (ctx, keyPathList) {
6099				// TODO: implement and improve
6100				// http://code.google.com/p/dynatree/issues/detail?id=222
6101			},
6102			/**
6103			 * Remove a single direct child of ctx.node.
6104			 * @param {EventData} ctx
6105			 * @param {FancytreeNode} childNode dircect child of ctx.node
6106			 */
6107			nodeRemoveChild: function (ctx, childNode) {
6108				var idx,
6109					node = ctx.node,
6110					// opts = ctx.options,
6111					subCtx = $.extend({}, ctx, { node: childNode }),
6112					children = node.children;
6113
6114				// FT.debug("nodeRemoveChild()", node.toString(), childNode.toString());
6115
6116				if (children.length === 1) {
6117					_assert(childNode === children[0], "invalid single child");
6118					return this.nodeRemoveChildren(ctx);
6119				}
6120				if (
6121					this.activeNode &&
6122					(childNode === this.activeNode ||
6123						this.activeNode.isDescendantOf(childNode))
6124				) {
6125					this.activeNode.setActive(false); // TODO: don't fire events
6126				}
6127				if (
6128					this.focusNode &&
6129					(childNode === this.focusNode ||
6130						this.focusNode.isDescendantOf(childNode))
6131				) {
6132					this.focusNode = null;
6133				}
6134				// TODO: persist must take care to clear select and expand cookies
6135				this.nodeRemoveMarkup(subCtx);
6136				this.nodeRemoveChildren(subCtx);
6137				idx = $.inArray(childNode, children);
6138				_assert(idx >= 0, "invalid child");
6139				// Notify listeners
6140				node.triggerModifyChild("remove", childNode);
6141				// Unlink to support GC
6142				childNode.visit(function (n) {
6143					n.parent = null;
6144				}, true);
6145				this._callHook("treeRegisterNode", this, false, childNode);
6146				// remove from child list
6147				children.splice(idx, 1);
6148			},
6149			/**Remove HTML markup for all descendents of ctx.node.
6150			 * @param {EventData} ctx
6151			 */
6152			nodeRemoveChildMarkup: function (ctx) {
6153				var node = ctx.node;
6154
6155				// FT.debug("nodeRemoveChildMarkup()", node.toString());
6156				// TODO: Unlink attr.ftnode to support GC
6157				if (node.ul) {
6158					if (node.isRootNode()) {
6159						$(node.ul).empty();
6160					} else {
6161						$(node.ul).remove();
6162						node.ul = null;
6163					}
6164					node.visit(function (n) {
6165						n.li = n.ul = null;
6166					});
6167				}
6168			},
6169			/**Remove all descendants of ctx.node.
6170			 * @param {EventData} ctx
6171			 */
6172			nodeRemoveChildren: function (ctx) {
6173				var //subCtx,
6174					tree = ctx.tree,
6175					node = ctx.node,
6176					children = node.children;
6177				// opts = ctx.options;
6178
6179				// FT.debug("nodeRemoveChildren()", node.toString());
6180				if (!children) {
6181					return;
6182				}
6183				if (this.activeNode && this.activeNode.isDescendantOf(node)) {
6184					this.activeNode.setActive(false); // TODO: don't fire events
6185				}
6186				if (this.focusNode && this.focusNode.isDescendantOf(node)) {
6187					this.focusNode = null;
6188				}
6189				// TODO: persist must take care to clear select and expand cookies
6190				this.nodeRemoveChildMarkup(ctx);
6191				// Unlink children to support GC
6192				// TODO: also delete this.children (not possible using visit())
6193				// subCtx = $.extend({}, ctx);
6194				node.triggerModifyChild("remove", null);
6195				node.visit(function (n) {
6196					n.parent = null;
6197					tree._callHook("treeRegisterNode", tree, false, n);
6198				});
6199				if (node.lazy) {
6200					// 'undefined' would be interpreted as 'not yet loaded' for lazy nodes
6201					node.children = [];
6202				} else {
6203					node.children = null;
6204				}
6205				if (!node.isRootNode()) {
6206					node.expanded = false; // #449, #459
6207				}
6208				this.nodeRenderStatus(ctx);
6209			},
6210			/**Remove HTML markup for ctx.node and all its descendents.
6211			 * @param {EventData} ctx
6212			 */
6213			nodeRemoveMarkup: function (ctx) {
6214				var node = ctx.node;
6215				// FT.debug("nodeRemoveMarkup()", node.toString());
6216				// TODO: Unlink attr.ftnode to support GC
6217				if (node.li) {
6218					$(node.li).remove();
6219					node.li = null;
6220				}
6221				this.nodeRemoveChildMarkup(ctx);
6222			},
6223			/**
6224			 * Create `<li><span>..</span> .. </li>` tags for this node.
6225			 *
6226			 * This method takes care that all HTML markup is created that is required
6227			 * to display this node in its current state.
6228			 *
6229			 * Call this method to create new nodes, or after the strucuture
6230			 * was changed (e.g. after moving this node or adding/removing children)
6231			 * nodeRenderTitle() and nodeRenderStatus() are implied.
6232			 *
6233			 * ```html
6234			 * <li id='KEY' ftnode=NODE>
6235			 *     <span class='fancytree-node fancytree-expanded fancytree-has-children fancytree-lastsib fancytree-exp-el fancytree-ico-e'>
6236			 *         <span class="fancytree-expander"></span>
6237			 *         <span class="fancytree-checkbox"></span> // only present in checkbox mode
6238			 *         <span class="fancytree-icon"></span>
6239			 *         <a href="#" class="fancytree-title"> Node 1 </a>
6240			 *     </span>
6241			 *     <ul> // only present if node has children
6242			 *         <li id='KEY' ftnode=NODE> child1 ... </li>
6243			 *         <li id='KEY' ftnode=NODE> child2 ... </li>
6244			 *     </ul>
6245			 * </li>
6246			 * ```
6247			 *
6248			 * @param {EventData} ctx
6249			 * @param {boolean} [force=false] re-render, even if html markup was already created
6250			 * @param {boolean} [deep=false] also render all descendants, even if parent is collapsed
6251			 * @param {boolean} [collapsed=false] force root node to be collapsed, so we can apply animated expand later
6252			 */
6253			nodeRender: function (ctx, force, deep, collapsed, _recursive) {
6254				/* This method must take care of all cases where the current data mode
6255				 * (i.e. node hierarchy) does not match the current markup.
6256				 *
6257				 * - node was not yet rendered:
6258				 *   create markup
6259				 * - node was rendered: exit fast
6260				 * - children have been added
6261				 * - children have been removed
6262				 */
6263				var childLI,
6264					childNode1,
6265					childNode2,
6266					i,
6267					l,
6268					next,
6269					subCtx,
6270					node = ctx.node,
6271					tree = ctx.tree,
6272					opts = ctx.options,
6273					aria = opts.aria,
6274					firstTime = false,
6275					parent = node.parent,
6276					isRootNode = !parent,
6277					children = node.children,
6278					successorLi = null;
6279				// FT.debug("nodeRender(" + !!force + ", " + !!deep + ")", node.toString());
6280
6281				if (tree._enableUpdate === false) {
6282					// tree.debug("no render", tree._enableUpdate);
6283					return;
6284				}
6285				if (!isRootNode && !parent.ul) {
6286					// Calling node.collapse on a deep, unrendered node
6287					return;
6288				}
6289				_assert(isRootNode || parent.ul, "parent UL must exist");
6290
6291				// Render the node
6292				if (!isRootNode) {
6293					// Discard markup on force-mode, or if it is not linked to parent <ul>
6294					if (
6295						node.li &&
6296						(force || node.li.parentNode !== node.parent.ul)
6297					) {
6298						if (node.li.parentNode === node.parent.ul) {
6299							// #486: store following node, so we can insert the new markup there later
6300							successorLi = node.li.nextSibling;
6301						} else {
6302							// May happen, when a top-level node was dropped over another
6303							this.debug(
6304								"Unlinking " +
6305									node +
6306									" (must be child of " +
6307									node.parent +
6308									")"
6309							);
6310						}
6311						//	            this.debug("nodeRemoveMarkup...");
6312						this.nodeRemoveMarkup(ctx);
6313					}
6314					// Create <li><span /> </li>
6315					// node.debug("render...");
6316					if (node.li) {
6317						// this.nodeRenderTitle(ctx);
6318						this.nodeRenderStatus(ctx);
6319					} else {
6320						// node.debug("render... really");
6321						firstTime = true;
6322						node.li = document.createElement("li");
6323						node.li.ftnode = node;
6324
6325						if (node.key && opts.generateIds) {
6326							node.li.id = opts.idPrefix + node.key;
6327						}
6328						node.span = document.createElement("span");
6329						node.span.className = "fancytree-node";
6330						if (aria && !node.tr) {
6331							$(node.li).attr("role", "treeitem");
6332						}
6333						node.li.appendChild(node.span);
6334
6335						// Create inner HTML for the <span> (expander, checkbox, icon, and title)
6336						this.nodeRenderTitle(ctx);
6337
6338						// Allow tweaking and binding, after node was created for the first time
6339						if (opts.createNode) {
6340							opts.createNode.call(
6341								tree,
6342								{ type: "createNode" },
6343								ctx
6344							);
6345						}
6346					}
6347					// Allow tweaking after node state was rendered
6348					if (opts.renderNode) {
6349						opts.renderNode.call(tree, { type: "renderNode" }, ctx);
6350					}
6351				}
6352
6353				// Visit child nodes
6354				if (children) {
6355					if (isRootNode || node.expanded || deep === true) {
6356						// Create a UL to hold the children
6357						if (!node.ul) {
6358							node.ul = document.createElement("ul");
6359							if (
6360								(collapsed === true && !_recursive) ||
6361								!node.expanded
6362							) {
6363								// hide top UL, so we can use an animation to show it later
6364								node.ul.style.display = "none";
6365							}
6366							if (aria) {
6367								$(node.ul).attr("role", "group");
6368							}
6369							if (node.li) {
6370								// issue #67
6371								node.li.appendChild(node.ul);
6372							} else {
6373								node.tree.$div.append(node.ul);
6374							}
6375						}
6376						// Add child markup
6377						for (i = 0, l = children.length; i < l; i++) {
6378							subCtx = $.extend({}, ctx, { node: children[i] });
6379							this.nodeRender(subCtx, force, deep, false, true);
6380						}
6381						// Remove <li> if nodes have moved to another parent
6382						childLI = node.ul.firstChild;
6383						while (childLI) {
6384							childNode2 = childLI.ftnode;
6385							if (childNode2 && childNode2.parent !== node) {
6386								node.debug(
6387									"_fixParent: remove missing " + childNode2,
6388									childLI
6389								);
6390								next = childLI.nextSibling;
6391								childLI.parentNode.removeChild(childLI);
6392								childLI = next;
6393							} else {
6394								childLI = childLI.nextSibling;
6395							}
6396						}
6397						// Make sure, that <li> order matches node.children order.
6398						childLI = node.ul.firstChild;
6399						for (i = 0, l = children.length - 1; i < l; i++) {
6400							childNode1 = children[i];
6401							childNode2 = childLI.ftnode;
6402							if (childNode1 === childNode2) {
6403								childLI = childLI.nextSibling;
6404							} else {
6405								// node.debug("_fixOrder: mismatch at index " + i + ": " + childNode1 + " != " + childNode2);
6406								node.ul.insertBefore(
6407									childNode1.li,
6408									childNode2.li
6409								);
6410							}
6411						}
6412					}
6413				} else {
6414					// No children: remove markup if any
6415					if (node.ul) {
6416						// alert("remove child markup for " + node);
6417						this.warn("remove child markup for " + node);
6418						this.nodeRemoveChildMarkup(ctx);
6419					}
6420				}
6421				if (!isRootNode) {
6422					// Update element classes according to node state
6423					// this.nodeRenderStatus(ctx);
6424					// Finally add the whole structure to the DOM, so the browser can render
6425					if (firstTime) {
6426						// #486: successorLi is set, if we re-rendered (i.e. discarded)
6427						// existing markup, which  we want to insert at the same position.
6428						// (null is equivalent to append)
6429						// 		parent.ul.appendChild(node.li);
6430						parent.ul.insertBefore(node.li, successorLi);
6431					}
6432				}
6433			},
6434			/** Create HTML inside the node's outer `<span>` (i.e. expander, checkbox,
6435			 * icon, and title).
6436			 *
6437			 * nodeRenderStatus() is implied.
6438			 * @param {EventData} ctx
6439			 * @param {string} [title] optinal new title
6440			 */
6441			nodeRenderTitle: function (ctx, title) {
6442				// set node connector images, links and text
6443				var checkbox,
6444					className,
6445					icon,
6446					nodeTitle,
6447					role,
6448					tabindex,
6449					tooltip,
6450					iconTooltip,
6451					node = ctx.node,
6452					tree = ctx.tree,
6453					opts = ctx.options,
6454					aria = opts.aria,
6455					level = node.getLevel(),
6456					ares = [];
6457
6458				if (title !== undefined) {
6459					node.title = title;
6460				}
6461				if (!node.span || tree._enableUpdate === false) {
6462					// Silently bail out if node was not rendered yet, assuming
6463					// node.render() will be called as the node becomes visible
6464					return;
6465				}
6466				// Connector (expanded, expandable or simple)
6467				role =
6468					aria && node.hasChildren() !== false
6469						? " role='button'"
6470						: "";
6471				if (level < opts.minExpandLevel) {
6472					if (!node.lazy) {
6473						node.expanded = true;
6474					}
6475					if (level > 1) {
6476						ares.push(
6477							"<span " +
6478								role +
6479								" class='fancytree-expander fancytree-expander-fixed'></span>"
6480						);
6481					}
6482					// .. else (i.e. for root level) skip expander/connector alltogether
6483				} else {
6484					ares.push(
6485						"<span " + role + " class='fancytree-expander'></span>"
6486					);
6487				}
6488				// Checkbox mode
6489				checkbox = FT.evalOption("checkbox", node, node, opts, false);
6490
6491				if (checkbox && !node.isStatusNode()) {
6492					role = aria ? " role='checkbox'" : "";
6493					className = "fancytree-checkbox";
6494					if (
6495						checkbox === "radio" ||
6496						(node.parent && node.parent.radiogroup)
6497					) {
6498						className += " fancytree-radio";
6499					}
6500					ares.push(
6501						"<span " + role + " class='" + className + "'></span>"
6502					);
6503				}
6504				// Folder or doctype icon
6505				if (node.data.iconClass !== undefined) {
6506					// 2015-11-16
6507					// Handle / warn about backward compatibility
6508					if (node.icon) {
6509						$.error(
6510							"'iconClass' node option is deprecated since v2.14.0: use 'icon' only instead"
6511						);
6512					} else {
6513						node.warn(
6514							"'iconClass' node option is deprecated since v2.14.0: use 'icon' instead"
6515						);
6516						node.icon = node.data.iconClass;
6517					}
6518				}
6519				// If opts.icon is a callback and returns something other than undefined, use that
6520				// else if node.icon is a boolean or string, use that
6521				// else if opts.icon is a boolean or string, use that
6522				// else show standard icon (which may be different for folders or documents)
6523				icon = FT.evalOption("icon", node, node, opts, true);
6524				// if( typeof icon !== "boolean" ) {
6525				// 	// icon is defined, but not true/false: must be a string
6526				// 	icon = "" + icon;
6527				// }
6528				if (icon !== false) {
6529					role = aria ? " role='presentation'" : "";
6530
6531					iconTooltip = FT.evalOption(
6532						"iconTooltip",
6533						node,
6534						node,
6535						opts,
6536						null
6537					);
6538					iconTooltip = iconTooltip
6539						? " title='" + _escapeTooltip(iconTooltip) + "'"
6540						: "";
6541
6542					if (typeof icon === "string") {
6543						if (TEST_IMG.test(icon)) {
6544							// node.icon is an image url. Prepend imagePath
6545							icon =
6546								icon.charAt(0) === "/"
6547									? icon
6548									: (opts.imagePath || "") + icon;
6549							ares.push(
6550								"<img src='" +
6551									icon +
6552									"' class='fancytree-icon'" +
6553									iconTooltip +
6554									" alt='' />"
6555							);
6556						} else {
6557							ares.push(
6558								"<span " +
6559									role +
6560									" class='fancytree-custom-icon " +
6561									icon +
6562									"'" +
6563									iconTooltip +
6564									"></span>"
6565							);
6566						}
6567					} else if (icon.text) {
6568						ares.push(
6569							"<span " +
6570								role +
6571								" class='fancytree-custom-icon " +
6572								(icon.addClass || "") +
6573								"'" +
6574								iconTooltip +
6575								">" +
6576								FT.escapeHtml(icon.text) +
6577								"</span>"
6578						);
6579					} else if (icon.html) {
6580						ares.push(
6581							"<span " +
6582								role +
6583								" class='fancytree-custom-icon " +
6584								(icon.addClass || "") +
6585								"'" +
6586								iconTooltip +
6587								">" +
6588								icon.html +
6589								"</span>"
6590						);
6591					} else {
6592						// standard icon: theme css will take care of this
6593						ares.push(
6594							"<span " +
6595								role +
6596								" class='fancytree-icon'" +
6597								iconTooltip +
6598								"></span>"
6599						);
6600					}
6601				}
6602				// Node title
6603				nodeTitle = "";
6604				if (opts.renderTitle) {
6605					nodeTitle =
6606						opts.renderTitle.call(
6607							tree,
6608							{ type: "renderTitle" },
6609							ctx
6610						) || "";
6611				}
6612				if (!nodeTitle) {
6613					tooltip = FT.evalOption("tooltip", node, node, opts, null);
6614					if (tooltip === true) {
6615						tooltip = node.title;
6616					}
6617					// if( node.tooltip ) {
6618					// 	tooltip = node.tooltip;
6619					// } else if ( opts.tooltip ) {
6620					// 	tooltip = opts.tooltip === true ? node.title : opts.tooltip.call(tree, node);
6621					// }
6622					tooltip = tooltip
6623						? " title='" + _escapeTooltip(tooltip) + "'"
6624						: "";
6625					tabindex = opts.titlesTabbable ? " tabindex='0'" : "";
6626
6627					nodeTitle =
6628						"<span class='fancytree-title'" +
6629						tooltip +
6630						tabindex +
6631						">" +
6632						(opts.escapeTitles
6633							? FT.escapeHtml(node.title)
6634							: node.title) +
6635						"</span>";
6636				}
6637				ares.push(nodeTitle);
6638				// Note: this will trigger focusout, if node had the focus
6639				//$(node.span).html(ares.join("")); // it will cleanup the jQuery data currently associated with SPAN (if any), but it executes more slowly
6640				node.span.innerHTML = ares.join("");
6641				// Update CSS classes
6642				this.nodeRenderStatus(ctx);
6643				if (opts.enhanceTitle) {
6644					ctx.$title = $(">span.fancytree-title", node.span);
6645					nodeTitle =
6646						opts.enhanceTitle.call(
6647							tree,
6648							{ type: "enhanceTitle" },
6649							ctx
6650						) || "";
6651				}
6652			},
6653			/** Update element classes according to node state.
6654			 * @param {EventData} ctx
6655			 */
6656			nodeRenderStatus: function (ctx) {
6657				// Set classes for current status
6658				var $ariaElem,
6659					node = ctx.node,
6660					tree = ctx.tree,
6661					opts = ctx.options,
6662					// 	nodeContainer = node[tree.nodeContainerAttrName],
6663					hasChildren = node.hasChildren(),
6664					isLastSib = node.isLastSibling(),
6665					aria = opts.aria,
6666					cn = opts._classNames,
6667					cnList = [],
6668					statusElem = node[tree.statusClassPropName];
6669
6670				if (!statusElem || tree._enableUpdate === false) {
6671					// if this function is called for an unrendered node, ignore it (will be updated on nect render anyway)
6672					return;
6673				}
6674				if (aria) {
6675					$ariaElem = $(node.tr || node.li);
6676				}
6677				// Build a list of class names that we will add to the node <span>
6678				cnList.push(cn.node);
6679				if (tree.activeNode === node) {
6680					cnList.push(cn.active);
6681					// 		$(">span.fancytree-title", statusElem).attr("tabindex", "0");
6682					// 		tree.$container.removeAttr("tabindex");
6683					// }else{
6684					// 		$(">span.fancytree-title", statusElem).removeAttr("tabindex");
6685					// 		tree.$container.attr("tabindex", "0");
6686				}
6687				if (tree.focusNode === node) {
6688					cnList.push(cn.focused);
6689				}
6690				if (node.expanded) {
6691					cnList.push(cn.expanded);
6692				}
6693				if (aria) {
6694					if (hasChildren === false) {
6695						$ariaElem.removeAttr("aria-expanded");
6696					} else {
6697						$ariaElem.attr("aria-expanded", Boolean(node.expanded));
6698					}
6699				}
6700				if (node.folder) {
6701					cnList.push(cn.folder);
6702				}
6703				if (hasChildren !== false) {
6704					cnList.push(cn.hasChildren);
6705				}
6706				// TODO: required?
6707				if (isLastSib) {
6708					cnList.push(cn.lastsib);
6709				}
6710				if (node.lazy && node.children == null) {
6711					cnList.push(cn.lazy);
6712				}
6713				if (node.partload) {
6714					cnList.push(cn.partload);
6715				}
6716				if (node.partsel) {
6717					cnList.push(cn.partsel);
6718				}
6719				if (FT.evalOption("unselectable", node, node, opts, false)) {
6720					cnList.push(cn.unselectable);
6721				}
6722				if (node._isLoading) {
6723					cnList.push(cn.loading);
6724				}
6725				if (node._error) {
6726					cnList.push(cn.error);
6727				}
6728				if (node.statusNodeType) {
6729					cnList.push(cn.statusNodePrefix + node.statusNodeType);
6730				}
6731				if (node.selected) {
6732					cnList.push(cn.selected);
6733					if (aria) {
6734						$ariaElem.attr("aria-selected", true);
6735					}
6736				} else if (aria) {
6737					$ariaElem.attr("aria-selected", false);
6738				}
6739				if (node.extraClasses) {
6740					cnList.push(node.extraClasses);
6741				}
6742				// IE6 doesn't correctly evaluate multiple class names,
6743				// so we create combined class names that can be used in the CSS
6744				if (hasChildren === false) {
6745					cnList.push(
6746						cn.combinedExpanderPrefix + "n" + (isLastSib ? "l" : "")
6747					);
6748				} else {
6749					cnList.push(
6750						cn.combinedExpanderPrefix +
6751							(node.expanded ? "e" : "c") +
6752							(node.lazy && node.children == null ? "d" : "") +
6753							(isLastSib ? "l" : "")
6754					);
6755				}
6756				cnList.push(
6757					cn.combinedIconPrefix +
6758						(node.expanded ? "e" : "c") +
6759						(node.folder ? "f" : "")
6760				);
6761				// node.span.className = cnList.join(" ");
6762				statusElem.className = cnList.join(" ");
6763
6764				// TODO: we should not set this in the <span> tag also, if we set it here:
6765				// Maybe most (all) of the classes should be set in LI instead of SPAN?
6766				if (node.li) {
6767					// #719: we have to consider that there may be already other classes:
6768					$(node.li).toggleClass(cn.lastsib, isLastSib);
6769				}
6770			},
6771			/** Activate node.
6772			 * flag defaults to true.
6773			 * If flag is true, the node is activated (must be a synchronous operation)
6774			 * If flag is false, the node is deactivated (must be a synchronous operation)
6775			 * @param {EventData} ctx
6776			 * @param {boolean} [flag=true]
6777			 * @param {object} [opts] additional options. Defaults to {noEvents: false, noFocus: false}
6778			 * @returns {$.Promise}
6779			 */
6780			nodeSetActive: function (ctx, flag, callOpts) {
6781				// Handle user click / [space] / [enter], according to clickFolderMode.
6782				callOpts = callOpts || {};
6783				var subCtx,
6784					node = ctx.node,
6785					tree = ctx.tree,
6786					opts = ctx.options,
6787					noEvents = callOpts.noEvents === true,
6788					noFocus = callOpts.noFocus === true,
6789					scroll = callOpts.scrollIntoView !== false,
6790					isActive = node === tree.activeNode;
6791
6792				// flag defaults to true
6793				flag = flag !== false;
6794				// node.debug("nodeSetActive", flag);
6795
6796				if (isActive === flag) {
6797					// Nothing to do
6798					return _getResolvedPromise(node);
6799				}
6800				// #1042: don't scroll between mousedown/-up when clicking an embedded link
6801				if (
6802					scroll &&
6803					ctx.originalEvent &&
6804					$(ctx.originalEvent.target).is("a,:checkbox")
6805				) {
6806					node.info("Not scrolling while clicking an embedded link.");
6807					scroll = false;
6808				}
6809				if (
6810					flag &&
6811					!noEvents &&
6812					this._triggerNodeEvent(
6813						"beforeActivate",
6814						node,
6815						ctx.originalEvent
6816					) === false
6817				) {
6818					// Callback returned false
6819					return _getRejectedPromise(node, ["rejected"]);
6820				}
6821				if (flag) {
6822					if (tree.activeNode) {
6823						_assert(
6824							tree.activeNode !== node,
6825							"node was active (inconsistency)"
6826						);
6827						subCtx = $.extend({}, ctx, { node: tree.activeNode });
6828						tree.nodeSetActive(subCtx, false);
6829						_assert(
6830							tree.activeNode === null,
6831							"deactivate was out of sync?"
6832						);
6833					}
6834
6835					if (opts.activeVisible) {
6836						// If no focus is set (noFocus: true) and there is no focused node, this node is made visible.
6837						// scroll = noFocus && tree.focusNode == null;
6838						// #863: scroll by default (unless `scrollIntoView: false` was passed)
6839						node.makeVisible({ scrollIntoView: scroll });
6840					}
6841					tree.activeNode = node;
6842					tree.nodeRenderStatus(ctx);
6843					if (!noFocus) {
6844						tree.nodeSetFocus(ctx);
6845					}
6846					if (!noEvents) {
6847						tree._triggerNodeEvent(
6848							"activate",
6849							node,
6850							ctx.originalEvent
6851						);
6852					}
6853				} else {
6854					_assert(
6855						tree.activeNode === node,
6856						"node was not active (inconsistency)"
6857					);
6858					tree.activeNode = null;
6859					this.nodeRenderStatus(ctx);
6860					if (!noEvents) {
6861						ctx.tree._triggerNodeEvent(
6862							"deactivate",
6863							node,
6864							ctx.originalEvent
6865						);
6866					}
6867				}
6868				return _getResolvedPromise(node);
6869			},
6870			/** Expand or collapse node, return Deferred.promise.
6871			 *
6872			 * @param {EventData} ctx
6873			 * @param {boolean} [flag=true]
6874			 * @param {object} [opts] additional options. Defaults to `{noAnimation: false, noEvents: false}`
6875			 * @returns {$.Promise} The deferred will be resolved as soon as the (lazy)
6876			 *     data was retrieved, rendered, and the expand animation finished.
6877			 */
6878			nodeSetExpanded: function (ctx, flag, callOpts) {
6879				callOpts = callOpts || {};
6880				var _afterLoad,
6881					dfd,
6882					i,
6883					l,
6884					parents,
6885					prevAC,
6886					node = ctx.node,
6887					tree = ctx.tree,
6888					opts = ctx.options,
6889					noAnimation = callOpts.noAnimation === true,
6890					noEvents = callOpts.noEvents === true;
6891
6892				// flag defaults to true
6893				flag = flag !== false;
6894
6895				// node.debug("nodeSetExpanded(" + flag + ")");
6896
6897				if ($(node.li).hasClass(opts._classNames.animating)) {
6898					node.warn(
6899						"setExpanded(" + flag + ") while animating: ignored."
6900					);
6901					return _getRejectedPromise(node, ["recursion"]);
6902				}
6903
6904				if ((node.expanded && flag) || (!node.expanded && !flag)) {
6905					// Nothing to do
6906					// node.debug("nodeSetExpanded(" + flag + "): nothing to do");
6907					return _getResolvedPromise(node);
6908				} else if (flag && !node.lazy && !node.hasChildren()) {
6909					// Prevent expanding of empty nodes
6910					// return _getRejectedPromise(node, ["empty"]);
6911					return _getResolvedPromise(node);
6912				} else if (!flag && node.getLevel() < opts.minExpandLevel) {
6913					// Prevent collapsing locked levels
6914					return _getRejectedPromise(node, ["locked"]);
6915				} else if (
6916					!noEvents &&
6917					this._triggerNodeEvent(
6918						"beforeExpand",
6919						node,
6920						ctx.originalEvent
6921					) === false
6922				) {
6923					// Callback returned false
6924					return _getRejectedPromise(node, ["rejected"]);
6925				}
6926				// If this node inside a collpased node, no animation and scrolling is needed
6927				if (!noAnimation && !node.isVisible()) {
6928					noAnimation = callOpts.noAnimation = true;
6929				}
6930
6931				dfd = new $.Deferred();
6932
6933				// Auto-collapse mode: collapse all siblings
6934				if (flag && !node.expanded && opts.autoCollapse) {
6935					parents = node.getParentList(false, true);
6936					prevAC = opts.autoCollapse;
6937					try {
6938						opts.autoCollapse = false;
6939						for (i = 0, l = parents.length; i < l; i++) {
6940							// TODO: should return promise?
6941							this._callHook(
6942								"nodeCollapseSiblings",
6943								parents[i],
6944								callOpts
6945							);
6946						}
6947					} finally {
6948						opts.autoCollapse = prevAC;
6949					}
6950				}
6951				// Trigger expand/collapse after expanding
6952				dfd.done(function () {
6953					var lastChild = node.getLastChild();
6954
6955					if (
6956						flag &&
6957						opts.autoScroll &&
6958						!noAnimation &&
6959						lastChild &&
6960						tree._enableUpdate
6961					) {
6962						// Scroll down to last child, but keep current node visible
6963						lastChild
6964							.scrollIntoView(true, { topNode: node })
6965							.always(function () {
6966								if (!noEvents) {
6967									ctx.tree._triggerNodeEvent(
6968										flag ? "expand" : "collapse",
6969										ctx
6970									);
6971								}
6972							});
6973					} else {
6974						if (!noEvents) {
6975							ctx.tree._triggerNodeEvent(
6976								flag ? "expand" : "collapse",
6977								ctx
6978							);
6979						}
6980					}
6981				});
6982				// vvv Code below is executed after loading finished:
6983				_afterLoad = function (callback) {
6984					var cn = opts._classNames,
6985						isVisible,
6986						isExpanded,
6987						effect = opts.toggleEffect;
6988
6989					node.expanded = flag;
6990					tree._callHook(
6991						"treeStructureChanged",
6992						ctx,
6993						flag ? "expand" : "collapse"
6994					);
6995					// Create required markup, but make sure the top UL is hidden, so we
6996					// can animate later
6997					tree._callHook("nodeRender", ctx, false, false, true);
6998
6999					// Hide children, if node is collapsed
7000					if (node.ul) {
7001						isVisible = node.ul.style.display !== "none";
7002						isExpanded = !!node.expanded;
7003						if (isVisible === isExpanded) {
7004							node.warn(
7005								"nodeSetExpanded: UL.style.display already set"
7006							);
7007						} else if (!effect || noAnimation) {
7008							node.ul.style.display =
7009								node.expanded || !parent ? "" : "none";
7010						} else {
7011							// The UI toggle() effect works with the ext-wide extension,
7012							// while jQuery.animate() has problems when the title span
7013							// has position: absolute.
7014							// Since jQuery UI 1.12, the blind effect requires the parent
7015							// element to have 'position: relative'.
7016							// See #716, #717
7017							$(node.li).addClass(cn.animating); // #717
7018
7019							if (_isFunction($(node.ul)[effect.effect])) {
7020								// tree.debug( "use jquery." + effect.effect + " method" );
7021								$(node.ul)[effect.effect]({
7022									duration: effect.duration,
7023									always: function () {
7024										// node.debug("fancytree-animating end: " + node.li.className);
7025										$(this).removeClass(cn.animating); // #716
7026										$(node.li).removeClass(cn.animating); // #717
7027										callback();
7028									},
7029								});
7030							} else {
7031								// The UI toggle() effect works with the ext-wide extension,
7032								// while jQuery.animate() has problems when the title span
7033								// has positon: absolute.
7034								// Since jQuery UI 1.12, the blind effect requires the parent
7035								// element to have 'position: relative'.
7036								// See #716, #717
7037								// tree.debug("use specified effect (" + effect.effect + ") with the jqueryui.toggle method");
7038
7039								// try to stop an animation that might be already in progress
7040								$(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"
7041
7042								// dirty fix to remove a defunct animation (effect: "blind") after resetLazy has been called
7043								$(node.ul)
7044									.parent()
7045									.find(".ui-effects-placeholder")
7046									.remove();
7047
7048								$(node.ul).toggle(
7049									effect.effect,
7050									effect.options,
7051									effect.duration,
7052									function () {
7053										// node.debug("fancytree-animating end: " + node.li.className);
7054										$(this).removeClass(cn.animating); // #716
7055										$(node.li).removeClass(cn.animating); // #717
7056										callback();
7057									}
7058								);
7059							}
7060							return;
7061						}
7062					}
7063					callback();
7064				};
7065				// ^^^ Code above is executed after loading finshed.
7066
7067				// Load lazy nodes, if any. Then continue with _afterLoad()
7068				if (flag && node.lazy && node.hasChildren() === undefined) {
7069					// node.debug("nodeSetExpanded: load start...");
7070					node.load()
7071						.done(function () {
7072							// node.debug("nodeSetExpanded: load done");
7073							if (dfd.notifyWith) {
7074								// requires jQuery 1.6+
7075								dfd.notifyWith(node, ["loaded"]);
7076							}
7077							_afterLoad(function () {
7078								dfd.resolveWith(node);
7079							});
7080						})
7081						.fail(function (errMsg) {
7082							_afterLoad(function () {
7083								dfd.rejectWith(node, [
7084									"load failed (" + errMsg + ")",
7085								]);
7086							});
7087						});
7088					/*
7089					var source = tree._triggerNodeEvent("lazyLoad", node, ctx.originalEvent);
7090					_assert(typeof source !== "boolean", "lazyLoad event must return source in data.result");
7091					node.debug("nodeSetExpanded: load start...");
7092					this._callHook("nodeLoadChildren", ctx, source).done(function(){
7093						node.debug("nodeSetExpanded: load done");
7094						if(dfd.notifyWith){ // requires jQuery 1.6+
7095							dfd.notifyWith(node, ["loaded"]);
7096						}
7097						_afterLoad.call(tree);
7098					}).fail(function(errMsg){
7099						dfd.rejectWith(node, ["load failed (" + errMsg + ")"]);
7100					});
7101					*/
7102				} else {
7103					_afterLoad(function () {
7104						dfd.resolveWith(node);
7105					});
7106				}
7107				// node.debug("nodeSetExpanded: returns");
7108				return dfd.promise();
7109			},
7110			/** Focus or blur this node.
7111			 * @param {EventData} ctx
7112			 * @param {boolean} [flag=true]
7113			 */
7114			nodeSetFocus: function (ctx, flag) {
7115				// ctx.node.debug("nodeSetFocus(" + flag + ")");
7116				var ctx2,
7117					tree = ctx.tree,
7118					node = ctx.node,
7119					opts = tree.options,
7120					// et = ctx.originalEvent && ctx.originalEvent.type,
7121					isInput = ctx.originalEvent
7122						? $(ctx.originalEvent.target).is(":input")
7123						: false;
7124
7125				flag = flag !== false;
7126
7127				// (node || tree).debug("nodeSetFocus(" + flag + "), event: " + et + ", isInput: "+ isInput);
7128				// Blur previous node if any
7129				if (tree.focusNode) {
7130					if (tree.focusNode === node && flag) {
7131						// node.debug("nodeSetFocus(" + flag + "): nothing to do");
7132						return;
7133					}
7134					ctx2 = $.extend({}, ctx, { node: tree.focusNode });
7135					tree.focusNode = null;
7136					this._triggerNodeEvent("blur", ctx2);
7137					this._callHook("nodeRenderStatus", ctx2);
7138				}
7139				// Set focus to container and node
7140				if (flag) {
7141					if (!this.hasFocus()) {
7142						node.debug("nodeSetFocus: forcing container focus");
7143						this._callHook("treeSetFocus", ctx, true, {
7144							calledByNode: true,
7145						});
7146					}
7147					node.makeVisible({ scrollIntoView: false });
7148					tree.focusNode = node;
7149					if (opts.titlesTabbable) {
7150						if (!isInput) {
7151							// #621
7152							$(node.span).find(".fancytree-title").focus();
7153						}
7154					}
7155					if (opts.aria) {
7156						// Set active descendant to node's span ID (create one, if needed)
7157						$(tree.$container).attr(
7158							"aria-activedescendant",
7159							$(node.tr || node.li)
7160								.uniqueId()
7161								.attr("id")
7162						);
7163						// "ftal_" + opts.idPrefix + node.key);
7164					}
7165					// $(node.span).find(".fancytree-title").focus();
7166					this._triggerNodeEvent("focus", ctx);
7167
7168					// determine if we have focus on or inside tree container
7169					var hasFancytreeFocus =
7170						document.activeElement === tree.$container.get(0) ||
7171						$(document.activeElement, tree.$container).length >= 1;
7172
7173					if (!hasFancytreeFocus) {
7174						// We cannot set KB focus to a node, so use the tree container
7175						// #563, #570: IE scrolls on every call to .focus(), if the container
7176						// is partially outside the viewport. So do it only, when absolutely
7177						// necessary.
7178						$(tree.$container).focus();
7179					}
7180
7181					// if( opts.autoActivate ){
7182					// 	tree.nodeSetActive(ctx, true);
7183					// }
7184					if (opts.autoScroll) {
7185						node.scrollIntoView();
7186					}
7187					this._callHook("nodeRenderStatus", ctx);
7188				}
7189			},
7190			/** (De)Select node, return new status (sync).
7191			 *
7192			 * @param {EventData} ctx
7193			 * @param {boolean} [flag=true]
7194			 * @param {object} [opts] additional options. Defaults to {noEvents: false,
7195			 *     propagateDown: null, propagateUp: null,
7196			 *     callback: null,
7197			 *     }
7198			 * @returns {boolean} previous status
7199			 */
7200			nodeSetSelected: function (ctx, flag, callOpts) {
7201				callOpts = callOpts || {};
7202				var node = ctx.node,
7203					tree = ctx.tree,
7204					opts = ctx.options,
7205					noEvents = callOpts.noEvents === true,
7206					parent = node.parent;
7207
7208				// flag defaults to true
7209				flag = flag !== false;
7210
7211				// node.debug("nodeSetSelected(" + flag + ")", ctx);
7212
7213				// Cannot (de)select unselectable nodes directly (only by propagation or
7214				// by setting the `.selected` property)
7215				if (FT.evalOption("unselectable", node, node, opts, false)) {
7216					return;
7217				}
7218
7219				// Remember the user's intent, in case down -> up propagation prevents
7220				// applying it to node.selected
7221				node._lastSelectIntent = flag; // Confusing use of '!'
7222
7223				// Nothing to do?
7224				if (!!node.selected === flag) {
7225					if (opts.selectMode === 3 && node.partsel && !flag) {
7226						// If propagation prevented selecting this node last time, we still
7227						// want to allow to apply setSelected(false) now
7228					} else {
7229						return flag;
7230					}
7231				}
7232
7233				if (
7234					!noEvents &&
7235					this._triggerNodeEvent(
7236						"beforeSelect",
7237						node,
7238						ctx.originalEvent
7239					) === false
7240				) {
7241					return !!node.selected;
7242				}
7243				if (flag && opts.selectMode === 1) {
7244					// single selection mode (we don't uncheck all tree nodes, for performance reasons)
7245					if (tree.lastSelectedNode) {
7246						tree.lastSelectedNode.setSelected(false);
7247					}
7248					node.selected = flag;
7249				} else if (
7250					opts.selectMode === 3 &&
7251					parent &&
7252					!parent.radiogroup &&
7253					!node.radiogroup
7254				) {
7255					// multi-hierarchical selection mode
7256					node.selected = flag;
7257					node.fixSelection3AfterClick(callOpts);
7258				} else if (parent && parent.radiogroup) {
7259					node.visitSiblings(function (n) {
7260						n._changeSelectStatusAttrs(flag && n === node);
7261					}, true);
7262				} else {
7263					// default: selectMode: 2, multi selection mode
7264					node.selected = flag;
7265				}
7266				this.nodeRenderStatus(ctx);
7267				tree.lastSelectedNode = flag ? node : null;
7268				if (!noEvents) {
7269					tree._triggerNodeEvent("select", ctx);
7270				}
7271			},
7272			/** Show node status (ok, loading, error, nodata) using styles and a dummy child node.
7273			 *
7274			 * @param {EventData} ctx
7275			 * @param status
7276			 * @param message
7277			 * @param details
7278			 * @since 2.3
7279			 */
7280			nodeSetStatus: function (ctx, status, message, details) {
7281				var node = ctx.node,
7282					tree = ctx.tree;
7283
7284				function _clearStatusNode() {
7285					// Remove dedicated dummy node, if any
7286					var firstChild = node.children ? node.children[0] : null;
7287					if (firstChild && firstChild.isStatusNode()) {
7288						try {
7289							// I've seen exceptions here with loadKeyPath...
7290							if (node.ul) {
7291								node.ul.removeChild(firstChild.li);
7292								firstChild.li = null; // avoid leaks (DT issue 215)
7293							}
7294						} catch (e) {}
7295						if (node.children.length === 1) {
7296							node.children = [];
7297						} else {
7298							node.children.shift();
7299						}
7300						tree._callHook(
7301							"treeStructureChanged",
7302							ctx,
7303							"clearStatusNode"
7304						);
7305					}
7306				}
7307				function _setStatusNode(data, type) {
7308					// Create/modify the dedicated dummy node for 'loading...' or
7309					// 'error!' status. (only called for direct child of the invisible
7310					// system root)
7311					var firstChild = node.children ? node.children[0] : null;
7312					if (firstChild && firstChild.isStatusNode()) {
7313						$.extend(firstChild, data);
7314						firstChild.statusNodeType = type;
7315						tree._callHook("nodeRenderTitle", firstChild);
7316					} else {
7317						node._setChildren([data]);
7318						tree._callHook(
7319							"treeStructureChanged",
7320							ctx,
7321							"setStatusNode"
7322						);
7323						node.children[0].statusNodeType = type;
7324						tree.render();
7325					}
7326					return node.children[0];
7327				}
7328
7329				switch (status) {
7330					case "ok":
7331						_clearStatusNode();
7332						node._isLoading = false;
7333						node._error = null;
7334						node.renderStatus();
7335						break;
7336					case "loading":
7337						if (!node.parent) {
7338							_setStatusNode(
7339								{
7340									title:
7341										tree.options.strings.loading +
7342										(message ? " (" + message + ")" : ""),
7343									// icon: true,  // needed for 'loding' icon
7344									checkbox: false,
7345									tooltip: details,
7346								},
7347								status
7348							);
7349						}
7350						node._isLoading = true;
7351						node._error = null;
7352						node.renderStatus();
7353						break;
7354					case "error":
7355						_setStatusNode(
7356							{
7357								title:
7358									tree.options.strings.loadError +
7359									(message ? " (" + message + ")" : ""),
7360								// icon: false,
7361								checkbox: false,
7362								tooltip: details,
7363							},
7364							status
7365						);
7366						node._isLoading = false;
7367						node._error = { message: message, details: details };
7368						node.renderStatus();
7369						break;
7370					case "nodata":
7371						_setStatusNode(
7372							{
7373								title: message || tree.options.strings.noData,
7374								// icon: false,
7375								checkbox: false,
7376								tooltip: details,
7377							},
7378							status
7379						);
7380						node._isLoading = false;
7381						node._error = null;
7382						node.renderStatus();
7383						break;
7384					default:
7385						$.error("invalid node status " + status);
7386				}
7387			},
7388			/**
7389			 *
7390			 * @param {EventData} ctx
7391			 */
7392			nodeToggleExpanded: function (ctx) {
7393				return this.nodeSetExpanded(ctx, !ctx.node.expanded);
7394			},
7395			/**
7396			 * @param {EventData} ctx
7397			 */
7398			nodeToggleSelected: function (ctx) {
7399				var node = ctx.node,
7400					flag = !node.selected;
7401
7402				// In selectMode: 3 this node may be unselected+partsel, even if
7403				// setSelected(true) was called before, due to `unselectable` children.
7404				// In this case, we now toggle as `setSelected(false)`
7405				if (
7406					node.partsel &&
7407					!node.selected &&
7408					node._lastSelectIntent === true
7409				) {
7410					flag = false;
7411					node.selected = true; // so it is not considered 'nothing to do'
7412				}
7413				node._lastSelectIntent = flag;
7414				return this.nodeSetSelected(ctx, flag);
7415			},
7416			/** Remove all nodes.
7417			 * @param {EventData} ctx
7418			 */
7419			treeClear: function (ctx) {
7420				var tree = ctx.tree;
7421				tree.activeNode = null;
7422				tree.focusNode = null;
7423				tree.$div.find(">ul.fancytree-container").empty();
7424				// TODO: call destructors and remove reference loops
7425				tree.rootNode.children = null;
7426				tree._callHook("treeStructureChanged", ctx, "clear");
7427			},
7428			/** Widget was created (called only once, even it re-initialized).
7429			 * @param {EventData} ctx
7430			 */
7431			treeCreate: function (ctx) {},
7432			/** Widget was destroyed.
7433			 * @param {EventData} ctx
7434			 */
7435			treeDestroy: function (ctx) {
7436				this.$div.find(">ul.fancytree-container").remove();
7437				if (this.$source) {
7438					this.$source.removeClass("fancytree-helper-hidden");
7439				}
7440			},
7441			/** Widget was (re-)initialized.
7442			 * @param {EventData} ctx
7443			 */
7444			treeInit: function (ctx) {
7445				var tree = ctx.tree,
7446					opts = tree.options;
7447
7448				//this.debug("Fancytree.treeInit()");
7449				// Add container to the TAB chain
7450				// See http://www.w3.org/TR/wai-aria-practices/#focus_activedescendant
7451				// #577: Allow to set tabindex to "0", "-1" and ""
7452				tree.$container.attr("tabindex", opts.tabindex);
7453
7454				// Copy some attributes to tree.data
7455				$.each(TREE_ATTRS, function (i, attr) {
7456					if (opts[attr] !== undefined) {
7457						tree.info("Move option " + attr + " to tree");
7458						tree[attr] = opts[attr];
7459						delete opts[attr];
7460					}
7461				});
7462
7463				if (opts.checkboxAutoHide) {
7464					tree.$container.addClass("fancytree-checkbox-auto-hide");
7465				}
7466				if (opts.rtl) {
7467					tree.$container
7468						.attr("DIR", "RTL")
7469						.addClass("fancytree-rtl");
7470				} else {
7471					tree.$container
7472						.removeAttr("DIR")
7473						.removeClass("fancytree-rtl");
7474				}
7475				if (opts.aria) {
7476					tree.$container.attr("role", "tree");
7477					if (opts.selectMode !== 1) {
7478						tree.$container.attr("aria-multiselectable", true);
7479					}
7480				}
7481				this.treeLoad(ctx);
7482			},
7483			/** Parse Fancytree from source, as configured in the options.
7484			 * @param {EventData} ctx
7485			 * @param {object} [source] optional new source (use last data otherwise)
7486			 */
7487			treeLoad: function (ctx, source) {
7488				var metaData,
7489					type,
7490					$ul,
7491					tree = ctx.tree,
7492					$container = ctx.widget.element,
7493					dfd,
7494					// calling context for root node
7495					rootCtx = $.extend({}, ctx, { node: this.rootNode });
7496
7497				if (tree.rootNode.children) {
7498					this.treeClear(ctx);
7499				}
7500				source = source || this.options.source;
7501
7502				if (!source) {
7503					type = $container.data("type") || "html";
7504					switch (type) {
7505						case "html":
7506							// There should be an embedded `<ul>` with initial nodes,
7507							// but another `<ul class='fancytree-container'>` is appended
7508							// to the tree's <div> on startup anyway.
7509							$ul = $container
7510								.find(">ul")
7511								.not(".fancytree-container")
7512								.first();
7513
7514							if ($ul.length) {
7515								$ul.addClass(
7516									"ui-fancytree-source fancytree-helper-hidden"
7517								);
7518								source = $.ui.fancytree.parseHtml($ul);
7519								// allow to init tree.data.foo from <ul data-foo=''>
7520								this.data = $.extend(
7521									this.data,
7522									_getElementDataAsDict($ul)
7523								);
7524							} else {
7525								FT.warn(
7526									"No `source` option was passed and container does not contain `<ul>`: assuming `source: []`."
7527								);
7528								source = [];
7529							}
7530							break;
7531						case "json":
7532							source = $.parseJSON($container.text());
7533							// $container already contains the <ul>, but we remove the plain (json) text
7534							// $container.empty();
7535							$container
7536								.contents()
7537								.filter(function () {
7538									return this.nodeType === 3;
7539								})
7540								.remove();
7541							if ($.isPlainObject(source)) {
7542								// We got {foo: 'abc', children: [...]}
7543								_assert(
7544									_isArray(source.children),
7545									"if an object is passed as source, it must contain a 'children' array (all other properties are added to 'tree.data')"
7546								);
7547								metaData = source;
7548								source = source.children;
7549								delete metaData.children;
7550								// Copy some attributes to tree.data
7551								$.each(TREE_ATTRS, function (i, attr) {
7552									if (metaData[attr] !== undefined) {
7553										tree[attr] = metaData[attr];
7554										delete metaData[attr];
7555									}
7556								});
7557								// Copy extra properties to tree.data.foo
7558								$.extend(tree.data, metaData);
7559							}
7560							break;
7561						default:
7562							$.error("Invalid data-type: " + type);
7563					}
7564				} else if (typeof source === "string") {
7565					// TODO: source is an element ID
7566					$.error("Not implemented");
7567				}
7568
7569				// preInit is fired when the widget markup is created, but nodes
7570				// not yet loaded
7571				tree._triggerTreeEvent("preInit", null);
7572
7573				// Trigger fancytreeinit after nodes have been loaded
7574				dfd = this.nodeLoadChildren(rootCtx, source)
7575					.done(function () {
7576						tree._callHook(
7577							"treeStructureChanged",
7578							ctx,
7579							"loadChildren"
7580						);
7581						tree.render();
7582						if (ctx.options.selectMode === 3) {
7583							tree.rootNode.fixSelection3FromEndNodes();
7584						}
7585						if (tree.activeNode && tree.options.activeVisible) {
7586							tree.activeNode.makeVisible();
7587						}
7588						tree._triggerTreeEvent("init", null, { status: true });
7589					})
7590					.fail(function () {
7591						tree.render();
7592						tree._triggerTreeEvent("init", null, { status: false });
7593					});
7594				return dfd;
7595			},
7596			/** Node was inserted into or removed from the tree.
7597			 * @param {EventData} ctx
7598			 * @param {boolean} add
7599			 * @param {FancytreeNode} node
7600			 */
7601			treeRegisterNode: function (ctx, add, node) {
7602				ctx.tree._callHook(
7603					"treeStructureChanged",
7604					ctx,
7605					add ? "addNode" : "removeNode"
7606				);
7607			},
7608			/** Widget got focus.
7609			 * @param {EventData} ctx
7610			 * @param {boolean} [flag=true]
7611			 */
7612			treeSetFocus: function (ctx, flag, callOpts) {
7613				var targetNode;
7614
7615				flag = flag !== false;
7616
7617				// this.debug("treeSetFocus(" + flag + "), callOpts: ", callOpts, this.hasFocus());
7618				// this.debug("    focusNode: " + this.focusNode);
7619				// this.debug("    activeNode: " + this.activeNode);
7620				if (flag !== this.hasFocus()) {
7621					this._hasFocus = flag;
7622					if (!flag && this.focusNode) {
7623						// Node also looses focus if widget blurs
7624						this.focusNode.setFocus(false);
7625					} else if (flag && (!callOpts || !callOpts.calledByNode)) {
7626						$(this.$container).focus();
7627					}
7628					this.$container.toggleClass("fancytree-treefocus", flag);
7629					this._triggerTreeEvent(flag ? "focusTree" : "blurTree");
7630					if (flag && !this.activeNode) {
7631						// #712: Use last mousedowned node ('click' event fires after focusin)
7632						targetNode =
7633							this._lastMousedownNode || this.getFirstChild();
7634						if (targetNode) {
7635							targetNode.setFocus();
7636						}
7637					}
7638				}
7639			},
7640			/** Widget option was set using `$().fancytree("option", "KEY", VALUE)`.
7641			 *
7642			 * Note: `key` may reference a nested option, e.g. 'dnd5.scroll'.
7643			 * In this case `value`contains the complete, modified `dnd5` option hash.
7644			 * We can check for changed values like
7645			 *     if( value.scroll !== tree.options.dnd5.scroll ) {...}
7646			 *
7647			 * @param {EventData} ctx
7648			 * @param {string} key option name
7649			 * @param {any} value option value
7650			 */
7651			treeSetOption: function (ctx, key, value) {
7652				var tree = ctx.tree,
7653					callDefault = true,
7654					callCreate = false,
7655					callRender = false;
7656
7657				switch (key) {
7658					case "aria":
7659					case "checkbox":
7660					case "icon":
7661					case "minExpandLevel":
7662					case "tabindex":
7663						// tree._callHook("treeCreate", tree);
7664						callCreate = true;
7665						callRender = true;
7666						break;
7667					case "checkboxAutoHide":
7668						tree.$container.toggleClass(
7669							"fancytree-checkbox-auto-hide",
7670							!!value
7671						);
7672						break;
7673					case "escapeTitles":
7674					case "tooltip":
7675						callRender = true;
7676						break;
7677					case "rtl":
7678						if (value === false) {
7679							tree.$container
7680								.removeAttr("DIR")
7681								.removeClass("fancytree-rtl");
7682						} else {
7683							tree.$container
7684								.attr("DIR", "RTL")
7685								.addClass("fancytree-rtl");
7686						}
7687						callRender = true;
7688						break;
7689					case "source":
7690						callDefault = false;
7691						tree._callHook("treeLoad", tree, value);
7692						callRender = true;
7693						break;
7694				}
7695				tree.debug(
7696					"set option " +
7697						key +
7698						"=" +
7699						value +
7700						" <" +
7701						typeof value +
7702						">"
7703				);
7704				if (callDefault) {
7705					if (this.widget._super) {
7706						// jQuery UI 1.9+
7707						this.widget._super.call(this.widget, key, value);
7708					} else {
7709						// jQuery UI <= 1.8, we have to manually invoke the _setOption method from the base widget
7710						$.Widget.prototype._setOption.call(
7711							this.widget,
7712							key,
7713							value
7714						);
7715					}
7716				}
7717				if (callCreate) {
7718					tree._callHook("treeCreate", tree);
7719				}
7720				if (callRender) {
7721					tree.render(true, false); // force, not-deep
7722				}
7723			},
7724			/** A Node was added, removed, moved, or it's visibility changed.
7725			 * @param {EventData} ctx
7726			 */
7727			treeStructureChanged: function (ctx, type) {},
7728		}
7729	);
7730
7731	/*******************************************************************************
7732	 * jQuery UI widget boilerplate
7733	 */
7734
7735	/**
7736	 * The plugin (derrived from [jQuery.Widget](http://api.jqueryui.com/jQuery.widget/)).
7737	 *
7738	 * **Note:**
7739	 * These methods implement the standard jQuery UI widget API.
7740	 * It is recommended to use methods of the {Fancytree} instance instead
7741	 *
7742	 * @example
7743	 * // DEPRECATED: Access jQuery UI widget methods and members:
7744	 * var tree = $("#tree").fancytree("getTree");
7745	 * var node = $("#tree").fancytree("getActiveNode");
7746	 *
7747	 * // RECOMMENDED: Use the Fancytree object API
7748	 * var tree = $.ui.fancytree.getTree("#tree");
7749	 * var node = tree.getActiveNode();
7750	 *
7751	 * // or you may already have stored the tree instance upon creation:
7752	 * import {createTree, version} from 'jquery.fancytree'
7753	 * const tree = createTree('#tree', { ... });
7754	 * var node = tree.getActiveNode();
7755	 *
7756	 * @see {Fancytree_Static#getTree}
7757	 * @deprecated Use methods of the {Fancytree} instance instead
7758	 * @mixin Fancytree_Widget
7759	 */
7760
7761	$.widget(
7762		"ui.fancytree",
7763		/** @lends Fancytree_Widget# */
7764		{
7765			/**These options will be used as defaults
7766			 * @type {FancytreeOptions}
7767			 */
7768			options: {
7769				activeVisible: true,
7770				ajax: {
7771					type: "GET",
7772					cache: false, // false: Append random '_' argument to the request url to prevent caching.
7773					// timeout: 0, // >0: Make sure we get an ajax error if server is unreachable
7774					dataType: "json", // Expect json format and pass json object to callbacks.
7775				},
7776				aria: true,
7777				autoActivate: true,
7778				autoCollapse: false,
7779				autoScroll: false,
7780				checkbox: false,
7781				clickFolderMode: 4,
7782				copyFunctionsToData: false,
7783				debugLevel: null, // 0..4 (null: use global setting $.ui.fancytree.debugLevel)
7784				disabled: false, // TODO: required anymore?
7785				enableAspx: 42, // TODO: this is truethy, but distinguishable from true: default will change to false in the future
7786				escapeTitles: false,
7787				extensions: [],
7788				focusOnSelect: false,
7789				generateIds: false,
7790				icon: true,
7791				idPrefix: "ft_",
7792				keyboard: true,
7793				keyPathSeparator: "/",
7794				minExpandLevel: 1,
7795				nodata: true, // (bool, string, or callback) display message, when no data available
7796				quicksearch: false,
7797				rtl: false,
7798				scrollOfs: { top: 0, bottom: 0 },
7799				scrollParent: null,
7800				selectMode: 2,
7801				strings: {
7802					loading: "Loading...", // &#8230; would be escaped when escapeTitles is true
7803					loadError: "Load error!",
7804					moreData: "More...",
7805					noData: "No data.",
7806				},
7807				tabindex: "0",
7808				titlesTabbable: false,
7809				toggleEffect: { effect: "slideToggle", duration: 200 }, //< "toggle" or "slideToggle" to use jQuery instead of jQueryUI for toggleEffect animation
7810				tooltip: false,
7811				treeId: null,
7812				_classNames: {
7813					active: "fancytree-active",
7814					animating: "fancytree-animating",
7815					combinedExpanderPrefix: "fancytree-exp-",
7816					combinedIconPrefix: "fancytree-ico-",
7817					error: "fancytree-error",
7818					expanded: "fancytree-expanded",
7819					focused: "fancytree-focused",
7820					folder: "fancytree-folder",
7821					hasChildren: "fancytree-has-children",
7822					lastsib: "fancytree-lastsib",
7823					lazy: "fancytree-lazy",
7824					loading: "fancytree-loading",
7825					node: "fancytree-node",
7826					partload: "fancytree-partload",
7827					partsel: "fancytree-partsel",
7828					radio: "fancytree-radio",
7829					selected: "fancytree-selected",
7830					statusNodePrefix: "fancytree-statusnode-",
7831					unselectable: "fancytree-unselectable",
7832				},
7833				// events
7834				lazyLoad: null,
7835				postProcess: null,
7836			},
7837			_deprecationWarning: function (name) {
7838				var tree = this.tree;
7839
7840				if (tree && tree.options.debugLevel >= 3) {
7841					tree.warn(
7842						"$().fancytree('" +
7843							name +
7844							"') is deprecated (see https://wwwendt.de/tech/fancytree/doc/jsdoc/Fancytree_Widget.html"
7845					);
7846				}
7847			},
7848			/* Set up the widget, Called on first $().fancytree() */
7849			_create: function () {
7850				this.tree = new Fancytree(this);
7851
7852				this.$source =
7853					this.source || this.element.data("type") === "json"
7854						? this.element
7855						: this.element.find(">ul").first();
7856				// Subclass Fancytree instance with all enabled extensions
7857				var extension,
7858					extName,
7859					i,
7860					opts = this.options,
7861					extensions = opts.extensions,
7862					base = this.tree;
7863
7864				for (i = 0; i < extensions.length; i++) {
7865					extName = extensions[i];
7866					extension = $.ui.fancytree._extensions[extName];
7867					if (!extension) {
7868						$.error(
7869							"Could not apply extension '" +
7870								extName +
7871								"' (it is not registered, did you forget to include it?)"
7872						);
7873					}
7874					// Add extension options as tree.options.EXTENSION
7875					// 	_assert(!this.tree.options[extName], "Extension name must not exist as option name: " + extName);
7876
7877					// console.info("extend " + extName, extension.options, this.tree.options[extName])
7878					// issue #876: we want to replace custom array-options, not merge them
7879					this.tree.options[extName] = _simpleDeepMerge(
7880						{},
7881						extension.options,
7882						this.tree.options[extName]
7883					);
7884					// this.tree.options[extName] = $.extend(true, {}, extension.options, this.tree.options[extName]);
7885
7886					// console.info("extend " + extName + " =>", this.tree.options[extName])
7887					// console.info("extend " + extName + " org default =>", extension.options)
7888
7889					// Add a namespace tree.ext.EXTENSION, to hold instance data
7890					_assert(
7891						this.tree.ext[extName] === undefined,
7892						"Extension name must not exist as Fancytree.ext attribute: '" +
7893							extName +
7894							"'"
7895					);
7896					// this.tree[extName] = extension;
7897					this.tree.ext[extName] = {};
7898					// Subclass Fancytree methods using proxies.
7899					_subclassObject(this.tree, base, extension, extName);
7900					// current extension becomes base for the next extension
7901					base = extension;
7902				}
7903				//
7904				if (opts.icons !== undefined) {
7905					// 2015-11-16
7906					if (opts.icon === true) {
7907						this.tree.warn(
7908							"'icons' tree option is deprecated since v2.14.0: use 'icon' instead"
7909						);
7910						opts.icon = opts.icons;
7911					} else {
7912						$.error(
7913							"'icons' tree option is deprecated since v2.14.0: use 'icon' only instead"
7914						);
7915					}
7916				}
7917				if (opts.iconClass !== undefined) {
7918					// 2015-11-16
7919					if (opts.icon) {
7920						$.error(
7921							"'iconClass' tree option is deprecated since v2.14.0: use 'icon' only instead"
7922						);
7923					} else {
7924						this.tree.warn(
7925							"'iconClass' tree option is deprecated since v2.14.0: use 'icon' instead"
7926						);
7927						opts.icon = opts.iconClass;
7928					}
7929				}
7930				if (opts.tabbable !== undefined) {
7931					// 2016-04-04
7932					opts.tabindex = opts.tabbable ? "0" : "-1";
7933					this.tree.warn(
7934						"'tabbable' tree option is deprecated since v2.17.0: use 'tabindex='" +
7935							opts.tabindex +
7936							"' instead"
7937					);
7938				}
7939				//
7940				this.tree._callHook("treeCreate", this.tree);
7941				// Note: 'fancytreecreate' event is fired by widget base class
7942				//        this.tree._triggerTreeEvent("create");
7943			},
7944
7945			/* Called on every $().fancytree() */
7946			_init: function () {
7947				this.tree._callHook("treeInit", this.tree);
7948				// TODO: currently we call bind after treeInit, because treeInit
7949				// might change tree.$container.
7950				// It would be better, to move event binding into hooks altogether
7951				this._bind();
7952			},
7953
7954			/* Use the _setOption method to respond to changes to options. */
7955			_setOption: function (key, value) {
7956				return this.tree._callHook(
7957					"treeSetOption",
7958					this.tree,
7959					key,
7960					value
7961				);
7962			},
7963
7964			/** Use the destroy method to clean up any modifications your widget has made to the DOM */
7965			_destroy: function () {
7966				this._unbind();
7967				this.tree._callHook("treeDestroy", this.tree);
7968				// In jQuery UI 1.8, you must invoke the destroy method from the base widget
7969				// $.Widget.prototype.destroy.call(this);
7970				// TODO: delete tree and nodes to make garbage collect easier?
7971				// TODO: In jQuery UI 1.9 and above, you would define _destroy instead of destroy and not call the base method
7972			},
7973
7974			// -------------------------------------------------------------------------
7975
7976			/* Remove all event handlers for our namespace */
7977			_unbind: function () {
7978				var ns = this.tree._ns;
7979				this.element.off(ns);
7980				this.tree.$container.off(ns);
7981				$(document).off(ns);
7982			},
7983			/* Add mouse and kyboard handlers to the container */
7984			_bind: function () {
7985				var self = this,
7986					opts = this.options,
7987					tree = this.tree,
7988					ns = tree._ns;
7989				// selstartEvent = ( $.support.selectstart ? "selectstart" : "mousedown" )
7990
7991				// Remove all previuous handlers for this tree
7992				this._unbind();
7993
7994				//alert("keydown" + ns + "foc=" + tree.hasFocus() + tree.$container);
7995				// tree.debug("bind events; container: ", tree.$container);
7996				tree.$container
7997					.on("focusin" + ns + " focusout" + ns, function (event) {
7998						var node = FT.getNode(event),
7999							flag = event.type === "focusin";
8000
8001						if (!flag && node && $(event.target).is("a")) {
8002							// #764
8003							node.debug(
8004								"Ignored focusout on embedded <a> element."
8005							);
8006							return;
8007						}
8008						// tree.treeOnFocusInOut.call(tree, event);
8009						// tree.debug("Tree container got event " + event.type, node, event, FT.getEventTarget(event));
8010						if (flag) {
8011							if (tree._getExpiringValue("focusin")) {
8012								// #789: IE 11 may send duplicate focusin events
8013								tree.debug("Ignored double focusin.");
8014								return;
8015							}
8016							tree._setExpiringValue("focusin", true, 50);
8017
8018							if (!node) {
8019								// #789: IE 11 may send focusin before mousdown(?)
8020								node = tree._getExpiringValue("mouseDownNode");
8021								if (node) {
8022									tree.debug(
8023										"Reconstruct mouse target for focusin from recent event."
8024									);
8025								}
8026							}
8027						}
8028						if (node) {
8029							// For example clicking into an <input> that is part of a node
8030							tree._callHook(
8031								"nodeSetFocus",
8032								tree._makeHookContext(node, event),
8033								flag
8034							);
8035						} else {
8036							if (
8037								tree.tbody &&
8038								$(event.target).parents(
8039									"table.fancytree-container > thead"
8040								).length
8041							) {
8042								// #767: ignore events in the table's header
8043								tree.debug(
8044									"Ignore focus event outside table body.",
8045									event
8046								);
8047							} else {
8048								tree._callHook("treeSetFocus", tree, flag);
8049							}
8050						}
8051					})
8052					.on(
8053						"selectstart" + ns,
8054						"span.fancytree-title",
8055						function (event) {
8056							// prevent mouse-drags to select text ranges
8057							// tree.debug("<span title> got event " + event.type);
8058							event.preventDefault();
8059						}
8060					)
8061					.on("keydown" + ns, function (event) {
8062						// TODO: also bind keyup and keypress
8063						// tree.debug("got event " + event.type + ", hasFocus:" + tree.hasFocus());
8064						// if(opts.disabled || opts.keyboard === false || !tree.hasFocus() ){
8065						if (opts.disabled || opts.keyboard === false) {
8066							return true;
8067						}
8068						var res,
8069							node = tree.focusNode, // node may be null
8070							ctx = tree._makeHookContext(node || tree, event),
8071							prevPhase = tree.phase;
8072
8073						try {
8074							tree.phase = "userEvent";
8075							// If a 'fancytreekeydown' handler returns false, skip the default
8076							// handling (implemented by tree.nodeKeydown()).
8077							if (node) {
8078								res = tree._triggerNodeEvent(
8079									"keydown",
8080									node,
8081									event
8082								);
8083							} else {
8084								res = tree._triggerTreeEvent("keydown", event);
8085							}
8086							if (res === "preventNav") {
8087								res = true; // prevent keyboard navigation, but don't prevent default handling of embedded input controls
8088							} else if (res !== false) {
8089								res = tree._callHook("nodeKeydown", ctx);
8090							}
8091							return res;
8092						} finally {
8093							tree.phase = prevPhase;
8094						}
8095					})
8096					.on("mousedown" + ns, function (event) {
8097						var et = FT.getEventTarget(event);
8098						// self.tree.debug("event(" + event.type + "): node: ", et.node);
8099						// #712: Store the clicked node, so we can use it when we get a focusin event
8100						//       ('click' event fires after focusin)
8101						// tree.debug("event(" + event.type + "): node: ", et.node);
8102						tree._lastMousedownNode = et ? et.node : null;
8103						// #789: Store the node also for a short period, so we can use it
8104						// in a *resulting* focusin event
8105						tree._setExpiringValue(
8106							"mouseDownNode",
8107							tree._lastMousedownNode
8108						);
8109					})
8110					.on("click" + ns + " dblclick" + ns, function (event) {
8111						if (opts.disabled) {
8112							return true;
8113						}
8114						var ctx,
8115							et = FT.getEventTarget(event),
8116							node = et.node,
8117							tree = self.tree,
8118							prevPhase = tree.phase;
8119
8120						// self.tree.debug("event(" + event.type + "): node: ", node);
8121						if (!node) {
8122							return true; // Allow bubbling of other events
8123						}
8124						ctx = tree._makeHookContext(node, event);
8125						// self.tree.debug("event(" + event.type + "): node: ", node);
8126						try {
8127							tree.phase = "userEvent";
8128							switch (event.type) {
8129								case "click":
8130									ctx.targetType = et.type;
8131									if (node.isPagingNode()) {
8132										return (
8133											tree._triggerNodeEvent(
8134												"clickPaging",
8135												ctx,
8136												event
8137											) === true
8138										);
8139									}
8140									return tree._triggerNodeEvent(
8141										"click",
8142										ctx,
8143										event
8144									) === false
8145										? false
8146										: tree._callHook("nodeClick", ctx);
8147								case "dblclick":
8148									ctx.targetType = et.type;
8149									return tree._triggerNodeEvent(
8150										"dblclick",
8151										ctx,
8152										event
8153									) === false
8154										? false
8155										: tree._callHook("nodeDblclick", ctx);
8156							}
8157						} finally {
8158							tree.phase = prevPhase;
8159						}
8160					});
8161			},
8162			/** Return the active node or null.
8163			 * @returns {FancytreeNode}
8164			 * @deprecated Use methods of the Fancytree instance instead (<a href="Fancytree_Widget.html">example above</a>).
8165			 */
8166			getActiveNode: function () {
8167				this._deprecationWarning("getActiveNode");
8168				return this.tree.activeNode;
8169			},
8170			/** Return the matching node or null.
8171			 * @param {string} key
8172			 * @returns {FancytreeNode}
8173			 * @deprecated Use methods of the Fancytree instance instead (<a href="Fancytree_Widget.html">example above</a>).
8174			 */
8175			getNodeByKey: function (key) {
8176				this._deprecationWarning("getNodeByKey");
8177				return this.tree.getNodeByKey(key);
8178			},
8179			/** Return the invisible system root node.
8180			 * @returns {FancytreeNode}
8181			 * @deprecated Use methods of the Fancytree instance instead (<a href="Fancytree_Widget.html">example above</a>).
8182			 */
8183			getRootNode: function () {
8184				this._deprecationWarning("getRootNode");
8185				return this.tree.rootNode;
8186			},
8187			/** Return the current tree instance.
8188			 * @returns {Fancytree}
8189			 * @deprecated Use `$.ui.fancytree.getTree()` instead (<a href="Fancytree_Widget.html">example above</a>).
8190			 */
8191			getTree: function () {
8192				this._deprecationWarning("getTree");
8193				return this.tree;
8194			},
8195		}
8196	);
8197
8198	// $.ui.fancytree was created by the widget factory. Create a local shortcut:
8199	FT = $.ui.fancytree;
8200
8201	/**
8202	 * Static members in the `$.ui.fancytree` namespace.
8203	 * This properties and methods can be accessed without instantiating a concrete
8204	 * Fancytree instance.
8205	 *
8206	 * @example
8207	 * // Access static members:
8208	 * var node = $.ui.fancytree.getNode(element);
8209	 * alert($.ui.fancytree.version);
8210	 *
8211	 * @mixin Fancytree_Static
8212	 */
8213	$.extend(
8214		$.ui.fancytree,
8215		/** @lends Fancytree_Static# */
8216		{
8217			/** Version number `"MAJOR.MINOR.PATCH"`
8218			 * @type {string} */
8219			version: "2.38.3", // Set to semver by 'grunt release'
8220			/** @type {string}
8221			 * @description `"production" for release builds` */
8222			buildType: "production", // Set to 'production' by 'grunt build'
8223			/** @type {int}
8224			 * @description 0: silent .. 5: verbose (default: 3 for release builds). */
8225			debugLevel: 3, // Set to 3 by 'grunt build'
8226			// Used by $.ui.fancytree.debug() and as default for tree.options.debugLevel
8227
8228			_nextId: 1,
8229			_nextNodeKey: 1,
8230			_extensions: {},
8231			// focusTree: null,
8232
8233			/** Expose class object as `$.ui.fancytree._FancytreeClass`.
8234			 * Useful to extend `$.ui.fancytree._FancytreeClass.prototype`.
8235			 * @type {Fancytree}
8236			 */
8237			_FancytreeClass: Fancytree,
8238			/** Expose class object as $.ui.fancytree._FancytreeNodeClass
8239			 * Useful to extend `$.ui.fancytree._FancytreeNodeClass.prototype`.
8240			 * @type {FancytreeNode}
8241			 */
8242			_FancytreeNodeClass: FancytreeNode,
8243			/* Feature checks to provide backwards compatibility */
8244			jquerySupports: {
8245				// http://jqueryui.com/upgrade-guide/1.9/#deprecated-offset-option-merged-into-my-and-at
8246				positionMyOfs: isVersionAtLeast($.ui.version, 1, 9),
8247			},
8248			/** Throw an error if condition fails (debug method).
8249			 * @param {boolean} cond
8250			 * @param {string} msg
8251			 */
8252			assert: function (cond, msg) {
8253				return _assert(cond, msg);
8254			},
8255			/** Create a new Fancytree instance on a target element.
8256			 *
8257			 * @param {Element | jQueryObject | string} el Target DOM element or selector
8258			 * @param {FancytreeOptions} [opts] Fancytree options
8259			 * @returns {Fancytree} new tree instance
8260			 * @example
8261			 * var tree = $.ui.fancytree.createTree("#tree", {
8262			 *     source: {url: "my/webservice"}
8263			 * }); // Create tree for this matching element
8264			 *
8265			 * @since 2.25
8266			 */
8267			createTree: function (el, opts) {
8268				var $tree = $(el).fancytree(opts);
8269				return FT.getTree($tree);
8270			},
8271			/** Return a function that executes *fn* at most every *timeout* ms.
8272			 * @param {integer} timeout
8273			 * @param {function} fn
8274			 * @param {boolean} [invokeAsap=false]
8275			 * @param {any} [ctx]
8276			 */
8277			debounce: function (timeout, fn, invokeAsap, ctx) {
8278				var timer;
8279				if (arguments.length === 3 && typeof invokeAsap !== "boolean") {
8280					ctx = invokeAsap;
8281					invokeAsap = false;
8282				}
8283				return function () {
8284					var args = arguments;
8285					ctx = ctx || this;
8286					// eslint-disable-next-line no-unused-expressions
8287					invokeAsap && !timer && fn.apply(ctx, args);
8288					clearTimeout(timer);
8289					timer = setTimeout(function () {
8290						// eslint-disable-next-line no-unused-expressions
8291						invokeAsap || fn.apply(ctx, args);
8292						timer = null;
8293					}, timeout);
8294				};
8295			},
8296			/** Write message to console if debugLevel >= 4
8297			 * @param {string} msg
8298			 */
8299			debug: function (msg) {
8300				if ($.ui.fancytree.debugLevel >= 4) {
8301					consoleApply("log", arguments);
8302				}
8303			},
8304			/** Write error message to console if debugLevel >= 1.
8305			 * @param {string} msg
8306			 */
8307			error: function (msg) {
8308				if ($.ui.fancytree.debugLevel >= 1) {
8309					consoleApply("error", arguments);
8310				}
8311			},
8312			/** Convert `<`, `>`, `&`, `"`, `'`, and `/` to the equivalent entities.
8313			 *
8314			 * @param {string} s
8315			 * @returns {string}
8316			 */
8317			escapeHtml: function (s) {
8318				return ("" + s).replace(REX_HTML, function (s) {
8319					return ENTITY_MAP[s];
8320				});
8321			},
8322			/** Make jQuery.position() arguments backwards compatible, i.e. if
8323			 * jQuery UI version <= 1.8, convert
8324			 *   { my: "left+3 center", at: "left bottom", of: $target }
8325			 * to
8326			 *   { my: "left center", at: "left bottom", of: $target, offset: "3  0" }
8327			 *
8328			 * See http://jqueryui.com/upgrade-guide/1.9/#deprecated-offset-option-merged-into-my-and-at
8329			 * and http://jsfiddle.net/mar10/6xtu9a4e/
8330			 *
8331			 * @param {object} opts
8332			 * @returns {object} the (potentially modified) original opts hash object
8333			 */
8334			fixPositionOptions: function (opts) {
8335				if (opts.offset || ("" + opts.my + opts.at).indexOf("%") >= 0) {
8336					$.error(
8337						"expected new position syntax (but '%' is not supported)"
8338					);
8339				}
8340				if (!$.ui.fancytree.jquerySupports.positionMyOfs) {
8341					var // parse 'left+3 center' into ['left+3 center', 'left', '+3', 'center', undefined]
8342						myParts = /(\w+)([+-]?\d+)?\s+(\w+)([+-]?\d+)?/.exec(
8343							opts.my
8344						),
8345						atParts = /(\w+)([+-]?\d+)?\s+(\w+)([+-]?\d+)?/.exec(
8346							opts.at
8347						),
8348						// convert to numbers
8349						dx =
8350							(myParts[2] ? +myParts[2] : 0) +
8351							(atParts[2] ? +atParts[2] : 0),
8352						dy =
8353							(myParts[4] ? +myParts[4] : 0) +
8354							(atParts[4] ? +atParts[4] : 0);
8355
8356					opts = $.extend({}, opts, {
8357						// make a copy and overwrite
8358						my: myParts[1] + " " + myParts[3],
8359						at: atParts[1] + " " + atParts[3],
8360					});
8361					if (dx || dy) {
8362						opts.offset = "" + dx + " " + dy;
8363					}
8364				}
8365				return opts;
8366			},
8367			/** Return a {node: FancytreeNode, type: TYPE} object for a mouse event.
8368			 *
8369			 * @param {Event} event Mouse event, e.g. click, ...
8370			 * @returns {object} Return a {node: FancytreeNode, type: TYPE} object
8371			 *     TYPE: 'title' | 'prefix' | 'expander' | 'checkbox' | 'icon' | undefined
8372			 */
8373			getEventTarget: function (event) {
8374				var $target,
8375					tree,
8376					tcn = event && event.target ? event.target.className : "",
8377					res = { node: this.getNode(event.target), type: undefined };
8378				// We use a fast version of $(res.node).hasClass()
8379				// See http://jsperf.com/test-for-classname/2
8380				if (/\bfancytree-title\b/.test(tcn)) {
8381					res.type = "title";
8382				} else if (/\bfancytree-expander\b/.test(tcn)) {
8383					res.type =
8384						res.node.hasChildren() === false
8385							? "prefix"
8386							: "expander";
8387					// }else if( /\bfancytree-checkbox\b/.test(tcn) || /\bfancytree-radio\b/.test(tcn) ){
8388				} else if (/\bfancytree-checkbox\b/.test(tcn)) {
8389					res.type = "checkbox";
8390				} else if (/\bfancytree(-custom)?-icon\b/.test(tcn)) {
8391					res.type = "icon";
8392				} else if (/\bfancytree-node\b/.test(tcn)) {
8393					// Somewhere near the title
8394					res.type = "title";
8395				} else if (event && event.target) {
8396					$target = $(event.target);
8397					if ($target.is("ul[role=group]")) {
8398						// #nnn: Clicking right to a node may hit the surrounding UL
8399						tree = res.node && res.node.tree;
8400						(tree || FT).debug("Ignoring click on outer UL.");
8401						res.node = null;
8402					} else if ($target.closest(".fancytree-title").length) {
8403						// #228: clicking an embedded element inside a title
8404						res.type = "title";
8405					} else if ($target.closest(".fancytree-checkbox").length) {
8406						// E.g. <svg> inside checkbox span
8407						res.type = "checkbox";
8408					} else if ($target.closest(".fancytree-expander").length) {
8409						res.type = "expander";
8410					}
8411				}
8412				return res;
8413			},
8414			/** Return a string describing the affected node region for a mouse event.
8415			 *
8416			 * @param {Event} event Mouse event, e.g. click, mousemove, ...
8417			 * @returns {string} 'title' | 'prefix' | 'expander' | 'checkbox' | 'icon' | undefined
8418			 */
8419			getEventTargetType: function (event) {
8420				return this.getEventTarget(event).type;
8421			},
8422			/** Return a FancytreeNode instance from element, event, or jQuery object.
8423			 *
8424			 * @param {Element | jQueryObject | Event} el
8425			 * @returns {FancytreeNode} matching node or null
8426			 */
8427			getNode: function (el) {
8428				if (el instanceof FancytreeNode) {
8429					return el; // el already was a FancytreeNode
8430				} else if (el instanceof $) {
8431					el = el[0]; // el was a jQuery object: use the DOM element
8432				} else if (el.originalEvent !== undefined) {
8433					el = el.target; // el was an Event
8434				}
8435				while (el) {
8436					if (el.ftnode) {
8437						return el.ftnode;
8438					}
8439					el = el.parentNode;
8440				}
8441				return null;
8442			},
8443			/** Return a Fancytree instance, from element, index, event, or jQueryObject.
8444			 *
8445			 * @param {Element | jQueryObject | Event | integer | string} [el]
8446			 * @returns {Fancytree} matching tree or null
8447			 * @example
8448			 * $.ui.fancytree.getTree();  // Get first Fancytree instance on page
8449			 * $.ui.fancytree.getTree(1);  // Get second Fancytree instance on page
8450			 * $.ui.fancytree.getTree(event);  // Get tree for this mouse- or keyboard event
8451			 * $.ui.fancytree.getTree("foo");  // Get tree for this `opts.treeId`
8452			 * $.ui.fancytree.getTree("#tree");  // Get tree for this matching element
8453			 *
8454			 * @since 2.13
8455			 */
8456			getTree: function (el) {
8457				var widget,
8458					orgEl = el;
8459
8460				if (el instanceof Fancytree) {
8461					return el; // el already was a Fancytree
8462				}
8463				if (el === undefined) {
8464					el = 0; // get first tree
8465				}
8466				if (typeof el === "number") {
8467					el = $(".fancytree-container").eq(el); // el was an integer: return nth instance
8468				} else if (typeof el === "string") {
8469					// `el` may be a treeId or a selector:
8470					el = $("#ft-id-" + orgEl).eq(0);
8471					if (!el.length) {
8472						el = $(orgEl).eq(0); // el was a selector: use first match
8473					}
8474				} else if (
8475					el instanceof Element ||
8476					el instanceof HTMLDocument
8477				) {
8478					el = $(el);
8479				} else if (el instanceof $) {
8480					el = el.eq(0); // el was a jQuery object: use the first
8481				} else if (el.originalEvent !== undefined) {
8482					el = $(el.target); // el was an Event
8483				}
8484				// el is a jQuery object wit one element here
8485				el = el.closest(":ui-fancytree");
8486				widget = el.data("ui-fancytree") || el.data("fancytree"); // the latter is required by jQuery <= 1.8
8487				return widget ? widget.tree : null;
8488			},
8489			/** Return an option value that has a default, but may be overridden by a
8490			 * callback or a node instance attribute.
8491			 *
8492			 * Evaluation sequence:
8493			 *
8494			 * If `tree.options.<optionName>` is a callback that returns something, use that.
8495			 * Else if `node.<optionName>` is defined, use that.
8496			 * Else if `tree.options.<optionName>` is a value, use that.
8497			 * Else use `defaultValue`.
8498			 *
8499			 * @param {string} optionName name of the option property (on node and tree)
8500			 * @param {FancytreeNode} node passed to the callback
8501			 * @param {object} nodeObject where to look for the local option property, e.g. `node` or `node.data`
8502			 * @param {object} treeOption where to look for the tree option, e.g. `tree.options` or `tree.options.dnd5`
8503			 * @param {any} [defaultValue]
8504			 * @returns {any}
8505			 *
8506			 * @example
8507			 * // Check for node.foo, tree,options.foo(), and tree.options.foo:
8508			 * $.ui.fancytree.evalOption("foo", node, node, tree.options);
8509			 * // Check for node.data.bar, tree,options.qux.bar(), and tree.options.qux.bar:
8510			 * $.ui.fancytree.evalOption("bar", node, node.data, tree.options.qux);
8511			 *
8512			 * @since 2.22
8513			 */
8514			evalOption: function (
8515				optionName,
8516				node,
8517				nodeObject,
8518				treeOptions,
8519				defaultValue
8520			) {
8521				var ctx,
8522					res,
8523					tree = node.tree,
8524					treeOpt = treeOptions[optionName],
8525					nodeOpt = nodeObject[optionName];
8526
8527				if (_isFunction(treeOpt)) {
8528					ctx = {
8529						node: node,
8530						tree: tree,
8531						widget: tree.widget,
8532						options: tree.widget.options,
8533						typeInfo: tree.types[node.type] || {},
8534					};
8535					res = treeOpt.call(tree, { type: optionName }, ctx);
8536					if (res == null) {
8537						res = nodeOpt;
8538					}
8539				} else {
8540					res = nodeOpt == null ? treeOpt : nodeOpt;
8541				}
8542				if (res == null) {
8543					res = defaultValue; // no option set at all: return default
8544				}
8545				return res;
8546			},
8547			/** Set expander, checkbox, or node icon, supporting string and object format.
8548			 *
8549			 * @param {Element | jQueryObject} span
8550			 * @param {string} baseClass
8551			 * @param {string | object} icon
8552			 * @since 2.27
8553			 */
8554			setSpanIcon: function (span, baseClass, icon) {
8555				var $span = $(span);
8556
8557				if (typeof icon === "string") {
8558					$span.attr("class", baseClass + " " + icon);
8559				} else {
8560					// support object syntax: { text: ligature, addClasse: classname }
8561					if (icon.text) {
8562						$span.text("" + icon.text);
8563					} else if (icon.html) {
8564						span.innerHTML = icon.html;
8565					}
8566					$span.attr(
8567						"class",
8568						baseClass + " " + (icon.addClass || "")
8569					);
8570				}
8571			},
8572			/** Convert a keydown or mouse event to a canonical string like 'ctrl+a',
8573			 * 'ctrl+shift+f2', 'shift+leftdblclick'.
8574			 *
8575			 * This is especially handy for switch-statements in event handlers.
8576			 *
8577			 * @param {event}
8578			 * @returns {string}
8579			 *
8580			 * @example
8581
8582			switch( $.ui.fancytree.eventToString(event) ) {
8583				case "-":
8584					tree.nodeSetExpanded(ctx, false);
8585					break;
8586				case "shift+return":
8587					tree.nodeSetActive(ctx, true);
8588					break;
8589				case "down":
8590					res = node.navigate(event.which, activate);
8591					break;
8592				default:
8593					handled = false;
8594			}
8595			if( handled ){
8596				event.preventDefault();
8597			}
8598			*/
8599			eventToString: function (event) {
8600				// Poor-man's hotkeys. See here for a complete implementation:
8601				//   https://github.com/jeresig/jquery.hotkeys
8602				var which = event.which,
8603					et = event.type,
8604					s = [];
8605
8606				if (event.altKey) {
8607					s.push("alt");
8608				}
8609				if (event.ctrlKey) {
8610					s.push("ctrl");
8611				}
8612				if (event.metaKey) {
8613					s.push("meta");
8614				}
8615				if (event.shiftKey) {
8616					s.push("shift");
8617				}
8618
8619				if (et === "click" || et === "dblclick") {
8620					s.push(MOUSE_BUTTONS[event.button] + et);
8621				} else if (et === "wheel") {
8622					s.push(et);
8623				} else if (!IGNORE_KEYCODES[which]) {
8624					s.push(
8625						SPECIAL_KEYCODES[which] ||
8626							String.fromCharCode(which).toLowerCase()
8627					);
8628				}
8629				return s.join("+");
8630			},
8631			/** Write message to console if debugLevel >= 3
8632			 * @param {string} msg
8633			 */
8634			info: function (msg) {
8635				if ($.ui.fancytree.debugLevel >= 3) {
8636					consoleApply("info", arguments);
8637				}
8638			},
8639			/* @deprecated: use eventToString(event) instead.
8640			 */
8641			keyEventToString: function (event) {
8642				this.warn(
8643					"keyEventToString() is deprecated: use eventToString()"
8644				);
8645				return this.eventToString(event);
8646			},
8647			/** Return a wrapped handler method, that provides `this._super`.
8648			 *
8649			 * @example
8650				// Implement `opts.createNode` event to add the 'draggable' attribute
8651				$.ui.fancytree.overrideMethod(ctx.options, "createNode", function(event, data) {
8652					// Default processing if any
8653					this._super.apply(this, arguments);
8654					// Add 'draggable' attribute
8655					data.node.span.draggable = true;
8656				});
8657			 *
8658			 * @param {object} instance
8659			 * @param {string} methodName
8660			 * @param {function} handler
8661			 * @param {object} [context] optional context
8662			 */
8663			overrideMethod: function (instance, methodName, handler, context) {
8664				var prevSuper,
8665					_super = instance[methodName] || $.noop;
8666
8667				instance[methodName] = function () {
8668					var self = context || this;
8669
8670					try {
8671						prevSuper = self._super;
8672						self._super = _super;
8673						return handler.apply(self, arguments);
8674					} finally {
8675						self._super = prevSuper;
8676					}
8677				};
8678			},
8679			/**
8680			 * Parse tree data from HTML <ul> markup
8681			 *
8682			 * @param {jQueryObject} $ul
8683			 * @returns {NodeData[]}
8684			 */
8685			parseHtml: function ($ul) {
8686				var classes,
8687					className,
8688					extraClasses,
8689					i,
8690					iPos,
8691					l,
8692					tmp,
8693					tmp2,
8694					$children = $ul.find(">li"),
8695					children = [];
8696
8697				$children.each(function () {
8698					var allData,
8699						lowerCaseAttr,
8700						$li = $(this),
8701						$liSpan = $li.find(">span", this).first(),
8702						$liA = $liSpan.length ? null : $li.find(">a").first(),
8703						d = { tooltip: null, data: {} };
8704
8705					if ($liSpan.length) {
8706						d.title = $liSpan.html();
8707					} else if ($liA && $liA.length) {
8708						// If a <li><a> tag is specified, use it literally and extract href/target.
8709						d.title = $liA.html();
8710						d.data.href = $liA.attr("href");
8711						d.data.target = $liA.attr("target");
8712						d.tooltip = $liA.attr("title");
8713					} else {
8714						// If only a <li> tag is specified, use the trimmed string up to
8715						// the next child <ul> tag.
8716						d.title = $li.html();
8717						iPos = d.title.search(/<ul/i);
8718						if (iPos >= 0) {
8719							d.title = d.title.substring(0, iPos);
8720						}
8721					}
8722					d.title = _trim(d.title);
8723
8724					// Make sure all fields exist
8725					for (i = 0, l = CLASS_ATTRS.length; i < l; i++) {
8726						d[CLASS_ATTRS[i]] = undefined;
8727					}
8728					// Initialize to `true`, if class is set and collect extraClasses
8729					classes = this.className.split(" ");
8730					extraClasses = [];
8731					for (i = 0, l = classes.length; i < l; i++) {
8732						className = classes[i];
8733						if (CLASS_ATTR_MAP[className]) {
8734							d[className] = true;
8735						} else {
8736							extraClasses.push(className);
8737						}
8738					}
8739					d.extraClasses = extraClasses.join(" ");
8740
8741					// Parse node options from ID, title and class attributes
8742					tmp = $li.attr("title");
8743					if (tmp) {
8744						d.tooltip = tmp; // overrides <a title='...'>
8745					}
8746					tmp = $li.attr("id");
8747					if (tmp) {
8748						d.key = tmp;
8749					}
8750					// Translate hideCheckbox -> checkbox:false
8751					if ($li.attr("hideCheckbox")) {
8752						d.checkbox = false;
8753					}
8754					// Add <li data-NAME='...'> as node.data.NAME
8755					allData = _getElementDataAsDict($li);
8756					if (allData && !$.isEmptyObject(allData)) {
8757						// #507: convert data-hidecheckbox (lower case) to hideCheckbox
8758						for (lowerCaseAttr in NODE_ATTR_LOWERCASE_MAP) {
8759							if (_hasProp(allData, lowerCaseAttr)) {
8760								allData[
8761									NODE_ATTR_LOWERCASE_MAP[lowerCaseAttr]
8762								] = allData[lowerCaseAttr];
8763								delete allData[lowerCaseAttr];
8764							}
8765						}
8766						// #56: Allow to set special node.attributes from data-...
8767						for (i = 0, l = NODE_ATTRS.length; i < l; i++) {
8768							tmp = NODE_ATTRS[i];
8769							tmp2 = allData[tmp];
8770							if (tmp2 != null) {
8771								delete allData[tmp];
8772								d[tmp] = tmp2;
8773							}
8774						}
8775						// All other data-... goes to node.data...
8776						$.extend(d.data, allData);
8777					}
8778					// Recursive reading of child nodes, if LI tag contains an UL tag
8779					$ul = $li.find(">ul").first();
8780					if ($ul.length) {
8781						d.children = $.ui.fancytree.parseHtml($ul);
8782					} else {
8783						d.children = d.lazy ? undefined : null;
8784					}
8785					children.push(d);
8786					// FT.debug("parse ", d, children);
8787				});
8788				return children;
8789			},
8790			/** Add Fancytree extension definition to the list of globally available extensions.
8791			 *
8792			 * @param {object} definition
8793			 */
8794			registerExtension: function (definition) {
8795				_assert(
8796					definition.name != null,
8797					"extensions must have a `name` property."
8798				);
8799				_assert(
8800					definition.version != null,
8801					"extensions must have a `version` property."
8802				);
8803				$.ui.fancytree._extensions[definition.name] = definition;
8804			},
8805			/** Replacement for the deprecated `jQuery.trim()`.
8806			 *
8807			 * @param {string} text
8808			 */
8809			trim: _trim,
8810			/** Inverse of escapeHtml().
8811			 *
8812			 * @param {string} s
8813			 * @returns {string}
8814			 */
8815			unescapeHtml: function (s) {
8816				var e = document.createElement("div");
8817				e.innerHTML = s;
8818				return e.childNodes.length === 0
8819					? ""
8820					: e.childNodes[0].nodeValue;
8821			},
8822			/** Write warning message to console if debugLevel >= 2.
8823			 * @param {string} msg
8824			 */
8825			warn: function (msg) {
8826				if ($.ui.fancytree.debugLevel >= 2) {
8827					consoleApply("warn", arguments);
8828				}
8829			},
8830		}
8831	);
8832
8833	// Value returned by `require('jquery.fancytree')`
8834	return $.ui.fancytree;
8835}); // End of closure
8836
8837
8838/*! Extension 'jquery.fancytree.childcounter.js' */// Extending Fancytree
8839// ===================
8840//
8841// See also the [live demo](https://wwWendt.de/tech/fancytree/demo/sample-ext-childcounter.html) of this code.
8842//
8843// Every extension should have a comment header containing some information
8844// about the author, copyright and licensing. Also a pointer to the latest
8845// source code.
8846// Prefix with `/*!` so the comment is not removed by the minifier.
8847
8848/*!
8849 * jquery.fancytree.childcounter.js
8850 *
8851 * Add a child counter bubble to tree nodes.
8852 * (Extension module for jquery.fancytree.js: https://github.com/mar10/fancytree/)
8853 *
8854 * Copyright (c) 2008-2023, Martin Wendt (https://wwWendt.de)
8855 *
8856 * Released under the MIT license
8857 * https://github.com/mar10/fancytree/wiki/LicenseInfo
8858 *
8859 * @version 2.38.3
8860 * @date 2023-02-01T20:52:50Z
8861 */
8862
8863// To keep the global namespace clean, we wrap everything in a closure.
8864// The UMD wrapper pattern defines the dependencies on jQuery and the
8865// Fancytree core module, and makes sure that we can use the `require()`
8866// syntax with package loaders.
8867
8868(function (factory) {
8869	if (typeof define === "function" && define.amd) {
8870		// AMD. Register as an anonymous module.
8871		define(["jquery", "./jquery.fancytree"], factory);
8872	} else if (typeof module === "object" && module.exports) {
8873		// Node/CommonJS
8874		require("./jquery.fancytree");
8875		module.exports = factory(require("jquery"));
8876	} else {
8877		// Browser globals
8878		factory(jQuery);
8879	}
8880})(function ($) {
8881	// Consider to use [strict mode](http://ejohn.org/blog/ecmascript-5-strict-mode-json-and-more/)
8882	"use strict";
8883
8884	// The [coding guidelines](http://contribute.jquery.org/style-guide/js/)
8885	// require jshint /eslint compliance.
8886	// But for this sample, we want to allow unused variables for demonstration purpose.
8887
8888	/*eslint-disable no-unused-vars */
8889
8890	// Adding methods
8891	// --------------
8892
8893	// New member functions can be added to the `Fancytree` class.
8894	// This function will be available for every tree instance:
8895	//
8896	//     var tree = $.ui.fancytree.getTree("#tree");
8897	//     tree.countSelected(false);
8898
8899	$.ui.fancytree._FancytreeClass.prototype.countSelected = function (
8900		topOnly
8901	) {
8902		var tree = this,
8903			treeOptions = tree.options;
8904
8905		return tree.getSelectedNodes(topOnly).length;
8906	};
8907
8908	// The `FancytreeNode` class can also be easily extended. This would be called
8909	// like
8910	//     node.updateCounters();
8911	//
8912	// It is also good practice to add a docstring comment.
8913	/**
8914	 * [ext-childcounter] Update counter badges for `node` and its parents.
8915	 * May be called in the `loadChildren` event, to update parents of lazy loaded
8916	 * nodes.
8917	 * @alias FancytreeNode#updateCounters
8918	 * @requires jquery.fancytree.childcounters.js
8919	 */
8920	$.ui.fancytree._FancytreeNodeClass.prototype.updateCounters = function () {
8921		var node = this,
8922			$badge = $("span.fancytree-childcounter", node.span),
8923			extOpts = node.tree.options.childcounter,
8924			count = node.countChildren(extOpts.deep);
8925
8926		node.data.childCounter = count;
8927		if (
8928			(count || !extOpts.hideZeros) &&
8929			(!node.isExpanded() || !extOpts.hideExpanded)
8930		) {
8931			if (!$badge.length) {
8932				$badge = $("<span class='fancytree-childcounter'/>").appendTo(
8933					$(
8934						"span.fancytree-icon,span.fancytree-custom-icon",
8935						node.span
8936					)
8937				);
8938			}
8939			$badge.text(count);
8940		} else {
8941			$badge.remove();
8942		}
8943		if (extOpts.deep && !node.isTopLevel() && !node.isRootNode()) {
8944			node.parent.updateCounters();
8945		}
8946	};
8947
8948	// Finally, we can extend the widget API and create functions that are called
8949	// like so:
8950	//
8951	//     $("#tree").fancytree("widgetMethod1", "abc");
8952
8953	$.ui.fancytree.prototype.widgetMethod1 = function (arg1) {
8954		var tree = this.tree;
8955		return arg1;
8956	};
8957
8958	// Register a Fancytree extension
8959	// ------------------------------
8960	// A full blown extension, extension is available for all trees and can be
8961	// enabled like so (see also the [live demo](https://wwWendt.de/tech/fancytree/demo/sample-ext-childcounter.html)):
8962	//
8963	//    <script src="../src/jquery.fancytree.js"></script>
8964	//    <script src="../src/jquery.fancytree.childcounter.js"></script>
8965	//    ...
8966	//
8967	//     $("#tree").fancytree({
8968	//         extensions: ["childcounter"],
8969	//         childcounter: {
8970	//             hideExpanded: true
8971	//         },
8972	//         ...
8973	//     });
8974	//
8975
8976	/* 'childcounter' extension */
8977	$.ui.fancytree.registerExtension({
8978		// Every extension must be registered by a unique name.
8979		name: "childcounter",
8980		// Version information should be compliant with [semver](http://semver.org)
8981		version: "2.38.3",
8982
8983		// Extension specific options and their defaults.
8984		// This options will be available as `tree.options.childcounter.hideExpanded`
8985
8986		options: {
8987			deep: true,
8988			hideZeros: true,
8989			hideExpanded: false,
8990		},
8991
8992		// Attributes other than `options` (or functions) can be defined here, and
8993		// will be added to the tree.ext.EXTNAME namespace, in this case `tree.ext.childcounter.foo`.
8994		// They can also be accessed as `this._local.foo` from within the extension
8995		// methods.
8996		foo: 42,
8997
8998		// Local functions are prefixed with an underscore '_'.
8999		// Callable as `this._local._appendCounter()`.
9000
9001		_appendCounter: function (bar) {
9002			var tree = this;
9003		},
9004
9005		// **Override virtual methods for this extension.**
9006		//
9007		// Fancytree implements a number of 'hook methods', prefixed by 'node...' or 'tree...'.
9008		// with a `ctx` argument (see [EventData](https://wwWendt.de/tech/fancytree/doc/jsdoc/global.html#EventData)
9009		// for details) and an extended calling context:<br>
9010		// `this`       : the Fancytree instance<br>
9011		// `this._local`: the namespace that contains extension attributes and private methods (same as this.ext.EXTNAME)<br>
9012		// `this._super`: the virtual function that was overridden (member of previous extension or Fancytree)
9013		//
9014		// See also the [complete list of available hook functions](https://wwWendt.de/tech/fancytree/doc/jsdoc/Fancytree_Hooks.html).
9015
9016		/* Init */
9017		// `treeInit` is triggered when a tree is initalized. We can set up classes or
9018		// bind event handlers here...
9019		treeInit: function (ctx) {
9020			var tree = this, // same as ctx.tree,
9021				opts = ctx.options,
9022				extOpts = ctx.options.childcounter;
9023			// Optionally check for dependencies with other extensions
9024			/* this._requireExtension("glyph", false, false); */
9025			// Call the base implementation
9026			this._superApply(arguments);
9027			// Add a class to the tree container
9028			this.$container.addClass("fancytree-ext-childcounter");
9029		},
9030
9031		// Destroy this tree instance (we only call the default implementation, so
9032		// this method could as well be omitted).
9033
9034		treeDestroy: function (ctx) {
9035			this._superApply(arguments);
9036		},
9037
9038		// Overload the `renderTitle` hook, to append a counter badge
9039		nodeRenderTitle: function (ctx, title) {
9040			var node = ctx.node,
9041				extOpts = ctx.options.childcounter,
9042				count =
9043					node.data.childCounter == null
9044						? node.countChildren(extOpts.deep)
9045						: +node.data.childCounter;
9046			// Let the base implementation render the title
9047			// We use `_super()` instead of `_superApply()` here, since it is a little bit
9048			// more performant when called often
9049			this._super(ctx, title);
9050			// Append a counter badge
9051			if (
9052				(count || !extOpts.hideZeros) &&
9053				(!node.isExpanded() || !extOpts.hideExpanded)
9054			) {
9055				$(
9056					"span.fancytree-icon,span.fancytree-custom-icon",
9057					node.span
9058				).append(
9059					$("<span class='fancytree-childcounter'/>").text(count)
9060				);
9061			}
9062		},
9063		// Overload the `setExpanded` hook, so the counters are updated
9064		nodeSetExpanded: function (ctx, flag, callOpts) {
9065			var tree = ctx.tree,
9066				node = ctx.node;
9067			// Let the base implementation expand/collapse the node, then redraw the title
9068			// after the animation has finished
9069			return this._superApply(arguments).always(function () {
9070				tree.nodeRenderTitle(ctx);
9071			});
9072		},
9073
9074		// End of extension definition
9075	});
9076	// Value returned by `require('jquery.fancytree..')`
9077	return $.ui.fancytree;
9078}); // End of closure
9079
9080
9081/*! Extension 'jquery.fancytree.clones.js' *//*!
9082 *
9083 * jquery.fancytree.clones.js
9084 * Support faster lookup of nodes by key and shared ref-ids.
9085 * (Extension module for jquery.fancytree.js: https://github.com/mar10/fancytree/)
9086 *
9087 * Copyright (c) 2008-2023, Martin Wendt (https://wwWendt.de)
9088 *
9089 * Released under the MIT license
9090 * https://github.com/mar10/fancytree/wiki/LicenseInfo
9091 *
9092 * @version 2.38.3
9093 * @date 2023-02-01T20:52:50Z
9094 */
9095
9096(function (factory) {
9097	if (typeof define === "function" && define.amd) {
9098		// AMD. Register as an anonymous module.
9099		define(["jquery", "./jquery.fancytree"], factory);
9100	} else if (typeof module === "object" && module.exports) {
9101		// Node/CommonJS
9102		require("./jquery.fancytree");
9103		module.exports = factory(require("jquery"));
9104	} else {
9105		// Browser globals
9106		factory(jQuery);
9107	}
9108})(function ($) {
9109	"use strict";
9110
9111	/*******************************************************************************
9112	 * Private functions and variables
9113	 */
9114
9115	var _assert = $.ui.fancytree.assert;
9116
9117	/* Return first occurrence of member from array. */
9118	function _removeArrayMember(arr, elem) {
9119		// TODO: use Array.indexOf for IE >= 9
9120		var i;
9121		for (i = arr.length - 1; i >= 0; i--) {
9122			if (arr[i] === elem) {
9123				arr.splice(i, 1);
9124				return true;
9125			}
9126		}
9127		return false;
9128	}
9129
9130	/**
9131	 * JS Implementation of MurmurHash3 (r136) (as of May 20, 2011)
9132	 *
9133	 * @author <a href="mailto:gary.court@gmail.com">Gary Court</a>
9134	 * @see http://github.com/garycourt/murmurhash-js
9135	 * @author <a href="mailto:aappleby@gmail.com">Austin Appleby</a>
9136	 * @see http://sites.google.com/site/murmurhash/
9137	 *
9138	 * @param {string} key ASCII only
9139	 * @param {boolean} [asString=false]
9140	 * @param {number} seed Positive integer only
9141	 * @return {number} 32-bit positive integer hash
9142	 */
9143	function hashMurmur3(key, asString, seed) {
9144		/*eslint-disable no-bitwise */
9145		var h1b,
9146			k1,
9147			remainder = key.length & 3,
9148			bytes = key.length - remainder,
9149			h1 = seed,
9150			c1 = 0xcc9e2d51,
9151			c2 = 0x1b873593,
9152			i = 0;
9153
9154		while (i < bytes) {
9155			k1 =
9156				(key.charCodeAt(i) & 0xff) |
9157				((key.charCodeAt(++i) & 0xff) << 8) |
9158				((key.charCodeAt(++i) & 0xff) << 16) |
9159				((key.charCodeAt(++i) & 0xff) << 24);
9160			++i;
9161
9162			k1 =
9163				((k1 & 0xffff) * c1 + ((((k1 >>> 16) * c1) & 0xffff) << 16)) &
9164				0xffffffff;
9165			k1 = (k1 << 15) | (k1 >>> 17);
9166			k1 =
9167				((k1 & 0xffff) * c2 + ((((k1 >>> 16) * c2) & 0xffff) << 16)) &
9168				0xffffffff;
9169
9170			h1 ^= k1;
9171			h1 = (h1 << 13) | (h1 >>> 19);
9172			h1b =
9173				((h1 & 0xffff) * 5 + ((((h1 >>> 16) * 5) & 0xffff) << 16)) &
9174				0xffffffff;
9175			h1 =
9176				(h1b & 0xffff) +
9177				0x6b64 +
9178				((((h1b >>> 16) + 0xe654) & 0xffff) << 16);
9179		}
9180
9181		k1 = 0;
9182
9183		switch (remainder) {
9184			case 3:
9185				k1 ^= (key.charCodeAt(i + 2) & 0xff) << 16;
9186			// fall through
9187			case 2:
9188				k1 ^= (key.charCodeAt(i + 1) & 0xff) << 8;
9189			// fall through
9190			case 1:
9191				k1 ^= key.charCodeAt(i) & 0xff;
9192
9193				k1 =
9194					((k1 & 0xffff) * c1 +
9195						((((k1 >>> 16) * c1) & 0xffff) << 16)) &
9196					0xffffffff;
9197				k1 = (k1 << 15) | (k1 >>> 17);
9198				k1 =
9199					((k1 & 0xffff) * c2 +
9200						((((k1 >>> 16) * c2) & 0xffff) << 16)) &
9201					0xffffffff;
9202				h1 ^= k1;
9203		}
9204
9205		h1 ^= key.length;
9206
9207		h1 ^= h1 >>> 16;
9208		h1 =
9209			((h1 & 0xffff) * 0x85ebca6b +
9210				((((h1 >>> 16) * 0x85ebca6b) & 0xffff) << 16)) &
9211			0xffffffff;
9212		h1 ^= h1 >>> 13;
9213		h1 =
9214			((h1 & 0xffff) * 0xc2b2ae35 +
9215				((((h1 >>> 16) * 0xc2b2ae35) & 0xffff) << 16)) &
9216			0xffffffff;
9217		h1 ^= h1 >>> 16;
9218
9219		if (asString) {
9220			// Convert to 8 digit hex string
9221			return ("0000000" + (h1 >>> 0).toString(16)).substr(-8);
9222		}
9223		return h1 >>> 0;
9224		/*eslint-enable no-bitwise */
9225	}
9226
9227	/*
9228	 * Return a unique key for node by calculating the hash of the parents refKey-list.
9229	 */
9230	function calcUniqueKey(node) {
9231		var key,
9232			h1,
9233			path = $.map(node.getParentList(false, true), function (e) {
9234				return e.refKey || e.key;
9235			});
9236
9237		path = path.join("/");
9238		// 32-bit has a high probability of collisions, so we pump up to 64-bit
9239		// https://security.stackexchange.com/q/209882/207588
9240
9241		h1 = hashMurmur3(path, true);
9242		key = "id_" + h1 + hashMurmur3(h1 + path, true);
9243
9244		return key;
9245	}
9246
9247	/**
9248	 * [ext-clones] Return a list of clone-nodes (i.e. same refKey) or null.
9249	 * @param {boolean} [includeSelf=false]
9250	 * @returns {FancytreeNode[] | null}
9251	 *
9252	 * @alias FancytreeNode#getCloneList
9253	 * @requires jquery.fancytree.clones.js
9254	 */
9255	$.ui.fancytree._FancytreeNodeClass.prototype.getCloneList = function (
9256		includeSelf
9257	) {
9258		var key,
9259			tree = this.tree,
9260			refList = tree.refMap[this.refKey] || null,
9261			keyMap = tree.keyMap;
9262
9263		if (refList) {
9264			key = this.key;
9265			// Convert key list to node list
9266			if (includeSelf) {
9267				refList = $.map(refList, function (val) {
9268					return keyMap[val];
9269				});
9270			} else {
9271				refList = $.map(refList, function (val) {
9272					return val === key ? null : keyMap[val];
9273				});
9274				if (refList.length < 1) {
9275					refList = null;
9276				}
9277			}
9278		}
9279		return refList;
9280	};
9281
9282	/**
9283	 * [ext-clones] Return true if this node has at least another clone with same refKey.
9284	 * @returns {boolean}
9285	 *
9286	 * @alias FancytreeNode#isClone
9287	 * @requires jquery.fancytree.clones.js
9288	 */
9289	$.ui.fancytree._FancytreeNodeClass.prototype.isClone = function () {
9290		var refKey = this.refKey || null,
9291			refList = (refKey && this.tree.refMap[refKey]) || null;
9292		return !!(refList && refList.length > 1);
9293	};
9294
9295	/**
9296	 * [ext-clones] Update key and/or refKey for an existing node.
9297	 * @param {string} key
9298	 * @param {string} refKey
9299	 * @returns {boolean}
9300	 *
9301	 * @alias FancytreeNode#reRegister
9302	 * @requires jquery.fancytree.clones.js
9303	 */
9304	$.ui.fancytree._FancytreeNodeClass.prototype.reRegister = function (
9305		key,
9306		refKey
9307	) {
9308		key = key == null ? null : "" + key;
9309		refKey = refKey == null ? null : "" + refKey;
9310		// this.debug("reRegister", key, refKey);
9311
9312		var tree = this.tree,
9313			prevKey = this.key,
9314			prevRefKey = this.refKey,
9315			keyMap = tree.keyMap,
9316			refMap = tree.refMap,
9317			refList = refMap[prevRefKey] || null,
9318			//		curCloneKeys = refList ? node.getCloneList(true),
9319			modified = false;
9320
9321		// Key has changed: update all references
9322		if (key != null && key !== this.key) {
9323			if (keyMap[key]) {
9324				$.error(
9325					"[ext-clones] reRegister(" +
9326						key +
9327						"): already exists: " +
9328						this
9329				);
9330			}
9331			// Update keyMap
9332			delete keyMap[prevKey];
9333			keyMap[key] = this;
9334			// Update refMap
9335			if (refList) {
9336				refMap[prevRefKey] = $.map(refList, function (e) {
9337					return e === prevKey ? key : e;
9338				});
9339			}
9340			this.key = key;
9341			modified = true;
9342		}
9343
9344		// refKey has changed
9345		if (refKey != null && refKey !== this.refKey) {
9346			// Remove previous refKeys
9347			if (refList) {
9348				if (refList.length === 1) {
9349					delete refMap[prevRefKey];
9350				} else {
9351					refMap[prevRefKey] = $.map(refList, function (e) {
9352						return e === prevKey ? null : e;
9353					});
9354				}
9355			}
9356			// Add refKey
9357			if (refMap[refKey]) {
9358				refMap[refKey].append(key);
9359			} else {
9360				refMap[refKey] = [this.key];
9361			}
9362			this.refKey = refKey;
9363			modified = true;
9364		}
9365		return modified;
9366	};
9367
9368	/**
9369	 * [ext-clones] Define a refKey for an existing node.
9370	 * @param {string} refKey
9371	 * @returns {boolean}
9372	 *
9373	 * @alias FancytreeNode#setRefKey
9374	 * @requires jquery.fancytree.clones.js
9375	 * @since 2.16
9376	 */
9377	$.ui.fancytree._FancytreeNodeClass.prototype.setRefKey = function (refKey) {
9378		return this.reRegister(null, refKey);
9379	};
9380
9381	/**
9382	 * [ext-clones] Return all nodes with a given refKey (null if not found).
9383	 * @param {string} refKey
9384	 * @param {FancytreeNode} [rootNode] optionally restrict results to descendants of this node
9385	 * @returns {FancytreeNode[] | null}
9386	 * @alias Fancytree#getNodesByRef
9387	 * @requires jquery.fancytree.clones.js
9388	 */
9389	$.ui.fancytree._FancytreeClass.prototype.getNodesByRef = function (
9390		refKey,
9391		rootNode
9392	) {
9393		var keyMap = this.keyMap,
9394			refList = this.refMap[refKey] || null;
9395
9396		if (refList) {
9397			// Convert key list to node list
9398			if (rootNode) {
9399				refList = $.map(refList, function (val) {
9400					var node = keyMap[val];
9401					return node.isDescendantOf(rootNode) ? node : null;
9402				});
9403			} else {
9404				refList = $.map(refList, function (val) {
9405					return keyMap[val];
9406				});
9407			}
9408			if (refList.length < 1) {
9409				refList = null;
9410			}
9411		}
9412		return refList;
9413	};
9414
9415	/**
9416	 * [ext-clones] Replace a refKey with a new one.
9417	 * @param {string} oldRefKey
9418	 * @param {string} newRefKey
9419	 * @alias Fancytree#changeRefKey
9420	 * @requires jquery.fancytree.clones.js
9421	 */
9422	$.ui.fancytree._FancytreeClass.prototype.changeRefKey = function (
9423		oldRefKey,
9424		newRefKey
9425	) {
9426		var i,
9427			node,
9428			keyMap = this.keyMap,
9429			refList = this.refMap[oldRefKey] || null;
9430
9431		if (refList) {
9432			for (i = 0; i < refList.length; i++) {
9433				node = keyMap[refList[i]];
9434				node.refKey = newRefKey;
9435			}
9436			delete this.refMap[oldRefKey];
9437			this.refMap[newRefKey] = refList;
9438		}
9439	};
9440
9441	/*******************************************************************************
9442	 * Extension code
9443	 */
9444	$.ui.fancytree.registerExtension({
9445		name: "clones",
9446		version: "2.38.3",
9447		// Default options for this extension.
9448		options: {
9449			highlightActiveClones: true, // set 'fancytree-active-clone' on active clones and all peers
9450			highlightClones: false, // set 'fancytree-clone' class on any node that has at least one clone
9451		},
9452
9453		treeCreate: function (ctx) {
9454			this._superApply(arguments);
9455			ctx.tree.refMap = {};
9456			ctx.tree.keyMap = {};
9457		},
9458		treeInit: function (ctx) {
9459			this.$container.addClass("fancytree-ext-clones");
9460			_assert(ctx.options.defaultKey == null);
9461			// Generate unique / reproducible default keys
9462			ctx.options.defaultKey = function (node) {
9463				return calcUniqueKey(node);
9464			};
9465			// The default implementation loads initial data
9466			this._superApply(arguments);
9467		},
9468		treeClear: function (ctx) {
9469			ctx.tree.refMap = {};
9470			ctx.tree.keyMap = {};
9471			return this._superApply(arguments);
9472		},
9473		treeRegisterNode: function (ctx, add, node) {
9474			var refList,
9475				len,
9476				tree = ctx.tree,
9477				keyMap = tree.keyMap,
9478				refMap = tree.refMap,
9479				key = node.key,
9480				refKey = node && node.refKey != null ? "" + node.refKey : null;
9481
9482			//		ctx.tree.debug("clones.treeRegisterNode", add, node);
9483
9484			if (node.isStatusNode()) {
9485				return this._super(ctx, add, node);
9486			}
9487
9488			if (add) {
9489				if (keyMap[node.key] != null) {
9490					var other = keyMap[node.key],
9491						msg =
9492							"clones.treeRegisterNode: duplicate key '" +
9493							node.key +
9494							"': /" +
9495							node.getPath(true) +
9496							" => " +
9497							other.getPath(true);
9498					// Sometimes this exception is not visible in the console,
9499					// so we also write it:
9500					tree.error(msg);
9501					$.error(msg);
9502				}
9503				keyMap[key] = node;
9504
9505				if (refKey) {
9506					refList = refMap[refKey];
9507					if (refList) {
9508						refList.push(key);
9509						if (
9510							refList.length === 2 &&
9511							ctx.options.clones.highlightClones
9512						) {
9513							// Mark peer node, if it just became a clone (no need to
9514							// mark current node, since it will be rendered later anyway)
9515							keyMap[refList[0]].renderStatus();
9516						}
9517					} else {
9518						refMap[refKey] = [key];
9519					}
9520					// node.debug("clones.treeRegisterNode: add clone =>", refMap[refKey]);
9521				}
9522			} else {
9523				if (keyMap[key] == null) {
9524					$.error(
9525						"clones.treeRegisterNode: node.key not registered: " +
9526							node.key
9527					);
9528				}
9529				delete keyMap[key];
9530				if (refKey) {
9531					refList = refMap[refKey];
9532					// node.debug("clones.treeRegisterNode: remove clone BEFORE =>", refMap[refKey]);
9533					if (refList) {
9534						len = refList.length;
9535						if (len <= 1) {
9536							_assert(len === 1);
9537							_assert(refList[0] === key);
9538							delete refMap[refKey];
9539						} else {
9540							_removeArrayMember(refList, key);
9541							// Unmark peer node, if this was the only clone
9542							if (
9543								len === 2 &&
9544								ctx.options.clones.highlightClones
9545							) {
9546								//							node.debug("clones.treeRegisterNode: last =>", node.getCloneList());
9547								keyMap[refList[0]].renderStatus();
9548							}
9549						}
9550						// node.debug("clones.treeRegisterNode: remove clone =>", refMap[refKey]);
9551					}
9552				}
9553			}
9554			return this._super(ctx, add, node);
9555		},
9556		nodeRenderStatus: function (ctx) {
9557			var $span,
9558				res,
9559				node = ctx.node;
9560
9561			res = this._super(ctx);
9562
9563			if (ctx.options.clones.highlightClones) {
9564				$span = $(node[ctx.tree.statusClassPropName]);
9565				// Only if span already exists
9566				if ($span.length && node.isClone()) {
9567					//				node.debug("clones.nodeRenderStatus: ", ctx.options.clones.highlightClones);
9568					$span.addClass("fancytree-clone");
9569				}
9570			}
9571			return res;
9572		},
9573		nodeSetActive: function (ctx, flag, callOpts) {
9574			var res,
9575				scpn = ctx.tree.statusClassPropName,
9576				node = ctx.node;
9577
9578			res = this._superApply(arguments);
9579
9580			if (ctx.options.clones.highlightActiveClones && node.isClone()) {
9581				$.each(node.getCloneList(true), function (idx, n) {
9582					// n.debug("clones.nodeSetActive: ", flag !== false);
9583					$(n[scpn]).toggleClass(
9584						"fancytree-active-clone",
9585						flag !== false
9586					);
9587				});
9588			}
9589			return res;
9590		},
9591	});
9592	// Value returned by `require('jquery.fancytree..')`
9593	return $.ui.fancytree;
9594}); // End of closure
9595
9596
9597/*! Extension 'jquery.fancytree.dnd5.js' *//*!
9598 * jquery.fancytree.dnd5.js
9599 *
9600 * Drag-and-drop support (native HTML5).
9601 * (Extension module for jquery.fancytree.js: https://github.com/mar10/fancytree/)
9602 *
9603 * Copyright (c) 2008-2023, Martin Wendt (https://wwWendt.de)
9604 *
9605 * Released under the MIT license
9606 * https://github.com/mar10/fancytree/wiki/LicenseInfo
9607 *
9608 * @version 2.38.3
9609 * @date 2023-02-01T20:52:50Z
9610 */
9611
9612/*
9613 #TODO
9614	Compatiblity when dragging between *separate* windows:
9615
9616		   Drag from Chrome   Edge    FF    IE11    Safari
9617	  To Chrome      ok       ok      ok    NO      ?
9618		 Edge        ok       ok      ok    NO      ?
9619		 FF          ok       ok      ok    NO      ?
9620		 IE 11       ok       ok      ok    ok      ?
9621		 Safari      ?        ?       ?     ?       ok
9622
9623 */
9624
9625(function (factory) {
9626	if (typeof define === "function" && define.amd) {
9627		// AMD. Register as an anonymous module.
9628		define(["jquery", "./jquery.fancytree"], factory);
9629	} else if (typeof module === "object" && module.exports) {
9630		// Node/CommonJS
9631		require("./jquery.fancytree");
9632		module.exports = factory(require("jquery"));
9633	} else {
9634		// Browser globals
9635		factory(jQuery);
9636	}
9637})(function ($) {
9638	"use strict";
9639
9640	/******************************************************************************
9641	 * Private functions and variables
9642	 */
9643	var FT = $.ui.fancytree,
9644		isMac = /Mac/.test(navigator.platform),
9645		classDragSource = "fancytree-drag-source",
9646		classDragRemove = "fancytree-drag-remove",
9647		classDropAccept = "fancytree-drop-accept",
9648		classDropAfter = "fancytree-drop-after",
9649		classDropBefore = "fancytree-drop-before",
9650		classDropOver = "fancytree-drop-over",
9651		classDropReject = "fancytree-drop-reject",
9652		classDropTarget = "fancytree-drop-target",
9653		nodeMimeType = "application/x-fancytree-node",
9654		$dropMarker = null,
9655		$dragImage,
9656		$extraHelper,
9657		SOURCE_NODE = null,
9658		SOURCE_NODE_LIST = null,
9659		$sourceList = null,
9660		DRAG_ENTER_RESPONSE = null,
9661		// SESSION_DATA = null, // plain object passed to events as `data`
9662		SUGGESTED_DROP_EFFECT = null,
9663		REQUESTED_DROP_EFFECT = null,
9664		REQUESTED_EFFECT_ALLOWED = null,
9665		LAST_HIT_MODE = null,
9666		DRAG_OVER_STAMP = null; // Time when a node entered the 'over' hitmode
9667
9668	/* */
9669	function _clearGlobals() {
9670		DRAG_ENTER_RESPONSE = null;
9671		DRAG_OVER_STAMP = null;
9672		REQUESTED_DROP_EFFECT = null;
9673		REQUESTED_EFFECT_ALLOWED = null;
9674		SUGGESTED_DROP_EFFECT = null;
9675		SOURCE_NODE = null;
9676		SOURCE_NODE_LIST = null;
9677		if ($sourceList) {
9678			$sourceList.removeClass(classDragSource + " " + classDragRemove);
9679		}
9680		$sourceList = null;
9681		if ($dropMarker) {
9682			$dropMarker.hide();
9683		}
9684		// Take this badge off of me - I can't use it anymore:
9685		if ($extraHelper) {
9686			$extraHelper.remove();
9687			$extraHelper = null;
9688		}
9689	}
9690
9691	/* Convert number to string and prepend +/-; return empty string for 0.*/
9692	function offsetString(n) {
9693		// eslint-disable-next-line no-nested-ternary
9694		return n === 0 ? "" : n > 0 ? "+" + n : "" + n;
9695	}
9696
9697	/* Convert a dragEnter() or dragOver() response to a canonical form.
9698	 * Return false or plain object
9699	 * @param {string|object|boolean} r
9700	 * @return {object|false}
9701	 */
9702	function normalizeDragEnterResponse(r) {
9703		var res;
9704
9705		if (!r) {
9706			return false;
9707		}
9708		if ($.isPlainObject(r)) {
9709			res = {
9710				over: !!r.over,
9711				before: !!r.before,
9712				after: !!r.after,
9713			};
9714		} else if (Array.isArray(r)) {
9715			res = {
9716				over: $.inArray("over", r) >= 0,
9717				before: $.inArray("before", r) >= 0,
9718				after: $.inArray("after", r) >= 0,
9719			};
9720		} else {
9721			res = {
9722				over: r === true || r === "over",
9723				before: r === true || r === "before",
9724				after: r === true || r === "after",
9725			};
9726		}
9727		if (Object.keys(res).length === 0) {
9728			return false;
9729		}
9730		// if( Object.keys(res).length === 1 ) {
9731		// 	res.unique = res[0];
9732		// }
9733		return res;
9734	}
9735
9736	/* Convert a dataTransfer.effectAllowed to a canonical form.
9737	 * Return false or plain object
9738	 * @param {string|boolean} r
9739	 * @return {object|false}
9740	 */
9741	// function normalizeEffectAllowed(r) {
9742	// 	if (!r || r === "none") {
9743	// 		return false;
9744	// 	}
9745	// 	var all = r === "all",
9746	// 		res = {
9747	// 			copy: all || /copy/i.test(r),
9748	// 			link: all || /link/i.test(r),
9749	// 			move: all || /move/i.test(r),
9750	// 		};
9751
9752	// 	return res;
9753	// }
9754
9755	/* Implement auto scrolling when drag cursor is in top/bottom area of scroll parent. */
9756	function autoScroll(tree, event) {
9757		var spOfs,
9758			scrollTop,
9759			delta,
9760			dndOpts = tree.options.dnd5,
9761			sp = tree.$scrollParent[0],
9762			sensitivity = dndOpts.scrollSensitivity,
9763			speed = dndOpts.scrollSpeed,
9764			scrolled = 0;
9765
9766		if (sp !== document && sp.tagName !== "HTML") {
9767			spOfs = tree.$scrollParent.offset();
9768			scrollTop = sp.scrollTop;
9769			if (spOfs.top + sp.offsetHeight - event.pageY < sensitivity) {
9770				delta =
9771					sp.scrollHeight -
9772					tree.$scrollParent.innerHeight() -
9773					scrollTop;
9774				// console.log ("sp.offsetHeight: " + sp.offsetHeight
9775				// 	+ ", spOfs.top: " + spOfs.top
9776				// 	+ ", scrollTop: " + scrollTop
9777				// 	+ ", innerHeight: " + tree.$scrollParent.innerHeight()
9778				// 	+ ", scrollHeight: " + sp.scrollHeight
9779				// 	+ ", delta: " + delta
9780				// 	);
9781				if (delta > 0) {
9782					sp.scrollTop = scrolled = scrollTop + speed;
9783				}
9784			} else if (scrollTop > 0 && event.pageY - spOfs.top < sensitivity) {
9785				sp.scrollTop = scrolled = scrollTop - speed;
9786			}
9787		} else {
9788			scrollTop = $(document).scrollTop();
9789			if (scrollTop > 0 && event.pageY - scrollTop < sensitivity) {
9790				scrolled = scrollTop - speed;
9791				$(document).scrollTop(scrolled);
9792			} else if (
9793				$(window).height() - (event.pageY - scrollTop) <
9794				sensitivity
9795			) {
9796				scrolled = scrollTop + speed;
9797				$(document).scrollTop(scrolled);
9798			}
9799		}
9800		if (scrolled) {
9801			tree.debug("autoScroll: " + scrolled + "px");
9802		}
9803		return scrolled;
9804	}
9805
9806	/* Guess dropEffect from modifier keys.
9807	 * Using rules suggested here:
9808	 *     https://ux.stackexchange.com/a/83769
9809	 * @returns
9810	 *     'copy', 'link', 'move', or 'none'
9811	 */
9812	function evalEffectModifiers(tree, event, effectDefault) {
9813		var res = effectDefault;
9814
9815		if (isMac) {
9816			if (event.metaKey && event.altKey) {
9817				// Mac: [Control] + [Option]
9818				res = "link";
9819			} else if (event.ctrlKey) {
9820				// Chrome on Mac: [Control]
9821				res = "link";
9822			} else if (event.metaKey) {
9823				// Mac: [Command]
9824				res = "move";
9825			} else if (event.altKey) {
9826				// Mac: [Option]
9827				res = "copy";
9828			}
9829		} else {
9830			if (event.ctrlKey) {
9831				// Windows: [Ctrl]
9832				res = "copy";
9833			} else if (event.shiftKey) {
9834				// Windows: [Shift]
9835				res = "move";
9836			} else if (event.altKey) {
9837				// Windows: [Alt]
9838				res = "link";
9839			}
9840		}
9841		if (res !== SUGGESTED_DROP_EFFECT) {
9842			tree.info(
9843				"evalEffectModifiers: " +
9844					event.type +
9845					" - evalEffectModifiers(): " +
9846					SUGGESTED_DROP_EFFECT +
9847					" -> " +
9848					res
9849			);
9850		}
9851		SUGGESTED_DROP_EFFECT = res;
9852		// tree.debug("evalEffectModifiers: " + res);
9853		return res;
9854	}
9855	/*
9856	 * Check if the previous callback (dragEnter, dragOver, ...) has changed
9857	 * the `data` object and apply those settings.
9858	 *
9859	 * Safari:
9860	 *     It seems that `dataTransfer.dropEffect` can only be set on dragStart, and will remain
9861	 *     even if the cursor changes when [Alt] or [Ctrl] are pressed (?)
9862	 * Using rules suggested here:
9863	 *     https://ux.stackexchange.com/a/83769
9864	 * @returns
9865	 *     'copy', 'link', 'move', or 'none'
9866	 */
9867	function prepareDropEffectCallback(event, data) {
9868		var tree = data.tree,
9869			dataTransfer = data.dataTransfer;
9870
9871		if (event.type === "dragstart") {
9872			data.effectAllowed = tree.options.dnd5.effectAllowed;
9873			data.dropEffect = tree.options.dnd5.dropEffectDefault;
9874		} else {
9875			data.effectAllowed = REQUESTED_EFFECT_ALLOWED;
9876			data.dropEffect = REQUESTED_DROP_EFFECT;
9877		}
9878		data.dropEffectSuggested = evalEffectModifiers(
9879			tree,
9880			event,
9881			tree.options.dnd5.dropEffectDefault
9882		);
9883		data.isMove = data.dropEffect === "move";
9884		data.files = dataTransfer.files || [];
9885
9886		// if (REQUESTED_EFFECT_ALLOWED !== dataTransfer.effectAllowed) {
9887		// 	tree.warn(
9888		// 		"prepareDropEffectCallback(" +
9889		// 			event.type +
9890		// 			"): dataTransfer.effectAllowed changed from " +
9891		// 			REQUESTED_EFFECT_ALLOWED +
9892		// 			" -> " +
9893		// 			dataTransfer.effectAllowed
9894		// 	);
9895		// }
9896		// if (REQUESTED_DROP_EFFECT !== dataTransfer.dropEffect) {
9897		// 	tree.warn(
9898		// 		"prepareDropEffectCallback(" +
9899		// 			event.type +
9900		// 			"): dataTransfer.dropEffect changed from requested " +
9901		// 			REQUESTED_DROP_EFFECT +
9902		// 			" to " +
9903		// 			dataTransfer.dropEffect
9904		// 	);
9905		// }
9906	}
9907
9908	function applyDropEffectCallback(event, data, allowDrop) {
9909		var tree = data.tree,
9910			dataTransfer = data.dataTransfer;
9911
9912		if (
9913			event.type !== "dragstart" &&
9914			REQUESTED_EFFECT_ALLOWED !== data.effectAllowed
9915		) {
9916			tree.warn(
9917				"effectAllowed should only be changed in dragstart event: " +
9918					event.type +
9919					": data.effectAllowed changed from " +
9920					REQUESTED_EFFECT_ALLOWED +
9921					" -> " +
9922					data.effectAllowed
9923			);
9924		}
9925
9926		if (allowDrop === false) {
9927			tree.info("applyDropEffectCallback: allowDrop === false");
9928			data.effectAllowed = "none";
9929			data.dropEffect = "none";
9930		}
9931		// if (REQUESTED_DROP_EFFECT !== data.dropEffect) {
9932		// 	tree.debug(
9933		// 		"applyDropEffectCallback(" +
9934		// 			event.type +
9935		// 			"): data.dropEffect changed from previous " +
9936		// 			REQUESTED_DROP_EFFECT +
9937		// 			" to " +
9938		// 			data.dropEffect
9939		// 	);
9940		// }
9941
9942		data.isMove = data.dropEffect === "move";
9943		// data.isMove = data.dropEffectSuggested === "move";
9944
9945		// `effectAllowed` must only be defined in dragstart event, so we
9946		// store it in a global variable for reference
9947		if (event.type === "dragstart") {
9948			REQUESTED_EFFECT_ALLOWED = data.effectAllowed;
9949			REQUESTED_DROP_EFFECT = data.dropEffect;
9950		}
9951
9952		// if (REQUESTED_DROP_EFFECT !== dataTransfer.dropEffect) {
9953		// 	data.tree.info(
9954		// 		"applyDropEffectCallback(" +
9955		// 			event.type +
9956		// 			"): dataTransfer.dropEffect changed from " +
9957		// 			REQUESTED_DROP_EFFECT +
9958		// 			" -> " +
9959		// 			dataTransfer.dropEffect
9960		// 	);
9961		// }
9962		dataTransfer.effectAllowed = REQUESTED_EFFECT_ALLOWED;
9963		dataTransfer.dropEffect = REQUESTED_DROP_EFFECT;
9964
9965		// tree.debug(
9966		// 	"applyDropEffectCallback(" +
9967		// 		event.type +
9968		// 		"): set " +
9969		// 		dataTransfer.dropEffect +
9970		// 		"/" +
9971		// 		dataTransfer.effectAllowed
9972		// );
9973		// if (REQUESTED_DROP_EFFECT !== dataTransfer.dropEffect) {
9974		// 	data.tree.warn(
9975		// 		"applyDropEffectCallback(" +
9976		// 			event.type +
9977		// 			"): could not set dataTransfer.dropEffect to " +
9978		// 			REQUESTED_DROP_EFFECT +
9979		// 			": got " +
9980		// 			dataTransfer.dropEffect
9981		// 	);
9982		// }
9983		return REQUESTED_DROP_EFFECT;
9984	}
9985
9986	/* Handle dragover event (fired every x ms) on valid drop targets.
9987	 *
9988	 * - Auto-scroll when cursor is in border regions
9989	 * - Apply restrictioan like 'preventVoidMoves'
9990	 * - Calculate hit mode
9991	 * - Calculate drop effect
9992	 * - Trigger dragOver() callback to let user modify hit mode and drop effect
9993	 * - Adjust the drop marker accordingly
9994	 *
9995	 * @returns hitMode
9996	 */
9997	function handleDragOver(event, data) {
9998		// Implement auto-scrolling
9999		if (data.options.dnd5.scroll) {
10000			autoScroll(data.tree, event);
10001		}
10002		// Bail out with previous response if we get an invalid dragover
10003		if (!data.node) {
10004			data.tree.warn("Ignored dragover for non-node"); //, event, data);
10005			return LAST_HIT_MODE;
10006		}
10007
10008		var markerOffsetX,
10009			nodeOfs,
10010			pos,
10011			relPosY,
10012			hitMode = null,
10013			tree = data.tree,
10014			options = tree.options,
10015			dndOpts = options.dnd5,
10016			targetNode = data.node,
10017			sourceNode = data.otherNode,
10018			markerAt = "center",
10019			$target = $(targetNode.span),
10020			$targetTitle = $target.find("span.fancytree-title");
10021
10022		if (DRAG_ENTER_RESPONSE === false) {
10023			tree.debug("Ignored dragover, since dragenter returned false.");
10024			return false;
10025		} else if (typeof DRAG_ENTER_RESPONSE === "string") {
10026			$.error("assert failed: dragenter returned string");
10027		}
10028		// Calculate hitMode from relative cursor position.
10029		nodeOfs = $target.offset();
10030		relPosY = (event.pageY - nodeOfs.top) / $target.height();
10031		if (event.pageY === undefined) {
10032			tree.warn("event.pageY is undefined: see issue #1013.");
10033		}
10034
10035		if (DRAG_ENTER_RESPONSE.after && relPosY > 0.75) {
10036			hitMode = "after";
10037		} else if (
10038			!DRAG_ENTER_RESPONSE.over &&
10039			DRAG_ENTER_RESPONSE.after &&
10040			relPosY > 0.5
10041		) {
10042			hitMode = "after";
10043		} else if (DRAG_ENTER_RESPONSE.before && relPosY <= 0.25) {
10044			hitMode = "before";
10045		} else if (
10046			!DRAG_ENTER_RESPONSE.over &&
10047			DRAG_ENTER_RESPONSE.before &&
10048			relPosY <= 0.5
10049		) {
10050			hitMode = "before";
10051		} else if (DRAG_ENTER_RESPONSE.over) {
10052			hitMode = "over";
10053		}
10054		// Prevent no-ops like 'before source node'
10055		// TODO: these are no-ops when moving nodes, but not in copy mode
10056		if (dndOpts.preventVoidMoves && data.dropEffect === "move") {
10057			if (targetNode === sourceNode) {
10058				targetNode.debug("Drop over source node prevented.");
10059				hitMode = null;
10060			} else if (
10061				hitMode === "before" &&
10062				sourceNode &&
10063				targetNode === sourceNode.getNextSibling()
10064			) {
10065				targetNode.debug("Drop after source node prevented.");
10066				hitMode = null;
10067			} else if (
10068				hitMode === "after" &&
10069				sourceNode &&
10070				targetNode === sourceNode.getPrevSibling()
10071			) {
10072				targetNode.debug("Drop before source node prevented.");
10073				hitMode = null;
10074			} else if (
10075				hitMode === "over" &&
10076				sourceNode &&
10077				sourceNode.parent === targetNode &&
10078				sourceNode.isLastSibling()
10079			) {
10080				targetNode.debug("Drop last child over own parent prevented.");
10081				hitMode = null;
10082			}
10083		}
10084		// Let callback modify the calculated hitMode
10085		data.hitMode = hitMode;
10086		if (hitMode && dndOpts.dragOver) {
10087			prepareDropEffectCallback(event, data);
10088			dndOpts.dragOver(targetNode, data);
10089			var allowDrop = !!hitMode;
10090			applyDropEffectCallback(event, data, allowDrop);
10091			hitMode = data.hitMode;
10092		}
10093		LAST_HIT_MODE = hitMode;
10094		//
10095		if (hitMode === "after" || hitMode === "before" || hitMode === "over") {
10096			markerOffsetX = dndOpts.dropMarkerOffsetX || 0;
10097			switch (hitMode) {
10098				case "before":
10099					markerAt = "top";
10100					markerOffsetX += dndOpts.dropMarkerInsertOffsetX || 0;
10101					break;
10102				case "after":
10103					markerAt = "bottom";
10104					markerOffsetX += dndOpts.dropMarkerInsertOffsetX || 0;
10105					break;
10106			}
10107
10108			pos = {
10109				my: "left" + offsetString(markerOffsetX) + " center",
10110				at: "left " + markerAt,
10111				of: $targetTitle,
10112			};
10113			if (options.rtl) {
10114				pos.my = "right" + offsetString(-markerOffsetX) + " center";
10115				pos.at = "right " + markerAt;
10116				// console.log("rtl", pos);
10117			}
10118			$dropMarker
10119				.toggleClass(classDropAfter, hitMode === "after")
10120				.toggleClass(classDropOver, hitMode === "over")
10121				.toggleClass(classDropBefore, hitMode === "before")
10122				.show()
10123				.position(FT.fixPositionOptions(pos));
10124		} else {
10125			$dropMarker.hide();
10126			// console.log("hide dropmarker")
10127		}
10128
10129		$(targetNode.span)
10130			.toggleClass(
10131				classDropTarget,
10132				hitMode === "after" ||
10133					hitMode === "before" ||
10134					hitMode === "over"
10135			)
10136			.toggleClass(classDropAfter, hitMode === "after")
10137			.toggleClass(classDropBefore, hitMode === "before")
10138			.toggleClass(classDropAccept, hitMode === "over")
10139			.toggleClass(classDropReject, hitMode === false);
10140
10141		return hitMode;
10142	}
10143
10144	/*
10145	 * Handle dragstart drag dragend events on the container
10146	 */
10147	function onDragEvent(event) {
10148		var json,
10149			tree = this,
10150			dndOpts = tree.options.dnd5,
10151			node = FT.getNode(event),
10152			dataTransfer =
10153				event.dataTransfer || event.originalEvent.dataTransfer,
10154			data = {
10155				tree: tree,
10156				node: node,
10157				options: tree.options,
10158				originalEvent: event.originalEvent,
10159				widget: tree.widget,
10160				dataTransfer: dataTransfer,
10161				useDefaultImage: true,
10162				dropEffect: undefined,
10163				dropEffectSuggested: undefined,
10164				effectAllowed: undefined, // set by dragstart
10165				files: undefined, // only for drop events
10166				isCancelled: undefined, // set by dragend
10167				isMove: undefined,
10168			};
10169
10170		switch (event.type) {
10171			case "dragstart":
10172				if (!node) {
10173					tree.info("Ignored dragstart on a non-node.");
10174					return false;
10175				}
10176				// Store current source node in different formats
10177				SOURCE_NODE = node;
10178
10179				// Also optionally store selected nodes
10180				if (dndOpts.multiSource === false) {
10181					SOURCE_NODE_LIST = [node];
10182				} else if (dndOpts.multiSource === true) {
10183					if (node.isSelected()) {
10184						SOURCE_NODE_LIST = tree.getSelectedNodes();
10185					} else {
10186						SOURCE_NODE_LIST = [node];
10187					}
10188				} else {
10189					SOURCE_NODE_LIST = dndOpts.multiSource(node, data);
10190				}
10191				// Cache as array of jQuery objects for faster access:
10192				$sourceList = $(
10193					$.map(SOURCE_NODE_LIST, function (n) {
10194						return n.span;
10195					})
10196				);
10197				// Set visual feedback
10198				$sourceList.addClass(classDragSource);
10199
10200				// Set payload
10201				// Note:
10202				// Transfer data is only accessible on dragstart and drop!
10203				// For all other events the formats and kinds in the drag
10204				// data store list of items representing dragged data can be
10205				// enumerated, but the data itself is unavailable and no new
10206				// data can be added.
10207				var nodeData = node.toDict(true, dndOpts.sourceCopyHook);
10208				nodeData.treeId = node.tree._id;
10209				json = JSON.stringify(nodeData);
10210				try {
10211					dataTransfer.setData(nodeMimeType, json);
10212					dataTransfer.setData("text/html", $(node.span).html());
10213					dataTransfer.setData("text/plain", node.title);
10214				} catch (ex) {
10215					// IE only accepts 'text' type
10216					tree.warn(
10217						"Could not set data (IE only accepts 'text') - " + ex
10218					);
10219				}
10220				// We always need to set the 'text' type if we want to drag
10221				// Because IE 11 only accepts this single type.
10222				// If we pass JSON here, IE can can access all node properties,
10223				// even when the source lives in another window. (D'n'd inside
10224				// the same window will always work.)
10225				// The drawback is, that in this case ALL browsers will see
10226				// the JSON representation as 'text', so dragging
10227				// to a text field will insert the JSON string instead of
10228				// the node title.
10229				if (dndOpts.setTextTypeJson) {
10230					dataTransfer.setData("text", json);
10231				} else {
10232					dataTransfer.setData("text", node.title);
10233				}
10234
10235				// Set the allowed drag modes (combinations of move, copy, and link)
10236				// (effectAllowed can only be set in the dragstart event.)
10237				// This can be overridden in the dragStart() callback
10238				prepareDropEffectCallback(event, data);
10239
10240				// Let user cancel or modify above settings
10241				// Realize potential changes by previous callback
10242				if (dndOpts.dragStart(node, data) === false) {
10243					// Cancel dragging
10244					// dataTransfer.dropEffect = "none";
10245					_clearGlobals();
10246					return false;
10247				}
10248				applyDropEffectCallback(event, data);
10249
10250				// Unless user set `data.useDefaultImage` to false in dragStart,
10251				// generata a default drag image now:
10252				$extraHelper = null;
10253
10254				if (data.useDefaultImage) {
10255					// Set the title as drag image (otherwise it would contain the expander)
10256					$dragImage = $(node.span).find(".fancytree-title");
10257
10258					if (SOURCE_NODE_LIST && SOURCE_NODE_LIST.length > 1) {
10259						// Add a counter badge to node title if dragging more than one node.
10260						// We want this, because the element that is used as drag image
10261						// must be *visible* in the DOM, so we cannot create some hidden
10262						// custom markup.
10263						// See https://kryogenix.org/code/browser/custom-drag-image.html
10264						// Also, since IE 11 and Edge don't support setDragImage() alltogether,
10265						// it gives som feedback to the user.
10266						// The badge will be removed later on drag end.
10267						$extraHelper = $(
10268							"<span class='fancytree-childcounter'/>"
10269						)
10270							.text("+" + (SOURCE_NODE_LIST.length - 1))
10271							.appendTo($dragImage);
10272					}
10273					if (dataTransfer.setDragImage) {
10274						// IE 11 and Edge do not support this
10275						dataTransfer.setDragImage($dragImage[0], -10, -10);
10276					}
10277				}
10278				return true;
10279
10280			case "drag":
10281				// Called every few milliseconds (no matter if the
10282				// cursor is over a valid drop target)
10283				// data.tree.info("drag", SOURCE_NODE)
10284				prepareDropEffectCallback(event, data);
10285				dndOpts.dragDrag(node, data);
10286				applyDropEffectCallback(event, data);
10287
10288				$sourceList.toggleClass(classDragRemove, data.isMove);
10289				break;
10290
10291			case "dragend":
10292				// Called at the end of a d'n'd process (after drop)
10293				// Note caveat: If drop removed the dragged source element,
10294				// we may not get this event, since the target does not exist
10295				// anymore
10296				prepareDropEffectCallback(event, data);
10297
10298				_clearGlobals();
10299
10300				data.isCancelled = !LAST_HIT_MODE;
10301				dndOpts.dragEnd(node, data, !LAST_HIT_MODE);
10302				// applyDropEffectCallback(event, data);
10303				break;
10304		}
10305	}
10306	/*
10307	 * Handle dragenter dragover dragleave drop events on the container
10308	 */
10309	function onDropEvent(event) {
10310		var json,
10311			allowAutoExpand,
10312			nodeData,
10313			isSourceFtNode,
10314			r,
10315			res,
10316			tree = this,
10317			dndOpts = tree.options.dnd5,
10318			allowDrop = null,
10319			node = FT.getNode(event),
10320			dataTransfer =
10321				event.dataTransfer || event.originalEvent.dataTransfer,
10322			data = {
10323				tree: tree,
10324				node: node,
10325				options: tree.options,
10326				originalEvent: event.originalEvent,
10327				widget: tree.widget,
10328				hitMode: DRAG_ENTER_RESPONSE,
10329				dataTransfer: dataTransfer,
10330				otherNode: SOURCE_NODE || null,
10331				otherNodeList: SOURCE_NODE_LIST || null,
10332				otherNodeData: null, // set by drop event
10333				useDefaultImage: true,
10334				dropEffect: undefined,
10335				dropEffectSuggested: undefined,
10336				effectAllowed: undefined, // set by dragstart
10337				files: null, // list of File objects (may be [])
10338				isCancelled: undefined, // set by drop event
10339				isMove: undefined,
10340			};
10341
10342		// data.isMove = dropEffect === "move";
10343
10344		switch (event.type) {
10345			case "dragenter":
10346				// The dragenter event is fired when a dragged element or
10347				// text selection enters a valid drop target.
10348
10349				DRAG_OVER_STAMP = null;
10350				if (!node) {
10351					// Sometimes we get dragenter for the container element
10352					tree.debug(
10353						"Ignore non-node " +
10354							event.type +
10355							": " +
10356							event.target.tagName +
10357							"." +
10358							event.target.className
10359					);
10360					DRAG_ENTER_RESPONSE = false;
10361					break;
10362				}
10363
10364				$(node.span)
10365					.addClass(classDropOver)
10366					.removeClass(classDropAccept + " " + classDropReject);
10367
10368				// Data is only readable in the dragstart and drop event,
10369				// but we can check for the type:
10370				isSourceFtNode =
10371					$.inArray(nodeMimeType, dataTransfer.types) >= 0;
10372
10373				if (dndOpts.preventNonNodes && !isSourceFtNode) {
10374					node.debug("Reject dropping a non-node.");
10375					DRAG_ENTER_RESPONSE = false;
10376					break;
10377				} else if (
10378					dndOpts.preventForeignNodes &&
10379					(!SOURCE_NODE || SOURCE_NODE.tree !== node.tree)
10380				) {
10381					node.debug("Reject dropping a foreign node.");
10382					DRAG_ENTER_RESPONSE = false;
10383					break;
10384				} else if (
10385					dndOpts.preventSameParent &&
10386					data.otherNode &&
10387					data.otherNode.tree === node.tree &&
10388					node.parent === data.otherNode.parent
10389				) {
10390					node.debug("Reject dropping as sibling (same parent).");
10391					DRAG_ENTER_RESPONSE = false;
10392					break;
10393				} else if (
10394					dndOpts.preventRecursion &&
10395					data.otherNode &&
10396					data.otherNode.tree === node.tree &&
10397					node.isDescendantOf(data.otherNode)
10398				) {
10399					node.debug("Reject dropping below own ancestor.");
10400					DRAG_ENTER_RESPONSE = false;
10401					break;
10402				} else if (dndOpts.preventLazyParents && !node.isLoaded()) {
10403					node.warn("Drop over unloaded target node prevented.");
10404					DRAG_ENTER_RESPONSE = false;
10405					break;
10406				}
10407				$dropMarker.show();
10408
10409				// Call dragEnter() to figure out if (and where) dropping is allowed
10410				prepareDropEffectCallback(event, data);
10411				r = dndOpts.dragEnter(node, data);
10412
10413				res = normalizeDragEnterResponse(r);
10414				// alert("res:" + JSON.stringify(res))
10415				DRAG_ENTER_RESPONSE = res;
10416
10417				allowDrop = res && (res.over || res.before || res.after);
10418
10419				applyDropEffectCallback(event, data, allowDrop);
10420				break;
10421
10422			case "dragover":
10423				if (!node) {
10424					tree.debug(
10425						"Ignore non-node " +
10426							event.type +
10427							": " +
10428							event.target.tagName +
10429							"." +
10430							event.target.className
10431					);
10432					break;
10433				}
10434				// The dragover event is fired when an element or text
10435				// selection is being dragged over a valid drop target
10436				// (every few hundred milliseconds).
10437				// tree.debug(
10438				// 	event.type +
10439				// 		": dropEffect: " +
10440				// 		dataTransfer.dropEffect
10441				// );
10442				prepareDropEffectCallback(event, data);
10443				LAST_HIT_MODE = handleDragOver(event, data);
10444
10445				// The flag controls the preventDefault() below:
10446				allowDrop = !!LAST_HIT_MODE;
10447				allowAutoExpand =
10448					LAST_HIT_MODE === "over" || LAST_HIT_MODE === false;
10449
10450				if (
10451					allowAutoExpand &&
10452					!node.expanded &&
10453					node.hasChildren() !== false
10454				) {
10455					if (!DRAG_OVER_STAMP) {
10456						DRAG_OVER_STAMP = Date.now();
10457					} else if (
10458						dndOpts.autoExpandMS &&
10459						Date.now() - DRAG_OVER_STAMP > dndOpts.autoExpandMS &&
10460						!node.isLoading() &&
10461						(!dndOpts.dragExpand ||
10462							dndOpts.dragExpand(node, data) !== false)
10463					) {
10464						node.setExpanded();
10465					}
10466				} else {
10467					DRAG_OVER_STAMP = null;
10468				}
10469				break;
10470
10471			case "dragleave":
10472				// NOTE: dragleave is fired AFTER the dragenter event of the
10473				// FOLLOWING element.
10474				if (!node) {
10475					tree.debug(
10476						"Ignore non-node " +
10477							event.type +
10478							": " +
10479							event.target.tagName +
10480							"." +
10481							event.target.className
10482					);
10483					break;
10484				}
10485				if (!$(node.span).hasClass(classDropOver)) {
10486					node.debug("Ignore dragleave (multi).");
10487					break;
10488				}
10489				$(node.span).removeClass(
10490					classDropOver +
10491						" " +
10492						classDropAccept +
10493						" " +
10494						classDropReject
10495				);
10496				node.scheduleAction("cancel");
10497				dndOpts.dragLeave(node, data);
10498				$dropMarker.hide();
10499				break;
10500
10501			case "drop":
10502				// Data is only readable in the (dragstart and) drop event:
10503
10504				if ($.inArray(nodeMimeType, dataTransfer.types) >= 0) {
10505					nodeData = dataTransfer.getData(nodeMimeType);
10506					tree.info(
10507						event.type +
10508							": getData('application/x-fancytree-node'): '" +
10509							nodeData +
10510							"'"
10511					);
10512				}
10513				if (!nodeData) {
10514					// 1. Source is not a Fancytree node, or
10515					// 2. If the FT mime type was set, but returns '', this
10516					//    is probably IE 11 (which only supports 'text')
10517					nodeData = dataTransfer.getData("text");
10518					tree.info(
10519						event.type + ": getData('text'): '" + nodeData + "'"
10520					);
10521				}
10522				if (nodeData) {
10523					try {
10524						// 'text' type may contain JSON if IE is involved
10525						// and setTextTypeJson option was set
10526						json = JSON.parse(nodeData);
10527						if (json.title !== undefined) {
10528							data.otherNodeData = json;
10529						}
10530					} catch (ex) {
10531						// assume 'text' type contains plain text, so `otherNodeData`
10532						// should not be set
10533					}
10534				}
10535				tree.debug(
10536					event.type +
10537						": nodeData: '" +
10538						nodeData +
10539						"', otherNodeData: ",
10540					data.otherNodeData
10541				);
10542
10543				$(node.span).removeClass(
10544					classDropOver +
10545						" " +
10546						classDropAccept +
10547						" " +
10548						classDropReject
10549				);
10550
10551				// Let user implement the actual drop operation
10552				data.hitMode = LAST_HIT_MODE;
10553				prepareDropEffectCallback(event, data, !LAST_HIT_MODE);
10554				data.isCancelled = !LAST_HIT_MODE;
10555
10556				var orgSourceElem = SOURCE_NODE && SOURCE_NODE.span,
10557					orgSourceTree = SOURCE_NODE && SOURCE_NODE.tree;
10558
10559				dndOpts.dragDrop(node, data);
10560				// applyDropEffectCallback(event, data);
10561
10562				// Prevent browser's default drop handling, i.e. open as link, ...
10563				event.preventDefault();
10564
10565				if (orgSourceElem && !document.body.contains(orgSourceElem)) {
10566					// The drop handler removed the original drag source from
10567					// the DOM, so the dragend event will probaly not fire.
10568					if (orgSourceTree === tree) {
10569						tree.debug(
10570							"Drop handler removed source element: generating dragEnd."
10571						);
10572						dndOpts.dragEnd(SOURCE_NODE, data);
10573					} else {
10574						tree.warn(
10575							"Drop handler removed source element: dragend event may be lost."
10576						);
10577					}
10578				}
10579
10580				_clearGlobals();
10581
10582				break;
10583		}
10584		// Dnd API madness: we must PREVENT default handling to enable dropping
10585		if (allowDrop) {
10586			event.preventDefault();
10587			return false;
10588		}
10589	}
10590
10591	/** [ext-dnd5] Return a Fancytree instance, from element, index, event, or jQueryObject.
10592	 *
10593	 * @returns {FancytreeNode[]} List of nodes (empty if no drag operation)
10594	 * @example
10595	 * $.ui.fancytree.getDragNodeList();
10596	 *
10597	 * @alias Fancytree_Static#getDragNodeList
10598	 * @requires jquery.fancytree.dnd5.js
10599	 * @since 2.31
10600	 */
10601	$.ui.fancytree.getDragNodeList = function () {
10602		return SOURCE_NODE_LIST || [];
10603	};
10604
10605	/** [ext-dnd5] Return the FancytreeNode that is currently being dragged.
10606	 *
10607	 * If multiple nodes are dragged, only the first is returned.
10608	 *
10609	 * @returns {FancytreeNode | null} dragged nodes or null if no drag operation
10610	 * @example
10611	 * $.ui.fancytree.getDragNode();
10612	 *
10613	 * @alias Fancytree_Static#getDragNode
10614	 * @requires jquery.fancytree.dnd5.js
10615	 * @since 2.31
10616	 */
10617	$.ui.fancytree.getDragNode = function () {
10618		return SOURCE_NODE;
10619	};
10620
10621	/******************************************************************************
10622	 *
10623	 */
10624
10625	$.ui.fancytree.registerExtension({
10626		name: "dnd5",
10627		version: "2.38.3",
10628		// Default options for this extension.
10629		options: {
10630			autoExpandMS: 1500, // Expand nodes after n milliseconds of hovering
10631			dropMarkerInsertOffsetX: -16, // Additional offset for drop-marker with hitMode = "before"/"after"
10632			dropMarkerOffsetX: -24, // Absolute position offset for .fancytree-drop-marker relatively to ..fancytree-title (icon/img near a node accepting drop)
10633			// #1021 `document.body` is not available yet
10634			dropMarkerParent: "body", // Root Container used for drop marker (could be a shadow root)
10635			multiSource: false, // true: Drag multiple (i.e. selected) nodes. Also a callback() is allowed
10636			effectAllowed: "all", // Restrict the possible cursor shapes and modifier operations (can also be set in the dragStart event)
10637			// dropEffect: "auto", // 'copy'|'link'|'move'|'auto'(calculate from `effectAllowed`+modifier keys) or callback(node, data) that returns such string.
10638			dropEffectDefault: "move", // Default dropEffect ('copy', 'link', or 'move') when no modifier is pressed (overide in dragDrag, dragOver).
10639			preventForeignNodes: false, // Prevent dropping nodes from different Fancytrees
10640			preventLazyParents: true, // Prevent dropping items on unloaded lazy Fancytree nodes
10641			preventNonNodes: false, // Prevent dropping items other than Fancytree nodes
10642			preventRecursion: true, // Prevent dropping nodes on own descendants
10643			preventSameParent: false, // Prevent dropping nodes under same direct parent
10644			preventVoidMoves: true, // Prevent dropping nodes 'before self', etc.
10645			scroll: true, // Enable auto-scrolling while dragging
10646			scrollSensitivity: 20, // Active top/bottom margin in pixel
10647			scrollSpeed: 5, // Pixel per event
10648			setTextTypeJson: false, // Allow dragging of nodes to different IE windows
10649			sourceCopyHook: null, // Optional callback passed to `toDict` on dragStart @since 2.38
10650			// Events (drag support)
10651			dragStart: null, // Callback(sourceNode, data), return true, to enable dnd drag
10652			dragDrag: $.noop, // Callback(sourceNode, data)
10653			dragEnd: $.noop, // Callback(sourceNode, data)
10654			// Events (drop support)
10655			dragEnter: null, // Callback(targetNode, data), return true, to enable dnd drop
10656			dragOver: $.noop, // Callback(targetNode, data)
10657			dragExpand: $.noop, // Callback(targetNode, data), return false to prevent autoExpand
10658			dragDrop: $.noop, // Callback(targetNode, data)
10659			dragLeave: $.noop, // Callback(targetNode, data)
10660		},
10661
10662		treeInit: function (ctx) {
10663			var $temp,
10664				tree = ctx.tree,
10665				opts = ctx.options,
10666				glyph = opts.glyph || null,
10667				dndOpts = opts.dnd5;
10668
10669			if ($.inArray("dnd", opts.extensions) >= 0) {
10670				$.error("Extensions 'dnd' and 'dnd5' are mutually exclusive.");
10671			}
10672			if (dndOpts.dragStop) {
10673				$.error(
10674					"dragStop is not used by ext-dnd5. Use dragEnd instead."
10675				);
10676			}
10677			if (dndOpts.preventRecursiveMoves != null) {
10678				$.error(
10679					"preventRecursiveMoves was renamed to preventRecursion."
10680				);
10681			}
10682
10683			// Implement `opts.createNode` event to add the 'draggable' attribute
10684			// #680: this must happen before calling super.treeInit()
10685			if (dndOpts.dragStart) {
10686				FT.overrideMethod(
10687					ctx.options,
10688					"createNode",
10689					function (event, data) {
10690						// Default processing if any
10691						this._super.apply(this, arguments);
10692						if (data.node.span) {
10693							data.node.span.draggable = true;
10694						} else {
10695							data.node.warn(
10696								"Cannot add `draggable`: no span tag"
10697							);
10698						}
10699					}
10700				);
10701			}
10702			this._superApply(arguments);
10703
10704			this.$container.addClass("fancytree-ext-dnd5");
10705
10706			// Store the current scroll parent, which may be the tree
10707			// container, any enclosing div, or the document.
10708			// #761: scrollParent() always needs a container child
10709			$temp = $("<span>").appendTo(this.$container);
10710			this.$scrollParent = $temp.scrollParent();
10711			$temp.remove();
10712
10713			$dropMarker = $("#fancytree-drop-marker");
10714			if (!$dropMarker.length) {
10715				$dropMarker = $("<div id='fancytree-drop-marker'></div>")
10716					.hide()
10717					.css({
10718						"z-index": 1000,
10719						// Drop marker should not steal dragenter/dragover events:
10720						"pointer-events": "none",
10721					})
10722					.prependTo(dndOpts.dropMarkerParent);
10723				if (glyph) {
10724					FT.setSpanIcon(
10725						$dropMarker[0],
10726						glyph.map._addClass,
10727						glyph.map.dropMarker
10728					);
10729				}
10730			}
10731			$dropMarker.toggleClass("fancytree-rtl", !!opts.rtl);
10732
10733			// Enable drag support if dragStart() is specified:
10734			if (dndOpts.dragStart) {
10735				// Bind drag event handlers
10736				tree.$container.on(
10737					"dragstart drag dragend",
10738					onDragEvent.bind(tree)
10739				);
10740			}
10741			// Enable drop support if dragEnter() is specified:
10742			if (dndOpts.dragEnter) {
10743				// Bind drop event handlers
10744				tree.$container.on(
10745					"dragenter dragover dragleave drop",
10746					onDropEvent.bind(tree)
10747				);
10748			}
10749		},
10750	});
10751	// Value returned by `require('jquery.fancytree..')`
10752	return $.ui.fancytree;
10753}); // End of closure
10754
10755
10756/*! Extension 'jquery.fancytree.edit.js' *//*!
10757 * jquery.fancytree.edit.js
10758 *
10759 * Make node titles editable.
10760 * (Extension module for jquery.fancytree.js: https://github.com/mar10/fancytree/)
10761 *
10762 * Copyright (c) 2008-2023, Martin Wendt (https://wwWendt.de)
10763 *
10764 * Released under the MIT license
10765 * https://github.com/mar10/fancytree/wiki/LicenseInfo
10766 *
10767 * @version 2.38.3
10768 * @date 2023-02-01T20:52:50Z
10769 */
10770
10771(function (factory) {
10772	if (typeof define === "function" && define.amd) {
10773		// AMD. Register as an anonymous module.
10774		define(["jquery", "./jquery.fancytree"], factory);
10775	} else if (typeof module === "object" && module.exports) {
10776		// Node/CommonJS
10777		require("./jquery.fancytree");
10778		module.exports = factory(require("jquery"));
10779	} else {
10780		// Browser globals
10781		factory(jQuery);
10782	}
10783})(function ($) {
10784	"use strict";
10785
10786	/*******************************************************************************
10787	 * Private functions and variables
10788	 */
10789
10790	var isMac = /Mac/.test(navigator.platform),
10791		escapeHtml = $.ui.fancytree.escapeHtml,
10792		trim = $.ui.fancytree.trim,
10793		unescapeHtml = $.ui.fancytree.unescapeHtml;
10794
10795	/**
10796	 * [ext-edit] Start inline editing of current node title.
10797	 *
10798	 * @alias FancytreeNode#editStart
10799	 * @requires Fancytree
10800	 */
10801	$.ui.fancytree._FancytreeNodeClass.prototype.editStart = function () {
10802		var $input,
10803			node = this,
10804			tree = this.tree,
10805			local = tree.ext.edit,
10806			instOpts = tree.options.edit,
10807			$title = $(".fancytree-title", node.span),
10808			eventData = {
10809				node: node,
10810				tree: tree,
10811				options: tree.options,
10812				isNew: $(node[tree.statusClassPropName]).hasClass(
10813					"fancytree-edit-new"
10814				),
10815				orgTitle: node.title,
10816				input: null,
10817				dirty: false,
10818			};
10819
10820		// beforeEdit may want to modify the title before editing
10821		if (
10822			instOpts.beforeEdit.call(
10823				node,
10824				{ type: "beforeEdit" },
10825				eventData
10826			) === false
10827		) {
10828			return false;
10829		}
10830		$.ui.fancytree.assert(!local.currentNode, "recursive edit");
10831		local.currentNode = this;
10832		local.eventData = eventData;
10833
10834		// Disable standard Fancytree mouse- and key handling
10835		tree.widget._unbind();
10836
10837		local.lastDraggableAttrValue = node.span.draggable;
10838		if (local.lastDraggableAttrValue) {
10839			node.span.draggable = false;
10840		}
10841
10842		// #116: ext-dnd prevents the blur event, so we have to catch outer clicks
10843		$(document).on("mousedown.fancytree-edit", function (event) {
10844			if (!$(event.target).hasClass("fancytree-edit-input")) {
10845				node.editEnd(true, event);
10846			}
10847		});
10848
10849		// Replace node with <input>
10850		$input = $("<input />", {
10851			class: "fancytree-edit-input",
10852			type: "text",
10853			value: tree.options.escapeTitles
10854				? eventData.orgTitle
10855				: unescapeHtml(eventData.orgTitle),
10856		});
10857		local.eventData.input = $input;
10858		if (instOpts.adjustWidthOfs != null) {
10859			$input.width($title.width() + instOpts.adjustWidthOfs);
10860		}
10861		if (instOpts.inputCss != null) {
10862			$input.css(instOpts.inputCss);
10863		}
10864
10865		$title.html($input);
10866
10867		// Focus <input> and bind keyboard handler
10868		$input
10869			.focus()
10870			.change(function (event) {
10871				$input.addClass("fancytree-edit-dirty");
10872			})
10873			.on("keydown", function (event) {
10874				switch (event.which) {
10875					case $.ui.keyCode.ESCAPE:
10876						node.editEnd(false, event);
10877						break;
10878					case $.ui.keyCode.ENTER:
10879						node.editEnd(true, event);
10880						return false; // so we don't start editmode on Mac
10881				}
10882				event.stopPropagation();
10883			})
10884			.blur(function (event) {
10885				return node.editEnd(true, event);
10886			});
10887
10888		instOpts.edit.call(node, { type: "edit" }, eventData);
10889	};
10890
10891	/**
10892	 * [ext-edit] Stop inline editing.
10893	 * @param {Boolean} [applyChanges=false] false: cancel edit, true: save (if modified)
10894	 * @alias FancytreeNode#editEnd
10895	 * @requires jquery.fancytree.edit.js
10896	 */
10897	$.ui.fancytree._FancytreeNodeClass.prototype.editEnd = function (
10898		applyChanges,
10899		_event
10900	) {
10901		var newVal,
10902			node = this,
10903			tree = this.tree,
10904			local = tree.ext.edit,
10905			eventData = local.eventData,
10906			instOpts = tree.options.edit,
10907			$title = $(".fancytree-title", node.span),
10908			$input = $title.find("input.fancytree-edit-input");
10909
10910		if (instOpts.trim) {
10911			$input.val(trim($input.val()));
10912		}
10913		newVal = $input.val();
10914
10915		eventData.dirty = newVal !== node.title;
10916		eventData.originalEvent = _event;
10917
10918		// Find out, if saving is required
10919		if (applyChanges === false) {
10920			// If true/false was passed, honor this (except in rename mode, if unchanged)
10921			eventData.save = false;
10922		} else if (eventData.isNew) {
10923			// In create mode, we save everything, except for empty text
10924			eventData.save = newVal !== "";
10925		} else {
10926			// In rename mode, we save everyting, except for empty or unchanged text
10927			eventData.save = eventData.dirty && newVal !== "";
10928		}
10929		// Allow to break (keep editor open), modify input, or re-define data.save
10930		if (
10931			instOpts.beforeClose.call(
10932				node,
10933				{ type: "beforeClose" },
10934				eventData
10935			) === false
10936		) {
10937			return false;
10938		}
10939		if (
10940			eventData.save &&
10941			instOpts.save.call(node, { type: "save" }, eventData) === false
10942		) {
10943			return false;
10944		}
10945		$input.removeClass("fancytree-edit-dirty").off();
10946		// Unbind outer-click handler
10947		$(document).off(".fancytree-edit");
10948
10949		if (eventData.save) {
10950			// # 171: escape user input (not required if global escaping is on)
10951			node.setTitle(
10952				tree.options.escapeTitles ? newVal : escapeHtml(newVal)
10953			);
10954			node.setFocus();
10955		} else {
10956			if (eventData.isNew) {
10957				node.remove();
10958				node = eventData.node = null;
10959				local.relatedNode.setFocus();
10960			} else {
10961				node.renderTitle();
10962				node.setFocus();
10963			}
10964		}
10965		local.eventData = null;
10966		local.currentNode = null;
10967		local.relatedNode = null;
10968		// Re-enable mouse and keyboard handling
10969		tree.widget._bind();
10970
10971		if (node && local.lastDraggableAttrValue) {
10972			node.span.draggable = true;
10973		}
10974
10975		// Set keyboard focus, even if setFocus() claims 'nothing to do'
10976		tree.$container.get(0).focus({ preventScroll: true });
10977		eventData.input = null;
10978		instOpts.close.call(node, { type: "close" }, eventData);
10979		return true;
10980	};
10981
10982	/**
10983	 * [ext-edit] Create a new child or sibling node and start edit mode.
10984	 *
10985	 * @param {String} [mode='child'] 'before', 'after', or 'child'
10986	 * @param {Object} [init] NodeData (or simple title string)
10987	 * @alias FancytreeNode#editCreateNode
10988	 * @requires jquery.fancytree.edit.js
10989	 * @since 2.4
10990	 */
10991	$.ui.fancytree._FancytreeNodeClass.prototype.editCreateNode = function (
10992		mode,
10993		init
10994	) {
10995		var newNode,
10996			tree = this.tree,
10997			self = this;
10998
10999		mode = mode || "child";
11000		if (init == null) {
11001			init = { title: "" };
11002		} else if (typeof init === "string") {
11003			init = { title: init };
11004		} else {
11005			$.ui.fancytree.assert($.isPlainObject(init));
11006		}
11007		// Make sure node is expanded (and loaded) in 'child' mode
11008		if (
11009			mode === "child" &&
11010			!this.isExpanded() &&
11011			this.hasChildren() !== false
11012		) {
11013			this.setExpanded().done(function () {
11014				self.editCreateNode(mode, init);
11015			});
11016			return;
11017		}
11018		newNode = this.addNode(init, mode);
11019
11020		// #644: Don't filter new nodes.
11021		newNode.match = true;
11022		$(newNode[tree.statusClassPropName])
11023			.removeClass("fancytree-hide")
11024			.addClass("fancytree-match");
11025
11026		newNode.makeVisible(/*{noAnimation: true}*/).done(function () {
11027			$(newNode[tree.statusClassPropName]).addClass("fancytree-edit-new");
11028			self.tree.ext.edit.relatedNode = self;
11029			newNode.editStart();
11030		});
11031	};
11032
11033	/**
11034	 * [ext-edit] Check if any node in this tree  in edit mode.
11035	 *
11036	 * @returns {FancytreeNode | null}
11037	 * @alias Fancytree#isEditing
11038	 * @requires jquery.fancytree.edit.js
11039	 */
11040	$.ui.fancytree._FancytreeClass.prototype.isEditing = function () {
11041		return this.ext.edit ? this.ext.edit.currentNode : null;
11042	};
11043
11044	/**
11045	 * [ext-edit] Check if this node is in edit mode.
11046	 * @returns {Boolean} true if node is currently beeing edited
11047	 * @alias FancytreeNode#isEditing
11048	 * @requires jquery.fancytree.edit.js
11049	 */
11050	$.ui.fancytree._FancytreeNodeClass.prototype.isEditing = function () {
11051		return this.tree.ext.edit
11052			? this.tree.ext.edit.currentNode === this
11053			: false;
11054	};
11055
11056	/*******************************************************************************
11057	 * Extension code
11058	 */
11059	$.ui.fancytree.registerExtension({
11060		name: "edit",
11061		version: "2.38.3",
11062		// Default options for this extension.
11063		options: {
11064			adjustWidthOfs: 4, // null: don't adjust input size to content
11065			allowEmpty: false, // Prevent empty input
11066			inputCss: { minWidth: "3em" },
11067			// triggerCancel: ["esc", "tab", "click"],
11068			triggerStart: ["f2", "mac+enter", "shift+click"],
11069			trim: true, // Trim whitespace before save
11070			// Events:
11071			beforeClose: $.noop, // Return false to prevent cancel/save (data.input is available)
11072			beforeEdit: $.noop, // Return false to prevent edit mode
11073			close: $.noop, // Editor was removed
11074			edit: $.noop, // Editor was opened (available as data.input)
11075			//		keypress: $.noop,    // Not yet implemented
11076			save: $.noop, // Save data.input.val() or return false to keep editor open
11077		},
11078		// Local attributes
11079		currentNode: null,
11080
11081		treeInit: function (ctx) {
11082			var tree = ctx.tree;
11083
11084			this._superApply(arguments);
11085
11086			this.$container
11087				.addClass("fancytree-ext-edit")
11088				.on("fancytreebeforeupdateviewport", function (event, data) {
11089					var editNode = tree.isEditing();
11090					// When scrolling, the TR may be re-used by another node, so the
11091					// active cell marker an
11092					if (editNode) {
11093						editNode.info("Cancel edit due to scroll event.");
11094						editNode.editEnd(false, event);
11095					}
11096				});
11097		},
11098		nodeClick: function (ctx) {
11099			var eventStr = $.ui.fancytree.eventToString(ctx.originalEvent),
11100				triggerStart = ctx.options.edit.triggerStart;
11101
11102			if (
11103				eventStr === "shift+click" &&
11104				$.inArray("shift+click", triggerStart) >= 0
11105			) {
11106				if (ctx.originalEvent.shiftKey) {
11107					ctx.node.editStart();
11108					return false;
11109				}
11110			}
11111			if (
11112				eventStr === "click" &&
11113				$.inArray("clickActive", triggerStart) >= 0
11114			) {
11115				// Only when click was inside title text (not aynwhere else in the row)
11116				if (
11117					ctx.node.isActive() &&
11118					!ctx.node.isEditing() &&
11119					$(ctx.originalEvent.target).hasClass("fancytree-title")
11120				) {
11121					ctx.node.editStart();
11122					return false;
11123				}
11124			}
11125			return this._superApply(arguments);
11126		},
11127		nodeDblclick: function (ctx) {
11128			if ($.inArray("dblclick", ctx.options.edit.triggerStart) >= 0) {
11129				ctx.node.editStart();
11130				return false;
11131			}
11132			return this._superApply(arguments);
11133		},
11134		nodeKeydown: function (ctx) {
11135			switch (ctx.originalEvent.which) {
11136				case 113: // [F2]
11137					if ($.inArray("f2", ctx.options.edit.triggerStart) >= 0) {
11138						ctx.node.editStart();
11139						return false;
11140					}
11141					break;
11142				case $.ui.keyCode.ENTER:
11143					if (
11144						$.inArray("mac+enter", ctx.options.edit.triggerStart) >=
11145							0 &&
11146						isMac
11147					) {
11148						ctx.node.editStart();
11149						return false;
11150					}
11151					break;
11152			}
11153			return this._superApply(arguments);
11154		},
11155	});
11156	// Value returned by `require('jquery.fancytree..')`
11157	return $.ui.fancytree;
11158}); // End of closure
11159
11160
11161/*! Extension 'jquery.fancytree.filter.js' *//*!
11162 * jquery.fancytree.filter.js
11163 *
11164 * Remove or highlight tree nodes, based on a filter.
11165 * (Extension module for jquery.fancytree.js: https://github.com/mar10/fancytree/)
11166 *
11167 * Copyright (c) 2008-2023, Martin Wendt (https://wwWendt.de)
11168 *
11169 * Released under the MIT license
11170 * https://github.com/mar10/fancytree/wiki/LicenseInfo
11171 *
11172 * @version 2.38.3
11173 * @date 2023-02-01T20:52:50Z
11174 */
11175
11176(function (factory) {
11177	if (typeof define === "function" && define.amd) {
11178		// AMD. Register as an anonymous module.
11179		define(["jquery", "./jquery.fancytree"], factory);
11180	} else if (typeof module === "object" && module.exports) {
11181		// Node/CommonJS
11182		require("./jquery.fancytree");
11183		module.exports = factory(require("jquery"));
11184	} else {
11185		// Browser globals
11186		factory(jQuery);
11187	}
11188})(function ($) {
11189	"use strict";
11190
11191	/*******************************************************************************
11192	 * Private functions and variables
11193	 */
11194
11195	var KeyNoData = "__not_found__",
11196		escapeHtml = $.ui.fancytree.escapeHtml,
11197		exoticStartChar = "\uFFF7",
11198		exoticEndChar = "\uFFF8";
11199	function _escapeRegex(str) {
11200		return (str + "").replace(/([.?*+^$[\]\\(){}|-])/g, "\\$1");
11201	}
11202
11203	function extractHtmlText(s) {
11204		if (s.indexOf(">") >= 0) {
11205			return $("<div/>").html(s).text();
11206		}
11207		return s;
11208	}
11209
11210	/**
11211	 * @description Marks the matching charecters of `text` either by `mark` or
11212	 * by exotic*Chars (if `escapeTitles` is `true`) based on `regexMatchArray`
11213	 * which is an array of matching groups.
11214	 * @param {string} text
11215	 * @param {RegExpMatchArray} regexMatchArray
11216	 */
11217	function _markFuzzyMatchedChars(text, regexMatchArray, escapeTitles) {
11218		// It is extremely infuriating that we can not use `let` or `const` or arrow functions.
11219		// Damn you IE!!!
11220		var matchingIndices = [];
11221		// get the indices of matched characters (Iterate through `RegExpMatchArray`)
11222		for (
11223			var _matchingArrIdx = 1;
11224			_matchingArrIdx < regexMatchArray.length;
11225			_matchingArrIdx++
11226		) {
11227			var _mIdx =
11228				// get matching char index by cumulatively adding
11229				// the matched group length
11230				regexMatchArray[_matchingArrIdx].length +
11231				(_matchingArrIdx === 1 ? 0 : 1) +
11232				(matchingIndices[matchingIndices.length - 1] || 0);
11233			matchingIndices.push(_mIdx);
11234		}
11235		// Map each `text` char to its position and store in `textPoses`.
11236		var textPoses = text.split("");
11237		if (escapeTitles) {
11238			// If escaping the title, then wrap the matchng char within exotic chars
11239			matchingIndices.forEach(function (v) {
11240				textPoses[v] = exoticStartChar + textPoses[v] + exoticEndChar;
11241			});
11242		} else {
11243			// Otherwise, Wrap the matching chars within `mark`.
11244			matchingIndices.forEach(function (v) {
11245				textPoses[v] = "<mark>" + textPoses[v] + "</mark>";
11246			});
11247		}
11248		// Join back the modified `textPoses` to create final highlight markup.
11249		return textPoses.join("");
11250	}
11251	$.ui.fancytree._FancytreeClass.prototype._applyFilterImpl = function (
11252		filter,
11253		branchMode,
11254		_opts
11255	) {
11256		var match,
11257			statusNode,
11258			re,
11259			reHighlight,
11260			reExoticStartChar,
11261			reExoticEndChar,
11262			temp,
11263			prevEnableUpdate,
11264			count = 0,
11265			treeOpts = this.options,
11266			escapeTitles = treeOpts.escapeTitles,
11267			prevAutoCollapse = treeOpts.autoCollapse,
11268			opts = $.extend({}, treeOpts.filter, _opts),
11269			hideMode = opts.mode === "hide",
11270			leavesOnly = !!opts.leavesOnly && !branchMode;
11271
11272		// Default to 'match title substring (not case sensitive)'
11273		if (typeof filter === "string") {
11274			if (filter === "") {
11275				this.warn(
11276					"Fancytree passing an empty string as a filter is handled as clearFilter()."
11277				);
11278				this.clearFilter();
11279				return;
11280			}
11281			if (opts.fuzzy) {
11282				// See https://codereview.stackexchange.com/questions/23899/faster-javascript-fuzzy-string-matching-function/23905#23905
11283				// and http://www.quora.com/How-is-the-fuzzy-search-algorithm-in-Sublime-Text-designed
11284				// and http://www.dustindiaz.com/autocomplete-fuzzy-matching
11285				match = filter
11286					.split("")
11287					// Escaping the `filter` will not work because,
11288					// it gets further split into individual characters. So,
11289					// escape each character after splitting
11290					.map(_escapeRegex)
11291					.reduce(function (a, b) {
11292						// create capture groups for parts that comes before
11293						// the character
11294						return a + "([^" + b + "]*)" + b;
11295					}, "");
11296			} else {
11297				match = _escapeRegex(filter); // make sure a '.' is treated literally
11298			}
11299			re = new RegExp(match, "i");
11300			reHighlight = new RegExp(_escapeRegex(filter), "gi");
11301			if (escapeTitles) {
11302				reExoticStartChar = new RegExp(
11303					_escapeRegex(exoticStartChar),
11304					"g"
11305				);
11306				reExoticEndChar = new RegExp(_escapeRegex(exoticEndChar), "g");
11307			}
11308			filter = function (node) {
11309				if (!node.title) {
11310					return false;
11311				}
11312				var text = escapeTitles
11313						? node.title
11314						: extractHtmlText(node.title),
11315					// `.match` instead of `.test` to get the capture groups
11316					res = text.match(re);
11317				if (res && opts.highlight) {
11318					if (escapeTitles) {
11319						if (opts.fuzzy) {
11320							temp = _markFuzzyMatchedChars(
11321								text,
11322								res,
11323								escapeTitles
11324							);
11325						} else {
11326							// #740: we must not apply the marks to escaped entity names, e.g. `&quot;`
11327							// Use some exotic characters to mark matches:
11328							temp = text.replace(reHighlight, function (s) {
11329								return exoticStartChar + s + exoticEndChar;
11330							});
11331						}
11332						// now we can escape the title...
11333						node.titleWithHighlight = escapeHtml(temp)
11334							// ... and finally insert the desired `<mark>` tags
11335							.replace(reExoticStartChar, "<mark>")
11336							.replace(reExoticEndChar, "</mark>");
11337					} else {
11338						if (opts.fuzzy) {
11339							node.titleWithHighlight = _markFuzzyMatchedChars(
11340								text,
11341								res
11342							);
11343						} else {
11344							node.titleWithHighlight = text.replace(
11345								reHighlight,
11346								function (s) {
11347									return "<mark>" + s + "</mark>";
11348								}
11349							);
11350						}
11351					}
11352					// node.debug("filter", escapeTitles, text, node.titleWithHighlight);
11353				}
11354				return !!res;
11355			};
11356		}
11357
11358		this.enableFilter = true;
11359		this.lastFilterArgs = arguments;
11360
11361		prevEnableUpdate = this.enableUpdate(false);
11362
11363		this.$div.addClass("fancytree-ext-filter");
11364		if (hideMode) {
11365			this.$div.addClass("fancytree-ext-filter-hide");
11366		} else {
11367			this.$div.addClass("fancytree-ext-filter-dimm");
11368		}
11369		this.$div.toggleClass(
11370			"fancytree-ext-filter-hide-expanders",
11371			!!opts.hideExpanders
11372		);
11373		// Reset current filter
11374		this.rootNode.subMatchCount = 0;
11375		this.visit(function (node) {
11376			delete node.match;
11377			delete node.titleWithHighlight;
11378			node.subMatchCount = 0;
11379		});
11380		statusNode = this.getRootNode()._findDirectChild(KeyNoData);
11381		if (statusNode) {
11382			statusNode.remove();
11383		}
11384
11385		// Adjust node.hide, .match, and .subMatchCount properties
11386		treeOpts.autoCollapse = false; // #528
11387
11388		this.visit(function (node) {
11389			if (leavesOnly && node.children != null) {
11390				return;
11391			}
11392			var res = filter(node),
11393				matchedByBranch = false;
11394
11395			if (res === "skip") {
11396				node.visit(function (c) {
11397					c.match = false;
11398				}, true);
11399				return "skip";
11400			}
11401			if (!res && (branchMode || res === "branch") && node.parent.match) {
11402				res = true;
11403				matchedByBranch = true;
11404			}
11405			if (res) {
11406				count++;
11407				node.match = true;
11408				node.visitParents(function (p) {
11409					if (p !== node) {
11410						p.subMatchCount += 1;
11411					}
11412					// Expand match (unless this is no real match, but only a node in a matched branch)
11413					if (opts.autoExpand && !matchedByBranch && !p.expanded) {
11414						p.setExpanded(true, {
11415							noAnimation: true,
11416							noEvents: true,
11417							scrollIntoView: false,
11418						});
11419						p._filterAutoExpanded = true;
11420					}
11421				}, true);
11422			}
11423		});
11424		treeOpts.autoCollapse = prevAutoCollapse;
11425
11426		if (count === 0 && opts.nodata && hideMode) {
11427			statusNode = opts.nodata;
11428			if (typeof statusNode === "function") {
11429				statusNode = statusNode();
11430			}
11431			if (statusNode === true) {
11432				statusNode = {};
11433			} else if (typeof statusNode === "string") {
11434				statusNode = { title: statusNode };
11435			}
11436			statusNode = $.extend(
11437				{
11438					statusNodeType: "nodata",
11439					key: KeyNoData,
11440					title: this.options.strings.noData,
11441				},
11442				statusNode
11443			);
11444
11445			this.getRootNode().addNode(statusNode).match = true;
11446		}
11447		// Redraw whole tree
11448		this._callHook("treeStructureChanged", this, "applyFilter");
11449		// this.render();
11450		this.enableUpdate(prevEnableUpdate);
11451		return count;
11452	};
11453
11454	/**
11455	 * [ext-filter] Dimm or hide nodes.
11456	 *
11457	 * @param {function | string} filter
11458	 * @param {boolean} [opts={autoExpand: false, leavesOnly: false}]
11459	 * @returns {integer} count
11460	 * @alias Fancytree#filterNodes
11461	 * @requires jquery.fancytree.filter.js
11462	 */
11463	$.ui.fancytree._FancytreeClass.prototype.filterNodes = function (
11464		filter,
11465		opts
11466	) {
11467		if (typeof opts === "boolean") {
11468			opts = { leavesOnly: opts };
11469			this.warn(
11470				"Fancytree.filterNodes() leavesOnly option is deprecated since 2.9.0 / 2015-04-19. Use opts.leavesOnly instead."
11471			);
11472		}
11473		return this._applyFilterImpl(filter, false, opts);
11474	};
11475
11476	/**
11477	 * [ext-filter] Dimm or hide whole branches.
11478	 *
11479	 * @param {function | string} filter
11480	 * @param {boolean} [opts={autoExpand: false}]
11481	 * @returns {integer} count
11482	 * @alias Fancytree#filterBranches
11483	 * @requires jquery.fancytree.filter.js
11484	 */
11485	$.ui.fancytree._FancytreeClass.prototype.filterBranches = function (
11486		filter,
11487		opts
11488	) {
11489		return this._applyFilterImpl(filter, true, opts);
11490	};
11491
11492	/**
11493	 * [ext-filter] Re-apply current filter.
11494	 *
11495	 * @returns {integer} count
11496	 * @alias Fancytree#updateFilter
11497	 * @requires jquery.fancytree.filter.js
11498	 * @since 2.38
11499	 */
11500	$.ui.fancytree._FancytreeClass.prototype.updateFilter = function () {
11501		if (
11502			this.enableFilter &&
11503			this.lastFilterArgs &&
11504			this.options.filter.autoApply
11505		) {
11506			this._applyFilterImpl.apply(this, this.lastFilterArgs);
11507		} else {
11508			this.warn("updateFilter(): no filter active.");
11509		}
11510	};
11511
11512	/**
11513	 * [ext-filter] Reset the filter.
11514	 *
11515	 * @alias Fancytree#clearFilter
11516	 * @requires jquery.fancytree.filter.js
11517	 */
11518	$.ui.fancytree._FancytreeClass.prototype.clearFilter = function () {
11519		var $title,
11520			statusNode = this.getRootNode()._findDirectChild(KeyNoData),
11521			escapeTitles = this.options.escapeTitles,
11522			enhanceTitle = this.options.enhanceTitle,
11523			prevEnableUpdate = this.enableUpdate(false);
11524
11525		if (statusNode) {
11526			statusNode.remove();
11527		}
11528		// we also counted root node's subMatchCount
11529		delete this.rootNode.match;
11530		delete this.rootNode.subMatchCount;
11531
11532		this.visit(function (node) {
11533			if (node.match && node.span) {
11534				// #491, #601
11535				$title = $(node.span).find(">span.fancytree-title");
11536				if (escapeTitles) {
11537					$title.text(node.title);
11538				} else {
11539					$title.html(node.title);
11540				}
11541				if (enhanceTitle) {
11542					enhanceTitle(
11543						{ type: "enhanceTitle" },
11544						{ node: node, $title: $title }
11545					);
11546				}
11547			}
11548			delete node.match;
11549			delete node.subMatchCount;
11550			delete node.titleWithHighlight;
11551			if (node.$subMatchBadge) {
11552				node.$subMatchBadge.remove();
11553				delete node.$subMatchBadge;
11554			}
11555			if (node._filterAutoExpanded && node.expanded) {
11556				node.setExpanded(false, {
11557					noAnimation: true,
11558					noEvents: true,
11559					scrollIntoView: false,
11560				});
11561			}
11562			delete node._filterAutoExpanded;
11563		});
11564		this.enableFilter = false;
11565		this.lastFilterArgs = null;
11566		this.$div.removeClass(
11567			"fancytree-ext-filter fancytree-ext-filter-dimm fancytree-ext-filter-hide"
11568		);
11569		this._callHook("treeStructureChanged", this, "clearFilter");
11570		// this.render();
11571		this.enableUpdate(prevEnableUpdate);
11572	};
11573
11574	/**
11575	 * [ext-filter] Return true if a filter is currently applied.
11576	 *
11577	 * @returns {Boolean}
11578	 * @alias Fancytree#isFilterActive
11579	 * @requires jquery.fancytree.filter.js
11580	 * @since 2.13
11581	 */
11582	$.ui.fancytree._FancytreeClass.prototype.isFilterActive = function () {
11583		return !!this.enableFilter;
11584	};
11585
11586	/**
11587	 * [ext-filter] Return true if this node is matched by current filter (or no filter is active).
11588	 *
11589	 * @returns {Boolean}
11590	 * @alias FancytreeNode#isMatched
11591	 * @requires jquery.fancytree.filter.js
11592	 * @since 2.13
11593	 */
11594	$.ui.fancytree._FancytreeNodeClass.prototype.isMatched = function () {
11595		return !(this.tree.enableFilter && !this.match);
11596	};
11597
11598	/*******************************************************************************
11599	 * Extension code
11600	 */
11601	$.ui.fancytree.registerExtension({
11602		name: "filter",
11603		version: "2.38.3",
11604		// Default options for this extension.
11605		options: {
11606			autoApply: true, // Re-apply last filter if lazy data is loaded
11607			autoExpand: false, // Expand all branches that contain matches while filtered
11608			counter: true, // Show a badge with number of matching child nodes near parent icons
11609			fuzzy: false, // Match single characters in order, e.g. 'fb' will match 'FooBar'
11610			hideExpandedCounter: true, // Hide counter badge if parent is expanded
11611			hideExpanders: false, // Hide expanders if all child nodes are hidden by filter
11612			highlight: true, // Highlight matches by wrapping inside <mark> tags
11613			leavesOnly: false, // Match end nodes only
11614			nodata: true, // Display a 'no data' status node if result is empty
11615			mode: "dimm", // Grayout unmatched nodes (pass "hide" to remove unmatched node instead)
11616		},
11617		nodeLoadChildren: function (ctx, source) {
11618			var tree = ctx.tree;
11619
11620			return this._superApply(arguments).done(function () {
11621				if (
11622					tree.enableFilter &&
11623					tree.lastFilterArgs &&
11624					ctx.options.filter.autoApply
11625				) {
11626					tree._applyFilterImpl.apply(tree, tree.lastFilterArgs);
11627				}
11628			});
11629		},
11630		nodeSetExpanded: function (ctx, flag, callOpts) {
11631			var node = ctx.node;
11632
11633			delete node._filterAutoExpanded;
11634			// Make sure counter badge is displayed again, when node is beeing collapsed
11635			if (
11636				!flag &&
11637				ctx.options.filter.hideExpandedCounter &&
11638				node.$subMatchBadge
11639			) {
11640				node.$subMatchBadge.show();
11641			}
11642			return this._superApply(arguments);
11643		},
11644		nodeRenderStatus: function (ctx) {
11645			// Set classes for current status
11646			var res,
11647				node = ctx.node,
11648				tree = ctx.tree,
11649				opts = ctx.options.filter,
11650				$title = $(node.span).find("span.fancytree-title"),
11651				$span = $(node[tree.statusClassPropName]),
11652				enhanceTitle = ctx.options.enhanceTitle,
11653				escapeTitles = ctx.options.escapeTitles;
11654
11655			res = this._super(ctx);
11656			// nothing to do, if node was not yet rendered
11657			if (!$span.length || !tree.enableFilter) {
11658				return res;
11659			}
11660			$span
11661				.toggleClass("fancytree-match", !!node.match)
11662				.toggleClass("fancytree-submatch", !!node.subMatchCount)
11663				.toggleClass(
11664					"fancytree-hide",
11665					!(node.match || node.subMatchCount)
11666				);
11667			// Add/update counter badge
11668			if (
11669				opts.counter &&
11670				node.subMatchCount &&
11671				(!node.isExpanded() || !opts.hideExpandedCounter)
11672			) {
11673				if (!node.$subMatchBadge) {
11674					node.$subMatchBadge = $(
11675						"<span class='fancytree-childcounter'/>"
11676					);
11677					$(
11678						"span.fancytree-icon, span.fancytree-custom-icon",
11679						node.span
11680					).append(node.$subMatchBadge);
11681				}
11682				node.$subMatchBadge.show().text(node.subMatchCount);
11683			} else if (node.$subMatchBadge) {
11684				node.$subMatchBadge.hide();
11685			}
11686			// node.debug("nodeRenderStatus", node.titleWithHighlight, node.title)
11687			// #601: also check for $title.length, because we don't need to render
11688			// if node.span is null (i.e. not rendered)
11689			if (node.span && (!node.isEditing || !node.isEditing.call(node))) {
11690				if (node.titleWithHighlight) {
11691					$title.html(node.titleWithHighlight);
11692				} else if (escapeTitles) {
11693					$title.text(node.title);
11694				} else {
11695					$title.html(node.title);
11696				}
11697				if (enhanceTitle) {
11698					enhanceTitle(
11699						{ type: "enhanceTitle" },
11700						{ node: node, $title: $title }
11701					);
11702				}
11703			}
11704			return res;
11705		},
11706	});
11707	// Value returned by `require('jquery.fancytree..')`
11708	return $.ui.fancytree;
11709}); // End of closure
11710
11711
11712/*! Extension 'jquery.fancytree.glyph.js' *//*!
11713 * jquery.fancytree.glyph.js
11714 *
11715 * Use glyph-fonts, ligature-fonts, or SVG icons instead of icon sprites.
11716 * (Extension module for jquery.fancytree.js: https://github.com/mar10/fancytree/)
11717 *
11718 * Copyright (c) 2008-2023, Martin Wendt (https://wwWendt.de)
11719 *
11720 * Released under the MIT license
11721 * https://github.com/mar10/fancytree/wiki/LicenseInfo
11722 *
11723 * @version 2.38.3
11724 * @date 2023-02-01T20:52:50Z
11725 */
11726
11727(function (factory) {
11728	if (typeof define === "function" && define.amd) {
11729		// AMD. Register as an anonymous module.
11730		define(["jquery", "./jquery.fancytree"], factory);
11731	} else if (typeof module === "object" && module.exports) {
11732		// Node/CommonJS
11733		require("./jquery.fancytree");
11734		module.exports = factory(require("jquery"));
11735	} else {
11736		// Browser globals
11737		factory(jQuery);
11738	}
11739})(function ($) {
11740	"use strict";
11741
11742	/******************************************************************************
11743	 * Private functions and variables
11744	 */
11745
11746	var FT = $.ui.fancytree,
11747		PRESETS = {
11748			awesome3: {
11749				// Outdated!
11750				_addClass: "",
11751				checkbox: "icon-check-empty",
11752				checkboxSelected: "icon-check",
11753				checkboxUnknown: "icon-check icon-muted",
11754				dragHelper: "icon-caret-right",
11755				dropMarker: "icon-caret-right",
11756				error: "icon-exclamation-sign",
11757				expanderClosed: "icon-caret-right",
11758				expanderLazy: "icon-angle-right",
11759				expanderOpen: "icon-caret-down",
11760				loading: "icon-refresh icon-spin",
11761				nodata: "icon-meh",
11762				noExpander: "",
11763				radio: "icon-circle-blank",
11764				radioSelected: "icon-circle",
11765				// radioUnknown: "icon-circle icon-muted",
11766				// Default node icons.
11767				// (Use tree.options.icon callback to define custom icons based on node data)
11768				doc: "icon-file-alt",
11769				docOpen: "icon-file-alt",
11770				folder: "icon-folder-close-alt",
11771				folderOpen: "icon-folder-open-alt",
11772			},
11773			awesome4: {
11774				_addClass: "fa",
11775				checkbox: "fa-square-o",
11776				checkboxSelected: "fa-check-square-o",
11777				checkboxUnknown: "fa-square fancytree-helper-indeterminate-cb",
11778				dragHelper: "fa-arrow-right",
11779				dropMarker: "fa-long-arrow-right",
11780				error: "fa-warning",
11781				expanderClosed: "fa-caret-right",
11782				expanderLazy: "fa-angle-right",
11783				expanderOpen: "fa-caret-down",
11784				// We may prevent wobbling rotations on FF by creating a separate sub element:
11785				loading: { html: "<span class='fa fa-spinner fa-pulse' />" },
11786				nodata: "fa-meh-o",
11787				noExpander: "",
11788				radio: "fa-circle-thin", // "fa-circle-o"
11789				radioSelected: "fa-circle",
11790				// radioUnknown: "fa-dot-circle-o",
11791				// Default node icons.
11792				// (Use tree.options.icon callback to define custom icons based on node data)
11793				doc: "fa-file-o",
11794				docOpen: "fa-file-o",
11795				folder: "fa-folder-o",
11796				folderOpen: "fa-folder-open-o",
11797			},
11798			awesome5: {
11799				// fontawesome 5 have several different base classes
11800				// "far, fas, fal and fab" The rendered svg puts that prefix
11801				// in a different location so we have to keep them separate here
11802				_addClass: "",
11803				checkbox: "far fa-square",
11804				checkboxSelected: "far fa-check-square",
11805				// checkboxUnknown: "far fa-window-close",
11806				checkboxUnknown:
11807					"fas fa-square fancytree-helper-indeterminate-cb",
11808				radio: "far fa-circle",
11809				radioSelected: "fas fa-circle",
11810				radioUnknown: "far fa-dot-circle",
11811				dragHelper: "fas fa-arrow-right",
11812				dropMarker: "fas fa-long-arrow-alt-right",
11813				error: "fas fa-exclamation-triangle",
11814				expanderClosed: "fas fa-caret-right",
11815				expanderLazy: "fas fa-angle-right",
11816				expanderOpen: "fas fa-caret-down",
11817				loading: "fas fa-spinner fa-pulse",
11818				nodata: "far fa-meh",
11819				noExpander: "",
11820				// Default node icons.
11821				// (Use tree.options.icon callback to define custom icons based on node data)
11822				doc: "far fa-file",
11823				docOpen: "far fa-file",
11824				folder: "far fa-folder",
11825				folderOpen: "far fa-folder-open",
11826			},
11827			bootstrap3: {
11828				_addClass: "glyphicon",
11829				checkbox: "glyphicon-unchecked",
11830				checkboxSelected: "glyphicon-check",
11831				checkboxUnknown:
11832					"glyphicon-expand fancytree-helper-indeterminate-cb", // "glyphicon-share",
11833				dragHelper: "glyphicon-play",
11834				dropMarker: "glyphicon-arrow-right",
11835				error: "glyphicon-warning-sign",
11836				expanderClosed: "glyphicon-menu-right", // glyphicon-plus-sign
11837				expanderLazy: "glyphicon-menu-right", // glyphicon-plus-sign
11838				expanderOpen: "glyphicon-menu-down", // glyphicon-minus-sign
11839				loading: "glyphicon-refresh fancytree-helper-spin",
11840				nodata: "glyphicon-info-sign",
11841				noExpander: "",
11842				radio: "glyphicon-remove-circle", // "glyphicon-unchecked",
11843				radioSelected: "glyphicon-ok-circle", // "glyphicon-check",
11844				// radioUnknown: "glyphicon-ban-circle",
11845				// Default node icons.
11846				// (Use tree.options.icon callback to define custom icons based on node data)
11847				doc: "glyphicon-file",
11848				docOpen: "glyphicon-file",
11849				folder: "glyphicon-folder-close",
11850				folderOpen: "glyphicon-folder-open",
11851			},
11852			material: {
11853				_addClass: "material-icons",
11854				checkbox: { text: "check_box_outline_blank" },
11855				checkboxSelected: { text: "check_box" },
11856				checkboxUnknown: { text: "indeterminate_check_box" },
11857				dragHelper: { text: "play_arrow" },
11858				dropMarker: { text: "arrow-forward" },
11859				error: { text: "warning" },
11860				expanderClosed: { text: "chevron_right" },
11861				expanderLazy: { text: "last_page" },
11862				expanderOpen: { text: "expand_more" },
11863				loading: {
11864					text: "autorenew",
11865					addClass: "fancytree-helper-spin",
11866				},
11867				nodata: { text: "info" },
11868				noExpander: { text: "" },
11869				radio: { text: "radio_button_unchecked" },
11870				radioSelected: { text: "radio_button_checked" },
11871				// Default node icons.
11872				// (Use tree.options.icon callback to define custom icons based on node data)
11873				doc: { text: "insert_drive_file" },
11874				docOpen: { text: "insert_drive_file" },
11875				folder: { text: "folder" },
11876				folderOpen: { text: "folder_open" },
11877			},
11878		};
11879
11880	function setIcon(node, span, baseClass, opts, type) {
11881		var map = opts.map,
11882			icon = map[type],
11883			$span = $(span),
11884			$counter = $span.find(".fancytree-childcounter"),
11885			setClass = baseClass + " " + (map._addClass || "");
11886
11887		// #871 Allow a callback
11888		if (typeof icon === "function") {
11889			icon = icon.call(this, node, span, type);
11890		}
11891		// node.debug( "setIcon(" + baseClass + ", " + type + "): " + "oldIcon" + " -> " + icon );
11892		// #871: propsed this, but I am not sure how robust this is, e.g.
11893		// the prefix (fas, far) class changes are not considered?
11894		// if (span.tagName === "svg" && opts.preset === "awesome5") {
11895		// 	// fa5 script converts <i> to <svg> so call a specific handler.
11896		// 	var oldIcon = "fa-" + $span.data("icon");
11897		// 	// node.debug( "setIcon(" + baseClass + ", " + type + "): " + oldIcon + " -> " + icon );
11898		// 	if (typeof oldIcon === "string") {
11899		// 		$span.removeClass(oldIcon);
11900		// 	}
11901		// 	if (typeof icon === "string") {
11902		// 		$span.addClass(icon);
11903		// 	}
11904		// 	return;
11905		// }
11906		if (typeof icon === "string") {
11907			// #883: remove inner html that may be added by prev. mode
11908			span.innerHTML = "";
11909			$span.attr("class", setClass + " " + icon).append($counter);
11910		} else if (icon) {
11911			if (icon.text) {
11912				span.textContent = "" + icon.text;
11913			} else if (icon.html) {
11914				span.innerHTML = icon.html;
11915			} else {
11916				span.innerHTML = "";
11917			}
11918			$span
11919				.attr("class", setClass + " " + (icon.addClass || ""))
11920				.append($counter);
11921		}
11922	}
11923
11924	$.ui.fancytree.registerExtension({
11925		name: "glyph",
11926		version: "2.38.3",
11927		// Default options for this extension.
11928		options: {
11929			preset: null, // 'awesome3', 'awesome4', 'bootstrap3', 'material'
11930			map: {},
11931		},
11932
11933		treeInit: function (ctx) {
11934			var tree = ctx.tree,
11935				opts = ctx.options.glyph;
11936
11937			if (opts.preset) {
11938				FT.assert(
11939					!!PRESETS[opts.preset],
11940					"Invalid value for `options.glyph.preset`: " + opts.preset
11941				);
11942				opts.map = $.extend({}, PRESETS[opts.preset], opts.map);
11943			} else {
11944				tree.warn("ext-glyph: missing `preset` option.");
11945			}
11946			this._superApply(arguments);
11947			tree.$container.addClass("fancytree-ext-glyph");
11948		},
11949		nodeRenderStatus: function (ctx) {
11950			var checkbox,
11951				icon,
11952				res,
11953				span,
11954				node = ctx.node,
11955				$span = $(node.span),
11956				opts = ctx.options.glyph;
11957
11958			res = this._super(ctx);
11959
11960			if (node.isRootNode()) {
11961				return res;
11962			}
11963			span = $span.children(".fancytree-expander").get(0);
11964			if (span) {
11965				// if( node.isLoading() ){
11966				// icon = "loading";
11967				if (node.expanded && node.hasChildren()) {
11968					icon = "expanderOpen";
11969				} else if (node.isUndefined()) {
11970					icon = "expanderLazy";
11971				} else if (node.hasChildren()) {
11972					icon = "expanderClosed";
11973				} else {
11974					icon = "noExpander";
11975				}
11976				// span.className = "fancytree-expander " + map[icon];
11977				setIcon(node, span, "fancytree-expander", opts, icon);
11978			}
11979
11980			if (node.tr) {
11981				span = $("td", node.tr).find(".fancytree-checkbox").get(0);
11982			} else {
11983				span = $span.children(".fancytree-checkbox").get(0);
11984			}
11985			if (span) {
11986				checkbox = FT.evalOption("checkbox", node, node, opts, false);
11987				if (
11988					(node.parent && node.parent.radiogroup) ||
11989					checkbox === "radio"
11990				) {
11991					icon = node.selected ? "radioSelected" : "radio";
11992					setIcon(
11993						node,
11994						span,
11995						"fancytree-checkbox fancytree-radio",
11996						opts,
11997						icon
11998					);
11999				} else {
12000					// eslint-disable-next-line no-nested-ternary
12001					icon = node.selected
12002						? "checkboxSelected"
12003						: node.partsel
12004						? "checkboxUnknown"
12005						: "checkbox";
12006					// span.className = "fancytree-checkbox " + map[icon];
12007					setIcon(node, span, "fancytree-checkbox", opts, icon);
12008				}
12009			}
12010
12011			// Standard icon (note that this does not match .fancytree-custom-icon,
12012			// that might be set by opts.icon callbacks)
12013			span = $span.children(".fancytree-icon").get(0);
12014			if (span) {
12015				if (node.statusNodeType) {
12016					icon = node.statusNodeType; // loading, error
12017				} else if (node.folder) {
12018					icon =
12019						node.expanded && node.hasChildren()
12020							? "folderOpen"
12021							: "folder";
12022				} else {
12023					icon = node.expanded ? "docOpen" : "doc";
12024				}
12025				setIcon(node, span, "fancytree-icon", opts, icon);
12026			}
12027			return res;
12028		},
12029		nodeSetStatus: function (ctx, status, message, details) {
12030			var res,
12031				span,
12032				opts = ctx.options.glyph,
12033				node = ctx.node;
12034
12035			res = this._superApply(arguments);
12036
12037			if (
12038				status === "error" ||
12039				status === "loading" ||
12040				status === "nodata"
12041			) {
12042				if (node.parent) {
12043					span = $(".fancytree-expander", node.span).get(0);
12044					if (span) {
12045						setIcon(node, span, "fancytree-expander", opts, status);
12046					}
12047				} else {
12048					//
12049					span = $(
12050						".fancytree-statusnode-" + status,
12051						node[this.nodeContainerAttrName]
12052					)
12053						.find(".fancytree-icon")
12054						.get(0);
12055					if (span) {
12056						setIcon(node, span, "fancytree-icon", opts, status);
12057					}
12058				}
12059			}
12060			return res;
12061		},
12062	});
12063	// Value returned by `require('jquery.fancytree..')`
12064	return $.ui.fancytree;
12065}); // End of closure
12066
12067
12068/*! Extension 'jquery.fancytree.gridnav.js' *//*!
12069 * jquery.fancytree.gridnav.js
12070 *
12071 * Support keyboard navigation for trees with embedded input controls.
12072 * (Extension module for jquery.fancytree.js: https://github.com/mar10/fancytree/)
12073 *
12074 * Copyright (c) 2008-2023, Martin Wendt (https://wwWendt.de)
12075 *
12076 * Released under the MIT license
12077 * https://github.com/mar10/fancytree/wiki/LicenseInfo
12078 *
12079 * @version 2.38.3
12080 * @date 2023-02-01T20:52:50Z
12081 */
12082
12083(function (factory) {
12084	if (typeof define === "function" && define.amd) {
12085		// AMD. Register as an anonymous module.
12086		define([
12087			"jquery",
12088			"./jquery.fancytree",
12089			"./jquery.fancytree.table",
12090		], factory);
12091	} else if (typeof module === "object" && module.exports) {
12092		// Node/CommonJS
12093		require("./jquery.fancytree.table"); // core + table
12094		module.exports = factory(require("jquery"));
12095	} else {
12096		// Browser globals
12097		factory(jQuery);
12098	}
12099})(function ($) {
12100	"use strict";
12101
12102	/*******************************************************************************
12103	 * Private functions and variables
12104	 */
12105
12106	// Allow these navigation keys even when input controls are focused
12107
12108	var KC = $.ui.keyCode,
12109		// which keys are *not* handled by embedded control, but passed to tree
12110		// navigation handler:
12111		NAV_KEYS = {
12112			text: [KC.UP, KC.DOWN],
12113			checkbox: [KC.UP, KC.DOWN, KC.LEFT, KC.RIGHT],
12114			link: [KC.UP, KC.DOWN, KC.LEFT, KC.RIGHT],
12115			radiobutton: [KC.UP, KC.DOWN, KC.LEFT, KC.RIGHT],
12116			"select-one": [KC.LEFT, KC.RIGHT],
12117			"select-multiple": [KC.LEFT, KC.RIGHT],
12118		};
12119
12120	/* Calculate TD column index (considering colspans).*/
12121	function getColIdx($tr, $td) {
12122		var colspan,
12123			td = $td.get(0),
12124			idx = 0;
12125
12126		$tr.children().each(function () {
12127			if (this === td) {
12128				return false;
12129			}
12130			colspan = $(this).prop("colspan");
12131			idx += colspan ? colspan : 1;
12132		});
12133		return idx;
12134	}
12135
12136	/* Find TD at given column index (considering colspans).*/
12137	function findTdAtColIdx($tr, colIdx) {
12138		var colspan,
12139			res = null,
12140			idx = 0;
12141
12142		$tr.children().each(function () {
12143			if (idx >= colIdx) {
12144				res = $(this);
12145				return false;
12146			}
12147			colspan = $(this).prop("colspan");
12148			idx += colspan ? colspan : 1;
12149		});
12150		return res;
12151	}
12152
12153	/* Find adjacent cell for a given direction. Skip empty cells and consider merged cells */
12154	function findNeighbourTd($target, keyCode) {
12155		var $tr,
12156			colIdx,
12157			$td = $target.closest("td"),
12158			$tdNext = null;
12159
12160		switch (keyCode) {
12161			case KC.LEFT:
12162				$tdNext = $td.prev();
12163				break;
12164			case KC.RIGHT:
12165				$tdNext = $td.next();
12166				break;
12167			case KC.UP:
12168			case KC.DOWN:
12169				$tr = $td.parent();
12170				colIdx = getColIdx($tr, $td);
12171				while (true) {
12172					$tr = keyCode === KC.UP ? $tr.prev() : $tr.next();
12173					if (!$tr.length) {
12174						break;
12175					}
12176					// Skip hidden rows
12177					if ($tr.is(":hidden")) {
12178						continue;
12179					}
12180					// Find adjacent cell in the same column
12181					$tdNext = findTdAtColIdx($tr, colIdx);
12182					// Skip cells that don't conatain a focusable element
12183					if ($tdNext && $tdNext.find(":input,a").length) {
12184						break;
12185					}
12186				}
12187				break;
12188		}
12189		return $tdNext;
12190	}
12191
12192	/*******************************************************************************
12193	 * Extension code
12194	 */
12195	$.ui.fancytree.registerExtension({
12196		name: "gridnav",
12197		version: "2.38.3",
12198		// Default options for this extension.
12199		options: {
12200			autofocusInput: false, // Focus first embedded input if node gets activated
12201			handleCursorKeys: true, // Allow UP/DOWN in inputs to move to prev/next node
12202		},
12203
12204		treeInit: function (ctx) {
12205			// gridnav requires the table extension to be loaded before itself
12206			this._requireExtension("table", true, true);
12207			this._superApply(arguments);
12208
12209			this.$container.addClass("fancytree-ext-gridnav");
12210
12211			// Activate node if embedded input gets focus (due to a click)
12212			this.$container.on("focusin", function (event) {
12213				var ctx2,
12214					node = $.ui.fancytree.getNode(event.target);
12215
12216				if (node && !node.isActive()) {
12217					// Call node.setActive(), but also pass the event
12218					ctx2 = ctx.tree._makeHookContext(node, event);
12219					ctx.tree._callHook("nodeSetActive", ctx2, true);
12220				}
12221			});
12222		},
12223		nodeSetActive: function (ctx, flag, callOpts) {
12224			var $outer,
12225				opts = ctx.options.gridnav,
12226				node = ctx.node,
12227				event = ctx.originalEvent || {},
12228				triggeredByInput = $(event.target).is(":input");
12229
12230			flag = flag !== false;
12231
12232			this._superApply(arguments);
12233
12234			if (flag) {
12235				if (ctx.options.titlesTabbable) {
12236					if (!triggeredByInput) {
12237						$(node.span).find("span.fancytree-title").focus();
12238						node.setFocus();
12239					}
12240					// If one node is tabbable, the container no longer needs to be
12241					ctx.tree.$container.attr("tabindex", "-1");
12242					// ctx.tree.$container.removeAttr("tabindex");
12243				} else if (opts.autofocusInput && !triggeredByInput) {
12244					// Set focus to input sub input (if node was clicked, but not
12245					// when TAB was pressed )
12246					$outer = $(node.tr || node.span);
12247					$outer.find(":input:enabled").first().focus();
12248				}
12249			}
12250		},
12251		nodeKeydown: function (ctx) {
12252			var inputType,
12253				handleKeys,
12254				$td,
12255				opts = ctx.options.gridnav,
12256				event = ctx.originalEvent,
12257				$target = $(event.target);
12258
12259			if ($target.is(":input:enabled")) {
12260				inputType = $target.prop("type");
12261			} else if ($target.is("a")) {
12262				inputType = "link";
12263			}
12264			// ctx.tree.debug("ext-gridnav nodeKeydown", event, inputType);
12265
12266			if (inputType && opts.handleCursorKeys) {
12267				handleKeys = NAV_KEYS[inputType];
12268				if (handleKeys && $.inArray(event.which, handleKeys) >= 0) {
12269					$td = findNeighbourTd($target, event.which);
12270					if ($td && $td.length) {
12271						// ctx.node.debug("ignore keydown in input", event.which, handleKeys);
12272						$td.find(":input:enabled,a").focus();
12273						// Prevent Fancytree default navigation
12274						return false;
12275					}
12276				}
12277				return true;
12278			}
12279			// ctx.tree.debug("ext-gridnav NOT HANDLED", event, inputType);
12280			return this._superApply(arguments);
12281		},
12282	});
12283	// Value returned by `require('jquery.fancytree..')`
12284	return $.ui.fancytree;
12285}); // End of closure
12286
12287
12288/*! Extension 'jquery.fancytree.multi.js' *//*!
12289 * jquery.fancytree.multi.js
12290 *
12291 * Allow multiple selection of nodes  by mouse or keyboard.
12292 * (Extension module for jquery.fancytree.js: https://github.com/mar10/fancytree/)
12293 *
12294 * Copyright (c) 2008-2023, Martin Wendt (https://wwWendt.de)
12295 *
12296 * Released under the MIT license
12297 * https://github.com/mar10/fancytree/wiki/LicenseInfo
12298 *
12299 * @version 2.38.3
12300 * @date 2023-02-01T20:52:50Z
12301 */
12302
12303(function (factory) {
12304	if (typeof define === "function" && define.amd) {
12305		// AMD. Register as an anonymous module.
12306		define(["jquery", "./jquery.fancytree"], factory);
12307	} else if (typeof module === "object" && module.exports) {
12308		// Node/CommonJS
12309		require("./jquery.fancytree");
12310		module.exports = factory(require("jquery"));
12311	} else {
12312		// Browser globals
12313		factory(jQuery);
12314	}
12315})(function ($) {
12316	"use strict";
12317
12318	/*******************************************************************************
12319	 * Private functions and variables
12320	 */
12321
12322	// var isMac = /Mac/.test(navigator.platform);
12323
12324	/*******************************************************************************
12325	 * Extension code
12326	 */
12327	$.ui.fancytree.registerExtension({
12328		name: "multi",
12329		version: "2.38.3",
12330		// Default options for this extension.
12331		options: {
12332			allowNoSelect: false, //
12333			mode: "sameParent", //
12334			// Events:
12335			// beforeSelect: $.noop  // Return false to prevent cancel/save (data.input is available)
12336		},
12337
12338		treeInit: function (ctx) {
12339			this._superApply(arguments);
12340			this.$container.addClass("fancytree-ext-multi");
12341			if (ctx.options.selectMode === 1) {
12342				$.error(
12343					"Fancytree ext-multi: selectMode: 1 (single) is not compatible."
12344				);
12345			}
12346		},
12347		nodeClick: function (ctx) {
12348			var //pluginOpts = ctx.options.multi,
12349				tree = ctx.tree,
12350				node = ctx.node,
12351				activeNode = tree.getActiveNode() || tree.getFirstChild(),
12352				isCbClick = ctx.targetType === "checkbox",
12353				isExpanderClick = ctx.targetType === "expander",
12354				eventStr = $.ui.fancytree.eventToString(ctx.originalEvent);
12355
12356			switch (eventStr) {
12357				case "click":
12358					if (isExpanderClick) {
12359						break;
12360					} // Default handler will expand/collapse
12361					if (!isCbClick) {
12362						tree.selectAll(false);
12363						// Select clicked node (radio-button  mode)
12364						node.setSelected();
12365					}
12366					// Default handler will toggle checkbox clicks and activate
12367					break;
12368				case "shift+click":
12369					// node.debug("click")
12370					tree.visitRows(
12371						function (n) {
12372							// n.debug("click2", n===node, node)
12373							n.setSelected();
12374							if (n === node) {
12375								return false;
12376							}
12377						},
12378						{
12379							start: activeNode,
12380							reverse: activeNode.isBelowOf(node),
12381						}
12382					);
12383					break;
12384				case "ctrl+click":
12385				case "meta+click": // Mac: [Command]
12386					node.toggleSelected();
12387					return;
12388			}
12389			return this._superApply(arguments);
12390		},
12391		nodeKeydown: function (ctx) {
12392			var tree = ctx.tree,
12393				node = ctx.node,
12394				event = ctx.originalEvent,
12395				eventStr = $.ui.fancytree.eventToString(event);
12396
12397			switch (eventStr) {
12398				case "up":
12399				case "down":
12400					tree.selectAll(false);
12401					node.navigate(event.which, true);
12402					tree.getActiveNode().setSelected();
12403					break;
12404				case "shift+up":
12405				case "shift+down":
12406					node.navigate(event.which, true);
12407					tree.getActiveNode().setSelected();
12408					break;
12409			}
12410			return this._superApply(arguments);
12411		},
12412	});
12413	// Value returned by `require('jquery.fancytree..')`
12414	return $.ui.fancytree;
12415}); // End of closure
12416
12417
12418/*! Extension 'jquery.fancytree.persist.js' *//*!
12419 * jquery.fancytree.persist.js
12420 *
12421 * Persist tree status in cookiesRemove or highlight tree nodes, based on a filter.
12422 * (Extension module for jquery.fancytree.js: https://github.com/mar10/fancytree/)
12423 *
12424 * @depends: js-cookie or jquery-cookie
12425 *
12426 * Copyright (c) 2008-2023, Martin Wendt (https://wwWendt.de)
12427 *
12428 * Released under the MIT license
12429 * https://github.com/mar10/fancytree/wiki/LicenseInfo
12430 *
12431 * @version 2.38.3
12432 * @date 2023-02-01T20:52:50Z
12433 */
12434
12435(function (factory) {
12436	if (typeof define === "function" && define.amd) {
12437		// AMD. Register as an anonymous module.
12438		define(["jquery", "./jquery.fancytree"], factory);
12439	} else if (typeof module === "object" && module.exports) {
12440		// Node/CommonJS
12441		require("./jquery.fancytree");
12442		module.exports = factory(require("jquery"));
12443	} else {
12444		// Browser globals
12445		factory(jQuery);
12446	}
12447})(function ($) {
12448	"use strict";
12449	/* global Cookies:false */
12450
12451	/*******************************************************************************
12452	 * Private functions and variables
12453	 */
12454	var cookieStore = null,
12455		localStorageStore = null,
12456		sessionStorageStore = null,
12457		_assert = $.ui.fancytree.assert,
12458		ACTIVE = "active",
12459		EXPANDED = "expanded",
12460		FOCUS = "focus",
12461		SELECTED = "selected";
12462
12463	// Accessing window.xxxStorage may raise security exceptions (see #1022)
12464	try {
12465		_assert(window.localStorage && window.localStorage.getItem);
12466		localStorageStore = {
12467			get: function (key) {
12468				return window.localStorage.getItem(key);
12469			},
12470			set: function (key, value) {
12471				window.localStorage.setItem(key, value);
12472			},
12473			remove: function (key) {
12474				window.localStorage.removeItem(key);
12475			},
12476		};
12477	} catch (e) {
12478		$.ui.fancytree.warn("Could not access window.localStorage", e);
12479	}
12480
12481	try {
12482		_assert(window.sessionStorage && window.sessionStorage.getItem);
12483		sessionStorageStore = {
12484			get: function (key) {
12485				return window.sessionStorage.getItem(key);
12486			},
12487			set: function (key, value) {
12488				window.sessionStorage.setItem(key, value);
12489			},
12490			remove: function (key) {
12491				window.sessionStorage.removeItem(key);
12492			},
12493		};
12494	} catch (e) {
12495		$.ui.fancytree.warn("Could not access window.sessionStorage", e);
12496	}
12497
12498	if (typeof Cookies === "function") {
12499		// Assume https://github.com/js-cookie/js-cookie
12500		cookieStore = {
12501			get: Cookies.get,
12502			set: function (key, value) {
12503				Cookies.set(key, value, this.options.persist.cookie);
12504			},
12505			remove: Cookies.remove,
12506		};
12507	} else if ($ && typeof $.cookie === "function") {
12508		// Fall back to https://github.com/carhartl/jquery-cookie
12509		cookieStore = {
12510			get: $.cookie,
12511			set: function (key, value) {
12512				$.cookie(key, value, this.options.persist.cookie);
12513			},
12514			remove: $.removeCookie,
12515		};
12516	}
12517
12518	/* Recursively load lazy nodes
12519	 * @param {string} mode 'load', 'expand', false
12520	 */
12521	function _loadLazyNodes(tree, local, keyList, mode, dfd) {
12522		var i,
12523			key,
12524			l,
12525			node,
12526			foundOne = false,
12527			expandOpts = tree.options.persist.expandOpts,
12528			deferredList = [],
12529			missingKeyList = [];
12530
12531		keyList = keyList || [];
12532		dfd = dfd || $.Deferred();
12533
12534		for (i = 0, l = keyList.length; i < l; i++) {
12535			key = keyList[i];
12536			node = tree.getNodeByKey(key);
12537			if (node) {
12538				if (mode && node.isUndefined()) {
12539					foundOne = true;
12540					tree.debug(
12541						"_loadLazyNodes: " + node + " is lazy: loading..."
12542					);
12543					if (mode === "expand") {
12544						deferredList.push(node.setExpanded(true, expandOpts));
12545					} else {
12546						deferredList.push(node.load());
12547					}
12548				} else {
12549					tree.debug("_loadLazyNodes: " + node + " already loaded.");
12550					node.setExpanded(true, expandOpts);
12551				}
12552			} else {
12553				missingKeyList.push(key);
12554				tree.debug("_loadLazyNodes: " + node + " was not yet found.");
12555			}
12556		}
12557
12558		$.when.apply($, deferredList).always(function () {
12559			// All lazy-expands have finished
12560			if (foundOne && missingKeyList.length > 0) {
12561				// If we read new nodes from server, try to resolve yet-missing keys
12562				_loadLazyNodes(tree, local, missingKeyList, mode, dfd);
12563			} else {
12564				if (missingKeyList.length) {
12565					tree.warn(
12566						"_loadLazyNodes: could not load those keys: ",
12567						missingKeyList
12568					);
12569					for (i = 0, l = missingKeyList.length; i < l; i++) {
12570						key = keyList[i];
12571						local._appendKey(EXPANDED, keyList[i], false);
12572					}
12573				}
12574				dfd.resolve();
12575			}
12576		});
12577		return dfd;
12578	}
12579
12580	/**
12581	 * [ext-persist] Remove persistence data of the given type(s).
12582	 * Called like
12583	 *     $.ui.fancytree.getTree("#tree").clearCookies("active expanded focus selected");
12584	 *
12585	 * @alias Fancytree#clearPersistData
12586	 * @requires jquery.fancytree.persist.js
12587	 */
12588	$.ui.fancytree._FancytreeClass.prototype.clearPersistData = function (
12589		types
12590	) {
12591		var local = this.ext.persist,
12592			prefix = local.cookiePrefix;
12593
12594		types = types || "active expanded focus selected";
12595		if (types.indexOf(ACTIVE) >= 0) {
12596			local._data(prefix + ACTIVE, null);
12597		}
12598		if (types.indexOf(EXPANDED) >= 0) {
12599			local._data(prefix + EXPANDED, null);
12600		}
12601		if (types.indexOf(FOCUS) >= 0) {
12602			local._data(prefix + FOCUS, null);
12603		}
12604		if (types.indexOf(SELECTED) >= 0) {
12605			local._data(prefix + SELECTED, null);
12606		}
12607	};
12608
12609	$.ui.fancytree._FancytreeClass.prototype.clearCookies = function (types) {
12610		this.warn(
12611			"'tree.clearCookies()' is deprecated since v2.27.0: use 'clearPersistData()' instead."
12612		);
12613		return this.clearPersistData(types);
12614	};
12615
12616	/**
12617	 * [ext-persist] Return persistence information from cookies
12618	 *
12619	 * Called like
12620	 *     $.ui.fancytree.getTree("#tree").getPersistData();
12621	 *
12622	 * @alias Fancytree#getPersistData
12623	 * @requires jquery.fancytree.persist.js
12624	 */
12625	$.ui.fancytree._FancytreeClass.prototype.getPersistData = function () {
12626		var local = this.ext.persist,
12627			prefix = local.cookiePrefix,
12628			delim = local.cookieDelimiter,
12629			res = {};
12630
12631		res[ACTIVE] = local._data(prefix + ACTIVE);
12632		res[EXPANDED] = (local._data(prefix + EXPANDED) || "").split(delim);
12633		res[SELECTED] = (local._data(prefix + SELECTED) || "").split(delim);
12634		res[FOCUS] = local._data(prefix + FOCUS);
12635		return res;
12636	};
12637
12638	/******************************************************************************
12639	 * Extension code
12640	 */
12641	$.ui.fancytree.registerExtension({
12642		name: "persist",
12643		version: "2.38.3",
12644		// Default options for this extension.
12645		options: {
12646			cookieDelimiter: "~",
12647			cookiePrefix: undefined, // 'fancytree-<treeId>-' by default
12648			cookie: {
12649				raw: false,
12650				expires: "",
12651				path: "",
12652				domain: "",
12653				secure: false,
12654			},
12655			expandLazy: false, // true: recursively expand and load lazy nodes
12656			expandOpts: undefined, // optional `opts` argument passed to setExpanded()
12657			fireActivate: true, // false: suppress `activate` event after active node was restored
12658			overrideSource: true, // true: cookie takes precedence over `source` data attributes.
12659			store: "auto", // 'cookie': force cookie, 'local': force localStore, 'session': force sessionStore
12660			types: "active expanded focus selected",
12661		},
12662
12663		/* Generic read/write string data to cookie, sessionStorage or localStorage. */
12664		_data: function (key, value) {
12665			var store = this._local.store;
12666
12667			if (value === undefined) {
12668				return store.get.call(this, key);
12669			} else if (value === null) {
12670				store.remove.call(this, key);
12671			} else {
12672				store.set.call(this, key, value);
12673			}
12674		},
12675
12676		/* Append `key` to a cookie. */
12677		_appendKey: function (type, key, flag) {
12678			key = "" + key; // #90
12679			var local = this._local,
12680				instOpts = this.options.persist,
12681				delim = instOpts.cookieDelimiter,
12682				cookieName = local.cookiePrefix + type,
12683				data = local._data(cookieName),
12684				keyList = data ? data.split(delim) : [],
12685				idx = $.inArray(key, keyList);
12686			// Remove, even if we add a key,  so the key is always the last entry
12687			if (idx >= 0) {
12688				keyList.splice(idx, 1);
12689			}
12690			// Append key to cookie
12691			if (flag) {
12692				keyList.push(key);
12693			}
12694			local._data(cookieName, keyList.join(delim));
12695		},
12696
12697		treeInit: function (ctx) {
12698			var tree = ctx.tree,
12699				opts = ctx.options,
12700				local = this._local,
12701				instOpts = this.options.persist;
12702
12703			// // For 'auto' or 'cookie' mode, the cookie plugin must be available
12704			// _assert((instOpts.store !== "auto" && instOpts.store !== "cookie") || cookieStore,
12705			// 	"Missing required plugin for 'persist' extension: js.cookie.js or jquery.cookie.js");
12706
12707			local.cookiePrefix =
12708				instOpts.cookiePrefix || "fancytree-" + tree._id + "-";
12709			local.storeActive = instOpts.types.indexOf(ACTIVE) >= 0;
12710			local.storeExpanded = instOpts.types.indexOf(EXPANDED) >= 0;
12711			local.storeSelected = instOpts.types.indexOf(SELECTED) >= 0;
12712			local.storeFocus = instOpts.types.indexOf(FOCUS) >= 0;
12713			local.store = null;
12714
12715			if (instOpts.store === "auto") {
12716				instOpts.store = localStorageStore ? "local" : "cookie";
12717			}
12718			if ($.isPlainObject(instOpts.store)) {
12719				local.store = instOpts.store;
12720			} else if (instOpts.store === "cookie") {
12721				local.store = cookieStore;
12722			} else if (instOpts.store === "local") {
12723				local.store =
12724					instOpts.store === "local"
12725						? localStorageStore
12726						: sessionStorageStore;
12727			} else if (instOpts.store === "session") {
12728				local.store =
12729					instOpts.store === "local"
12730						? localStorageStore
12731						: sessionStorageStore;
12732			}
12733			_assert(local.store, "Need a valid store.");
12734
12735			// Bind init-handler to apply cookie state
12736			tree.$div.on("fancytreeinit", function (event) {
12737				if (
12738					tree._triggerTreeEvent("beforeRestore", null, {}) === false
12739				) {
12740					return;
12741				}
12742
12743				var cookie,
12744					dfd,
12745					i,
12746					keyList,
12747					node,
12748					prevFocus = local._data(local.cookiePrefix + FOCUS), // record this before node.setActive() overrides it;
12749					noEvents = instOpts.fireActivate === false;
12750
12751				// tree.debug("document.cookie:", document.cookie);
12752
12753				cookie = local._data(local.cookiePrefix + EXPANDED);
12754				keyList = cookie && cookie.split(instOpts.cookieDelimiter);
12755
12756				if (local.storeExpanded) {
12757					// Recursively load nested lazy nodes if expandLazy is 'expand' or 'load'
12758					// Also remove expand-cookies for unmatched nodes
12759					dfd = _loadLazyNodes(
12760						tree,
12761						local,
12762						keyList,
12763						instOpts.expandLazy ? "expand" : false,
12764						null
12765					);
12766				} else {
12767					// nothing to do
12768					dfd = new $.Deferred().resolve();
12769				}
12770
12771				dfd.done(function () {
12772					if (local.storeSelected) {
12773						cookie = local._data(local.cookiePrefix + SELECTED);
12774						if (cookie) {
12775							keyList = cookie.split(instOpts.cookieDelimiter);
12776							for (i = 0; i < keyList.length; i++) {
12777								node = tree.getNodeByKey(keyList[i]);
12778								if (node) {
12779									if (
12780										node.selected === undefined ||
12781										(instOpts.overrideSource &&
12782											node.selected === false)
12783									) {
12784										//									node.setSelected();
12785										node.selected = true;
12786										node.renderStatus();
12787									}
12788								} else {
12789									// node is no longer member of the tree: remove from cookie also
12790									local._appendKey(
12791										SELECTED,
12792										keyList[i],
12793										false
12794									);
12795								}
12796							}
12797						}
12798						// In selectMode 3 we have to fix the child nodes, since we
12799						// only stored the selected *top* nodes
12800						if (tree.options.selectMode === 3) {
12801							tree.visit(function (n) {
12802								if (n.selected) {
12803									n.fixSelection3AfterClick();
12804									return "skip";
12805								}
12806							});
12807						}
12808					}
12809					if (local.storeActive) {
12810						cookie = local._data(local.cookiePrefix + ACTIVE);
12811						if (
12812							cookie &&
12813							(opts.persist.overrideSource || !tree.activeNode)
12814						) {
12815							node = tree.getNodeByKey(cookie);
12816							if (node) {
12817								node.debug("persist: set active", cookie);
12818								// We only want to set the focus if the container
12819								// had the keyboard focus before
12820								node.setActive(true, {
12821									noFocus: true,
12822									noEvents: noEvents,
12823								});
12824							}
12825						}
12826					}
12827					if (local.storeFocus && prevFocus) {
12828						node = tree.getNodeByKey(prevFocus);
12829						if (node) {
12830							// node.debug("persist: set focus", cookie);
12831							if (tree.options.titlesTabbable) {
12832								$(node.span).find(".fancytree-title").focus();
12833							} else {
12834								$(tree.$container).focus();
12835							}
12836							// node.setFocus();
12837						}
12838					}
12839					tree._triggerTreeEvent("restore", null, {});
12840				});
12841			});
12842			// Init the tree
12843			return this._superApply(arguments);
12844		},
12845		nodeSetActive: function (ctx, flag, callOpts) {
12846			var res,
12847				local = this._local;
12848
12849			flag = flag !== false;
12850			res = this._superApply(arguments);
12851
12852			if (local.storeActive) {
12853				local._data(
12854					local.cookiePrefix + ACTIVE,
12855					this.activeNode ? this.activeNode.key : null
12856				);
12857			}
12858			return res;
12859		},
12860		nodeSetExpanded: function (ctx, flag, callOpts) {
12861			var res,
12862				node = ctx.node,
12863				local = this._local;
12864
12865			flag = flag !== false;
12866			res = this._superApply(arguments);
12867
12868			if (local.storeExpanded) {
12869				local._appendKey(EXPANDED, node.key, flag);
12870			}
12871			return res;
12872		},
12873		nodeSetFocus: function (ctx, flag) {
12874			var res,
12875				local = this._local;
12876
12877			flag = flag !== false;
12878			res = this._superApply(arguments);
12879
12880			if (local.storeFocus) {
12881				local._data(
12882					local.cookiePrefix + FOCUS,
12883					this.focusNode ? this.focusNode.key : null
12884				);
12885			}
12886			return res;
12887		},
12888		nodeSetSelected: function (ctx, flag, callOpts) {
12889			var res,
12890				selNodes,
12891				tree = ctx.tree,
12892				node = ctx.node,
12893				local = this._local;
12894
12895			flag = flag !== false;
12896			res = this._superApply(arguments);
12897
12898			if (local.storeSelected) {
12899				if (tree.options.selectMode === 3) {
12900					// In selectMode 3 we only store the the selected *top* nodes.
12901					// De-selecting a node may also de-select some parents, so we
12902					// calculate the current status again
12903					selNodes = $.map(tree.getSelectedNodes(true), function (n) {
12904						return n.key;
12905					});
12906					selNodes = selNodes.join(
12907						ctx.options.persist.cookieDelimiter
12908					);
12909					local._data(local.cookiePrefix + SELECTED, selNodes);
12910				} else {
12911					// beforeSelect can prevent the change - flag doesn't reflect the node.selected state
12912					local._appendKey(SELECTED, node.key, node.selected);
12913				}
12914			}
12915			return res;
12916		},
12917	});
12918	// Value returned by `require('jquery.fancytree..')`
12919	return $.ui.fancytree;
12920}); // End of closure
12921
12922
12923/*! Extension 'jquery.fancytree.table.js' *//*!
12924 * jquery.fancytree.table.js
12925 *
12926 * Render tree as table (aka 'tree grid', 'table tree').
12927 * (Extension module for jquery.fancytree.js: https://github.com/mar10/fancytree/)
12928 *
12929 * Copyright (c) 2008-2023, Martin Wendt (https://wwWendt.de)
12930 *
12931 * Released under the MIT license
12932 * https://github.com/mar10/fancytree/wiki/LicenseInfo
12933 *
12934 * @version 2.38.3
12935 * @date 2023-02-01T20:52:50Z
12936 */
12937
12938(function (factory) {
12939	if (typeof define === "function" && define.amd) {
12940		// AMD. Register as an anonymous module.
12941		define(["jquery", "./jquery.fancytree"], factory);
12942	} else if (typeof module === "object" && module.exports) {
12943		// Node/CommonJS
12944		require("./jquery.fancytree");
12945		module.exports = factory(require("jquery"));
12946	} else {
12947		// Browser globals
12948		factory(jQuery);
12949	}
12950})(function ($) {
12951	"use strict";
12952
12953	/******************************************************************************
12954	 * Private functions and variables
12955	 */
12956	var _assert = $.ui.fancytree.assert;
12957
12958	function insertFirstChild(referenceNode, newNode) {
12959		referenceNode.insertBefore(newNode, referenceNode.firstChild);
12960	}
12961
12962	function insertSiblingAfter(referenceNode, newNode) {
12963		referenceNode.parentNode.insertBefore(
12964			newNode,
12965			referenceNode.nextSibling
12966		);
12967	}
12968
12969	/* Show/hide all rows that are structural descendants of `parent`. */
12970	function setChildRowVisibility(parent, flag) {
12971		parent.visit(function (node) {
12972			var tr = node.tr;
12973			// currentFlag = node.hide ? false : flag; // fix for ext-filter
12974			if (tr) {
12975				tr.style.display = node.hide || !flag ? "none" : "";
12976			}
12977			if (!node.expanded) {
12978				return "skip";
12979			}
12980		});
12981	}
12982
12983	/* Find node that is rendered in previous row. */
12984	function findPrevRowNode(node) {
12985		var i,
12986			last,
12987			prev,
12988			parent = node.parent,
12989			siblings = parent ? parent.children : null;
12990
12991		if (siblings && siblings.length > 1 && siblings[0] !== node) {
12992			// use the lowest descendant of the preceeding sibling
12993			i = $.inArray(node, siblings);
12994			prev = siblings[i - 1];
12995			_assert(prev.tr);
12996			// descend to lowest child (with a <tr> tag)
12997			while (prev.children && prev.children.length) {
12998				last = prev.children[prev.children.length - 1];
12999				if (!last.tr) {
13000					break;
13001				}
13002				prev = last;
13003			}
13004		} else {
13005			// if there is no preceding sibling, use the direct parent
13006			prev = parent;
13007		}
13008		return prev;
13009	}
13010
13011	$.ui.fancytree.registerExtension({
13012		name: "table",
13013		version: "2.38.3",
13014		// Default options for this extension.
13015		options: {
13016			checkboxColumnIdx: null, // render the checkboxes into the this column index (default: nodeColumnIdx)
13017			indentation: 16, // indent every node level by 16px
13018			mergeStatusColumns: true, // display 'nodata', 'loading', 'error' centered in a single, merged TR
13019			nodeColumnIdx: 0, // render node expander, icon, and title to this column (default: #0)
13020		},
13021		// Overide virtual methods for this extension.
13022		// `this`       : is this extension object
13023		// `this._super`: the virtual function that was overriden (member of prev. extension or Fancytree)
13024		treeInit: function (ctx) {
13025			var i,
13026				n,
13027				$row,
13028				$tbody,
13029				tree = ctx.tree,
13030				opts = ctx.options,
13031				tableOpts = opts.table,
13032				$table = tree.widget.element;
13033
13034			if (tableOpts.customStatus != null) {
13035				if (opts.renderStatusColumns == null) {
13036					tree.warn(
13037						"The 'customStatus' option is deprecated since v2.15.0. Use 'renderStatusColumns' instead."
13038					);
13039					opts.renderStatusColumns = tableOpts.customStatus;
13040				} else {
13041					$.error(
13042						"The 'customStatus' option is deprecated since v2.15.0. Use 'renderStatusColumns' only instead."
13043					);
13044				}
13045			}
13046			if (opts.renderStatusColumns) {
13047				if (opts.renderStatusColumns === true) {
13048					opts.renderStatusColumns = opts.renderColumns;
13049					// } else if( opts.renderStatusColumns === "wide" ) {
13050					// 	opts.renderStatusColumns = _renderStatusNodeWide;
13051				}
13052			}
13053
13054			$table.addClass("fancytree-container fancytree-ext-table");
13055			$tbody = $table.find(">tbody");
13056			if (!$tbody.length) {
13057				// TODO: not sure if we can rely on browsers to insert missing <tbody> before <tr>s:
13058				if ($table.find(">tr").length) {
13059					$.error(
13060						"Expected table > tbody > tr. If you see this please open an issue."
13061					);
13062				}
13063				$tbody = $("<tbody>").appendTo($table);
13064			}
13065
13066			tree.tbody = $tbody[0];
13067
13068			// Prepare row templates:
13069			// Determine column count from table header if any
13070			tree.columnCount = $("thead >tr", $table)
13071				.last()
13072				.find(">th", $table).length;
13073			// Read TR templates from tbody if any
13074			$row = $tbody.children("tr").first();
13075			if ($row.length) {
13076				n = $row.children("td").length;
13077				if (tree.columnCount && n !== tree.columnCount) {
13078					tree.warn(
13079						"Column count mismatch between thead (" +
13080							tree.columnCount +
13081							") and tbody (" +
13082							n +
13083							"): using tbody."
13084					);
13085					tree.columnCount = n;
13086				}
13087				$row = $row.clone();
13088			} else {
13089				// Only thead is defined: create default row markup
13090				_assert(
13091					tree.columnCount >= 1,
13092					"Need either <thead> or <tbody> with <td> elements to determine column count."
13093				);
13094				$row = $("<tr />");
13095				for (i = 0; i < tree.columnCount; i++) {
13096					$row.append("<td />");
13097				}
13098			}
13099			$row.find(">td")
13100				.eq(tableOpts.nodeColumnIdx)
13101				.html("<span class='fancytree-node' />");
13102			if (opts.aria) {
13103				$row.attr("role", "row");
13104				$row.find("td").attr("role", "gridcell");
13105			}
13106			tree.rowFragment = document.createDocumentFragment();
13107			tree.rowFragment.appendChild($row.get(0));
13108
13109			// // If tbody contains a second row, use this as status node template
13110			// $row = $tbody.children("tr").eq(1);
13111			// if( $row.length === 0 ) {
13112			// 	tree.statusRowFragment = tree.rowFragment;
13113			// } else {
13114			// 	$row = $row.clone();
13115			// 	tree.statusRowFragment = document.createDocumentFragment();
13116			// 	tree.statusRowFragment.appendChild($row.get(0));
13117			// }
13118			//
13119			$tbody.empty();
13120
13121			// Make sure that status classes are set on the node's <tr> elements
13122			tree.statusClassPropName = "tr";
13123			tree.ariaPropName = "tr";
13124			this.nodeContainerAttrName = "tr";
13125
13126			// #489: make sure $container is set to <table>, even if ext-dnd is listed before ext-table
13127			tree.$container = $table;
13128
13129			this._superApply(arguments);
13130
13131			// standard Fancytree created a root UL
13132			$(tree.rootNode.ul).remove();
13133			tree.rootNode.ul = null;
13134
13135			// Add container to the TAB chain
13136			// #577: Allow to set tabindex to "0", "-1" and ""
13137			this.$container.attr("tabindex", opts.tabindex);
13138			// this.$container.attr("tabindex", opts.tabbable ? "0" : "-1");
13139			if (opts.aria) {
13140				tree.$container
13141					.attr("role", "treegrid")
13142					.attr("aria-readonly", true);
13143			}
13144		},
13145		nodeRemoveChildMarkup: function (ctx) {
13146			var node = ctx.node;
13147			//		node.debug("nodeRemoveChildMarkup()");
13148			node.visit(function (n) {
13149				if (n.tr) {
13150					$(n.tr).remove();
13151					n.tr = null;
13152				}
13153			});
13154		},
13155		nodeRemoveMarkup: function (ctx) {
13156			var node = ctx.node;
13157			//		node.debug("nodeRemoveMarkup()");
13158			if (node.tr) {
13159				$(node.tr).remove();
13160				node.tr = null;
13161			}
13162			this.nodeRemoveChildMarkup(ctx);
13163		},
13164		/* Override standard render. */
13165		nodeRender: function (ctx, force, deep, collapsed, _recursive) {
13166			var children,
13167				firstTr,
13168				i,
13169				l,
13170				newRow,
13171				prevNode,
13172				prevTr,
13173				subCtx,
13174				tree = ctx.tree,
13175				node = ctx.node,
13176				opts = ctx.options,
13177				isRootNode = !node.parent;
13178
13179			if (tree._enableUpdate === false) {
13180				// $.ui.fancytree.debug("*** nodeRender _enableUpdate: false");
13181				return;
13182			}
13183			if (!_recursive) {
13184				ctx.hasCollapsedParents = node.parent && !node.parent.expanded;
13185			}
13186			// $.ui.fancytree.debug("*** nodeRender " + node + ", isRoot=" + isRootNode, "tr=" + node.tr, "hcp=" + ctx.hasCollapsedParents, "parent.tr=" + (node.parent && node.parent.tr));
13187			if (!isRootNode) {
13188				if (node.tr && force) {
13189					this.nodeRemoveMarkup(ctx);
13190				}
13191				if (node.tr) {
13192					if (force) {
13193						// Set icon, link, and title (normally this is only required on initial render)
13194						this.nodeRenderTitle(ctx); // triggers renderColumns()
13195					} else {
13196						// Update element classes according to node state
13197						this.nodeRenderStatus(ctx);
13198					}
13199				} else {
13200					if (ctx.hasCollapsedParents && !deep) {
13201						// #166: we assume that the parent will be (recursively) rendered
13202						// later anyway.
13203						// node.debug("nodeRender ignored due to unrendered parent");
13204						return;
13205					}
13206					// Create new <tr> after previous row
13207					// if( node.isStatusNode() ) {
13208					// 	newRow = tree.statusRowFragment.firstChild.cloneNode(true);
13209					// } else {
13210					newRow = tree.rowFragment.firstChild.cloneNode(true);
13211					// }
13212					prevNode = findPrevRowNode(node);
13213					// $.ui.fancytree.debug("*** nodeRender " + node + ": prev: " + prevNode.key);
13214					_assert(prevNode);
13215					if (collapsed === true && _recursive) {
13216						// hide all child rows, so we can use an animation to show it later
13217						newRow.style.display = "none";
13218					} else if (deep && ctx.hasCollapsedParents) {
13219						// also hide this row if deep === true but any parent is collapsed
13220						newRow.style.display = "none";
13221						//					newRow.style.color = "red";
13222					}
13223					if (prevNode.tr) {
13224						insertSiblingAfter(prevNode.tr, newRow);
13225					} else {
13226						_assert(
13227							!prevNode.parent,
13228							"prev. row must have a tr, or be system root"
13229						);
13230						// tree.tbody.appendChild(newRow);
13231						insertFirstChild(tree.tbody, newRow); // #675
13232					}
13233					node.tr = newRow;
13234					if (node.key && opts.generateIds) {
13235						node.tr.id = opts.idPrefix + node.key;
13236					}
13237					node.tr.ftnode = node;
13238					// if(opts.aria){
13239					// 	$(node.tr).attr("aria-labelledby", "ftal_" + opts.idPrefix + node.key);
13240					// }
13241					node.span = $("span.fancytree-node", node.tr).get(0);
13242					// Set icon, link, and title (normally this is only required on initial render)
13243					this.nodeRenderTitle(ctx);
13244					// Allow tweaking, binding, after node was created for the first time
13245					//				tree._triggerNodeEvent("createNode", ctx);
13246					if (opts.createNode) {
13247						opts.createNode.call(tree, { type: "createNode" }, ctx);
13248					}
13249				}
13250			}
13251			// Allow tweaking after node state was rendered
13252			//		tree._triggerNodeEvent("renderNode", ctx);
13253			if (opts.renderNode) {
13254				opts.renderNode.call(tree, { type: "renderNode" }, ctx);
13255			}
13256			// Visit child nodes
13257			// Add child markup
13258			children = node.children;
13259			if (children && (isRootNode || deep || node.expanded)) {
13260				for (i = 0, l = children.length; i < l; i++) {
13261					subCtx = $.extend({}, ctx, { node: children[i] });
13262					subCtx.hasCollapsedParents =
13263						subCtx.hasCollapsedParents || !node.expanded;
13264					this.nodeRender(subCtx, force, deep, collapsed, true);
13265				}
13266			}
13267			// Make sure, that <tr> order matches node.children order.
13268			if (children && !_recursive) {
13269				// we only have to do it once, for the root branch
13270				prevTr = node.tr || null;
13271				firstTr = tree.tbody.firstChild;
13272				// Iterate over all descendants
13273				node.visit(function (n) {
13274					if (n.tr) {
13275						if (
13276							!n.parent.expanded &&
13277							n.tr.style.display !== "none"
13278						) {
13279							// fix after a node was dropped over a collapsed
13280							n.tr.style.display = "none";
13281							setChildRowVisibility(n, false);
13282						}
13283						if (n.tr.previousSibling !== prevTr) {
13284							node.debug("_fixOrder: mismatch at node: " + n);
13285							var nextTr = prevTr ? prevTr.nextSibling : firstTr;
13286							tree.tbody.insertBefore(n.tr, nextTr);
13287						}
13288						prevTr = n.tr;
13289					}
13290				});
13291			}
13292			// Update element classes according to node state
13293			// if(!isRootNode){
13294			// 	this.nodeRenderStatus(ctx);
13295			// }
13296		},
13297		nodeRenderTitle: function (ctx, title) {
13298			var $cb,
13299				res,
13300				tree = ctx.tree,
13301				node = ctx.node,
13302				opts = ctx.options,
13303				isStatusNode = node.isStatusNode();
13304
13305			res = this._super(ctx, title);
13306
13307			if (node.isRootNode()) {
13308				return res;
13309			}
13310			// Move checkbox to custom column
13311			if (
13312				opts.checkbox &&
13313				!isStatusNode &&
13314				opts.table.checkboxColumnIdx != null
13315			) {
13316				$cb = $("span.fancytree-checkbox", node.span); //.detach();
13317				$(node.tr)
13318					.find("td")
13319					.eq(+opts.table.checkboxColumnIdx)
13320					.html($cb);
13321			}
13322			// Update element classes according to node state
13323			this.nodeRenderStatus(ctx);
13324
13325			if (isStatusNode) {
13326				if (opts.renderStatusColumns) {
13327					// Let user code write column content
13328					opts.renderStatusColumns.call(
13329						tree,
13330						{ type: "renderStatusColumns" },
13331						ctx
13332					);
13333				} else if (opts.table.mergeStatusColumns && node.isTopLevel()) {
13334					$(node.tr)
13335						.find(">td")
13336						.eq(0)
13337						.prop("colspan", tree.columnCount)
13338						.text(node.title)
13339						.addClass("fancytree-status-merged")
13340						.nextAll()
13341						.remove();
13342				} // else: default rendering for status node: leave other cells empty
13343			} else if (opts.renderColumns) {
13344				opts.renderColumns.call(tree, { type: "renderColumns" }, ctx);
13345			}
13346			return res;
13347		},
13348		nodeRenderStatus: function (ctx) {
13349			var indent,
13350				node = ctx.node,
13351				opts = ctx.options;
13352
13353			this._super(ctx);
13354
13355			$(node.tr).removeClass("fancytree-node");
13356			// indent
13357			indent = (node.getLevel() - 1) * opts.table.indentation;
13358			if (opts.rtl) {
13359				$(node.span).css({ paddingRight: indent + "px" });
13360			} else {
13361				$(node.span).css({ paddingLeft: indent + "px" });
13362			}
13363		},
13364		/* Expand node, return Deferred.promise. */
13365		nodeSetExpanded: function (ctx, flag, callOpts) {
13366			// flag defaults to true
13367			flag = flag !== false;
13368
13369			if ((ctx.node.expanded && flag) || (!ctx.node.expanded && !flag)) {
13370				// Expanded state isn't changed - just call base implementation
13371				return this._superApply(arguments);
13372			}
13373
13374			var dfd = new $.Deferred(),
13375				subOpts = $.extend({}, callOpts, {
13376					noEvents: true,
13377					noAnimation: true,
13378				});
13379
13380			callOpts = callOpts || {};
13381
13382			function _afterExpand(ok, args) {
13383				// ctx.tree.info("ok:" + ok, args);
13384				if (ok) {
13385					// #1108 minExpandLevel: 2 together with table extension does not work
13386					// don't call when 'ok' is false:
13387					setChildRowVisibility(ctx.node, flag);
13388					if (
13389						flag &&
13390						ctx.options.autoScroll &&
13391						!callOpts.noAnimation &&
13392						ctx.node.hasChildren()
13393					) {
13394						// Scroll down to last child, but keep current node visible
13395						ctx.node
13396							.getLastChild()
13397							.scrollIntoView(true, { topNode: ctx.node })
13398							.always(function () {
13399								if (!callOpts.noEvents) {
13400									ctx.tree._triggerNodeEvent(
13401										flag ? "expand" : "collapse",
13402										ctx
13403									);
13404								}
13405								dfd.resolveWith(ctx.node);
13406							});
13407					} else {
13408						if (!callOpts.noEvents) {
13409							ctx.tree._triggerNodeEvent(
13410								flag ? "expand" : "collapse",
13411								ctx
13412							);
13413						}
13414						dfd.resolveWith(ctx.node);
13415					}
13416				} else {
13417					if (!callOpts.noEvents) {
13418						ctx.tree._triggerNodeEvent(
13419							flag ? "expand" : "collapse",
13420							ctx
13421						);
13422					}
13423					dfd.rejectWith(ctx.node);
13424				}
13425			}
13426			// Call base-expand with disabled events and animation
13427			this._super(ctx, flag, subOpts)
13428				.done(function () {
13429					_afterExpand(true, arguments);
13430				})
13431				.fail(function () {
13432					_afterExpand(false, arguments);
13433				});
13434			return dfd.promise();
13435		},
13436		nodeSetStatus: function (ctx, status, message, details) {
13437			if (status === "ok") {
13438				var node = ctx.node,
13439					firstChild = node.children ? node.children[0] : null;
13440				if (firstChild && firstChild.isStatusNode()) {
13441					$(firstChild.tr).remove();
13442				}
13443			}
13444			return this._superApply(arguments);
13445		},
13446		treeClear: function (ctx) {
13447			this.nodeRemoveChildMarkup(this._makeHookContext(this.rootNode));
13448			return this._superApply(arguments);
13449		},
13450		treeDestroy: function (ctx) {
13451			this.$container.find("tbody").empty();
13452			if (this.$source) {
13453				this.$source.removeClass("fancytree-helper-hidden");
13454			}
13455			return this._superApply(arguments);
13456		},
13457		/*,
13458	treeSetFocus: function(ctx, flag) {
13459//	        alert("treeSetFocus" + ctx.tree.$container);
13460		ctx.tree.$container.focus();
13461		$.ui.fancytree.focusTree = ctx.tree;
13462	}*/
13463	});
13464	// Value returned by `require('jquery.fancytree..')`
13465	return $.ui.fancytree;
13466}); // End of closure
13467
13468
13469/*! Extension 'jquery.fancytree.themeroller.js' *//*!
13470 * jquery.fancytree.themeroller.js
13471 *
13472 * Enable jQuery UI ThemeRoller styles.
13473 * (Extension module for jquery.fancytree.js: https://github.com/mar10/fancytree/)
13474 *
13475 * @see http://jqueryui.com/themeroller/
13476 *
13477 * Copyright (c) 2008-2023, Martin Wendt (https://wwWendt.de)
13478 *
13479 * Released under the MIT license
13480 * https://github.com/mar10/fancytree/wiki/LicenseInfo
13481 *
13482 * @version 2.38.3
13483 * @date 2023-02-01T20:52:50Z
13484 */
13485
13486(function (factory) {
13487	if (typeof define === "function" && define.amd) {
13488		// AMD. Register as an anonymous module.
13489		define(["jquery", "./jquery.fancytree"], factory);
13490	} else if (typeof module === "object" && module.exports) {
13491		// Node/CommonJS
13492		require("./jquery.fancytree");
13493		module.exports = factory(require("jquery"));
13494	} else {
13495		// Browser globals
13496		factory(jQuery);
13497	}
13498})(function ($) {
13499	"use strict";
13500
13501	/*******************************************************************************
13502	 * Extension code
13503	 */
13504	$.ui.fancytree.registerExtension({
13505		name: "themeroller",
13506		version: "2.38.3",
13507		// Default options for this extension.
13508		options: {
13509			activeClass: "ui-state-active", // Class added to active node
13510			// activeClass: "ui-state-highlight",
13511			addClass: "ui-corner-all", // Class added to all nodes
13512			focusClass: "ui-state-focus", // Class added to focused node
13513			hoverClass: "ui-state-hover", // Class added to hovered node
13514			selectedClass: "ui-state-highlight", // Class added to selected nodes
13515			// selectedClass: "ui-state-active"
13516		},
13517
13518		treeInit: function (ctx) {
13519			var $el = ctx.widget.element,
13520				opts = ctx.options.themeroller;
13521
13522			this._superApply(arguments);
13523
13524			if ($el[0].nodeName === "TABLE") {
13525				$el.addClass("ui-widget ui-corner-all");
13526				$el.find(">thead tr").addClass("ui-widget-header");
13527				$el.find(">tbody").addClass("ui-widget-conent");
13528			} else {
13529				$el.addClass("ui-widget ui-widget-content ui-corner-all");
13530			}
13531
13532			$el.on(
13533				"mouseenter mouseleave",
13534				".fancytree-node",
13535				function (event) {
13536					var node = $.ui.fancytree.getNode(event.target),
13537						flag = event.type === "mouseenter";
13538
13539					$(node.tr ? node.tr : node.span).toggleClass(
13540						opts.hoverClass + " " + opts.addClass,
13541						flag
13542					);
13543				}
13544			);
13545		},
13546		treeDestroy: function (ctx) {
13547			this._superApply(arguments);
13548			ctx.widget.element.removeClass(
13549				"ui-widget ui-widget-content ui-corner-all"
13550			);
13551		},
13552		nodeRenderStatus: function (ctx) {
13553			var classes = {},
13554				node = ctx.node,
13555				$el = $(node.tr ? node.tr : node.span),
13556				opts = ctx.options.themeroller;
13557
13558			this._super(ctx);
13559			/*
13560		.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.
13561		.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.
13562		.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.
13563
13564		.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.
13565		.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.
13566		.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.
13567		.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.
13568*/
13569			// Set ui-state-* class (handle the case that the same class is assigned
13570			// to different states)
13571			classes[opts.activeClass] = false;
13572			classes[opts.focusClass] = false;
13573			classes[opts.selectedClass] = false;
13574			if (node.isActive()) {
13575				classes[opts.activeClass] = true;
13576			}
13577			if (node.hasFocus()) {
13578				classes[opts.focusClass] = true;
13579			}
13580			// activeClass takes precedence before selectedClass:
13581			if (node.isSelected() && !node.isActive()) {
13582				classes[opts.selectedClass] = true;
13583			}
13584			$el.toggleClass(opts.activeClass, classes[opts.activeClass]);
13585			$el.toggleClass(opts.focusClass, classes[opts.focusClass]);
13586			$el.toggleClass(opts.selectedClass, classes[opts.selectedClass]);
13587			// Additional classes (e.g. 'ui-corner-all')
13588			$el.addClass(opts.addClass);
13589		},
13590	});
13591	// Value returned by `require('jquery.fancytree..')`
13592	return $.ui.fancytree;
13593}); // End of closure
13594
13595
13596/*! Extension 'jquery.fancytree.wide.js' *//*!
13597 * jquery.fancytree.wide.js
13598 * Support for 100% wide selection bars.
13599 * (Extension module for jquery.fancytree.js: https://github.com/mar10/fancytree/)
13600 *
13601 * Copyright (c) 2008-2023, Martin Wendt (https://wwWendt.de)
13602 *
13603 * Released under the MIT license
13604 * https://github.com/mar10/fancytree/wiki/LicenseInfo
13605 *
13606 * @version 2.38.3
13607 * @date 2023-02-01T20:52:50Z
13608 */
13609
13610(function (factory) {
13611	if (typeof define === "function" && define.amd) {
13612		// AMD. Register as an anonymous module.
13613		define(["jquery", "./jquery.fancytree"], factory);
13614	} else if (typeof module === "object" && module.exports) {
13615		// Node/CommonJS
13616		require("./jquery.fancytree");
13617		module.exports = factory(require("jquery"));
13618	} else {
13619		// Browser globals
13620		factory(jQuery);
13621	}
13622})(function ($) {
13623	"use strict";
13624
13625	var reNumUnit = /^([+-]?(?:\d+|\d*\.\d+))([a-z]*|%)$/; // split "1.5em" to ["1.5", "em"]
13626
13627	/*******************************************************************************
13628	 * Private functions and variables
13629	 */
13630	// var _assert = $.ui.fancytree.assert;
13631
13632	/* Calculate inner width without scrollbar */
13633	// function realInnerWidth($el) {
13634	// 	// http://blog.jquery.com/2012/08/16/jquery-1-8-box-sizing-width-csswidth-and-outerwidth/
13635	// //	inst.contWidth = parseFloat(this.$container.css("width"), 10);
13636	// 	// 'Client width without scrollbar' - 'padding'
13637	// 	return $el[0].clientWidth - ($el.innerWidth() -  parseFloat($el.css("width"), 10));
13638	// }
13639
13640	/* Create a global embedded CSS style for the tree. */
13641	function defineHeadStyleElement(id, cssText) {
13642		id = "fancytree-style-" + id;
13643		var $headStyle = $("#" + id);
13644
13645		if (!cssText) {
13646			$headStyle.remove();
13647			return null;
13648		}
13649		if (!$headStyle.length) {
13650			$headStyle = $("<style />")
13651				.attr("id", id)
13652				.addClass("fancytree-style")
13653				.prop("type", "text/css")
13654				.appendTo("head");
13655		}
13656		try {
13657			$headStyle.html(cssText);
13658		} catch (e) {
13659			// fix for IE 6-8
13660			$headStyle[0].styleSheet.cssText = cssText;
13661		}
13662		return $headStyle;
13663	}
13664
13665	/* Calculate the CSS rules that indent title spans. */
13666	function renderLevelCss(
13667		containerId,
13668		depth,
13669		levelOfs,
13670		lineOfs,
13671		labelOfs,
13672		measureUnit
13673	) {
13674		var i,
13675			prefix = "#" + containerId + " span.fancytree-level-",
13676			rules = [];
13677
13678		for (i = 0; i < depth; i++) {
13679			rules.push(
13680				prefix +
13681					(i + 1) +
13682					" span.fancytree-title { padding-left: " +
13683					(i * levelOfs + lineOfs) +
13684					measureUnit +
13685					"; }"
13686			);
13687		}
13688		// Some UI animations wrap the UL inside a DIV and set position:relative on both.
13689		// This breaks the left:0 and padding-left:nn settings of the title
13690		rules.push(
13691			"#" +
13692				containerId +
13693				" div.ui-effects-wrapper ul li span.fancytree-title, " +
13694				"#" +
13695				containerId +
13696				" li.fancytree-animating span.fancytree-title " + // #716
13697				"{ padding-left: " +
13698				labelOfs +
13699				measureUnit +
13700				"; position: static; width: auto; }"
13701		);
13702		return rules.join("\n");
13703	}
13704
13705	// /**
13706	//  * [ext-wide] Recalculate the width of the selection bar after the tree container
13707	//  * was resized.<br>
13708	//  * May be called explicitly on container resize, since there is no resize event
13709	//  * for DIV tags.
13710	//  *
13711	//  * @alias Fancytree#wideUpdate
13712	//  * @requires jquery.fancytree.wide.js
13713	//  */
13714	// $.ui.fancytree._FancytreeClass.prototype.wideUpdate = function(){
13715	// 	var inst = this.ext.wide,
13716	// 		prevCw = inst.contWidth,
13717	// 		prevLo = inst.lineOfs;
13718
13719	// 	inst.contWidth = realInnerWidth(this.$container);
13720	// 	// Each title is precceeded by 2 or 3 icons (16px + 3 margin)
13721	// 	//     + 1px title border and 3px title padding
13722	// 	// TODO: use code from treeInit() below
13723	// 	inst.lineOfs = (this.options.checkbox ? 3 : 2) * 19;
13724	// 	if( prevCw !== inst.contWidth || prevLo !== inst.lineOfs ) {
13725	// 		this.debug("wideUpdate: " + inst.contWidth);
13726	// 		this.visit(function(node){
13727	// 			node.tree._callHook("nodeRenderTitle", node);
13728	// 		});
13729	// 	}
13730	// };
13731
13732	/*******************************************************************************
13733	 * Extension code
13734	 */
13735	$.ui.fancytree.registerExtension({
13736		name: "wide",
13737		version: "2.38.3",
13738		// Default options for this extension.
13739		options: {
13740			iconWidth: null, // Adjust this if @fancy-icon-width != "16px"
13741			iconSpacing: null, // Adjust this if @fancy-icon-spacing != "3px"
13742			labelSpacing: null, // Adjust this if padding between icon and label != "3px"
13743			levelOfs: null, // Adjust this if ul padding != "16px"
13744		},
13745
13746		treeCreate: function (ctx) {
13747			this._superApply(arguments);
13748			this.$container.addClass("fancytree-ext-wide");
13749
13750			var containerId,
13751				cssText,
13752				iconSpacingUnit,
13753				labelSpacingUnit,
13754				iconWidthUnit,
13755				levelOfsUnit,
13756				instOpts = ctx.options.wide,
13757				// css sniffing
13758				$dummyLI = $(
13759					"<li id='fancytreeTemp'><span class='fancytree-node'><span class='fancytree-icon' /><span class='fancytree-title' /></span><ul />"
13760				).appendTo(ctx.tree.$container),
13761				$dummyIcon = $dummyLI.find(".fancytree-icon"),
13762				$dummyUL = $dummyLI.find("ul"),
13763				// $dummyTitle = $dummyLI.find(".fancytree-title"),
13764				iconSpacing =
13765					instOpts.iconSpacing || $dummyIcon.css("margin-left"),
13766				iconWidth = instOpts.iconWidth || $dummyIcon.css("width"),
13767				labelSpacing = instOpts.labelSpacing || "3px",
13768				levelOfs = instOpts.levelOfs || $dummyUL.css("padding-left");
13769
13770			$dummyLI.remove();
13771
13772			iconSpacingUnit = iconSpacing.match(reNumUnit)[2];
13773			iconSpacing = parseFloat(iconSpacing, 10);
13774			labelSpacingUnit = labelSpacing.match(reNumUnit)[2];
13775			labelSpacing = parseFloat(labelSpacing, 10);
13776			iconWidthUnit = iconWidth.match(reNumUnit)[2];
13777			iconWidth = parseFloat(iconWidth, 10);
13778			levelOfsUnit = levelOfs.match(reNumUnit)[2];
13779			if (
13780				iconSpacingUnit !== iconWidthUnit ||
13781				levelOfsUnit !== iconWidthUnit ||
13782				labelSpacingUnit !== iconWidthUnit
13783			) {
13784				$.error(
13785					"iconWidth, iconSpacing, and levelOfs must have the same css measure unit"
13786				);
13787			}
13788			this._local.measureUnit = iconWidthUnit;
13789			this._local.levelOfs = parseFloat(levelOfs);
13790			this._local.lineOfs =
13791				(1 +
13792					(ctx.options.checkbox ? 1 : 0) +
13793					(ctx.options.icon === false ? 0 : 1)) *
13794					(iconWidth + iconSpacing) +
13795				iconSpacing;
13796			this._local.labelOfs = labelSpacing;
13797			this._local.maxDepth = 10;
13798
13799			// Get/Set a unique Id on the container (if not already exists)
13800			containerId = this.$container.uniqueId().attr("id");
13801			// Generated css rules for some levels (extended on demand)
13802			cssText = renderLevelCss(
13803				containerId,
13804				this._local.maxDepth,
13805				this._local.levelOfs,
13806				this._local.lineOfs,
13807				this._local.labelOfs,
13808				this._local.measureUnit
13809			);
13810			defineHeadStyleElement(containerId, cssText);
13811		},
13812		treeDestroy: function (ctx) {
13813			// Remove generated css rules
13814			defineHeadStyleElement(this.$container.attr("id"), null);
13815			return this._superApply(arguments);
13816		},
13817		nodeRenderStatus: function (ctx) {
13818			var containerId,
13819				cssText,
13820				res,
13821				node = ctx.node,
13822				level = node.getLevel();
13823
13824			res = this._super(ctx);
13825			// Generate some more level-n rules if required
13826			if (level > this._local.maxDepth) {
13827				containerId = this.$container.attr("id");
13828				this._local.maxDepth *= 2;
13829				node.debug(
13830					"Define global ext-wide css up to level " +
13831						this._local.maxDepth
13832				);
13833				cssText = renderLevelCss(
13834					containerId,
13835					this._local.maxDepth,
13836					this._local.levelOfs,
13837					this._local.lineOfs,
13838					this._local.labelSpacing,
13839					this._local.measureUnit
13840				);
13841				defineHeadStyleElement(containerId, cssText);
13842			}
13843			// Add level-n class to apply indentation padding.
13844			// (Setting element style would not work, since it cannot easily be
13845			// overriden while animations run)
13846			$(node.span).addClass("fancytree-level-" + level);
13847			return res;
13848		},
13849	});
13850	// Value returned by `require('jquery.fancytree..')`
13851	return $.ui.fancytree;
13852}); // End of closure
13853
13854// Value returned by `require('jquery.fancytree')`
13855return $.ui.fancytree;
13856}));  // End of closure
13857