/*! jQuery Fancytree Plugin - 2.30.2 - 2019-01-13T08:17:01Z * https://github.com/mar10/fancytree * Copyright (c) 2019 Martin Wendt; Licensed MIT */ /*! jQuery UI - v1.12.1 - 2018-05-20 * http://jqueryui.com * Includes: widget.js, position.js, keycode.js, scroll-parent.js, unique-id.js * Copyright jQuery Foundation and other contributors; Licensed MIT */ /* NOTE: Original jQuery UI wrapper was replaced with a simple IIFE. See README-Fancytree.md */ (function( $ ) { $.ui = $.ui || {}; var version = $.ui.version = "1.12.1"; /*! * jQuery UI Widget 1.12.1 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors * Released under the MIT license. * http://jquery.org/license */ //>>label: Widget //>>group: Core //>>description: Provides a factory for creating stateful widgets with a common API. //>>docs: http://api.jqueryui.com/jQuery.widget/ //>>demos: http://jqueryui.com/widget/ var widgetUuid = 0; var widgetSlice = Array.prototype.slice; $.cleanData = ( function( orig ) { return function( elems ) { var events, elem, i; for ( i = 0; ( elem = elems[ i ] ) != null; i++ ) { try { // Only trigger remove when necessary to save time events = $._data( elem, "events" ); if ( events && events.remove ) { $( elem ).triggerHandler( "remove" ); } // Http://bugs.jquery.com/ticket/8235 } catch ( e ) {} } orig( elems ); }; } )( $.cleanData ); $.widget = function( name, base, prototype ) { var existingConstructor, constructor, basePrototype; // ProxiedPrototype allows the provided prototype to remain unmodified // so that it can be used as a mixin for multiple widgets (#8876) var proxiedPrototype = {}; var namespace = name.split( "." )[ 0 ]; name = name.split( "." )[ 1 ]; var fullName = namespace + "-" + name; if ( !prototype ) { prototype = base; base = $.Widget; } if ( $.isArray( prototype ) ) { prototype = $.extend.apply( null, [ {} ].concat( prototype ) ); } // Create selector for plugin $.expr[ ":" ][ fullName.toLowerCase() ] = function( elem ) { return !!$.data( elem, fullName ); }; $[ namespace ] = $[ namespace ] || {}; existingConstructor = $[ namespace ][ name ]; constructor = $[ namespace ][ name ] = function( options, element ) { // Allow instantiation without "new" keyword if ( !this._createWidget ) { return new constructor( options, element ); } // Allow instantiation without initializing for simple inheritance // must use "new" keyword (the code above always passes args) if ( arguments.length ) { this._createWidget( options, element ); } }; // Extend with the existing constructor to carry over any static properties $.extend( constructor, existingConstructor, { version: prototype.version, // Copy the object used to create the prototype in case we need to // redefine the widget later _proto: $.extend( {}, prototype ), // Track widgets that inherit from this widget in case this widget is // redefined after a widget inherits from it _childConstructors: [] } ); basePrototype = new base(); // We need to make the options hash a property directly on the new instance // otherwise we'll modify the options hash on the prototype that we're // inheriting from basePrototype.options = $.widget.extend( {}, basePrototype.options ); $.each( prototype, function( prop, value ) { if ( !$.isFunction( value ) ) { proxiedPrototype[ prop ] = value; return; } proxiedPrototype[ prop ] = ( function() { function _super() { return base.prototype[ prop ].apply( this, arguments ); } function _superApply( args ) { return base.prototype[ prop ].apply( this, args ); } return function() { var __super = this._super; var __superApply = this._superApply; var returnValue; this._super = _super; this._superApply = _superApply; returnValue = value.apply( this, arguments ); this._super = __super; this._superApply = __superApply; return returnValue; }; } )(); } ); constructor.prototype = $.widget.extend( basePrototype, { // TODO: remove support for widgetEventPrefix // always use the name + a colon as the prefix, e.g., draggable:start // don't prefix for widgets that aren't DOM-based widgetEventPrefix: existingConstructor ? ( basePrototype.widgetEventPrefix || name ) : name }, proxiedPrototype, { constructor: constructor, namespace: namespace, widgetName: name, widgetFullName: fullName } ); // If this widget is being redefined then we need to find all widgets that // are inheriting from it and redefine all of them so that they inherit from // the new version of this widget. We're essentially trying to replace one // level in the prototype chain. if ( existingConstructor ) { $.each( existingConstructor._childConstructors, function( i, child ) { var childPrototype = child.prototype; // Redefine the child widget using the same prototype that was // originally used, but inherit from the new version of the base $.widget( childPrototype.namespace + "." + childPrototype.widgetName, constructor, child._proto ); } ); // Remove the list of existing child constructors from the old constructor // so the old child constructors can be garbage collected delete existingConstructor._childConstructors; } else { base._childConstructors.push( constructor ); } $.widget.bridge( name, constructor ); return constructor; }; $.widget.extend = function( target ) { var input = widgetSlice.call( arguments, 1 ); var inputIndex = 0; var inputLength = input.length; var key; var value; for ( ; inputIndex < inputLength; inputIndex++ ) { for ( key in input[ inputIndex ] ) { value = input[ inputIndex ][ key ]; if ( input[ inputIndex ].hasOwnProperty( key ) && value !== undefined ) { // Clone objects if ( $.isPlainObject( value ) ) { target[ key ] = $.isPlainObject( target[ key ] ) ? $.widget.extend( {}, target[ key ], value ) : // Don't extend strings, arrays, etc. with objects $.widget.extend( {}, value ); // Copy everything else by reference } else { target[ key ] = value; } } } } return target; }; $.widget.bridge = function( name, object ) { var fullName = object.prototype.widgetFullName || name; $.fn[ name ] = function( options ) { var isMethodCall = typeof options === "string"; var args = widgetSlice.call( arguments, 1 ); var returnValue = this; if ( isMethodCall ) { // If this is an empty collection, we need to have the instance method // return undefined instead of the jQuery instance if ( !this.length && options === "instance" ) { returnValue = undefined; } else { this.each( function() { var methodValue; var instance = $.data( this, fullName ); if ( options === "instance" ) { returnValue = instance; return false; } if ( !instance ) { return $.error( "cannot call methods on " + name + " prior to initialization; " + "attempted to call method '" + options + "'" ); } if ( !$.isFunction( instance[ options ] ) || options.charAt( 0 ) === "_" ) { return $.error( "no such method '" + options + "' for " + name + " widget instance" ); } methodValue = instance[ options ].apply( instance, args ); if ( methodValue !== instance && methodValue !== undefined ) { returnValue = methodValue && methodValue.jquery ? returnValue.pushStack( methodValue.get() ) : methodValue; return false; } } ); } } else { // Allow multiple hashes to be passed on init if ( args.length ) { options = $.widget.extend.apply( null, [ options ].concat( args ) ); } this.each( function() { var instance = $.data( this, fullName ); if ( instance ) { instance.option( options || {} ); if ( instance._init ) { instance._init(); } } else { $.data( this, fullName, new object( options, this ) ); } } ); } return returnValue; }; }; $.Widget = function( /* options, element */ ) {}; $.Widget._childConstructors = []; $.Widget.prototype = { widgetName: "widget", widgetEventPrefix: "", defaultElement: "
", options: { classes: {}, disabled: false, // Callbacks create: null }, _createWidget: function( options, element ) { element = $( element || this.defaultElement || this )[ 0 ]; this.element = $( element ); this.uuid = widgetUuid++; this.eventNamespace = "." + this.widgetName + this.uuid; this.bindings = $(); this.hoverable = $(); this.focusable = $(); this.classesElementLookup = {}; if ( element !== this ) { $.data( element, this.widgetFullName, this ); this._on( true, this.element, { remove: function( event ) { if ( event.target === element ) { this.destroy(); } } } ); this.document = $( element.style ? // Element within the document element.ownerDocument : // Element is window or document element.document || element ); this.window = $( this.document[ 0 ].defaultView || this.document[ 0 ].parentWindow ); } this.options = $.widget.extend( {}, this.options, this._getCreateOptions(), options ); this._create(); if ( this.options.disabled ) { this._setOptionDisabled( this.options.disabled ); } this._trigger( "create", null, this._getCreateEventData() ); this._init(); }, _getCreateOptions: function() { return {}; }, _getCreateEventData: $.noop, _create: $.noop, _init: $.noop, destroy: function() { var that = this; this._destroy(); $.each( this.classesElementLookup, function( key, value ) { that._removeClass( value, key ); } ); // We can probably remove the unbind calls in 2.0 // all event bindings should go through this._on() this.element .off( this.eventNamespace ) .removeData( this.widgetFullName ); this.widget() .off( this.eventNamespace ) .removeAttr( "aria-disabled" ); // Clean up events and states this.bindings.off( this.eventNamespace ); }, _destroy: $.noop, widget: function() { return this.element; }, option: function( key, value ) { var options = key; var parts; var curOption; var i; if ( arguments.length === 0 ) { // Don't return a reference to the internal hash return $.widget.extend( {}, this.options ); } if ( typeof key === "string" ) { // Handle nested keys, e.g., "foo.bar" => { foo: { bar: ___ } } options = {}; parts = key.split( "." ); key = parts.shift(); if ( parts.length ) { curOption = options[ key ] = $.widget.extend( {}, this.options[ key ] ); for ( i = 0; i < parts.length - 1; i++ ) { curOption[ parts[ i ] ] = curOption[ parts[ i ] ] || {}; curOption = curOption[ parts[ i ] ]; } key = parts.pop(); if ( arguments.length === 1 ) { return curOption[ key ] === undefined ? null : curOption[ key ]; } curOption[ key ] = value; } else { if ( arguments.length === 1 ) { return this.options[ key ] === undefined ? null : this.options[ key ]; } options[ key ] = value; } } this._setOptions( options ); return this; }, _setOptions: function( options ) { var key; for ( key in options ) { this._setOption( key, options[ key ] ); } return this; }, _setOption: function( key, value ) { if ( key === "classes" ) { this._setOptionClasses( value ); } this.options[ key ] = value; if ( key === "disabled" ) { this._setOptionDisabled( value ); } return this; }, _setOptionClasses: function( value ) { var classKey, elements, currentElements; for ( classKey in value ) { currentElements = this.classesElementLookup[ classKey ]; if ( value[ classKey ] === this.options.classes[ classKey ] || !currentElements || !currentElements.length ) { continue; } // We are doing this to create a new jQuery object because the _removeClass() call // on the next line is going to destroy the reference to the current elements being // tracked. We need to save a copy of this collection so that we can add the new classes // below. elements = $( currentElements.get() ); this._removeClass( currentElements, classKey ); // We don't use _addClass() here, because that uses this.options.classes // for generating the string of classes. We want to use the value passed in from // _setOption(), this is the new value of the classes option which was passed to // _setOption(). We pass this value directly to _classes(). elements.addClass( this._classes( { element: elements, keys: classKey, classes: value, add: true } ) ); } }, _setOptionDisabled: function( value ) { this._toggleClass( this.widget(), this.widgetFullName + "-disabled", null, !!value ); // If the widget is becoming disabled, then nothing is interactive if ( value ) { this._removeClass( this.hoverable, null, "ui-state-hover" ); this._removeClass( this.focusable, null, "ui-state-focus" ); } }, enable: function() { return this._setOptions( { disabled: false } ); }, disable: function() { return this._setOptions( { disabled: true } ); }, _classes: function( options ) { var full = []; var that = this; options = $.extend( { element: this.element, classes: this.options.classes || {} }, options ); function processClassString( classes, checkOption ) { var current, i; for ( i = 0; i < classes.length; i++ ) { current = that.classesElementLookup[ classes[ i ] ] || $(); if ( options.add ) { current = $( $.unique( current.get().concat( options.element.get() ) ) ); } else { current = $( current.not( options.element ).get() ); } that.classesElementLookup[ classes[ i ] ] = current; full.push( classes[ i ] ); if ( checkOption && options.classes[ classes[ i ] ] ) { full.push( options.classes[ classes[ i ] ] ); } } } this._on( options.element, { "remove": "_untrackClassesElement" } ); if ( options.keys ) { processClassString( options.keys.match( /\S+/g ) || [], true ); } if ( options.extra ) { processClassString( options.extra.match( /\S+/g ) || [] ); } return full.join( " " ); }, _untrackClassesElement: function( event ) { var that = this; $.each( that.classesElementLookup, function( key, value ) { if ( $.inArray( event.target, value ) !== -1 ) { that.classesElementLookup[ key ] = $( value.not( event.target ).get() ); } } ); }, _removeClass: function( element, keys, extra ) { return this._toggleClass( element, keys, extra, false ); }, _addClass: function( element, keys, extra ) { return this._toggleClass( element, keys, extra, true ); }, _toggleClass: function( element, keys, extra, add ) { add = ( typeof add === "boolean" ) ? add : extra; var shift = ( typeof element === "string" || element === null ), options = { extra: shift ? keys : extra, keys: shift ? element : keys, element: shift ? this.element : element, add: add }; options.element.toggleClass( this._classes( options ), add ); return this; }, _on: function( suppressDisabledCheck, element, handlers ) { var delegateElement; var instance = this; // No suppressDisabledCheck flag, shuffle arguments if ( typeof suppressDisabledCheck !== "boolean" ) { handlers = element; element = suppressDisabledCheck; suppressDisabledCheck = false; } // No element argument, shuffle and use this.element if ( !handlers ) { handlers = element; element = this.element; delegateElement = this.widget(); } else { element = delegateElement = $( element ); this.bindings = this.bindings.add( element ); } $.each( handlers, function( event, handler ) { function handlerProxy() { // Allow widgets to customize the disabled handling // - disabled as an array instead of boolean // - disabled class as method for disabling individual parts if ( !suppressDisabledCheck && ( instance.options.disabled === true || $( this ).hasClass( "ui-state-disabled" ) ) ) { return; } return ( typeof handler === "string" ? instance[ handler ] : handler ) .apply( instance, arguments ); } // Copy the guid so direct unbinding works if ( typeof handler !== "string" ) { handlerProxy.guid = handler.guid = handler.guid || handlerProxy.guid || $.guid++; } var match = event.match( /^([\w:-]*)\s*(.*)$/ ); var eventName = match[ 1 ] + instance.eventNamespace; var selector = match[ 2 ]; if ( selector ) { delegateElement.on( eventName, selector, handlerProxy ); } else { element.on( eventName, handlerProxy ); } } ); }, _off: function( element, eventName ) { eventName = ( eventName || "" ).split( " " ).join( this.eventNamespace + " " ) + this.eventNamespace; element.off( eventName ).off( eventName ); // Clear the stack to avoid memory leaks (#10056) this.bindings = $( this.bindings.not( element ).get() ); this.focusable = $( this.focusable.not( element ).get() ); this.hoverable = $( this.hoverable.not( element ).get() ); }, _delay: function( handler, delay ) { function handlerProxy() { return ( typeof handler === "string" ? instance[ handler ] : handler ) .apply( instance, arguments ); } var instance = this; return setTimeout( handlerProxy, delay || 0 ); }, _hoverable: function( element ) { this.hoverable = this.hoverable.add( element ); this._on( element, { mouseenter: function( event ) { this._addClass( $( event.currentTarget ), null, "ui-state-hover" ); }, mouseleave: function( event ) { this._removeClass( $( event.currentTarget ), null, "ui-state-hover" ); } } ); }, _focusable: function( element ) { this.focusable = this.focusable.add( element ); this._on( element, { focusin: function( event ) { this._addClass( $( event.currentTarget ), null, "ui-state-focus" ); }, focusout: function( event ) { this._removeClass( $( event.currentTarget ), null, "ui-state-focus" ); } } ); }, _trigger: function( type, event, data ) { var prop, orig; var callback = this.options[ type ]; data = data || {}; event = $.Event( event ); event.type = ( type === this.widgetEventPrefix ? type : this.widgetEventPrefix + type ).toLowerCase(); // The original event may come from any element // so we need to reset the target on the new event event.target = this.element[ 0 ]; // Copy original event properties over to the new event orig = event.originalEvent; if ( orig ) { for ( prop in orig ) { if ( !( prop in event ) ) { event[ prop ] = orig[ prop ]; } } } this.element.trigger( event, data ); return !( $.isFunction( callback ) && callback.apply( this.element[ 0 ], [ event ].concat( data ) ) === false || event.isDefaultPrevented() ); } }; $.each( { show: "fadeIn", hide: "fadeOut" }, function( method, defaultEffect ) { $.Widget.prototype[ "_" + method ] = function( element, options, callback ) { if ( typeof options === "string" ) { options = { effect: options }; } var hasOptions; var effectName = !options ? method : options === true || typeof options === "number" ? defaultEffect : options.effect || defaultEffect; options = options || {}; if ( typeof options === "number" ) { options = { duration: options }; } hasOptions = !$.isEmptyObject( options ); options.complete = callback; if ( options.delay ) { element.delay( options.delay ); } if ( hasOptions && $.effects && $.effects.effect[ effectName ] ) { element[ method ]( options ); } else if ( effectName !== method && element[ effectName ] ) { element[ effectName ]( options.duration, options.easing, callback ); } else { element.queue( function( next ) { $( this )[ method ](); if ( callback ) { callback.call( element[ 0 ] ); } next(); } ); } }; } ); var widget = $.widget; /*! * jQuery UI Position 1.12.1 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors * Released under the MIT license. * http://jquery.org/license * * http://api.jqueryui.com/position/ */ //>>label: Position //>>group: Core //>>description: Positions elements relative to other elements. //>>docs: http://api.jqueryui.com/position/ //>>demos: http://jqueryui.com/position/ ( function() { var cachedScrollbarWidth, max = Math.max, abs = Math.abs, rhorizontal = /left|center|right/, rvertical = /top|center|bottom/, roffset = /[\+\-]\d+(\.[\d]+)?%?/, rposition = /^\w+/, rpercent = /%$/, _position = $.fn.position; function getOffsets( offsets, width, height ) { return [ parseFloat( offsets[ 0 ] ) * ( rpercent.test( offsets[ 0 ] ) ? width / 100 : 1 ), parseFloat( offsets[ 1 ] ) * ( rpercent.test( offsets[ 1 ] ) ? height / 100 : 1 ) ]; } function parseCss( element, property ) { return parseInt( $.css( element, property ), 10 ) || 0; } function getDimensions( elem ) { var raw = elem[ 0 ]; if ( raw.nodeType === 9 ) { return { width: elem.width(), height: elem.height(), offset: { top: 0, left: 0 } }; } if ( $.isWindow( raw ) ) { return { width: elem.width(), height: elem.height(), offset: { top: elem.scrollTop(), left: elem.scrollLeft() } }; } if ( raw.preventDefault ) { return { width: 0, height: 0, offset: { top: raw.pageY, left: raw.pageX } }; } return { width: elem.outerWidth(), height: elem.outerHeight(), offset: elem.offset() }; } $.position = { scrollbarWidth: function() { if ( cachedScrollbarWidth !== undefined ) { return cachedScrollbarWidth; } var w1, w2, div = $( "
" + "
" ), innerDiv = div.children()[ 0 ]; $( "body" ).append( div ); w1 = innerDiv.offsetWidth; div.css( "overflow", "scroll" ); w2 = innerDiv.offsetWidth; if ( w1 === w2 ) { w2 = div[ 0 ].clientWidth; } div.remove(); return ( cachedScrollbarWidth = w1 - w2 ); }, getScrollInfo: function( within ) { var overflowX = within.isWindow || within.isDocument ? "" : within.element.css( "overflow-x" ), overflowY = within.isWindow || within.isDocument ? "" : within.element.css( "overflow-y" ), hasOverflowX = overflowX === "scroll" || ( overflowX === "auto" && within.width < within.element[ 0 ].scrollWidth ), hasOverflowY = overflowY === "scroll" || ( overflowY === "auto" && within.height < within.element[ 0 ].scrollHeight ); return { width: hasOverflowY ? $.position.scrollbarWidth() : 0, height: hasOverflowX ? $.position.scrollbarWidth() : 0 }; }, getWithinInfo: function( element ) { var withinElement = $( element || window ), isWindow = $.isWindow( withinElement[ 0 ] ), isDocument = !!withinElement[ 0 ] && withinElement[ 0 ].nodeType === 9, hasOffset = !isWindow && !isDocument; return { element: withinElement, isWindow: isWindow, isDocument: isDocument, offset: hasOffset ? $( element ).offset() : { left: 0, top: 0 }, scrollLeft: withinElement.scrollLeft(), scrollTop: withinElement.scrollTop(), width: withinElement.outerWidth(), height: withinElement.outerHeight() }; } }; $.fn.position = function( options ) { if ( !options || !options.of ) { return _position.apply( this, arguments ); } // Make a copy, we don't want to modify arguments options = $.extend( {}, options ); var atOffset, targetWidth, targetHeight, targetOffset, basePosition, dimensions, target = $( options.of ), within = $.position.getWithinInfo( options.within ), scrollInfo = $.position.getScrollInfo( within ), collision = ( options.collision || "flip" ).split( " " ), offsets = {}; dimensions = getDimensions( target ); if ( target[ 0 ].preventDefault ) { // Force left top to allow flipping options.at = "left top"; } targetWidth = dimensions.width; targetHeight = dimensions.height; targetOffset = dimensions.offset; // Clone to reuse original targetOffset later basePosition = $.extend( {}, targetOffset ); // Force my and at to have valid horizontal and vertical positions // if a value is missing or invalid, it will be converted to center $.each( [ "my", "at" ], function() { var pos = ( options[ this ] || "" ).split( " " ), horizontalOffset, verticalOffset; if ( pos.length === 1 ) { pos = rhorizontal.test( pos[ 0 ] ) ? pos.concat( [ "center" ] ) : rvertical.test( pos[ 0 ] ) ? [ "center" ].concat( pos ) : [ "center", "center" ]; } pos[ 0 ] = rhorizontal.test( pos[ 0 ] ) ? pos[ 0 ] : "center"; pos[ 1 ] = rvertical.test( pos[ 1 ] ) ? pos[ 1 ] : "center"; // Calculate offsets horizontalOffset = roffset.exec( pos[ 0 ] ); verticalOffset = roffset.exec( pos[ 1 ] ); offsets[ this ] = [ horizontalOffset ? horizontalOffset[ 0 ] : 0, verticalOffset ? verticalOffset[ 0 ] : 0 ]; // Reduce to just the positions without the offsets options[ this ] = [ rposition.exec( pos[ 0 ] )[ 0 ], rposition.exec( pos[ 1 ] )[ 0 ] ]; } ); // Normalize collision option if ( collision.length === 1 ) { collision[ 1 ] = collision[ 0 ]; } if ( options.at[ 0 ] === "right" ) { basePosition.left += targetWidth; } else if ( options.at[ 0 ] === "center" ) { basePosition.left += targetWidth / 2; } if ( options.at[ 1 ] === "bottom" ) { basePosition.top += targetHeight; } else if ( options.at[ 1 ] === "center" ) { basePosition.top += targetHeight / 2; } atOffset = getOffsets( offsets.at, targetWidth, targetHeight ); basePosition.left += atOffset[ 0 ]; basePosition.top += atOffset[ 1 ]; return this.each( function() { var collisionPosition, using, elem = $( this ), elemWidth = elem.outerWidth(), elemHeight = elem.outerHeight(), marginLeft = parseCss( this, "marginLeft" ), marginTop = parseCss( this, "marginTop" ), collisionWidth = elemWidth + marginLeft + parseCss( this, "marginRight" ) + scrollInfo.width, collisionHeight = elemHeight + marginTop + parseCss( this, "marginBottom" ) + scrollInfo.height, position = $.extend( {}, basePosition ), myOffset = getOffsets( offsets.my, elem.outerWidth(), elem.outerHeight() ); if ( options.my[ 0 ] === "right" ) { position.left -= elemWidth; } else if ( options.my[ 0 ] === "center" ) { position.left -= elemWidth / 2; } if ( options.my[ 1 ] === "bottom" ) { position.top -= elemHeight; } else if ( options.my[ 1 ] === "center" ) { position.top -= elemHeight / 2; } position.left += myOffset[ 0 ]; position.top += myOffset[ 1 ]; collisionPosition = { marginLeft: marginLeft, marginTop: marginTop }; $.each( [ "left", "top" ], function( i, dir ) { if ( $.ui.position[ collision[ i ] ] ) { $.ui.position[ collision[ i ] ][ dir ]( position, { targetWidth: targetWidth, targetHeight: targetHeight, elemWidth: elemWidth, elemHeight: elemHeight, collisionPosition: collisionPosition, collisionWidth: collisionWidth, collisionHeight: collisionHeight, offset: [ atOffset[ 0 ] + myOffset[ 0 ], atOffset [ 1 ] + myOffset[ 1 ] ], my: options.my, at: options.at, within: within, elem: elem } ); } } ); if ( options.using ) { // Adds feedback as second argument to using callback, if present using = function( props ) { var left = targetOffset.left - position.left, right = left + targetWidth - elemWidth, top = targetOffset.top - position.top, bottom = top + targetHeight - elemHeight, feedback = { target: { element: target, left: targetOffset.left, top: targetOffset.top, width: targetWidth, height: targetHeight }, element: { element: elem, left: position.left, top: position.top, width: elemWidth, height: elemHeight }, horizontal: right < 0 ? "left" : left > 0 ? "right" : "center", vertical: bottom < 0 ? "top" : top > 0 ? "bottom" : "middle" }; if ( targetWidth < elemWidth && abs( left + right ) < targetWidth ) { feedback.horizontal = "center"; } if ( targetHeight < elemHeight && abs( top + bottom ) < targetHeight ) { feedback.vertical = "middle"; } if ( max( abs( left ), abs( right ) ) > max( abs( top ), abs( bottom ) ) ) { feedback.important = "horizontal"; } else { feedback.important = "vertical"; } options.using.call( this, props, feedback ); }; } elem.offset( $.extend( position, { using: using } ) ); } ); }; $.ui.position = { fit: { left: function( position, data ) { var within = data.within, withinOffset = within.isWindow ? within.scrollLeft : within.offset.left, outerWidth = within.width, collisionPosLeft = position.left - data.collisionPosition.marginLeft, overLeft = withinOffset - collisionPosLeft, overRight = collisionPosLeft + data.collisionWidth - outerWidth - withinOffset, newOverRight; // Element is wider than within if ( data.collisionWidth > outerWidth ) { // Element is initially over the left side of within if ( overLeft > 0 && overRight <= 0 ) { newOverRight = position.left + overLeft + data.collisionWidth - outerWidth - withinOffset; position.left += overLeft - newOverRight; // Element is initially over right side of within } else if ( overRight > 0 && overLeft <= 0 ) { position.left = withinOffset; // Element is initially over both left and right sides of within } else { if ( overLeft > overRight ) { position.left = withinOffset + outerWidth - data.collisionWidth; } else { position.left = withinOffset; } } // Too far left -> align with left edge } else if ( overLeft > 0 ) { position.left += overLeft; // Too far right -> align with right edge } else if ( overRight > 0 ) { position.left -= overRight; // Adjust based on position and margin } else { position.left = max( position.left - collisionPosLeft, position.left ); } }, top: function( position, data ) { var within = data.within, withinOffset = within.isWindow ? within.scrollTop : within.offset.top, outerHeight = data.within.height, collisionPosTop = position.top - data.collisionPosition.marginTop, overTop = withinOffset - collisionPosTop, overBottom = collisionPosTop + data.collisionHeight - outerHeight - withinOffset, newOverBottom; // Element is taller than within if ( data.collisionHeight > outerHeight ) { // Element is initially over the top of within if ( overTop > 0 && overBottom <= 0 ) { newOverBottom = position.top + overTop + data.collisionHeight - outerHeight - withinOffset; position.top += overTop - newOverBottom; // Element is initially over bottom of within } else if ( overBottom > 0 && overTop <= 0 ) { position.top = withinOffset; // Element is initially over both top and bottom of within } else { if ( overTop > overBottom ) { position.top = withinOffset + outerHeight - data.collisionHeight; } else { position.top = withinOffset; } } // Too far up -> align with top } else if ( overTop > 0 ) { position.top += overTop; // Too far down -> align with bottom edge } else if ( overBottom > 0 ) { position.top -= overBottom; // Adjust based on position and margin } else { position.top = max( position.top - collisionPosTop, position.top ); } } }, flip: { left: function( position, data ) { var within = data.within, withinOffset = within.offset.left + within.scrollLeft, outerWidth = within.width, offsetLeft = within.isWindow ? within.scrollLeft : within.offset.left, collisionPosLeft = position.left - data.collisionPosition.marginLeft, overLeft = collisionPosLeft - offsetLeft, overRight = collisionPosLeft + data.collisionWidth - outerWidth - offsetLeft, myOffset = data.my[ 0 ] === "left" ? -data.elemWidth : data.my[ 0 ] === "right" ? data.elemWidth : 0, atOffset = data.at[ 0 ] === "left" ? data.targetWidth : data.at[ 0 ] === "right" ? -data.targetWidth : 0, offset = -2 * data.offset[ 0 ], newOverRight, newOverLeft; if ( overLeft < 0 ) { newOverRight = position.left + myOffset + atOffset + offset + data.collisionWidth - outerWidth - withinOffset; if ( newOverRight < 0 || newOverRight < abs( overLeft ) ) { position.left += myOffset + atOffset + offset; } } else if ( overRight > 0 ) { newOverLeft = position.left - data.collisionPosition.marginLeft + myOffset + atOffset + offset - offsetLeft; if ( newOverLeft > 0 || abs( newOverLeft ) < overRight ) { position.left += myOffset + atOffset + offset; } } }, top: function( position, data ) { var within = data.within, withinOffset = within.offset.top + within.scrollTop, outerHeight = within.height, offsetTop = within.isWindow ? within.scrollTop : within.offset.top, collisionPosTop = position.top - data.collisionPosition.marginTop, overTop = collisionPosTop - offsetTop, overBottom = collisionPosTop + data.collisionHeight - outerHeight - offsetTop, top = data.my[ 1 ] === "top", myOffset = top ? -data.elemHeight : data.my[ 1 ] === "bottom" ? data.elemHeight : 0, atOffset = data.at[ 1 ] === "top" ? data.targetHeight : data.at[ 1 ] === "bottom" ? -data.targetHeight : 0, offset = -2 * data.offset[ 1 ], newOverTop, newOverBottom; if ( overTop < 0 ) { newOverBottom = position.top + myOffset + atOffset + offset + data.collisionHeight - outerHeight - withinOffset; if ( newOverBottom < 0 || newOverBottom < abs( overTop ) ) { position.top += myOffset + atOffset + offset; } } else if ( overBottom > 0 ) { newOverTop = position.top - data.collisionPosition.marginTop + myOffset + atOffset + offset - offsetTop; if ( newOverTop > 0 || abs( newOverTop ) < overBottom ) { position.top += myOffset + atOffset + offset; } } } }, flipfit: { left: function() { $.ui.position.flip.left.apply( this, arguments ); $.ui.position.fit.left.apply( this, arguments ); }, top: function() { $.ui.position.flip.top.apply( this, arguments ); $.ui.position.fit.top.apply( this, arguments ); } } }; } )(); var position = $.ui.position; /*! * jQuery UI Keycode 1.12.1 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors * Released under the MIT license. * http://jquery.org/license */ //>>label: Keycode //>>group: Core //>>description: Provide keycodes as keynames //>>docs: http://api.jqueryui.com/jQuery.ui.keyCode/ var keycode = $.ui.keyCode = { BACKSPACE: 8, COMMA: 188, DELETE: 46, DOWN: 40, END: 35, ENTER: 13, ESCAPE: 27, HOME: 36, LEFT: 37, PAGE_DOWN: 34, PAGE_UP: 33, PERIOD: 190, RIGHT: 39, SPACE: 32, TAB: 9, UP: 38 }; /*! * jQuery UI Scroll Parent 1.12.1 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors * Released under the MIT license. * http://jquery.org/license */ //>>label: scrollParent //>>group: Core //>>description: Get the closest ancestor element that is scrollable. //>>docs: http://api.jqueryui.com/scrollParent/ var scrollParent = $.fn.scrollParent = function( includeHidden ) { var position = this.css( "position" ), excludeStaticParent = position === "absolute", overflowRegex = includeHidden ? /(auto|scroll|hidden)/ : /(auto|scroll)/, scrollParent = this.parents().filter( function() { var parent = $( this ); if ( excludeStaticParent && parent.css( "position" ) === "static" ) { return false; } return overflowRegex.test( parent.css( "overflow" ) + parent.css( "overflow-y" ) + parent.css( "overflow-x" ) ); } ).eq( 0 ); return position === "fixed" || !scrollParent.length ? $( this[ 0 ].ownerDocument || document ) : scrollParent; }; /*! * jQuery UI Unique ID 1.12.1 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors * Released under the MIT license. * http://jquery.org/license */ //>>label: uniqueId //>>group: Core //>>description: Functions to generate and remove uniqueId's //>>docs: http://api.jqueryui.com/uniqueId/ var uniqueId = $.fn.extend( { uniqueId: ( function() { var uuid = 0; return function() { return this.each( function() { if ( !this.id ) { this.id = "ui-id-" + ( ++uuid ); } } ); }; } )(), removeUniqueId: function() { return this.each( function() { if ( /^ui-id-\d+$/.test( this.id ) ) { $( this ).removeAttr( "id" ); } } ); } } ); // NOTE: Original jQuery UI wrapper was replaced. See README-Fancytree.md // })); })(jQuery); (function( factory ) { if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. define( [ "jquery" ], factory ); } else if ( typeof module === "object" && module.exports ) { // Node/CommonJS module.exports = factory(require("jquery")); } else { // Browser globals factory( jQuery ); } }(function( $ ) { /*! Fancytree Core *//*! * jquery.fancytree.js * Tree view control with support for lazy loading and much more. * https://github.com/mar10/fancytree/ * * Copyright (c) 2008-2019, Martin Wendt (http://wwWendt.de) * Released under the MIT license * https://github.com/mar10/fancytree/wiki/LicenseInfo * * @version 2.30.2 * @date 2019-01-13T08:17:01Z */ /** Core Fancytree module. */ // UMD wrapper for the Fancytree core module (function(factory) { if (typeof define === "function" && define.amd) { // AMD. Register as an anonymous module. define(["jquery", "./jquery.fancytree.ui-deps"], factory); } else if (typeof module === "object" && module.exports) { // Node/CommonJS require("./jquery.fancytree.ui-deps"); module.exports = factory(require("jquery")); } else { // Browser globals factory(jQuery); } })(function($) { "use strict"; // prevent duplicate loading if ($.ui && $.ui.fancytree) { $.ui.fancytree.warn("Fancytree: ignored duplicate include"); return; } /****************************************************************************** * Private functions and variables */ var i, attr, FT = null, // initialized below TEST_IMG = new RegExp(/\.|\//), // strings are considered image urls if they contain '.' or '/' REX_HTML = /[&<>"'\/]/g, // Escape those characters REX_TOOLTIP = /[<>"'\/]/g, // Don't escape `&` in tooltips RECURSIVE_REQUEST_ERROR = "$recursive_request", ENTITY_MAP = { "&": "&", "<": "<", ">": ">", '"': """, "'": "'", "/": "/", }, IGNORE_KEYCODES = { 16: true, 17: true, 18: true }, SPECIAL_KEYCODES = { 8: "backspace", 9: "tab", 10: "return", 13: "return", // 16: null, 17: null, 18: null, // ignore shift, ctrl, alt 19: "pause", 20: "capslock", 27: "esc", 32: "space", 33: "pageup", 34: "pagedown", 35: "end", 36: "home", 37: "left", 38: "up", 39: "right", 40: "down", 45: "insert", 46: "del", 59: ";", 61: "=", // 91: null, 93: null, // ignore left and right meta 96: "0", 97: "1", 98: "2", 99: "3", 100: "4", 101: "5", 102: "6", 103: "7", 104: "8", 105: "9", 106: "*", 107: "+", 109: "-", 110: ".", 111: "/", 112: "f1", 113: "f2", 114: "f3", 115: "f4", 116: "f5", 117: "f6", 118: "f7", 119: "f8", 120: "f9", 121: "f10", 122: "f11", 123: "f12", 144: "numlock", 145: "scroll", 173: "-", 186: ";", 187: "=", 188: ",", 189: "-", 190: ".", 191: "/", 192: "`", 219: "[", 220: "\\", 221: "]", 222: "'", }, MODIFIERS = { 16: "shift", 17: "ctrl", 18: "alt", 91: "meta", 93: "meta", }, MOUSE_BUTTONS = { 0: "", 1: "left", 2: "middle", 3: "right" }, // Boolean attributes that can be set with equivalent class names in the LI tags // Note: v2.23: checkbox and hideCheckbox are *not* in this list CLASS_ATTRS = "active expanded focus folder lazy radiogroup selected unselectable unselectableIgnore".split( " " ), CLASS_ATTR_MAP = {}, // Top-level Fancytree attributes, that can be set by dict TREE_ATTRS = "columns types".split(" "), // TREE_ATTR_MAP = {}, // Top-level FancytreeNode attributes, that can be set by dict NODE_ATTRS = "checkbox expanded extraClasses folder icon iconTooltip key lazy partsel radiogroup refKey selected statusNodeType title tooltip type unselectable unselectableIgnore unselectableStatus".split( " " ), NODE_ATTR_MAP = {}, // Mapping of lowercase -> real name (because HTML5 data-... attribute only supports lowercase) NODE_ATTR_LOWERCASE_MAP = {}, // Attribute names that should NOT be added to node.data NONE_NODE_DATA_MAP = { active: true, children: true, data: true, focus: true, }; for (i = 0; i < CLASS_ATTRS.length; i++) { CLASS_ATTR_MAP[CLASS_ATTRS[i]] = true; } for (i = 0; i < NODE_ATTRS.length; i++) { attr = NODE_ATTRS[i]; NODE_ATTR_MAP[attr] = true; if (attr !== attr.toLowerCase()) { NODE_ATTR_LOWERCASE_MAP[attr.toLowerCase()] = attr; } } // for(i=0; i t; } } return true; } /** * Deep-merge a list of objects (but replace array-type options). * * jQuery's $.extend(true, ...) method does a deep merge, that also merges Arrays. * This variant is used to merge extension defaults with user options, and should * merge objects, but override arrays (for example the `triggerStart: [...]` option * of ext-edit). Also `null` values are copied over and not skipped. * * See issue #876 * * Example: * _simpleDeepMerge({}, o1, o2); */ function _simpleDeepMerge() { var options, name, src, copy, clone, target = arguments[0] || {}, i = 1, length = arguments.length; // Handle case when target is a string or something (possible in deep copy) if (typeof target !== "object" && !$.isFunction(target)) { target = {}; } if (i === length) { throw "need at least two args"; } for (; i < length; i++) { // Only deal with non-null/undefined values if ((options = arguments[i]) != null) { // Extend the base object for (name in options) { src = target[name]; copy = options[name]; // Prevent never-ending loop if (target === copy) { continue; } // Recurse if we're merging plain objects // (NOTE: unlike $.extend, we don't merge arrays, but relace them) if (copy && $.isPlainObject(copy)) { clone = src && $.isPlainObject(src) ? src : {}; // Never move original objects, clone them target[name] = _simpleDeepMerge(clone, copy); // Don't bring in undefined values } else if (copy !== undefined) { target[name] = copy; } } } } // Return the modified object return target; } /** Return a wrapper that calls sub.methodName() and exposes * this : tree * this._local : tree.ext.EXTNAME * this._super : base.methodName.call() * this._superApply : base.methodName.apply() */ function _makeVirtualFunction(methodName, tree, base, extension, extName) { // $.ui.fancytree.debug("_makeVirtualFunction", methodName, tree, base, extension, extName); // if(rexTestSuper && !rexTestSuper.test(func)){ // // extension.methodName() doesn't call _super(), so no wrapper required // return func; // } // Use an immediate function as closure var proxy = (function() { var prevFunc = tree[methodName], // org. tree method or prev. proxy baseFunc = extension[methodName], // _local = tree.ext[extName], _super = function() { return prevFunc.apply(tree, arguments); }, _superApply = function(args) { return prevFunc.apply(tree, args); }; // Return the wrapper function return function() { var prevLocal = tree._local, prevSuper = tree._super, prevSuperApply = tree._superApply; try { tree._local = _local; tree._super = _super; tree._superApply = _superApply; return baseFunc.apply(tree, arguments); } finally { tree._local = prevLocal; tree._super = prevSuper; tree._superApply = prevSuperApply; } }; })(); // end of Immediate Function return proxy; } /** * Subclass `base` by creating proxy functions */ function _subclassObject(tree, base, extension, extName) { // $.ui.fancytree.debug("_subclassObject", tree, base, extension, extName); for (var attrName in extension) { if (typeof extension[attrName] === "function") { if (typeof tree[attrName] === "function") { // override existing method tree[attrName] = _makeVirtualFunction( attrName, tree, base, extension, extName ); } else if (attrName.charAt(0) === "_") { // Create private methods in tree.ext.EXTENSION namespace tree.ext[extName][attrName] = _makeVirtualFunction( attrName, tree, base, extension, extName ); } else { $.error( "Could not override tree." + attrName + ". Use prefix '_' to create tree." + extName + "._" + attrName ); } } else { // Create member variables in tree.ext.EXTENSION namespace if (attrName !== "options") { tree.ext[extName][attrName] = extension[attrName]; } } } } function _getResolvedPromise(context, argArray) { if (context === undefined) { return $.Deferred(function() { this.resolve(); }).promise(); } else { return $.Deferred(function() { this.resolveWith(context, argArray); }).promise(); } } function _getRejectedPromise(context, argArray) { if (context === undefined) { return $.Deferred(function() { this.reject(); }).promise(); } else { return $.Deferred(function() { this.rejectWith(context, argArray); }).promise(); } } function _makeResolveFunc(deferred, context) { return function() { deferred.resolveWith(context); }; } function _getElementDataAsDict($el) { // Evaluate 'data-NAME' attributes with special treatment for 'data-json'. var d = $.extend({}, $el.data()), json = d.json; delete d.fancytree; // added to container by widget factory (old jQuery UI) delete d.uiFancytree; // added to container by widget factory if (json) { delete d.json; //
  • is already returned as object (http://api.jquery.com/data/#data-html5) d = $.extend(d, json); } return d; } function _escapeTooltip(s) { return ("" + s).replace(REX_TOOLTIP, function(s) { return ENTITY_MAP[s]; }); } // TODO: use currying function _makeNodeTitleMatcher(s) { s = s.toLowerCase(); return function(node) { return node.title.toLowerCase().indexOf(s) >= 0; }; } function _makeNodeTitleStartMatcher(s) { var reMatch = new RegExp("^" + s, "i"); return function(node) { return reMatch.test(node.title); }; } /****************************************************************************** * FancytreeNode */ /** * Creates a new FancytreeNode * * @class FancytreeNode * @classdesc A FancytreeNode represents the hierarchical data model and operations. * * @param {FancytreeNode} parent * @param {NodeData} obj * * @property {Fancytree} tree The tree instance * @property {FancytreeNode} parent The parent node * @property {string} key Node id (must be unique inside the tree) * @property {string} title Display name (may contain HTML) * @property {object} data Contains all extra data that was passed on node creation * @property {FancytreeNode[] | null | undefined} children Array of child nodes.
    * For lazy nodes, null or undefined means 'not yet loaded'. Use an empty array * to define a node that has no children. * @property {boolean} expanded Use isExpanded(), setExpanded() to access this property. * @property {string} extraClasses Additional CSS classes, added to the node's `<span>`.
    * Note: use `node.add/remove/toggleClass()` to modify. * @property {boolean} folder Folder nodes have different default icons and click behavior.
    * Note: Also non-folders may have children. * @property {string} statusNodeType null for standard nodes. Otherwise type of special system node: 'error', 'loading', 'nodata', or 'paging'. * @property {boolean} lazy True if this node is loaded on demand, i.e. on first expansion. * @property {boolean} selected Use isSelected(), setSelected() to access this property. * @property {string} tooltip Alternative description used as hover popup * @property {string} iconTooltip Description used as hover popup for icon. @since 2.27 * @property {string} type Node type, used with tree.types map. @since 2.27 */ function FancytreeNode(parent, obj) { var i, l, name, cl; this.parent = parent; this.tree = parent.tree; this.ul = null; this.li = null; //
  • tag this.statusNodeType = null; // if this is a temp. node to display the status of its parent this._isLoading = false; // if this node itself is loading this._error = null; // {message: '...'} if a load error occurred this.data = {}; // TODO: merge this code with node.toDict() // copy attributes from obj object for (i = 0, l = NODE_ATTRS.length; i < l; i++) { name = NODE_ATTRS[i]; this[name] = obj[name]; } // unselectableIgnore and unselectableStatus imply unselectable if ( this.unselectableIgnore != null || this.unselectableStatus != null ) { this.unselectable = true; } if (obj.hideCheckbox) { $.error( "'hideCheckbox' node option was removed in v2.23.0: use 'checkbox: false'" ); } // node.data += obj.data if (obj.data) { $.extend(this.data, obj.data); } // Copy all other attributes to this.data.NAME for (name in obj) { if ( !NODE_ATTR_MAP[name] && !$.isFunction(obj[name]) && !NONE_NODE_DATA_MAP[name] ) { // node.data.NAME = obj.NAME this.data[name] = obj[name]; } } // Fix missing key if (this.key == null) { // test for null OR undefined if (this.tree.options.defaultKey) { this.key = this.tree.options.defaultKey(this); _assert(this.key, "defaultKey() must return a unique key"); } else { this.key = "_" + FT._nextNodeKey++; } } else { this.key = "" + this.key; // Convert to string (#217) } // Fix tree.activeNode // TODO: not elegant: we use obj.active as marker to set tree.activeNode // when loading from a dictionary. if (obj.active) { _assert( this.tree.activeNode === null, "only one active node allowed" ); this.tree.activeNode = this; } if (obj.selected) { // #186 this.tree.lastSelectedNode = this; } // TODO: handle obj.focus = true // Create child nodes cl = obj.children; if (cl) { if (cl.length) { this._setChildren(cl); } else { // if an empty array was passed for a lazy node, keep it, in order to mark it 'loaded' this.children = this.lazy ? [] : null; } } else { this.children = null; } // Add to key/ref map (except for root node) // if( parent ) { this.tree._callHook("treeRegisterNode", this.tree, true, this); // } } FancytreeNode.prototype = /** @lends FancytreeNode# */ { /* Return the direct child FancytreeNode with a given key, index. */ _findDirectChild: function(ptr) { var i, l, cl = this.children; if (cl) { if (typeof ptr === "string") { for (i = 0, l = cl.length; i < l; i++) { if (cl[i].key === ptr) { return cl[i]; } } } else if (typeof ptr === "number") { return this.children[ptr]; } else if (ptr.parent === this) { return ptr; } } return null; }, // TODO: activate() // TODO: activateSilently() /* Internal helper called in recursive addChildren sequence.*/ _setChildren: function(children) { _assert( children && (!this.children || this.children.length === 0), "only init supported" ); this.children = []; for (var i = 0, l = children.length; i < l; i++) { this.children.push(new FancytreeNode(this, children[i])); } }, /** * Append (or insert) a list of child nodes. * * @param {NodeData[]} children array of child node definitions (also single child accepted) * @param {FancytreeNode | string | Integer} [insertBefore] child node (or key or index of such). * If omitted, the new children are appended. * @returns {FancytreeNode} first child added * * @see FancytreeNode#applyPatch */ addChildren: function(children, insertBefore) { var i, l, pos, origFirstChild = this.getFirstChild(), origLastChild = this.getLastChild(), firstNode = null, nodeList = []; if ($.isPlainObject(children)) { children = [children]; } if (!this.children) { this.children = []; } for (i = 0, l = children.length; i < l; i++) { nodeList.push(new FancytreeNode(this, children[i])); } firstNode = nodeList[0]; if (insertBefore == null) { this.children = this.children.concat(nodeList); } else { // Returns null if insertBefore is not a direct child: insertBefore = this._findDirectChild(insertBefore); pos = $.inArray(insertBefore, this.children); _assert(pos >= 0, "insertBefore must be an existing child"); // insert nodeList after children[pos] this.children.splice.apply( this.children, [pos, 0].concat(nodeList) ); } if (origFirstChild && !insertBefore) { // #708: Fast path -- don't render every child of root, just the new ones! // #723, #729: but only if it's appended to an existing child list for (i = 0, l = nodeList.length; i < l; i++) { nodeList[i].render(); // New nodes were never rendered before } // Adjust classes where status may have changed // Has a first child if (origFirstChild !== this.getFirstChild()) { // Different first child -- recompute classes origFirstChild.renderStatus(); } if (origLastChild !== this.getLastChild()) { // Different last child -- recompute classes origLastChild.renderStatus(); } } else if (!this.parent || this.parent.ul || this.tr) { // render if the parent was rendered (or this is a root node) this.render(); } if (this.tree.options.selectMode === 3) { this.fixSelection3FromEndNodes(); } this.triggerModifyChild( "add", nodeList.length === 1 ? nodeList[0] : null ); return firstNode; }, /** * Add class to node's span tag and to .extraClasses. * * @param {string} className class name * * @since 2.17 */ addClass: function(className) { return this.toggleClass(className, true); }, /** * Append or prepend a node, or append a child node. * * This a convenience function that calls addChildren() * * @param {NodeData} node node definition * @param {string} [mode=child] 'before', 'after', 'firstChild', or 'child' ('over' is a synonym for 'child') * @returns {FancytreeNode} new node */ addNode: function(node, mode) { if (mode === undefined || mode === "over") { mode = "child"; } switch (mode) { case "after": return this.getParent().addChildren( node, this.getNextSibling() ); case "before": return this.getParent().addChildren(node, this); case "firstChild": // Insert before the first child if any var insertBefore = this.children ? this.children[0] : null; return this.addChildren(node, insertBefore); case "child": case "over": return this.addChildren(node); } _assert(false, "Invalid mode: " + mode); }, /**Add child status nodes that indicate 'More...', etc. * * This also maintains the node's `partload` property. * @param {boolean|object} node optional node definition. Pass `false` to remove all paging nodes. * @param {string} [mode='child'] 'child'|firstChild' * @since 2.15 */ addPagingNode: function(node, mode) { var i, n; mode = mode || "child"; if (node === false) { for (i = this.children.length - 1; i >= 0; i--) { n = this.children[i]; if (n.statusNodeType === "paging") { this.removeChild(n); } } this.partload = false; return; } node = $.extend( { title: this.tree.options.strings.moreData, statusNodeType: "paging", icon: false, }, node ); this.partload = true; return this.addNode(node, mode); }, /** * Append new node after this. * * This a convenience function that calls addNode(node, 'after') * * @param {NodeData} node node definition * @returns {FancytreeNode} new node */ appendSibling: function(node) { return this.addNode(node, "after"); }, /** * Modify existing child nodes. * * @param {NodePatch} patch * @returns {$.Promise} * @see FancytreeNode#addChildren */ applyPatch: function(patch) { // patch [key, null] means 'remove' if (patch === null) { this.remove(); return _getResolvedPromise(this); } // TODO: make sure that root node is not collapsed or modified // copy (most) attributes to node.ATTR or node.data.ATTR var name, promise, v, IGNORE_MAP = { children: true, expanded: true, parent: true }; // TODO: should be global for (name in patch) { v = patch[name]; if (!IGNORE_MAP[name] && !$.isFunction(v)) { if (NODE_ATTR_MAP[name]) { this[name] = v; } else { this.data[name] = v; } } } // Remove and/or create children if (patch.hasOwnProperty("children")) { this.removeChildren(); if (patch.children) { // only if not null and not empty list // TODO: addChildren instead? this._setChildren(patch.children); } // TODO: how can we APPEND or INSERT child nodes? } if (this.isVisible()) { this.renderTitle(); this.renderStatus(); } // Expand collapse (final step, since this may be async) if (patch.hasOwnProperty("expanded")) { promise = this.setExpanded(patch.expanded); } else { promise = _getResolvedPromise(this); } return promise; }, /** Collapse all sibling nodes. * @returns {$.Promise} */ collapseSiblings: function() { return this.tree._callHook("nodeCollapseSiblings", this); }, /** Copy this node as sibling or child of `node`. * * @param {FancytreeNode} node source node * @param {string} [mode=child] 'before' | 'after' | 'child' * @param {Function} [map] callback function(NodeData) that could modify the new node * @returns {FancytreeNode} new */ copyTo: function(node, mode, map) { return node.addNode(this.toDict(true, map), mode); }, /** Count direct and indirect children. * * @param {boolean} [deep=true] pass 'false' to only count direct children * @returns {int} number of child nodes */ countChildren: function(deep) { var cl = this.children, i, l, n; if (!cl) { return 0; } n = cl.length; if (deep !== false) { for (i = 0, l = n; i < l; i++) { n += cl[i].countChildren(); } } return n; }, // TODO: deactivate() /** Write to browser console if debugLevel >= 4 (prepending node info) * * @param {*} msg string or object or array of such */ debug: function(msg) { if (this.tree.options.debugLevel >= 4) { Array.prototype.unshift.call(arguments, this.toString()); consoleApply("log", arguments); } }, /** Deprecated. * @deprecated since 2014-02-16. Use resetLazy() instead. */ discard: function() { this.warn( "FancytreeNode.discard() is deprecated since 2014-02-16. Use .resetLazy() instead." ); return this.resetLazy(); }, /** Remove DOM elements for all descendents. May be called on .collapse event * to keep the DOM small. * @param {boolean} [includeSelf=false] */ discardMarkup: function(includeSelf) { var fn = includeSelf ? "nodeRemoveMarkup" : "nodeRemoveChildMarkup"; this.tree._callHook(fn, this); }, /** Write error to browser console if debugLevel >= 1 (prepending tree info) * * @param {*} msg string or object or array of such */ error: function(msg) { if (this.options.debugLevel >= 1) { Array.prototype.unshift.call(arguments, this.toString()); consoleApply("error", arguments); } }, /**Find all nodes that match condition (excluding self). * * @param {string | function(node)} match title string to search for, or a * callback function that returns `true` if a node is matched. * @returns {FancytreeNode[]} array of nodes (may be empty) */ findAll: function(match) { match = $.isFunction(match) ? match : _makeNodeTitleMatcher(match); var res = []; this.visit(function(n) { if (match(n)) { res.push(n); } }); return res; }, /**Find first node that matches condition (excluding self). * * @param {string | function(node)} match title string to search for, or a * callback function that returns `true` if a node is matched. * @returns {FancytreeNode} matching node or null * @see FancytreeNode#findAll */ findFirst: function(match) { match = $.isFunction(match) ? match : _makeNodeTitleMatcher(match); var res = null; this.visit(function(n) { if (match(n)) { res = n; return false; } }); return res; }, /* Apply selection state (internal use only) */ _changeSelectStatusAttrs: function(state) { var changed = false, opts = this.tree.options, unselectable = FT.evalOption( "unselectable", this, this, opts, false ), unselectableStatus = FT.evalOption( "unselectableStatus", this, this, opts, undefined ); if (unselectable && unselectableStatus != null) { state = unselectableStatus; } switch (state) { case false: changed = this.selected || this.partsel; this.selected = false; this.partsel = false; break; case true: changed = !this.selected || !this.partsel; this.selected = true; this.partsel = true; break; case undefined: changed = this.selected || !this.partsel; this.selected = false; this.partsel = true; break; default: _assert(false, "invalid state: " + state); } // this.debug("fixSelection3AfterLoad() _changeSelectStatusAttrs()", state, changed); if (changed) { this.renderStatus(); } return changed; }, /** * Fix selection status, after this node was (de)selected in multi-hier mode. * This includes (de)selecting all children. */ fixSelection3AfterClick: function(callOpts) { var flag = this.isSelected(); // this.debug("fixSelection3AfterClick()"); this.visit(function(node) { node._changeSelectStatusAttrs(flag); }); this.fixSelection3FromEndNodes(callOpts); }, /** * Fix selection status for multi-hier mode. * Only end-nodes are considered to update the descendants branch and parents. * Should be called after this node has loaded new children or after * children have been modified using the API. */ fixSelection3FromEndNodes: function(callOpts) { var opts = this.tree.options; // this.debug("fixSelection3FromEndNodes()"); _assert(opts.selectMode === 3, "expected selectMode 3"); // Visit all end nodes and adjust their parent's `selected` and `partsel` // attributes. Return selection state true, false, or undefined. function _walk(node) { var i, l, child, s, state, allSelected, someSelected, unselIgnore, unselState, children = node.children; if (children && children.length) { // check all children recursively allSelected = true; someSelected = false; for (i = 0, l = children.length; i < l; i++) { child = children[i]; // the selection state of a node is not relevant; we need the end-nodes s = _walk(child); // if( !child.unselectableIgnore ) { unselIgnore = FT.evalOption( "unselectableIgnore", child, child, opts, false ); if (!unselIgnore) { if (s !== false) { someSelected = true; } if (s !== true) { allSelected = false; } } } state = allSelected ? true : someSelected ? undefined : false; } else { // This is an end-node: simply report the status unselState = FT.evalOption( "unselectableStatus", node, node, opts, undefined ); state = unselState == null ? !!node.selected : !!unselState; } node._changeSelectStatusAttrs(state); return state; } _walk(this); // Update parent's state this.visitParents(function(node) { var i, l, child, state, unselIgnore, unselState, children = node.children, allSelected = true, someSelected = false; for (i = 0, l = children.length; i < l; i++) { child = children[i]; unselIgnore = FT.evalOption( "unselectableIgnore", child, child, opts, false ); if (!unselIgnore) { unselState = FT.evalOption( "unselectableStatus", child, child, opts, undefined ); state = unselState == null ? !!child.selected : !!unselState; // When fixing the parents, we trust the sibling status (i.e. // we don't recurse) if (state || child.partsel) { someSelected = true; } if (!state) { allSelected = false; } } } state = allSelected ? true : someSelected ? undefined : false; node._changeSelectStatusAttrs(state); }); }, // TODO: focus() /** * Update node data. If dict contains 'children', then also replace * the hole sub tree. * @param {NodeData} dict * * @see FancytreeNode#addChildren * @see FancytreeNode#applyPatch */ fromDict: function(dict) { // copy all other attributes to this.data.xxx for (var name in dict) { if (NODE_ATTR_MAP[name]) { // node.NAME = dict.NAME this[name] = dict[name]; } else if (name === "data") { // node.data += dict.data $.extend(this.data, dict.data); } else if ( !$.isFunction(dict[name]) && !NONE_NODE_DATA_MAP[name] ) { // node.data.NAME = dict.NAME this.data[name] = dict[name]; } } if (dict.children) { // recursively set children and render this.removeChildren(); this.addChildren(dict.children); } this.renderTitle(); /* var children = dict.children; if(children === undefined){ this.data = $.extend(this.data, dict); this.render(); return; } dict = $.extend({}, dict); dict.children = undefined; this.data = $.extend(this.data, dict); this.removeChildren(); this.addChild(children); */ }, /** Return the list of child nodes (undefined for unexpanded lazy nodes). * @returns {FancytreeNode[] | undefined} */ getChildren: function() { if (this.hasChildren() === undefined) { // TODO: only required for lazy nodes? return undefined; // Lazy node: unloaded, currently loading, or load error } return this.children; }, /** Return the first child node or null. * @returns {FancytreeNode | null} */ getFirstChild: function() { return this.children ? this.children[0] : null; }, /** Return the 0-based child index. * @returns {int} */ getIndex: function() { // return this.parent.children.indexOf(this); return $.inArray(this, this.parent.children); // indexOf doesn't work in IE7 }, /** Return the hierarchical child index (1-based, e.g. '3.2.4'). * @param {string} [separator="."] * @param {int} [digits=1] * @returns {string} */ getIndexHier: function(separator, digits) { separator = separator || "."; var s, res = []; $.each(this.getParentList(false, true), function(i, o) { s = "" + (o.getIndex() + 1); if (digits) { // prepend leading zeroes s = ("0000000" + s).substr(-digits); } res.push(s); }); return res.join(separator); }, /** Return the parent keys separated by options.keyPathSeparator, e.g. "id_1/id_17/id_32". * @param {boolean} [excludeSelf=false] * @returns {string} */ getKeyPath: function(excludeSelf) { var path = [], sep = this.tree.options.keyPathSeparator; this.visitParents(function(n) { if (n.parent) { path.unshift(n.key); } }, !excludeSelf); return sep + path.join(sep); }, /** Return the last child of this node or null. * @returns {FancytreeNode | null} */ getLastChild: function() { return this.children ? this.children[this.children.length - 1] : null; }, /** Return node depth. 0: System root node, 1: visible top-level node, 2: first sub-level, ... . * @returns {int} */ getLevel: function() { var level = 0, dtn = this.parent; while (dtn) { level++; dtn = dtn.parent; } return level; }, /** Return the successor node (under the same parent) or null. * @returns {FancytreeNode | null} */ getNextSibling: function() { // TODO: use indexOf, if available: (not in IE6) if (this.parent) { var i, l, ac = this.parent.children; for (i = 0, l = ac.length - 1; i < l; i++) { // up to length-2, so next(last) = null if (ac[i] === this) { return ac[i + 1]; } } } return null; }, /** Return the parent node (null for the system root node). * @returns {FancytreeNode | null} */ getParent: function() { // TODO: return null for top-level nodes? return this.parent; }, /** Return an array of all parent nodes (top-down). * @param {boolean} [includeRoot=false] Include the invisible system root node. * @param {boolean} [includeSelf=false] Include the node itself. * @returns {FancytreeNode[]} */ getParentList: function(includeRoot, includeSelf) { var l = [], dtn = includeSelf ? this : this.parent; while (dtn) { if (includeRoot || dtn.parent) { l.unshift(dtn); } dtn = dtn.parent; } return l; }, /** Return the predecessor node (under the same parent) or null. * @returns {FancytreeNode | null} */ getPrevSibling: function() { if (this.parent) { var i, l, ac = this.parent.children; for (i = 1, l = ac.length; i < l; i++) { // start with 1, so prev(first) = null if (ac[i] === this) { return ac[i - 1]; } } } return null; }, /** * Return an array of selected descendant nodes. * @param {boolean} [stopOnParents=false] only return the topmost selected * node (useful with selectMode 3) * @returns {FancytreeNode[]} */ getSelectedNodes: function(stopOnParents) { var nodeList = []; this.visit(function(node) { if (node.selected) { nodeList.push(node); if (stopOnParents === true) { return "skip"; // stop processing this branch } } }); return nodeList; }, /** Return true if node has children. Return undefined if not sure, i.e. the node is lazy and not yet loaded). * @returns {boolean | undefined} */ hasChildren: function() { if (this.lazy) { if (this.children == null) { // null or undefined: Not yet loaded return undefined; } else if (this.children.length === 0) { // Loaded, but response was empty return false; } else if ( this.children.length === 1 && this.children[0].isStatusNode() ) { // Currently loading or load error return undefined; } return true; } return !!(this.children && this.children.length); }, /** Return true if node has keyboard focus. * @returns {boolean} */ hasFocus: function() { return this.tree.hasFocus() && this.tree.focusNode === this; }, /** Write to browser console if debugLevel >= 3 (prepending node info) * * @param {*} msg string or object or array of such */ info: function(msg) { if (this.tree.options.debugLevel >= 3) { Array.prototype.unshift.call(arguments, this.toString()); consoleApply("info", arguments); } }, /** Return true if node is active (see also FancytreeNode#isSelected). * @returns {boolean} */ isActive: function() { return this.tree.activeNode === this; }, /** Return true if node is vertically below `otherNode`, i.e. rendered in a subsequent row. * @param {FancytreeNode} otherNode * @returns {boolean} * @since 2.28 */ isBelowOf: function(otherNode) { return this.getIndexHier(".", 5) > otherNode.getIndexHier(".", 5); }, /** Return true if node is a direct child of otherNode. * @param {FancytreeNode} otherNode * @returns {boolean} */ isChildOf: function(otherNode) { return this.parent && this.parent === otherNode; }, /** Return true, if node is a direct or indirect sub node of otherNode. * @param {FancytreeNode} otherNode * @returns {boolean} */ isDescendantOf: function(otherNode) { if (!otherNode || otherNode.tree !== this.tree) { return false; } var p = this.parent; while (p) { if (p === otherNode) { return true; } if (p === p.parent) { $.error("Recursive parent link: " + p); } p = p.parent; } return false; }, /** Return true if node is expanded. * @returns {boolean} */ isExpanded: function() { return !!this.expanded; }, /** Return true if node is the first node of its parent's children. * @returns {boolean} */ isFirstSibling: function() { var p = this.parent; return !p || p.children[0] === this; }, /** Return true if node is a folder, i.e. has the node.folder attribute set. * @returns {boolean} */ isFolder: function() { return !!this.folder; }, /** Return true if node is the last node of its parent's children. * @returns {boolean} */ isLastSibling: function() { var p = this.parent; return !p || p.children[p.children.length - 1] === this; }, /** Return true if node is lazy (even if data was already loaded) * @returns {boolean} */ isLazy: function() { return !!this.lazy; }, /** Return true if node is lazy and loaded. For non-lazy nodes always return true. * @returns {boolean} */ isLoaded: function() { return !this.lazy || this.hasChildren() !== undefined; // Also checks if the only child is a status node }, /** Return true if children are currently beeing loaded, i.e. a Ajax request is pending. * @returns {boolean} */ isLoading: function() { return !!this._isLoading; }, /* * @deprecated since v2.4.0: Use isRootNode() instead */ isRoot: function() { return this.isRootNode(); }, /** Return true if node is partially selected (tri-state). * @returns {boolean} * @since 2.23 */ isPartsel: function() { return !this.selected && !!this.partsel; }, /** (experimental) Return true if this is partially loaded. * @returns {boolean} * @since 2.15 */ isPartload: function() { return !!this.partload; }, /** Return true if this is the (invisible) system root node. * @returns {boolean} * @since 2.4 */ isRootNode: function() { return this.tree.rootNode === this; }, /** Return true if node is selected, i.e. has a checkmark set (see also FancytreeNode#isActive). * @returns {boolean} */ isSelected: function() { return !!this.selected; }, /** Return true if this node is a temporarily generated system node like * 'loading', 'paging', or 'error' (node.statusNodeType contains the type). * @returns {boolean} */ isStatusNode: function() { return !!this.statusNodeType; }, /** Return true if this node is a status node of type 'paging'. * @returns {boolean} * @since 2.15 */ isPagingNode: function() { return this.statusNodeType === "paging"; }, /** Return true if this a top level node, i.e. a direct child of the (invisible) system root node. * @returns {boolean} * @since 2.4 */ isTopLevel: function() { return this.tree.rootNode === this.parent; }, /** Return true if node is lazy and not yet loaded. For non-lazy nodes always return false. * @returns {boolean} */ isUndefined: function() { return this.hasChildren() === undefined; // also checks if the only child is a status node }, /** Return true if all parent nodes are expanded. Note: this does not check * whether the node is scrolled into the visible part of the screen. * @returns {boolean} */ isVisible: function() { var i, l, parents = this.getParentList(false, false); for (i = 0, l = parents.length; i < l; i++) { if (!parents[i].expanded) { return false; } } return true; }, /** Deprecated. * @deprecated since 2014-02-16: use load() instead. */ lazyLoad: function(discard) { this.warn( "FancytreeNode.lazyLoad() is deprecated since 2014-02-16. Use .load() instead." ); return this.load(discard); }, /** * Load all children of a lazy node if neccessary. The expanded state is maintained. * @param {boolean} [forceReload=false] Pass true to discard any existing nodes before. Otherwise this method does nothing if the node was already loaded. * @returns {$.Promise} */ load: function(forceReload) { var res, source, that = this, wasExpanded = this.isExpanded(); _assert(this.isLazy(), "load() requires a lazy node"); // _assert( forceReload || this.isUndefined(), "Pass forceReload=true to re-load a lazy node" ); if (!forceReload && !this.isUndefined()) { return _getResolvedPromise(this); } if (this.isLoaded()) { this.resetLazy(); // also collapses } // This method is also called by setExpanded() and loadKeyPath(), so we // have to avoid recursion. source = this.tree._triggerNodeEvent("lazyLoad", this); if (source === false) { // #69 return _getResolvedPromise(this); } _assert( typeof source !== "boolean", "lazyLoad event must return source in data.result" ); res = this.tree._callHook("nodeLoadChildren", this, source); if (wasExpanded) { this.expanded = true; res.always(function() { that.render(); }); } else { res.always(function() { that.renderStatus(); // fix expander icon to 'loaded' }); } return res; }, /** Expand all parents and optionally scroll into visible area as neccessary. * Promise is resolved, when lazy loading and animations are done. * @param {object} [opts] passed to `setExpanded()`. * Defaults to {noAnimation: false, noEvents: false, scrollIntoView: true} * @returns {$.Promise} */ makeVisible: function(opts) { var i, that = this, deferreds = [], dfd = new $.Deferred(), parents = this.getParentList(false, false), len = parents.length, effects = !(opts && opts.noAnimation === true), scroll = !(opts && opts.scrollIntoView === false); // Expand bottom-up, so only the top node is animated for (i = len - 1; i >= 0; i--) { // that.debug("pushexpand" + parents[i]); deferreds.push(parents[i].setExpanded(true, opts)); } $.when.apply($, deferreds).done(function() { // All expands have finished // that.debug("expand DONE", scroll); if (scroll) { that.scrollIntoView(effects).done(function() { // that.debug("scroll DONE"); dfd.resolve(); }); } else { dfd.resolve(); } }); return dfd.promise(); }, /** Move this node to targetNode. * @param {FancytreeNode} targetNode * @param {string} mode
    		 *      'child': append this node as last child of targetNode.
    		 *               This is the default. To be compatble with the D'n'd
    		 *               hitMode, we also accept 'over'.
    		 *      'firstChild': add this node as first child of targetNode.
    		 *      'before': add this node as sibling before targetNode.
    		 *      'after': add this node as sibling after targetNode.
    * @param {function} [map] optional callback(FancytreeNode) to allow modifcations */ moveTo: function(targetNode, mode, map) { if (mode === undefined || mode === "over") { mode = "child"; } else if (mode === "firstChild") { if (targetNode.children && targetNode.children.length) { mode = "before"; targetNode = targetNode.children[0]; } else { mode = "child"; } } var pos, prevParent = this.parent, targetParent = mode === "child" ? targetNode : targetNode.parent; if (this === targetNode) { return; } else if (!this.parent) { $.error("Cannot move system root"); } else if (targetParent.isDescendantOf(this)) { $.error("Cannot move a node to its own descendant"); } if (targetParent !== prevParent) { prevParent.triggerModifyChild("remove", this); } // Unlink this node from current parent if (this.parent.children.length === 1) { if (this.parent === targetParent) { return; // #258 } this.parent.children = this.parent.lazy ? [] : null; this.parent.expanded = false; } else { pos = $.inArray(this, this.parent.children); _assert(pos >= 0, "invalid source parent"); this.parent.children.splice(pos, 1); } // Remove from source DOM parent // if(this.parent.ul){ // this.parent.ul.removeChild(this.li); // } // Insert this node to target parent's child list this.parent = targetParent; if (targetParent.hasChildren()) { switch (mode) { case "child": // Append to existing target children targetParent.children.push(this); break; case "before": // Insert this node before target node pos = $.inArray(targetNode, targetParent.children); _assert(pos >= 0, "invalid target parent"); targetParent.children.splice(pos, 0, this); break; case "after": // Insert this node after target node pos = $.inArray(targetNode, targetParent.children); _assert(pos >= 0, "invalid target parent"); targetParent.children.splice(pos + 1, 0, this); break; default: $.error("Invalid mode " + mode); } } else { targetParent.children = [this]; } // Parent has no