2018-05-24 18:59:32 +00:00
/ * ! j Q u e r y F a n c y t r e e P l u g i n - 2 . 2 8 . 1 - 2 0 1 8 - 0 3 - 1 9 T 0 6 : 4 7 : 3 7 Z
2018-01-01 14:39:23 +00:00
* https : //github.com/mar10/fancytree
2018-05-24 18:59:32 +00:00
* Copyright ( c ) 2018 Martin Wendt ; Licensed MIT
2018-01-01 14:39:23 +00:00
* /
/ * ! j Q u e r y U I - v 1 . 1 2 . 1 - 2 0 1 7 - 0 2 - 2 3
* http : //jqueryui.com
* Includes : widget . js , position . js , keycode . js , scroll - parent . js , unique - id . js , effect . js , effects / effect - blind . 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 : "<div>" ,
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 = $ ( "<div " +
"style='display:block;position:absolute;width:50px;height:50px;overflow:hidden;'>" +
"<div style='height:100px;width:auto;'></div></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" ) ;
}
} ) ;
}
} ) ;
/ * !
* jQuery UI Effects 1.12 . 1
* http : //jqueryui.com
*
* Copyright jQuery Foundation and other contributors
* Released under the MIT license .
* http : //jquery.org/license
* /
//>>label: Effects Core
//>>group: Effects
// jscs:disable maximumLineLength
//>>description: Extends the internal jQuery effects. Includes morphing and easing. Required by all other effects.
// jscs:enable maximumLineLength
//>>docs: http://api.jqueryui.com/category/effects-core/
//>>demos: http://jqueryui.com/effect/
var dataSpace = "ui-effects-" ,
dataSpaceStyle = "ui-effects-style" ,
dataSpaceAnimated = "ui-effects-animated" ,
// Create a local jQuery because jQuery Color relies on it and the
// global may not exist with AMD and a custom build (#10199)
jQuery = $ ;
$ . effects = {
effect : { }
} ;
/ * !
* jQuery Color Animations v2 . 1.2
* https : //github.com/jquery/jquery-color
*
* Copyright 2014 jQuery Foundation and other contributors
* Released under the MIT license .
* http : //jquery.org/license
*
* Date : Wed Jan 16 08 : 47 : 09 2013 - 0600
* /
( function ( jQuery , undefined ) {
var stepHooks = "backgroundColor borderBottomColor borderLeftColor borderRightColor " +
"borderTopColor color columnRuleColor outlineColor textDecorationColor textEmphasisColor" ,
// Plusequals test for += 100 -= 100
rplusequals = /^([\-+])=\s*(\d+\.?\d*)/ ,
// A set of RE's that can match strings and generate color tuples.
stringParsers = [ {
re : /rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/ ,
parse : function ( execResult ) {
return [
execResult [ 1 ] ,
execResult [ 2 ] ,
execResult [ 3 ] ,
execResult [ 4 ]
] ;
}
} , {
re : /rgba?\(\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/ ,
parse : function ( execResult ) {
return [
execResult [ 1 ] * 2.55 ,
execResult [ 2 ] * 2.55 ,
execResult [ 3 ] * 2.55 ,
execResult [ 4 ]
] ;
}
} , {
// This regex ignores A-F because it's compared against an already lowercased string
re : /#([a-f0-9]{2})([a-f0-9]{2})([a-f0-9]{2})/ ,
parse : function ( execResult ) {
return [
parseInt ( execResult [ 1 ] , 16 ) ,
parseInt ( execResult [ 2 ] , 16 ) ,
parseInt ( execResult [ 3 ] , 16 )
] ;
}
} , {
// This regex ignores A-F because it's compared against an already lowercased string
re : /#([a-f0-9])([a-f0-9])([a-f0-9])/ ,
parse : function ( execResult ) {
return [
parseInt ( execResult [ 1 ] + execResult [ 1 ] , 16 ) ,
parseInt ( execResult [ 2 ] + execResult [ 2 ] , 16 ) ,
parseInt ( execResult [ 3 ] + execResult [ 3 ] , 16 )
] ;
}
} , {
re : /hsla?\(\s*(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/ ,
space : "hsla" ,
parse : function ( execResult ) {
return [
execResult [ 1 ] ,
execResult [ 2 ] / 100 ,
execResult [ 3 ] / 100 ,
execResult [ 4 ]
] ;
}
} ] ,
// JQuery.Color( )
color = jQuery . Color = function ( color , green , blue , alpha ) {
return new jQuery . Color . fn . parse ( color , green , blue , alpha ) ;
} ,
spaces = {
rgba : {
props : {
red : {
idx : 0 ,
type : "byte"
} ,
green : {
idx : 1 ,
type : "byte"
} ,
blue : {
idx : 2 ,
type : "byte"
}
}
} ,
hsla : {
props : {
hue : {
idx : 0 ,
type : "degrees"
} ,
saturation : {
idx : 1 ,
type : "percent"
} ,
lightness : {
idx : 2 ,
type : "percent"
}
}
}
} ,
propTypes = {
"byte" : {
floor : true ,
max : 255
} ,
"percent" : {
max : 1
} ,
"degrees" : {
mod : 360 ,
floor : true
}
} ,
support = color . support = { } ,
// Element for support tests
supportElem = jQuery ( "<p>" ) [ 0 ] ,
// Colors = jQuery.Color.names
colors ,
// Local aliases of functions called often
each = jQuery . each ;
// Determine rgba support immediately
supportElem . style . cssText = "background-color:rgba(1,1,1,.5)" ;
support . rgba = supportElem . style . backgroundColor . indexOf ( "rgba" ) > - 1 ;
// Define cache name and alpha properties
// for rgba and hsla spaces
each ( spaces , function ( spaceName , space ) {
space . cache = "_" + spaceName ;
space . props . alpha = {
idx : 3 ,
type : "percent" ,
def : 1
} ;
} ) ;
function clamp ( value , prop , allowEmpty ) {
var type = propTypes [ prop . type ] || { } ;
if ( value == null ) {
return ( allowEmpty || ! prop . def ) ? null : prop . def ;
}
// ~~ is an short way of doing floor for positive numbers
value = type . floor ? ~ ~ value : parseFloat ( value ) ;
// IE will pass in empty strings as value for alpha,
// which will hit this case
if ( isNaN ( value ) ) {
return prop . def ;
}
if ( type . mod ) {
// We add mod before modding to make sure that negatives values
// get converted properly: -10 -> 350
return ( value + type . mod ) % type . mod ;
}
// For now all property types without mod have min and max
return 0 > value ? 0 : type . max < value ? type . max : value ;
}
function stringParse ( string ) {
var inst = color ( ) ,
rgba = inst . _rgba = [ ] ;
string = string . toLowerCase ( ) ;
each ( stringParsers , function ( i , parser ) {
var parsed ,
match = parser . re . exec ( string ) ,
values = match && parser . parse ( match ) ,
spaceName = parser . space || "rgba" ;
if ( values ) {
parsed = inst [ spaceName ] ( values ) ;
// If this was an rgba parse the assignment might happen twice
// oh well....
inst [ spaces [ spaceName ] . cache ] = parsed [ spaces [ spaceName ] . cache ] ;
rgba = inst . _rgba = parsed . _rgba ;
// Exit each( stringParsers ) here because we matched
return false ;
}
} ) ;
// Found a stringParser that handled it
if ( rgba . length ) {
// If this came from a parsed string, force "transparent" when alpha is 0
// chrome, (and maybe others) return "transparent" as rgba(0,0,0,0)
if ( rgba . join ( ) === "0,0,0,0" ) {
jQuery . extend ( rgba , colors . transparent ) ;
}
return inst ;
}
// Named colors
return colors [ string ] ;
}
color . fn = jQuery . extend ( color . prototype , {
parse : function ( red , green , blue , alpha ) {
if ( red === undefined ) {
this . _rgba = [ null , null , null , null ] ;
return this ;
}
if ( red . jquery || red . nodeType ) {
red = jQuery ( red ) . css ( green ) ;
green = undefined ;
}
var inst = this ,
type = jQuery . type ( red ) ,
rgba = this . _rgba = [ ] ;
// More than 1 argument specified - assume ( red, green, blue, alpha )
if ( green !== undefined ) {
red = [ red , green , blue , alpha ] ;
type = "array" ;
}
if ( type === "string" ) {
return this . parse ( stringParse ( red ) || colors . _default ) ;
}
if ( type === "array" ) {
each ( spaces . rgba . props , function ( key , prop ) {
rgba [ prop . idx ] = clamp ( red [ prop . idx ] , prop ) ;
} ) ;
return this ;
}
if ( type === "object" ) {
if ( red instanceof color ) {
each ( spaces , function ( spaceName , space ) {
if ( red [ space . cache ] ) {
inst [ space . cache ] = red [ space . cache ] . slice ( ) ;
}
} ) ;
} else {
each ( spaces , function ( spaceName , space ) {
var cache = space . cache ;
each ( space . props , function ( key , prop ) {
// If the cache doesn't exist, and we know how to convert
if ( ! inst [ cache ] && space . to ) {
// If the value was null, we don't need to copy it
// if the key was alpha, we don't need to copy it either
if ( key === "alpha" || red [ key ] == null ) {
return ;
}
inst [ cache ] = space . to ( inst . _rgba ) ;
}
// This is the only case where we allow nulls for ALL properties.
// call clamp with alwaysAllowEmpty
inst [ cache ] [ prop . idx ] = clamp ( red [ key ] , prop , true ) ;
} ) ;
// Everything defined but alpha?
if ( inst [ cache ] &&
jQuery . inArray ( null , inst [ cache ] . slice ( 0 , 3 ) ) < 0 ) {
// Use the default of 1
inst [ cache ] [ 3 ] = 1 ;
if ( space . from ) {
inst . _rgba = space . from ( inst [ cache ] ) ;
}
}
} ) ;
}
return this ;
}
} ,
is : function ( compare ) {
var is = color ( compare ) ,
same = true ,
inst = this ;
each ( spaces , function ( _ , space ) {
var localCache ,
isCache = is [ space . cache ] ;
if ( isCache ) {
localCache = inst [ space . cache ] || space . to && space . to ( inst . _rgba ) || [ ] ;
each ( space . props , function ( _ , prop ) {
if ( isCache [ prop . idx ] != null ) {
same = ( isCache [ prop . idx ] === localCache [ prop . idx ] ) ;
return same ;
}
} ) ;
}
return same ;
} ) ;
return same ;
} ,
_space : function ( ) {
var used = [ ] ,
inst = this ;
each ( spaces , function ( spaceName , space ) {
if ( inst [ space . cache ] ) {
used . push ( spaceName ) ;
}
} ) ;
return used . pop ( ) ;
} ,
transition : function ( other , distance ) {
var end = color ( other ) ,
spaceName = end . _space ( ) ,
space = spaces [ spaceName ] ,
startColor = this . alpha ( ) === 0 ? color ( "transparent" ) : this ,
start = startColor [ space . cache ] || space . to ( startColor . _rgba ) ,
result = start . slice ( ) ;
end = end [ space . cache ] ;
each ( space . props , function ( key , prop ) {
var index = prop . idx ,
startValue = start [ index ] ,
endValue = end [ index ] ,
type = propTypes [ prop . type ] || { } ;
// If null, don't override start value
if ( endValue === null ) {
return ;
}
// If null - use end
if ( startValue === null ) {
result [ index ] = endValue ;
} else {
if ( type . mod ) {
if ( endValue - startValue > type . mod / 2 ) {
startValue += type . mod ;
} else if ( startValue - endValue > type . mod / 2 ) {
startValue -= type . mod ;
}
}
result [ index ] = clamp ( ( endValue - startValue ) * distance + startValue , prop ) ;
}
} ) ;
return this [ spaceName ] ( result ) ;
} ,
blend : function ( opaque ) {
// If we are already opaque - return ourself
if ( this . _rgba [ 3 ] === 1 ) {
return this ;
}
var rgb = this . _rgba . slice ( ) ,
a = rgb . pop ( ) ,
blend = color ( opaque ) . _rgba ;
return color ( jQuery . map ( rgb , function ( v , i ) {
return ( 1 - a ) * blend [ i ] + a * v ;
} ) ) ;
} ,
toRgbaString : function ( ) {
var prefix = "rgba(" ,
rgba = jQuery . map ( this . _rgba , function ( v , i ) {
return v == null ? ( i > 2 ? 1 : 0 ) : v ;
} ) ;
if ( rgba [ 3 ] === 1 ) {
rgba . pop ( ) ;
prefix = "rgb(" ;
}
return prefix + rgba . join ( ) + ")" ;
} ,
toHslaString : function ( ) {
var prefix = "hsla(" ,
hsla = jQuery . map ( this . hsla ( ) , function ( v , i ) {
if ( v == null ) {
v = i > 2 ? 1 : 0 ;
}
// Catch 1 and 2
if ( i && i < 3 ) {
v = Math . round ( v * 100 ) + "%" ;
}
return v ;
} ) ;
if ( hsla [ 3 ] === 1 ) {
hsla . pop ( ) ;
prefix = "hsl(" ;
}
return prefix + hsla . join ( ) + ")" ;
} ,
toHexString : function ( includeAlpha ) {
var rgba = this . _rgba . slice ( ) ,
alpha = rgba . pop ( ) ;
if ( includeAlpha ) {
rgba . push ( ~ ~ ( alpha * 255 ) ) ;
}
return "#" + jQuery . map ( rgba , function ( v ) {
// Default to 0 when nulls exist
v = ( v || 0 ) . toString ( 16 ) ;
return v . length === 1 ? "0" + v : v ;
} ) . join ( "" ) ;
} ,
toString : function ( ) {
return this . _rgba [ 3 ] === 0 ? "transparent" : this . toRgbaString ( ) ;
}
} ) ;
color . fn . parse . prototype = color . fn ;
// Hsla conversions adapted from:
// https://code.google.com/p/maashaack/source/browse/packages/graphics/trunk/src/graphics/colors/HUE2RGB.as?r=5021
function hue2rgb ( p , q , h ) {
h = ( h + 1 ) % 1 ;
if ( h * 6 < 1 ) {
return p + ( q - p ) * h * 6 ;
}
if ( h * 2 < 1 ) {
return q ;
}
if ( h * 3 < 2 ) {
return p + ( q - p ) * ( ( 2 / 3 ) - h ) * 6 ;
}
return p ;
}
spaces . hsla . to = function ( rgba ) {
if ( rgba [ 0 ] == null || rgba [ 1 ] == null || rgba [ 2 ] == null ) {
return [ null , null , null , rgba [ 3 ] ] ;
}
var r = rgba [ 0 ] / 255 ,
g = rgba [ 1 ] / 255 ,
b = rgba [ 2 ] / 255 ,
a = rgba [ 3 ] ,
max = Math . max ( r , g , b ) ,
min = Math . min ( r , g , b ) ,
diff = max - min ,
add = max + min ,
l = add * 0.5 ,
h , s ;
if ( min === max ) {
h = 0 ;
} else if ( r === max ) {
h = ( 60 * ( g - b ) / diff ) + 360 ;
} else if ( g === max ) {
h = ( 60 * ( b - r ) / diff ) + 120 ;
} else {
h = ( 60 * ( r - g ) / diff ) + 240 ;
}
// Chroma (diff) == 0 means greyscale which, by definition, saturation = 0%
// otherwise, saturation is based on the ratio of chroma (diff) to lightness (add)
if ( diff === 0 ) {
s = 0 ;
} else if ( l <= 0.5 ) {
s = diff / add ;
} else {
s = diff / ( 2 - add ) ;
}
return [ Math . round ( h ) % 360 , s , l , a == null ? 1 : a ] ;
} ;
spaces . hsla . from = function ( hsla ) {
if ( hsla [ 0 ] == null || hsla [ 1 ] == null || hsla [ 2 ] == null ) {
return [ null , null , null , hsla [ 3 ] ] ;
}
var h = hsla [ 0 ] / 360 ,
s = hsla [ 1 ] ,
l = hsla [ 2 ] ,
a = hsla [ 3 ] ,
q = l <= 0.5 ? l * ( 1 + s ) : l + s - l * s ,
p = 2 * l - q ;
return [
Math . round ( hue2rgb ( p , q , h + ( 1 / 3 ) ) * 255 ) ,
Math . round ( hue2rgb ( p , q , h ) * 255 ) ,
Math . round ( hue2rgb ( p , q , h - ( 1 / 3 ) ) * 255 ) ,
a
] ;
} ;
each ( spaces , function ( spaceName , space ) {
var props = space . props ,
cache = space . cache ,
to = space . to ,
from = space . from ;
// Makes rgba() and hsla()
color . fn [ spaceName ] = function ( value ) {
// Generate a cache for this space if it doesn't exist
if ( to && ! this [ cache ] ) {
this [ cache ] = to ( this . _rgba ) ;
}
if ( value === undefined ) {
return this [ cache ] . slice ( ) ;
}
var ret ,
type = jQuery . type ( value ) ,
arr = ( type === "array" || type === "object" ) ? value : arguments ,
local = this [ cache ] . slice ( ) ;
each ( props , function ( key , prop ) {
var val = arr [ type === "object" ? key : prop . idx ] ;
if ( val == null ) {
val = local [ prop . idx ] ;
}
local [ prop . idx ] = clamp ( val , prop ) ;
} ) ;
if ( from ) {
ret = color ( from ( local ) ) ;
ret [ cache ] = local ;
return ret ;
} else {
return color ( local ) ;
}
} ;
// Makes red() green() blue() alpha() hue() saturation() lightness()
each ( props , function ( key , prop ) {
// Alpha is included in more than one space
if ( color . fn [ key ] ) {
return ;
}
color . fn [ key ] = function ( value ) {
var vtype = jQuery . type ( value ) ,
fn = ( key === "alpha" ? ( this . _hsla ? "hsla" : "rgba" ) : spaceName ) ,
local = this [ fn ] ( ) ,
cur = local [ prop . idx ] ,
match ;
if ( vtype === "undefined" ) {
return cur ;
}
if ( vtype === "function" ) {
value = value . call ( this , cur ) ;
vtype = jQuery . type ( value ) ;
}
if ( value == null && prop . empty ) {
return this ;
}
if ( vtype === "string" ) {
match = rplusequals . exec ( value ) ;
if ( match ) {
value = cur + parseFloat ( match [ 2 ] ) * ( match [ 1 ] === "+" ? 1 : - 1 ) ;
}
}
local [ prop . idx ] = value ;
return this [ fn ] ( local ) ;
} ;
} ) ;
} ) ;
// Add cssHook and .fx.step function for each named hook.
// accept a space separated string of properties
color . hook = function ( hook ) {
var hooks = hook . split ( " " ) ;
each ( hooks , function ( i , hook ) {
jQuery . cssHooks [ hook ] = {
set : function ( elem , value ) {
var parsed , curElem ,
backgroundColor = "" ;
if ( value !== "transparent" && ( jQuery . type ( value ) !== "string" ||
( parsed = stringParse ( value ) ) ) ) {
value = color ( parsed || value ) ;
if ( ! support . rgba && value . _rgba [ 3 ] !== 1 ) {
curElem = hook === "backgroundColor" ? elem . parentNode : elem ;
while (
( backgroundColor === "" || backgroundColor === "transparent" ) &&
curElem && curElem . style
) {
try {
backgroundColor = jQuery . css ( curElem , "backgroundColor" ) ;
curElem = curElem . parentNode ;
} catch ( e ) {
}
}
value = value . blend ( backgroundColor && backgroundColor !== "transparent" ?
backgroundColor :
"_default" ) ;
}
value = value . toRgbaString ( ) ;
}
try {
elem . style [ hook ] = value ;
} catch ( e ) {
// Wrapped to prevent IE from throwing errors on "invalid" values like
// 'auto' or 'inherit'
}
}
} ;
jQuery . fx . step [ hook ] = function ( fx ) {
if ( ! fx . colorInit ) {
fx . start = color ( fx . elem , hook ) ;
fx . end = color ( fx . end ) ;
fx . colorInit = true ;
}
jQuery . cssHooks [ hook ] . set ( fx . elem , fx . start . transition ( fx . end , fx . pos ) ) ;
} ;
} ) ;
} ;
color . hook ( stepHooks ) ;
jQuery . cssHooks . borderColor = {
expand : function ( value ) {
var expanded = { } ;
each ( [ "Top" , "Right" , "Bottom" , "Left" ] , function ( i , part ) {
expanded [ "border" + part + "Color" ] = value ;
} ) ;
return expanded ;
}
} ;
// Basic color names only.
// Usage of any of the other color names requires adding yourself or including
// jquery.color.svg-names.js.
colors = jQuery . Color . names = {
// 4.1. Basic color keywords
aqua : "#00ffff" ,
black : "#000000" ,
blue : "#0000ff" ,
fuchsia : "#ff00ff" ,
gray : "#808080" ,
green : "#008000" ,
lime : "#00ff00" ,
maroon : "#800000" ,
navy : "#000080" ,
olive : "#808000" ,
purple : "#800080" ,
red : "#ff0000" ,
silver : "#c0c0c0" ,
teal : "#008080" ,
white : "#ffffff" ,
yellow : "#ffff00" ,
// 4.2.3. "transparent" color keyword
transparent : [ null , null , null , 0 ] ,
_default : "#ffffff"
} ;
} ) ( jQuery ) ;
/******************************************************************************/
/****************************** CLASS ANIMATIONS ******************************/
/******************************************************************************/
( function ( ) {
var classAnimationActions = [ "add" , "remove" , "toggle" ] ,
shorthandStyles = {
border : 1 ,
borderBottom : 1 ,
borderColor : 1 ,
borderLeft : 1 ,
borderRight : 1 ,
borderTop : 1 ,
borderWidth : 1 ,
margin : 1 ,
padding : 1
} ;
$ . each (
[ "borderLeftStyle" , "borderRightStyle" , "borderBottomStyle" , "borderTopStyle" ] ,
function ( _ , prop ) {
$ . fx . step [ prop ] = function ( fx ) {
if ( fx . end !== "none" && ! fx . setAttr || fx . pos === 1 && ! fx . setAttr ) {
jQuery . style ( fx . elem , prop , fx . end ) ;
fx . setAttr = true ;
}
} ;
}
) ;
function getElementStyles ( elem ) {
var key , len ,
style = elem . ownerDocument . defaultView ?
elem . ownerDocument . defaultView . getComputedStyle ( elem , null ) :
elem . currentStyle ,
styles = { } ;
if ( style && style . length && style [ 0 ] && style [ style [ 0 ] ] ) {
len = style . length ;
while ( len -- ) {
key = style [ len ] ;
if ( typeof style [ key ] === "string" ) {
styles [ $ . camelCase ( key ) ] = style [ key ] ;
}
}
// Support: Opera, IE <9
} else {
for ( key in style ) {
if ( typeof style [ key ] === "string" ) {
styles [ key ] = style [ key ] ;
}
}
}
return styles ;
}
function styleDifference ( oldStyle , newStyle ) {
var diff = { } ,
name , value ;
for ( name in newStyle ) {
value = newStyle [ name ] ;
if ( oldStyle [ name ] !== value ) {
if ( ! shorthandStyles [ name ] ) {
if ( $ . fx . step [ name ] || ! isNaN ( parseFloat ( value ) ) ) {
diff [ name ] = value ;
}
}
}
}
return diff ;
}
// Support: jQuery <1.8
if ( ! $ . fn . addBack ) {
$ . fn . addBack = function ( selector ) {
return this . add ( selector == null ?
this . prevObject : this . prevObject . filter ( selector )
) ;
} ;
}
$ . effects . animateClass = function ( value , duration , easing , callback ) {
var o = $ . speed ( duration , easing , callback ) ;
return this . queue ( function ( ) {
var animated = $ ( this ) ,
baseClass = animated . attr ( "class" ) || "" ,
applyClassChange ,
allAnimations = o . children ? animated . find ( "*" ) . addBack ( ) : animated ;
// Map the animated objects to store the original styles.
allAnimations = allAnimations . map ( function ( ) {
var el = $ ( this ) ;
return {
el : el ,
start : getElementStyles ( this )
} ;
} ) ;
// Apply class change
applyClassChange = function ( ) {
$ . each ( classAnimationActions , function ( i , action ) {
if ( value [ action ] ) {
animated [ action + "Class" ] ( value [ action ] ) ;
}
} ) ;
} ;
applyClassChange ( ) ;
// Map all animated objects again - calculate new styles and diff
allAnimations = allAnimations . map ( function ( ) {
this . end = getElementStyles ( this . el [ 0 ] ) ;
this . diff = styleDifference ( this . start , this . end ) ;
return this ;
} ) ;
// Apply original class
animated . attr ( "class" , baseClass ) ;
// Map all animated objects again - this time collecting a promise
allAnimations = allAnimations . map ( function ( ) {
var styleInfo = this ,
dfd = $ . Deferred ( ) ,
opts = $ . extend ( { } , o , {
queue : false ,
complete : function ( ) {
dfd . resolve ( styleInfo ) ;
}
} ) ;
this . el . animate ( this . diff , opts ) ;
return dfd . promise ( ) ;
} ) ;
// Once all animations have completed:
$ . when . apply ( $ , allAnimations . get ( ) ) . done ( function ( ) {
// Set the final class
applyClassChange ( ) ;
// For each animated element,
// clear all css properties that were animated
$ . each ( arguments , function ( ) {
var el = this . el ;
$ . each ( this . diff , function ( key ) {
el . css ( key , "" ) ;
} ) ;
} ) ;
// This is guarnteed to be there if you use jQuery.speed()
// it also handles dequeuing the next anim...
o . complete . call ( animated [ 0 ] ) ;
} ) ;
} ) ;
} ;
$ . fn . extend ( {
addClass : ( function ( orig ) {
return function ( classNames , speed , easing , callback ) {
return speed ?
$ . effects . animateClass . call ( this ,
{ add : classNames } , speed , easing , callback ) :
orig . apply ( this , arguments ) ;
} ;
} ) ( $ . fn . addClass ) ,
removeClass : ( function ( orig ) {
return function ( classNames , speed , easing , callback ) {
return arguments . length > 1 ?
$ . effects . animateClass . call ( this ,
{ remove : classNames } , speed , easing , callback ) :
orig . apply ( this , arguments ) ;
} ;
} ) ( $ . fn . removeClass ) ,
toggleClass : ( function ( orig ) {
return function ( classNames , force , speed , easing , callback ) {
if ( typeof force === "boolean" || force === undefined ) {
if ( ! speed ) {
// Without speed parameter
return orig . apply ( this , arguments ) ;
} else {
return $ . effects . animateClass . call ( this ,
( force ? { add : classNames } : { remove : classNames } ) ,
speed , easing , callback ) ;
}
} else {
// Without force parameter
return $ . effects . animateClass . call ( this ,
{ toggle : classNames } , force , speed , easing ) ;
}
} ;
} ) ( $ . fn . toggleClass ) ,
switchClass : function ( remove , add , speed , easing , callback ) {
return $ . effects . animateClass . call ( this , {
add : add ,
remove : remove
} , speed , easing , callback ) ;
}
} ) ;
} ) ( ) ;
/******************************************************************************/
/*********************************** EFFECTS **********************************/
/******************************************************************************/
( function ( ) {
if ( $ . expr && $ . expr . filters && $ . expr . filters . animated ) {
$ . expr . filters . animated = ( function ( orig ) {
return function ( elem ) {
return ! ! $ ( elem ) . data ( dataSpaceAnimated ) || orig ( elem ) ;
} ;
} ) ( $ . expr . filters . animated ) ;
}
if ( $ . uiBackCompat !== false ) {
$ . extend ( $ . effects , {
// Saves a set of properties in a data storage
save : function ( element , set ) {
var i = 0 , length = set . length ;
for ( ; i < length ; i ++ ) {
if ( set [ i ] !== null ) {
element . data ( dataSpace + set [ i ] , element [ 0 ] . style [ set [ i ] ] ) ;
}
}
} ,
// Restores a set of previously saved properties from a data storage
restore : function ( element , set ) {
var val , i = 0 , length = set . length ;
for ( ; i < length ; i ++ ) {
if ( set [ i ] !== null ) {
val = element . data ( dataSpace + set [ i ] ) ;
element . css ( set [ i ] , val ) ;
}
}
} ,
setMode : function ( el , mode ) {
if ( mode === "toggle" ) {
mode = el . is ( ":hidden" ) ? "show" : "hide" ;
}
return mode ;
} ,
// Wraps the element around a wrapper that copies position properties
createWrapper : function ( element ) {
// If the element is already wrapped, return it
if ( element . parent ( ) . is ( ".ui-effects-wrapper" ) ) {
return element . parent ( ) ;
}
// Wrap the element
var props = {
width : element . outerWidth ( true ) ,
height : element . outerHeight ( true ) ,
"float" : element . css ( "float" )
} ,
wrapper = $ ( "<div></div>" )
. addClass ( "ui-effects-wrapper" )
. css ( {
fontSize : "100%" ,
background : "transparent" ,
border : "none" ,
margin : 0 ,
padding : 0
} ) ,
// Store the size in case width/height are defined in % - Fixes #5245
size = {
width : element . width ( ) ,
height : element . height ( )
} ,
active = document . activeElement ;
// Support: Firefox
// Firefox incorrectly exposes anonymous content
// https://bugzilla.mozilla.org/show_bug.cgi?id=561664
try {
active . id ;
} catch ( e ) {
active = document . body ;
}
element . wrap ( wrapper ) ;
// Fixes #7595 - Elements lose focus when wrapped.
if ( element [ 0 ] === active || $ . contains ( element [ 0 ] , active ) ) {
$ ( active ) . trigger ( "focus" ) ;
}
// Hotfix for jQuery 1.4 since some change in wrap() seems to actually
// lose the reference to the wrapped element
wrapper = element . parent ( ) ;
// Transfer positioning properties to the wrapper
if ( element . css ( "position" ) === "static" ) {
wrapper . css ( { position : "relative" } ) ;
element . css ( { position : "relative" } ) ;
} else {
$ . extend ( props , {
position : element . css ( "position" ) ,
zIndex : element . css ( "z-index" )
} ) ;
$ . each ( [ "top" , "left" , "bottom" , "right" ] , function ( i , pos ) {
props [ pos ] = element . css ( pos ) ;
if ( isNaN ( parseInt ( props [ pos ] , 10 ) ) ) {
props [ pos ] = "auto" ;
}
} ) ;
element . css ( {
position : "relative" ,
top : 0 ,
left : 0 ,
right : "auto" ,
bottom : "auto"
} ) ;
}
element . css ( size ) ;
return wrapper . css ( props ) . show ( ) ;
} ,
removeWrapper : function ( element ) {
var active = document . activeElement ;
if ( element . parent ( ) . is ( ".ui-effects-wrapper" ) ) {
element . parent ( ) . replaceWith ( element ) ;
// Fixes #7595 - Elements lose focus when wrapped.
if ( element [ 0 ] === active || $ . contains ( element [ 0 ] , active ) ) {
$ ( active ) . trigger ( "focus" ) ;
}
}
return element ;
}
} ) ;
}
$ . extend ( $ . effects , {
version : "1.12.1" ,
define : function ( name , mode , effect ) {
if ( ! effect ) {
effect = mode ;
mode = "effect" ;
}
$ . effects . effect [ name ] = effect ;
$ . effects . effect [ name ] . mode = mode ;
return effect ;
} ,
scaledDimensions : function ( element , percent , direction ) {
if ( percent === 0 ) {
return {
height : 0 ,
width : 0 ,
outerHeight : 0 ,
outerWidth : 0
} ;
}
var x = direction !== "horizontal" ? ( ( percent || 100 ) / 100 ) : 1 ,
y = direction !== "vertical" ? ( ( percent || 100 ) / 100 ) : 1 ;
return {
height : element . height ( ) * y ,
width : element . width ( ) * x ,
outerHeight : element . outerHeight ( ) * y ,
outerWidth : element . outerWidth ( ) * x
} ;
} ,
clipToBox : function ( animation ) {
return {
width : animation . clip . right - animation . clip . left ,
height : animation . clip . bottom - animation . clip . top ,
left : animation . clip . left ,
top : animation . clip . top
} ;
} ,
// Injects recently queued functions to be first in line (after "inprogress")
unshift : function ( element , queueLength , count ) {
var queue = element . queue ( ) ;
if ( queueLength > 1 ) {
queue . splice . apply ( queue ,
[ 1 , 0 ] . concat ( queue . splice ( queueLength , count ) ) ) ;
}
element . dequeue ( ) ;
} ,
saveStyle : function ( element ) {
element . data ( dataSpaceStyle , element [ 0 ] . style . cssText ) ;
} ,
restoreStyle : function ( element ) {
element [ 0 ] . style . cssText = element . data ( dataSpaceStyle ) || "" ;
element . removeData ( dataSpaceStyle ) ;
} ,
mode : function ( element , mode ) {
var hidden = element . is ( ":hidden" ) ;
if ( mode === "toggle" ) {
mode = hidden ? "show" : "hide" ;
}
if ( hidden ? mode === "hide" : mode === "show" ) {
mode = "none" ;
}
return mode ;
} ,
// Translates a [top,left] array into a baseline value
getBaseline : function ( origin , original ) {
var y , x ;
switch ( origin [ 0 ] ) {
case "top" :
y = 0 ;
break ;
case "middle" :
y = 0.5 ;
break ;
case "bottom" :
y = 1 ;
break ;
default :
y = origin [ 0 ] / original . height ;
}
switch ( origin [ 1 ] ) {
case "left" :
x = 0 ;
break ;
case "center" :
x = 0.5 ;
break ;
case "right" :
x = 1 ;
break ;
default :
x = origin [ 1 ] / original . width ;
}
return {
x : x ,
y : y
} ;
} ,
// Creates a placeholder element so that the original element can be made absolute
createPlaceholder : function ( element ) {
var placeholder ,
cssPosition = element . css ( "position" ) ,
position = element . position ( ) ;
// Lock in margins first to account for form elements, which
// will change margin if you explicitly set height
// see: http://jsfiddle.net/JZSMt/3/ https://bugs.webkit.org/show_bug.cgi?id=107380
// Support: Safari
element . css ( {
marginTop : element . css ( "marginTop" ) ,
marginBottom : element . css ( "marginBottom" ) ,
marginLeft : element . css ( "marginLeft" ) ,
marginRight : element . css ( "marginRight" )
} )
. outerWidth ( element . outerWidth ( ) )
. outerHeight ( element . outerHeight ( ) ) ;
if ( /^(static|relative)/ . test ( cssPosition ) ) {
cssPosition = "absolute" ;
placeholder = $ ( "<" + element [ 0 ] . nodeName + ">" ) . insertAfter ( element ) . css ( {
// Convert inline to inline block to account for inline elements
// that turn to inline block based on content (like img)
display : /^(inline|ruby)/ . test ( element . css ( "display" ) ) ?
"inline-block" :
"block" ,
visibility : "hidden" ,
// Margins need to be set to account for margin collapse
marginTop : element . css ( "marginTop" ) ,
marginBottom : element . css ( "marginBottom" ) ,
marginLeft : element . css ( "marginLeft" ) ,
marginRight : element . css ( "marginRight" ) ,
"float" : element . css ( "float" )
} )
. outerWidth ( element . outerWidth ( ) )
. outerHeight ( element . outerHeight ( ) )
. addClass ( "ui-effects-placeholder" ) ;
element . data ( dataSpace + "placeholder" , placeholder ) ;
}
element . css ( {
position : cssPosition ,
left : position . left ,
top : position . top
} ) ;
return placeholder ;
} ,
removePlaceholder : function ( element ) {
var dataKey = dataSpace + "placeholder" ,
placeholder = element . data ( dataKey ) ;
if ( placeholder ) {
placeholder . remove ( ) ;
element . removeData ( dataKey ) ;
}
} ,
// Removes a placeholder if it exists and restores
// properties that were modified during placeholder creation
cleanUp : function ( element ) {
$ . effects . restoreStyle ( element ) ;
$ . effects . removePlaceholder ( element ) ;
} ,
setTransition : function ( element , list , factor , value ) {
value = value || { } ;
$ . each ( list , function ( i , x ) {
var unit = element . cssUnit ( x ) ;
if ( unit [ 0 ] > 0 ) {
value [ x ] = unit [ 0 ] * factor + unit [ 1 ] ;
}
} ) ;
return value ;
}
} ) ;
// Return an effect options object for the given parameters:
function _normalizeArguments ( effect , options , speed , callback ) {
// Allow passing all options as the first parameter
if ( $ . isPlainObject ( effect ) ) {
options = effect ;
effect = effect . effect ;
}
// Convert to an object
effect = { effect : effect } ;
// Catch (effect, null, ...)
if ( options == null ) {
options = { } ;
}
// Catch (effect, callback)
if ( $ . isFunction ( options ) ) {
callback = options ;
speed = null ;
options = { } ;
}
// Catch (effect, speed, ?)
if ( typeof options === "number" || $ . fx . speeds [ options ] ) {
callback = speed ;
speed = options ;
options = { } ;
}
// Catch (effect, options, callback)
if ( $ . isFunction ( speed ) ) {
callback = speed ;
speed = null ;
}
// Add options to effect
if ( options ) {
$ . extend ( effect , options ) ;
}
speed = speed || options . duration ;
effect . duration = $ . fx . off ? 0 :
typeof speed === "number" ? speed :
speed in $ . fx . speeds ? $ . fx . speeds [ speed ] :
$ . fx . speeds . _default ;
effect . complete = callback || options . complete ;
return effect ;
}
function standardAnimationOption ( option ) {
// Valid standard speeds (nothing, number, named speed)
if ( ! option || typeof option === "number" || $ . fx . speeds [ option ] ) {
return true ;
}
// Invalid strings - treat as "normal" speed
if ( typeof option === "string" && ! $ . effects . effect [ option ] ) {
return true ;
}
// Complete callback
if ( $ . isFunction ( option ) ) {
return true ;
}
// Options hash (but not naming an effect)
if ( typeof option === "object" && ! option . effect ) {
return true ;
}
// Didn't match any standard API
return false ;
}
$ . fn . extend ( {
effect : function ( /* effect, options, speed, callback */ ) {
var args = _normalizeArguments . apply ( this , arguments ) ,
effectMethod = $ . effects . effect [ args . effect ] ,
defaultMode = effectMethod . mode ,
queue = args . queue ,
queueName = queue || "fx" ,
complete = args . complete ,
mode = args . mode ,
modes = [ ] ,
prefilter = function ( next ) {
var el = $ ( this ) ,
normalizedMode = $ . effects . mode ( el , mode ) || defaultMode ;
// Sentinel for duck-punching the :animated psuedo-selector
el . data ( dataSpaceAnimated , true ) ;
// Save effect mode for later use,
// we can't just call $.effects.mode again later,
// as the .show() below destroys the initial state
modes . push ( normalizedMode ) ;
// See $.uiBackCompat inside of run() for removal of defaultMode in 1.13
if ( defaultMode && ( normalizedMode === "show" ||
( normalizedMode === defaultMode && normalizedMode === "hide" ) ) ) {
el . show ( ) ;
}
if ( ! defaultMode || normalizedMode !== "none" ) {
$ . effects . saveStyle ( el ) ;
}
if ( $ . isFunction ( next ) ) {
next ( ) ;
}
} ;
if ( $ . fx . off || ! effectMethod ) {
// Delegate to the original method (e.g., .show()) if possible
if ( mode ) {
return this [ mode ] ( args . duration , complete ) ;
} else {
return this . each ( function ( ) {
if ( complete ) {
complete . call ( this ) ;
}
} ) ;
}
}
function run ( next ) {
var elem = $ ( this ) ;
function cleanup ( ) {
elem . removeData ( dataSpaceAnimated ) ;
$ . effects . cleanUp ( elem ) ;
if ( args . mode === "hide" ) {
elem . hide ( ) ;
}
done ( ) ;
}
function done ( ) {
if ( $ . isFunction ( complete ) ) {
complete . call ( elem [ 0 ] ) ;
}
if ( $ . isFunction ( next ) ) {
next ( ) ;
}
}
// Override mode option on a per element basis,
// as toggle can be either show or hide depending on element state
args . mode = modes . shift ( ) ;
if ( $ . uiBackCompat !== false && ! defaultMode ) {
if ( elem . is ( ":hidden" ) ? mode === "hide" : mode === "show" ) {
// Call the core method to track "olddisplay" properly
elem [ mode ] ( ) ;
done ( ) ;
} else {
effectMethod . call ( elem [ 0 ] , args , done ) ;
}
} else {
if ( args . mode === "none" ) {
// Call the core method to track "olddisplay" properly
elem [ mode ] ( ) ;
done ( ) ;
} else {
effectMethod . call ( elem [ 0 ] , args , cleanup ) ;
}
}
}
// Run prefilter on all elements first to ensure that
// any showing or hiding happens before placeholder creation,
// which ensures that any layout changes are correctly captured.
return queue === false ?
this . each ( prefilter ) . each ( run ) :
this . queue ( queueName , prefilter ) . queue ( queueName , run ) ;
} ,
show : ( function ( orig ) {
return function ( option ) {
if ( standardAnimationOption ( option ) ) {
return orig . apply ( this , arguments ) ;
} else {
var args = _normalizeArguments . apply ( this , arguments ) ;
args . mode = "show" ;
return this . effect . call ( this , args ) ;
}
} ;
} ) ( $ . fn . show ) ,
hide : ( function ( orig ) {
return function ( option ) {
if ( standardAnimationOption ( option ) ) {
return orig . apply ( this , arguments ) ;
} else {
var args = _normalizeArguments . apply ( this , arguments ) ;
args . mode = "hide" ;
return this . effect . call ( this , args ) ;
}
} ;
} ) ( $ . fn . hide ) ,
toggle : ( function ( orig ) {
return function ( option ) {
if ( standardAnimationOption ( option ) || typeof option === "boolean" ) {
return orig . apply ( this , arguments ) ;
} else {
var args = _normalizeArguments . apply ( this , arguments ) ;
args . mode = "toggle" ;
return this . effect . call ( this , args ) ;
}
} ;
} ) ( $ . fn . toggle ) ,
cssUnit : function ( key ) {
var style = this . css ( key ) ,
val = [ ] ;
$ . each ( [ "em" , "px" , "%" , "pt" ] , function ( i , unit ) {
if ( style . indexOf ( unit ) > 0 ) {
val = [ parseFloat ( style ) , unit ] ;
}
} ) ;
return val ;
} ,
cssClip : function ( clipObj ) {
if ( clipObj ) {
return this . css ( "clip" , "rect(" + clipObj . top + "px " + clipObj . right + "px " +
clipObj . bottom + "px " + clipObj . left + "px)" ) ;
}
return parseClip ( this . css ( "clip" ) , this ) ;
} ,
transfer : function ( options , done ) {
var element = $ ( this ) ,
target = $ ( options . to ) ,
targetFixed = target . css ( "position" ) === "fixed" ,
body = $ ( "body" ) ,
fixTop = targetFixed ? body . scrollTop ( ) : 0 ,
fixLeft = targetFixed ? body . scrollLeft ( ) : 0 ,
endPosition = target . offset ( ) ,
animation = {
top : endPosition . top - fixTop ,
left : endPosition . left - fixLeft ,
height : target . innerHeight ( ) ,
width : target . innerWidth ( )
} ,
startPosition = element . offset ( ) ,
transfer = $ ( "<div class='ui-effects-transfer'></div>" )
. appendTo ( "body" )
. addClass ( options . className )
. css ( {
top : startPosition . top - fixTop ,
left : startPosition . left - fixLeft ,
height : element . innerHeight ( ) ,
width : element . innerWidth ( ) ,
position : targetFixed ? "fixed" : "absolute"
} )
. animate ( animation , options . duration , options . easing , function ( ) {
transfer . remove ( ) ;
if ( $ . isFunction ( done ) ) {
done ( ) ;
}
} ) ;
}
} ) ;
function parseClip ( str , element ) {
var outerWidth = element . outerWidth ( ) ,
outerHeight = element . outerHeight ( ) ,
clipRegex = /^rect\((-?\d*\.?\d*px|-?\d+%|auto),?\s*(-?\d*\.?\d*px|-?\d+%|auto),?\s*(-?\d*\.?\d*px|-?\d+%|auto),?\s*(-?\d*\.?\d*px|-?\d+%|auto)\)$/ ,
values = clipRegex . exec ( str ) || [ "" , 0 , outerWidth , outerHeight , 0 ] ;
return {
top : parseFloat ( values [ 1 ] ) || 0 ,
right : values [ 2 ] === "auto" ? outerWidth : parseFloat ( values [ 2 ] ) ,
bottom : values [ 3 ] === "auto" ? outerHeight : parseFloat ( values [ 3 ] ) ,
left : parseFloat ( values [ 4 ] ) || 0
} ;
}
$ . fx . step . clip = function ( fx ) {
if ( ! fx . clipInit ) {
fx . start = $ ( fx . elem ) . cssClip ( ) ;
if ( typeof fx . end === "string" ) {
fx . end = parseClip ( fx . end , fx . elem ) ;
}
fx . clipInit = true ;
}
$ ( fx . elem ) . cssClip ( {
top : fx . pos * ( fx . end . top - fx . start . top ) + fx . start . top ,
right : fx . pos * ( fx . end . right - fx . start . right ) + fx . start . right ,
bottom : fx . pos * ( fx . end . bottom - fx . start . bottom ) + fx . start . bottom ,
left : fx . pos * ( fx . end . left - fx . start . left ) + fx . start . left
} ) ;
} ;
} ) ( ) ;
/******************************************************************************/
/*********************************** EASING ***********************************/
/******************************************************************************/
( function ( ) {
// Based on easing equations from Robert Penner (http://www.robertpenner.com/easing)
var baseEasings = { } ;
$ . each ( [ "Quad" , "Cubic" , "Quart" , "Quint" , "Expo" ] , function ( i , name ) {
baseEasings [ name ] = function ( p ) {
return Math . pow ( p , i + 2 ) ;
} ;
} ) ;
$ . extend ( baseEasings , {
Sine : function ( p ) {
return 1 - Math . cos ( p * Math . PI / 2 ) ;
} ,
Circ : function ( p ) {
return 1 - Math . sqrt ( 1 - p * p ) ;
} ,
Elastic : function ( p ) {
return p === 0 || p === 1 ? p :
- Math . pow ( 2 , 8 * ( p - 1 ) ) * Math . sin ( ( ( p - 1 ) * 80 - 7.5 ) * Math . PI / 15 ) ;
} ,
Back : function ( p ) {
return p * p * ( 3 * p - 2 ) ;
} ,
Bounce : function ( p ) {
var pow2 ,
bounce = 4 ;
while ( p < ( ( pow2 = Math . pow ( 2 , -- bounce ) ) - 1 ) / 11 ) { }
return 1 / Math . pow ( 4 , 3 - bounce ) - 7.5625 * Math . pow ( ( pow2 * 3 - 2 ) / 22 - p , 2 ) ;
}
} ) ;
$ . each ( baseEasings , function ( name , easeIn ) {
$ . easing [ "easeIn" + name ] = easeIn ;
$ . easing [ "easeOut" + name ] = function ( p ) {
return 1 - easeIn ( 1 - p ) ;
} ;
$ . easing [ "easeInOut" + name ] = function ( p ) {
return p < 0.5 ?
easeIn ( p * 2 ) / 2 :
1 - easeIn ( p * - 2 + 2 ) / 2 ;
} ;
} ) ;
} ) ( ) ;
var effect = $ . effects ;
/ * !
* jQuery UI Effects Blind 1.12 . 1
* http : //jqueryui.com
*
* Copyright jQuery Foundation and other contributors
* Released under the MIT license .
* http : //jquery.org/license
* /
//>>label: Blind Effect
//>>group: Effects
//>>description: Blinds the element.
//>>docs: http://api.jqueryui.com/blind-effect/
//>>demos: http://jqueryui.com/effect/
var effectsEffectBlind = $ . effects . define ( "blind" , "hide" , function ( options , done ) {
var map = {
up : [ "bottom" , "top" ] ,
vertical : [ "bottom" , "top" ] ,
down : [ "top" , "bottom" ] ,
left : [ "right" , "left" ] ,
horizontal : [ "right" , "left" ] ,
right : [ "left" , "right" ]
} ,
element = $ ( this ) ,
direction = options . direction || "up" ,
start = element . cssClip ( ) ,
animate = { clip : $ . extend ( { } , start ) } ,
placeholder = $ . effects . createPlaceholder ( element ) ;
animate . clip [ map [ direction ] [ 0 ] ] = animate . clip [ map [ direction ] [ 1 ] ] ;
if ( options . mode === "show" ) {
element . cssClip ( animate . clip ) ;
if ( placeholder ) {
placeholder . css ( $ . effects . clipToBox ( animate ) ) ;
}
animate . clip = start ;
}
if ( placeholder ) {
placeholder . animate ( $ . effects . clipToBox ( animate ) , options . duration , options . easing ) ;
}
element . animate ( animate , {
queue : false ,
duration : options . duration ,
easing : options . easing ,
complete : done
} ) ;
} ) ;
// 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/
*
2018-05-24 18:59:32 +00:00
* Copyright ( c ) 2008 - 2018 , Martin Wendt ( http : //wwWendt.de)
2018-01-01 14:39:23 +00:00
* Released under the MIT license
* https : //github.com/mar10/fancytree/wiki/LicenseInfo
*
2018-05-24 18:59:32 +00:00
* @ version 2.28 . 1
* @ date 2018 - 03 - 19 T06 : 47 : 37 Z
2018-01-01 14:39:23 +00:00
* /
/ * * C o r e F a n c y t r e e m o d u l e .
* /
// 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
2018-05-24 18:59:32 +00:00
require ( "./jquery.fancytree.ui-deps" ) ;
2018-01-01 14:39:23 +00:00
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 '/'
2018-05-24 18:59:32 +00:00
REX _HTML = /[&<>"'\/]/g , // Escape those characters
REX _TOOLTIP = /[<>"'\/]/g , // Don't escape `&` in tooltips
2018-01-01 14:39:23 +00:00
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 : "=" ,
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 : "'" } ,
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 = { } ,
2018-05-24 18:59:32 +00:00
// 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 ( " " ) ,
2018-01-01 14:39:23 +00:00
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 ;
}
}
2018-05-24 18:59:32 +00:00
// for(i=0; i<TREE_ATTRS.length; i++) {
// TREE_ATTR_MAP[TREE_ATTRS[i]] = true;
// }
2018-01-01 14:39:23 +00:00
function _assert ( cond , msg ) {
// TODO: see qunit.js extractStacktrace()
if ( ! cond ) {
msg = msg ? ": " + msg : "" ;
// consoleApply("assert", [!!cond, msg]);
$ . error ( "Fancytree assertion failed" + msg ) ;
}
}
_assert ( $ . ui , "Fancytree requires jQuery UI (http://jqueryui.com)" ) ;
function consoleApply ( method , args ) {
var i , s ,
fn = window . console ? window . console [ method ] : null ;
if ( fn ) {
try {
fn . apply ( window . console , args ) ;
} catch ( e ) {
// IE 8?
s = "" ;
for ( i = 0 ; i < args . length ; i ++ ) {
s += args [ i ] ;
}
fn ( s ) ;
}
}
}
2018-05-24 18:59:32 +00:00
/* support: IE8 Polyfil for Date.now() */
if ( ! Date . now ) {
Date . now = function now ( ) { return new Date ( ) . getTime ( ) ; } ;
}
2018-01-01 14:39:23 +00:00
/*Return true if x is a FancytreeNode.*/
function _isNode ( x ) {
return ! ! ( x . tree && x . statusNodeType !== undefined ) ;
}
/ * * R e t u r n t r u e i f d o t t e d v e r s i o n s t r i n g i s e q u a l o r h i g h e r t h a n r e q u e s t e d v e r s i o n .
*
* See http : //jsfiddle.net/mar10/FjSAN/
* /
function isVersionAtLeast ( dottedVersion , major , minor , patch ) {
var i , v , t ,
verParts = $ . map ( $ . trim ( dottedVersion ) . split ( "." ) , function ( e ) { return parseInt ( e , 10 ) ; } ) ,
testParts = $ . map ( Array . prototype . slice . call ( arguments , 1 ) , function ( e ) { return parseInt ( e , 10 ) ; } ) ;
for ( i = 0 ; i < testParts . length ; i ++ ) {
v = verParts [ i ] || 0 ;
t = testParts [ i ] || 0 ;
if ( v !== t ) {
return ( v > t ) ;
}
}
return true ;
}
/ * * R e t u r n a w r a p p e r t h a t c a l l s s u b . m e t h o d N a m e ( ) a n d e x p o s e s
* 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 ;
// <li data-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 . < br >
* 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> ` . < br >
* Note : use ` node.add/remove/toggleClass() ` to modify .
* @ property { boolean } folder Folder nodes have different default icons and click behavior . < br >
* 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
2018-05-24 18:59:32 +00:00
* @ 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
2018-01-01 14:39:23 +00:00
* /
function FancytreeNode ( parent , obj ) {
var i , l , name , cl ;
this . parent = parent ;
this . tree = parent . tree ;
this . ul = null ;
this . li = null ; // <li id='key' ftnode=this> 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 ) ;
}
2018-05-24 18:59:32 +00:00
// Copy all other attributes to this.data.NAME
2018-01-01 14:39:23 +00:00
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 {
2018-05-24 18:59:32 +00:00
// Returns null if insertBefore is not a direct child:
2018-01-01 14:39:23 +00:00
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 ) ;
} ,
/ * * A d d c h i l d s t a t u s n o d e s t h a t i n d i c a t e ' M o r e . . . ' , e t c .
*
* 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 ;
} ,
/ * * C o l l a p s e a l l s i b l i n g n o d e s .
* @ returns { $ . Promise }
* /
collapseSiblings : function ( ) {
return this . tree . _callHook ( "nodeCollapseSiblings" , this ) ;
} ,
/ * * C o p y t h i s n o d e a s s i b l i n g o r c h i l d o f ` n o d e ` .
*
* @ 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 ) ;
} ,
/ * * C o u n t d i r e c t a n d i n d i r e c t c h i l d r e n .
*
* @ 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()
2018-05-24 18:59:32 +00:00
/ * * W r i t e t o b r o w s e r c o n s o l e i f d e b u g L e v e l > = 4 ( p r e p e n d i n g n o d e i n f o )
2018-01-01 14:39:23 +00:00
*
* @ param { * } msg string or object or array of such
* /
debug : function ( msg ) {
2018-05-24 18:59:32 +00:00
if ( this . tree . options . debugLevel >= 4 ) {
2018-01-01 14:39:23 +00:00
Array . prototype . unshift . call ( arguments , this . toString ( ) ) ;
consoleApply ( "log" , arguments ) ;
}
} ,
/ * * D e p r e c a t e d .
* @ 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 ( ) ;
} ,
/ * * R e m o v e D O M e l e m e n t s f o r a l l d e s c e n d e n t s . M a y b e c a l l e d o n . c o l l a p s e e v e n t
* to keep the DOM small .
* @ param { boolean } [ includeSelf = false ]
* /
discardMarkup : function ( includeSelf ) {
var fn = includeSelf ? "nodeRemoveMarkup" : "nodeRemoveChildMarkup" ;
this . tree . _callHook ( fn , this ) ;
} ,
2018-05-24 18:59:32 +00:00
/ * * W r i t e e r r o r t o b r o w s e r c o n s o l e i f d e b u g L e v e l > = 1 ( p r e p e n d i n g t r e e i n f o )
*
* @ 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 ) ;
}
} ,
2018-01-01 14:39:23 +00:00
/ * * F i n d a l l n o d e s t h a t m a t c h c o n d i t i o n ( e x c l u d i n g s e l f ) .
*
* @ 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 ;
} ,
/ * * F i n d f i r s t n o d e t h a t m a t c h e s c o n d i t i o n ( e x c l u d i n g s e l f ) .
*
* @ 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 ) ;
* /
} ,
/ * * R e t u r n t h e l i s t o f c h i l d n o d e s ( u n d e f i n e d f o r u n e x p a n d e d l a z y n o d e s ) .
* @ 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 ;
} ,
/ * * R e t u r n t h e f i r s t c h i l d n o d e o r n u l l .
* @ returns { FancytreeNode | null }
* /
getFirstChild : function ( ) {
return this . children ? this . children [ 0 ] : null ;
} ,
/ * * R e t u r n t h e 0 - b a s e d c h i l d i n d e x .
* @ returns { int }
* /
getIndex : function ( ) {
// return this.parent.children.indexOf(this);
return $ . inArray ( this , this . parent . children ) ; // indexOf doesn't work in IE7
} ,
/ * * R e t u r n t h e h i e r a r c h i c a l c h i l d i n d e x ( 1 - b a s e d , 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 ) ;
} ,
/ * * R e t u r n t h e p a r e n t k e y s s e p a r a t e d b y o p t i o n s . k e y P a t h S e p a r a t o r , e . g . " i d _ 1 / i d _ 1 7 / i d _ 3 2 " .
* @ 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 ) ;
} ,
/ * * R e t u r n t h e l a s t c h i l d o f t h i s n o d e o r n u l l .
* @ returns { FancytreeNode | null }
* /
getLastChild : function ( ) {
return this . children ? this . children [ this . children . length - 1 ] : null ;
} ,
/ * * R e t u r n n o d e d e p t h . 0 : S y s t e m r o o t n o d e , 1 : v i s i b l e t o p - l e v e l n o d e , 2 : f i r s t s u b - l e v e l , . . . .
* @ returns { int }
* /
getLevel : function ( ) {
var level = 0 ,
dtn = this . parent ;
while ( dtn ) {
level ++ ;
dtn = dtn . parent ;
}
return level ;
} ,
/ * * R e t u r n t h e s u c c e s s o r n o d e ( u n d e r t h e s a m e p a r e n t ) o r n u l l .
* @ 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 ;
} ,
/ * * R e t u r n t h e p a r e n t n o d e ( n u l l f o r t h e s y s t e m r o o t n o d e ) .
* @ returns { FancytreeNode | null }
* /
getParent : function ( ) {
// TODO: return null for top-level nodes?
return this . parent ;
} ,
/ * * R e t u r n a n a r r a y o f a l l p a r e n t n o d e s ( t o p - d o w n ) .
* @ 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 ;
} ,
/ * * R e t u r n t h e p r e d e c e s s o r n o d e ( u n d e r t h e s a m e p a r e n t ) o r n u l l .
* @ 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 ;
} ,
/ * * R e t u r n t r u e i f n o d e h a s c h i l d r e n . R e t u r n u n d e f i n e d i f n o t s u r e , i . e . t h e n o d e i s l a z y a n d n o t y e t l o a d e d ) .
* @ 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 ) ;
} ,
/ * * R e t u r n t r u e i f n o d e h a s k e y b o a r d f o c u s .
* @ returns { boolean }
* /
hasFocus : function ( ) {
return ( this . tree . hasFocus ( ) && this . tree . focusNode === this ) ;
} ,
2018-05-24 18:59:32 +00:00
/ * * W r i t e t o b r o w s e r c o n s o l e i f d e b u g L e v e l > = 3 ( p r e p e n d i n g n o d e i n f o )
2018-01-01 14:39:23 +00:00
*
* @ param { * } msg string or object or array of such
* /
info : function ( msg ) {
2018-05-24 18:59:32 +00:00
if ( this . tree . options . debugLevel >= 3 ) {
2018-01-01 14:39:23 +00:00
Array . prototype . unshift . call ( arguments , this . toString ( ) ) ;
consoleApply ( "info" , arguments ) ;
}
} ,
/ * * R e t u r n t r u e i f n o d e i s a c t i v e ( s e e a l s o F a n c y t r e e N o d e # i s S e l e c t e d ) .
* @ returns { boolean }
* /
isActive : function ( ) {
return ( this . tree . activeNode === this ) ;
} ,
2018-05-24 18:59:32 +00:00
/ * * R e t u r n t r u e i f n o d e i s v e r t i c a l l y b e l o w ` o t h e r N o d e ` , i . e . r e n d e r e d i n a s u b s e q u e n t r o w .
* @ param { FancytreeNode } otherNode
* @ returns { boolean }
* @ since 2.28
* /
isBelowOf : function ( otherNode ) {
return ( this . getIndexHier ( "." , 5 ) > otherNode . getIndexHier ( "." , 5 ) ) ;
} ,
2018-01-01 14:39:23 +00:00
/ * * R e t u r n t r u e i f n o d e i s a d i r e c t c h i l d o f o t h e r N o d e .
* @ param { FancytreeNode } otherNode
* @ returns { boolean }
* /
isChildOf : function ( otherNode ) {
return ( this . parent && this . parent === otherNode ) ;
} ,
/ * * R e t u r n t r u e , i f n o d e i s a d i r e c t o r i n d i r e c t s u b n o d e o f o t h e r N o d e .
* @ 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 ;
} ,
/ * * R e t u r n t r u e i f n o d e i s e x p a n d e d .
* @ returns { boolean }
* /
isExpanded : function ( ) {
return ! ! this . expanded ;
} ,
/ * * R e t u r n t r u e i f n o d e i s t h e f i r s t n o d e o f i t s p a r e n t ' s c h i l d r e n .
* @ returns { boolean }
* /
isFirstSibling : function ( ) {
var p = this . parent ;
return ! p || p . children [ 0 ] === this ;
} ,
/ * * R e t u r n t r u e i f n o d e i s a f o l d e r , i . e . h a s t h e n o d e . f o l d e r a t t r i b u t e s e t .
* @ returns { boolean }
* /
isFolder : function ( ) {
return ! ! this . folder ;
} ,
/ * * R e t u r n t r u e i f n o d e i s t h e l a s t n o d e o f i t s p a r e n t ' s c h i l d r e n .
* @ returns { boolean }
* /
isLastSibling : function ( ) {
var p = this . parent ;
return ! p || p . children [ p . children . length - 1 ] === this ;
} ,
/ * * R e t u r n t r u e i f n o d e i s l a z y ( e v e n i f d a t a w a s a l r e a d y l o a d e d )
* @ returns { boolean }
* /
isLazy : function ( ) {
return ! ! this . lazy ;
} ,
/ * * R e t u r n t r u e i f n o d e i s l a z y a n d l o a d e d . F o r n o n - l a z y n o d e s a l w a y s r e t u r n t r u e .
* @ returns { boolean }
* /
isLoaded : function ( ) {
return ! this . lazy || this . hasChildren ( ) !== undefined ; // Also checks if the only child is a status node
} ,
/ * * R e t u r n t r u e i f c h i l d r e n a r e c u r r e n t l y b e e i n g l o a d e d , i . e . a A j a x r e q u e s t i s p e n d i n g .
* @ returns { boolean }
* /
isLoading : function ( ) {
return ! ! this . _isLoading ;
} ,
/ *
* @ deprecated since v2 . 4.0 : Use isRootNode ( ) instead
* /
isRoot : function ( ) {
return this . isRootNode ( ) ;
} ,
/ * * R e t u r n t r u e i f n o d e i s p a r t i a l l y s e l e c t e d ( t r i - s t a t e ) .
* @ returns { boolean }
* @ since 2.23
* /
isPartsel : function ( ) {
return ! this . selected && ! ! this . partsel ;
} ,
/ * * ( e x p e r i m e n t a l ) R e t u r n t r u e i f t h i s i s p a r t i a l l y l o a d e d .
* @ returns { boolean }
* @ since 2.15
* /
isPartload : function ( ) {
return ! ! this . partload ;
} ,
/ * * R e t u r n t r u e i f t h i s i s t h e ( i n v i s i b l e ) s y s t e m r o o t n o d e .
* @ returns { boolean }
* @ since 2.4
* /
isRootNode : function ( ) {
return ( this . tree . rootNode === this ) ;
} ,
/ * * R e t u r n t r u e i f n o d e i s s e l e c t e d , i . e . h a s a c h e c k m a r k s e t ( s e e a l s o F a n c y t r e e N o d e # i s A c t i v e ) .
* @ returns { boolean }
* /
isSelected : function ( ) {
return ! ! this . selected ;
} ,
/ * * R e t u r n t r u e i f t h i s n o d e i s a t e m p o r a r i l y g e n e r a t e d s y s t e m n o d e l i k e
* 'loading' , 'paging' , or 'error' ( node . statusNodeType contains the type ) .
* @ returns { boolean }
* /
isStatusNode : function ( ) {
return ! ! this . statusNodeType ;
} ,
/ * * R e t u r n t r u e i f t h i s n o d e i s a s t a t u s n o d e o f t y p e ' p a g i n g ' .
* @ returns { boolean }
* @ since 2.15
* /
isPagingNode : function ( ) {
return this . statusNodeType === "paging" ;
} ,
/ * * R e t u r n t r u e i f t h i s a t o p l e v e l n o d e , i . e . a d i r e c t c h i l d o f t h e ( i n v i s i b l e ) s y s t e m r o o t n o d e .
* @ returns { boolean }
* @ since 2.4
* /
isTopLevel : function ( ) {
return ( this . tree . rootNode === this . parent ) ;
} ,
/ * * R e t u r n t r u e i f n o d e i s l a z y a n d n o t y e t l o a d e d . F o r n o n - l a z y n o d e s a l w a y s r e t u r n f a l s e .
* @ returns { boolean }
* /
isUndefined : function ( ) {
return this . hasChildren ( ) === undefined ; // also checks if the only child is a status node
} ,
/ * * R e t u r n t r u e i f a l l p a r e n t n o d e s a r e e x p a n d e d . N o t e : t h i s d o e s n o t c h e c k
* 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 ;
} ,
/ * * D e p r e c a t e d .
* @ 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 < i > expanded < / i > s t a t e i s m a i n t a i n e d .
* @ 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 ;
} ,
/ * * E x p a n d a l l p a r e n t s a n d o p t i o n a l l y s c r o l l i n t o v i s i b l e a r e a a s n e c c e s s a r y .
* 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 ( ) ;
} ,
/ * * M o v e t h i s n o d e t o t a r g e t N o d e .
* @ param { FancytreeNode } targetNode
* @ param { string } mode < pre >
* '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 . < / p r e >
* @ 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 <ul> tag yet:
// if( !targetParent.ul ) {
// // This is the parent's first child: create UL tag
// // (Hidden, because it will be
// targetParent.ul = document.createElement("ul");
// targetParent.ul.style.display = "none";
// targetParent.li.appendChild(targetParent.ul);
// }
// // Issue 319: Add to target DOM parent (only if node was already rendered(expanded))
// if(this.li){
// targetParent.ul.appendChild(this.li);
// }^
// Let caller modify the nodes
if ( map ) {
targetNode . visit ( map , true ) ;
}
if ( targetParent === prevParent ) {
targetParent . triggerModifyChild ( "move" , this ) ;
} else {
// prevParent.triggerModifyChild("remove", this);
targetParent . triggerModifyChild ( "add" , this ) ;
}
// Handle cross-tree moves
if ( this . tree !== targetNode . tree ) {
// Fix node.tree for all source nodes
// _assert(false, "Cross-tree move is not yet implemented.");
this . warn ( "Cross-tree moveTo is experimantal!" ) ;
this . visit ( function ( n ) {
// TODO: fix selection state and activation, ...
n . tree = targetNode . tree ;
} , true ) ;
}
// A collaposed node won't re-render children, so we have to remove it manually
// if( !targetParent.expanded ){
// prevParent.ul.removeChild(this.li);
// }
// Update HTML markup
if ( ! prevParent . isDescendantOf ( targetParent ) ) {
prevParent . render ( ) ;
}
if ( ! targetParent . isDescendantOf ( prevParent ) && targetParent !== prevParent ) {
targetParent . render ( ) ;
}
// TODO: fix selection state
// TODO: fix active state
/ *
var tree = this . tree ;
var opts = tree . options ;
var pers = tree . persistence ;
// Always expand, if it's below minExpandLevel
// tree.logDebug ("%s._addChildNode(%o), l=%o", this, ftnode, ftnode.getLevel());
if ( opts . minExpandLevel >= ftnode . getLevel ( ) ) {
// tree.logDebug ("Force expand for %o", ftnode);
this . bExpanded = true ;
}
// In multi-hier mode, update the parents selection state
// DT issue #82: only if not initializing, because the children may not exist yet
// if( !ftnode.data.isStatusNode() && opts.selectMode==3 && !isInitializing )
// ftnode._fixSelectionState();
// In multi-hier mode, update the parents selection state
if ( ftnode . bSelected && opts . selectMode == 3 ) {
var p = this ;
while ( p ) {
if ( ! p . hasSubSel )
p . _setSubSel ( true ) ;
p = p . parent ;
}
}
// render this node and the new child
if ( tree . bEnableUpdate )
this . render ( ) ;
return ftnode ;
* /
} ,
/ * * S e t f o c u s r e l a t i v e t o t h i s n o d e a n d o p t i o n a l l y a c t i v a t e .
*
* @ param { number } where The keyCode that would normally trigger this move ,
* e . g . ` $ .ui.keyCode.LEFT ` would collapse the node if it
* is expanded or move to the parent oterwise .
* @ param { boolean } [ activate = true ]
* @ returns { $ . Promise }
* /
navigate : function ( where , activate ) {
var i , parents , res ,
handled = true ,
KC = $ . ui . keyCode ,
sib = null ;
// Navigate to node
function _goto ( n ) {
if ( n ) {
// setFocus/setActive will scroll later (if autoScroll is specified)
try { n . makeVisible ( { scrollIntoView : false } ) ; } catch ( e ) { } // #272
// Node may still be hidden by a filter
if ( ! $ ( n . span ) . is ( ":visible" ) ) {
n . debug ( "Navigate: skipping hidden node" ) ;
n . navigate ( where , activate ) ;
return ;
}
return activate === false ? n . setFocus ( ) : n . setActive ( ) ;
}
}
switch ( where ) {
case KC . BACKSPACE :
if ( this . parent && this . parent . parent ) {
res = _goto ( this . parent ) ;
}
break ;
case KC . HOME :
this . tree . visit ( function ( n ) { // goto first visible node
if ( $ ( n . span ) . is ( ":visible" ) ) {
res = _goto ( n ) ;
return false ;
}
} ) ;
break ;
case KC . END :
this . tree . visit ( function ( n ) { // goto last visible node
if ( $ ( n . span ) . is ( ":visible" ) ) {
res = n ;
}
} ) ;
if ( res ) {
res = _goto ( res ) ;
}
break ;
case KC . LEFT :
if ( this . expanded ) {
this . setExpanded ( false ) ;
res = _goto ( this ) ;
} else if ( this . parent && this . parent . parent ) {
res = _goto ( this . parent ) ;
}
break ;
case KC . RIGHT :
if ( ! this . expanded && ( this . children || this . lazy ) ) {
this . setExpanded ( ) ;
res = _goto ( this ) ;
} else if ( this . children && this . children . length ) {
res = _goto ( this . children [ 0 ] ) ;
}
break ;
case KC . UP :
sib = this . getPrevSibling ( ) ;
// #359: skip hidden sibling nodes, preventing a _goto() recursion
while ( sib && ! $ ( sib . span ) . is ( ":visible" ) ) {
sib = sib . getPrevSibling ( ) ;
}
while ( sib && sib . expanded && sib . children && sib . children . length ) {
sib = sib . children [ sib . children . length - 1 ] ;
}
if ( ! sib && this . parent && this . parent . parent ) {
sib = this . parent ;
}
res = _goto ( sib ) ;
break ;
case KC . DOWN :
if ( this . expanded && this . children && this . children . length ) {
sib = this . children [ 0 ] ;
} else {
parents = this . getParentList ( false , true ) ;
for ( i = parents . length - 1 ; i >= 0 ; i -- ) {
sib = parents [ i ] . getNextSibling ( ) ;
// #359: skip hidden sibling nodes, preventing a _goto() recursion
while ( sib && ! $ ( sib . span ) . is ( ":visible" ) ) {
sib = sib . getNextSibling ( ) ;
}
if ( sib ) { break ; }
}
}
res = _goto ( sib ) ;
break ;
default :
handled = false ;
}
return res || _getResolvedPromise ( ) ;
} ,
/ * *
* Remove this node ( not allowed for system root ) .
* /
remove : function ( ) {
return this . parent . removeChild ( this ) ;
} ,
/ * *
* Remove childNode from list of direct children .
* @ param { FancytreeNode } childNode
* /
removeChild : function ( childNode ) {
return this . tree . _callHook ( "nodeRemoveChild" , this , childNode ) ;
} ,
/ * *
* Remove all child nodes and descendents . This converts the node into a leaf . < br >
* If this was a lazy node , it is still considered 'loaded' ; call node . resetLazy ( )
* in order to trigger lazyLoad on next expand .
* /
removeChildren : function ( ) {
return this . tree . _callHook ( "nodeRemoveChildren" , this ) ;
} ,
/ * *
* Remove class from node ' s span tag and . extraClasses .
*
* @ param { string } className class name
*
* @ since 2.17
* /
removeClass : function ( className ) {
return this . toggleClass ( className , false ) ;
} ,
/ * *
* This method renders and updates all HTML markup that is required
* to display this node in its current state . < br >
* Note :
* < ul >
* < li > It should only be neccessary to call this method after the node object
* was modified by direct access to its properties , because the common
* API methods ( node . setTitle ( ) , moveTo ( ) , addChildren ( ) , remove ( ) , ... )
* already handle this .
* < li > { @ link FancytreeNode # renderTitle } and { @ link FancytreeNode # renderStatus }
* are implied . If changes are more local , calling only renderTitle ( ) or
* renderStatus ( ) may be sufficient and faster .
* < / u l >
*
* @ param { boolean } [ force = false ] re - render , even if html markup was already created
* @ param { boolean } [ deep = false ] also render all descendants , even if parent is collapsed
* /
render : function ( force , deep ) {
return this . tree . _callHook ( "nodeRender" , this , force , deep ) ;
} ,
/ * * C r e a t e H T M L m a r k u p f o r t h e n o d e ' s o u t e r & l t ; s p a n > ( e x p a n d e r , c h e c k b o x , i c o n , a n d t i t l e ) .
* Implies { @ link FancytreeNode # renderStatus } .
* @ see Fancytree _Hooks # nodeRenderTitle
* /
renderTitle : function ( ) {
return this . tree . _callHook ( "nodeRenderTitle" , this ) ;
} ,
/ * * U p d a t e e l e m e n t ' s C S S c l a s s e s a c c o r d i n g t o n o d e s t a t e .
* @ see Fancytree _Hooks # nodeRenderStatus
* /
renderStatus : function ( ) {
return this . tree . _callHook ( "nodeRenderStatus" , this ) ;
} ,
/ * *
* ( experimental ) Replace this node with ` source ` .
* ( Currently only available for paging nodes . )
* @ param { NodeData [ ] } source List of child node definitions
* @ since 2.15
* /
replaceWith : function ( source ) {
var res ,
parent = this . parent ,
pos = $ . inArray ( this , parent . children ) ,
that = this ;
_assert ( this . isPagingNode ( ) , "replaceWith() currently requires a paging status node" ) ;
res = this . tree . _callHook ( "nodeLoadChildren" , this , source ) ;
res . done ( function ( data ) {
// New nodes are currently children of `this`.
var children = that . children ;
// Prepend newly loaded child nodes to `this`
// Move new children after self
for ( i = 0 ; i < children . length ; i ++ ) {
children [ i ] . parent = parent ;
}
parent . children . splice . apply ( parent . children , [ pos + 1 , 0 ] . concat ( children ) ) ;
// Remove self
that . children = null ;
that . remove ( ) ;
// Redraw new nodes
parent . render ( ) ;
// TODO: set node.partload = false if this was tha last paging node?
// parent.addPagingNode(false);
} ) . fail ( function ( ) {
that . setExpanded ( ) ;
} ) ;
return res ;
// $.error("Not implemented: replaceWith()");
} ,
/ * *
* Remove all children , collapse , and set the lazy - flag , so that the lazyLoad
* event is triggered on next expand .
* /
resetLazy : function ( ) {
this . removeChildren ( ) ;
this . expanded = false ;
this . lazy = true ;
this . children = undefined ;
this . renderStatus ( ) ;
} ,
/ * * S c h e d u l e a c t i v i t y f o r d e l a y e d e x e c u t i o n ( c a n c e l a n y p e n d i n g r e q u e s t ) .
* scheduleAction ( 'cancel' ) will only cancel a pending request ( if any ) .
* @ param { string } mode
* @ param { number } ms
* /
scheduleAction : function ( mode , ms ) {
if ( this . tree . timer ) {
clearTimeout ( this . tree . timer ) ;
// this.tree.debug("clearTimeout(%o)", this.tree.timer);
}
this . tree . timer = null ;
var self = this ; // required for closures
switch ( mode ) {
case "cancel" :
// Simply made sure that timer was cleared
break ;
case "expand" :
this . tree . timer = setTimeout ( function ( ) {
self . tree . debug ( "setTimeout: trigger expand" ) ;
self . setExpanded ( true ) ;
} , ms ) ;
break ;
case "activate" :
this . tree . timer = setTimeout ( function ( ) {
self . tree . debug ( "setTimeout: trigger activate" ) ;
self . setActive ( true ) ;
} , ms ) ;
break ;
default :
$ . error ( "Invalid mode " + mode ) ;
}
// this.tree.debug("setTimeout(%s, %s): %s", mode, ms, this.tree.timer);
} ,
/ * *
*
* @ param { boolean | PlainObject } [ effects = false ] animation options .
* @ param { object } [ options = null ] { topNode : null , effects : ... , parent : ... } this node will remain visible in
* any case , even if ` this ` is outside the scroll pane .
* @ returns { $ . Promise }
* /
scrollIntoView : function ( effects , options ) {
if ( options !== undefined && _isNode ( options ) ) {
this . warn ( "scrollIntoView() with 'topNode' option is deprecated since 2014-05-08. Use 'options.topNode' instead." ) ;
options = { topNode : options } ;
}
// this.$scrollParent = (this.options.scrollParent === "auto") ? $ul.scrollParent() : $(this.options.scrollParent);
// this.$scrollParent = this.$scrollParent.length ? this.$scrollParent || this.$container;
var topNodeY , nodeY , horzScrollbarHeight , containerOffsetTop ,
opts = $ . extend ( {
effects : ( effects === true ) ? { duration : 200 , queue : false } : effects ,
scrollOfs : this . tree . options . scrollOfs ,
scrollParent : this . tree . options . scrollParent || this . tree . $container ,
topNode : null
} , options ) ,
dfd = new $ . Deferred ( ) ,
that = this ,
nodeHeight = $ ( this . span ) . height ( ) ,
$container = $ ( opts . scrollParent ) ,
topOfs = opts . scrollOfs . top || 0 ,
bottomOfs = opts . scrollOfs . bottom || 0 ,
containerHeight = $container . height ( ) , // - topOfs - bottomOfs,
scrollTop = $container . scrollTop ( ) ,
$animateTarget = $container ,
isParentWindow = $container [ 0 ] === window ,
topNode = opts . topNode || null ,
newScrollTop = null ;
// this.debug("scrollIntoView(), scrollTop=" + scrollTop, opts.scrollOfs);
// _assert($(this.span).is(":visible"), "scrollIntoView node is invisible"); // otherwise we cannot calc offsets
if ( ! $ ( this . span ) . is ( ":visible" ) ) {
// We cannot calc offsets for hidden elements
this . warn ( "scrollIntoView(): node is invisible." ) ;
return _getResolvedPromise ( ) ;
}
if ( isParentWindow ) {
nodeY = $ ( this . span ) . offset ( ) . top ;
topNodeY = ( topNode && topNode . span ) ? $ ( topNode . span ) . offset ( ) . top : 0 ;
$animateTarget = $ ( "html,body" ) ;
} else {
_assert ( $container [ 0 ] !== document && $container [ 0 ] !== document . body ,
"scrollParent should be a simple element or `window`, not document or body." ) ;
containerOffsetTop = $container . offset ( ) . top ,
nodeY = $ ( this . span ) . offset ( ) . top - containerOffsetTop + scrollTop ; // relative to scroll parent
topNodeY = topNode ? $ ( topNode . span ) . offset ( ) . top - containerOffsetTop + scrollTop : 0 ;
horzScrollbarHeight = Math . max ( 0 , ( $container . innerHeight ( ) - $container [ 0 ] . clientHeight ) ) ;
containerHeight -= horzScrollbarHeight ;
}
// this.debug(" scrollIntoView(), nodeY=" + nodeY + ", containerHeight=" + containerHeight);
if ( nodeY < ( scrollTop + topOfs ) ) {
// Node is above visible container area
newScrollTop = nodeY - topOfs ;
// this.debug(" scrollIntoView(), UPPER newScrollTop=" + newScrollTop);
} else if ( ( nodeY + nodeHeight ) > ( scrollTop + containerHeight - bottomOfs ) ) {
newScrollTop = nodeY + nodeHeight - containerHeight + bottomOfs ;
// this.debug(" scrollIntoView(), LOWER newScrollTop=" + newScrollTop);
// If a topNode was passed, make sure that it is never scrolled
// outside the upper border
if ( topNode ) {
_assert ( topNode . isRootNode ( ) || $ ( topNode . span ) . is ( ":visible" ) , "topNode must be visible" ) ;
if ( topNodeY < newScrollTop ) {
newScrollTop = topNodeY - topOfs ;
// this.debug(" scrollIntoView(), TOP newScrollTop=" + newScrollTop);
}
}
}
if ( newScrollTop !== null ) {
// this.debug(" scrollIntoView(), SET newScrollTop=" + newScrollTop);
if ( opts . effects ) {
opts . effects . complete = function ( ) {
dfd . resolveWith ( that ) ;
} ;
$animateTarget . stop ( true ) . animate ( {
scrollTop : newScrollTop
} , opts . effects ) ;
} else {
$animateTarget [ 0 ] . scrollTop = newScrollTop ;
dfd . resolveWith ( this ) ;
}
} else {
dfd . resolveWith ( this ) ;
}
return dfd . promise ( ) ;
} ,
/ * * A c t i v a t e t h i s n o d e .
* @ param { boolean } [ flag = true ] pass false to deactivate
* @ param { object } [ opts ] additional options . Defaults to { noEvents : false , noFocus : false }
* @ returns { $ . Promise }
* /
setActive : function ( flag , opts ) {
return this . tree . _callHook ( "nodeSetActive" , this , flag , opts ) ;
} ,
/ * * E x p a n d o r c o l l a p s e t h i s n o d e . P r o m i s e i s r e s o l v e d , w h e n l a z y l o a d i n g a n d a n i m a t i o n s a r e d o n e .
* @ param { boolean } [ flag = true ] pass false to collapse
* @ param { object } [ opts ] additional options . Defaults to { noAnimation : false , noEvents : false }
* @ returns { $ . Promise }
* /
setExpanded : function ( flag , opts ) {
return this . tree . _callHook ( "nodeSetExpanded" , this , flag , opts ) ;
} ,
/ * * S e t k e y b o a r d f o c u s t o t h i s n o d e .
* @ param { boolean } [ flag = true ] pass false to blur
* @ see Fancytree # setFocus
* /
setFocus : function ( flag ) {
return this . tree . _callHook ( "nodeSetFocus" , this , flag ) ;
} ,
/ * * S e l e c t t h i s n o d e , i . e . c h e c k t h e c h e c k b o x .
* @ param { boolean } [ flag = true ] pass false to deselect
* @ param { object } [ opts ] additional options . Defaults to { noEvents : false , p
* propagateDown : null , propagateUp : null , callback : null }
* /
setSelected : function ( flag , opts ) {
return this . tree . _callHook ( "nodeSetSelected" , this , flag , opts ) ;
} ,
/ * * M a r k a l a z y n o d e a s ' e r r o r ' , ' l o a d i n g ' , ' n o d a t a ' , o r ' o k ' .
* @ param { string } status 'error' | 'empty' | 'ok'
* @ param { string } [ message ]
* @ param { string } [ details ]
* /
setStatus : function ( status , message , details ) {
return this . tree . _callHook ( "nodeSetStatus" , this , status , message , details ) ;
} ,
/ * * R e n a m e t h i s n o d e .
* @ param { string } title
* /
setTitle : function ( title ) {
this . title = title ;
this . renderTitle ( ) ;
this . triggerModify ( "rename" ) ;
} ,
/ * * S o r t c h i l d l i s t b y t i t l e .
* @ param { function } [ cmp ] custom compare function ( a , b ) that returns - 1 , 0 , or 1 ( defaults to sort by title ) .
* @ param { boolean } [ deep = false ] pass true to sort all descendant nodes
* /
sortChildren : function ( cmp , deep ) {
var i , l ,
cl = this . children ;
if ( ! cl ) {
return ;
}
cmp = cmp || function ( a , b ) {
var x = a . title . toLowerCase ( ) ,
y = b . title . toLowerCase ( ) ;
return x === y ? 0 : x > y ? 1 : - 1 ;
} ;
cl . sort ( cmp ) ;
if ( deep ) {
for ( i = 0 , l = cl . length ; i < l ; i ++ ) {
if ( cl [ i ] . children ) {
cl [ i ] . sortChildren ( cmp , "$norender$" ) ;
}
}
}
if ( deep !== "$norender$" ) {
this . render ( ) ;
}
this . triggerModifyChild ( "sort" ) ;
} ,
/ * * C o n v e r t n o d e ( o r w h o l e b r a n c h ) i n t o a p l a i n o b j e c t .
*
* The result is compatible with node . addChildren ( ) .
*
* @ param { boolean } [ recursive = false ] include child nodes
* @ param { function } [ callback ] callback ( dict , node ) is called for every node , in order to allow modifications
* @ returns { NodeData }
* /
toDict : function ( recursive , callback ) {
var i , l , node ,
dict = { } ,
self = this ;
$ . each ( NODE _ATTRS , function ( i , a ) {
if ( self [ a ] || self [ a ] === false ) {
dict [ a ] = self [ a ] ;
}
} ) ;
if ( ! $ . isEmptyObject ( this . data ) ) {
dict . data = $ . extend ( { } , this . data ) ;
if ( $ . isEmptyObject ( dict . data ) ) {
delete dict . data ;
}
}
if ( callback ) {
callback ( dict , self ) ;
}
if ( recursive ) {
if ( this . hasChildren ( ) ) {
dict . children = [ ] ;
for ( i = 0 , l = this . children . length ; i < l ; i ++ ) {
node = this . children [ i ] ;
if ( ! node . isStatusNode ( ) ) {
dict . children . push ( node . toDict ( true , callback ) ) ;
}
}
} else {
// dict.children = null;
}
}
return dict ;
} ,
/ * *
* Set , clear , or toggle class of node ' s span tag and . extraClasses .
*
* @ param { string } className class name ( separate multiple classes by space )
* @ param { boolean } [ flag ] true / false to add / remove class . If omitted , class is toggled .
* @ returns { boolean } true if a class was added
*
* @ since 2.17
* /
toggleClass : function ( value , flag ) {
var className , hasClass ,
rnotwhite = ( /\S+/g ) ,
classNames = value . match ( rnotwhite ) || [ ] ,
i = 0 ,
wasAdded = false ,
statusElem = this [ this . tree . statusClassPropName ] ,
curClasses = ( " " + ( this . extraClasses || "" ) + " " ) ;
// this.info("toggleClass('" + value + "', " + flag + ")", curClasses);
// Modify DOM element directly if it already exists
if ( statusElem ) {
$ ( statusElem ) . toggleClass ( value , flag ) ;
}
// Modify node.extraClasses to make this change persistent
// Toggle if flag was not passed
while ( className = classNames [ i ++ ] ) {
hasClass = curClasses . indexOf ( " " + className + " " ) >= 0 ;
flag = ( flag === undefined ) ? ( ! hasClass ) : ! ! flag ;
if ( flag ) {
if ( ! hasClass ) {
curClasses += className + " " ;
wasAdded = true ;
}
} else {
while ( curClasses . indexOf ( " " + className + " " ) > - 1 ) {
curClasses = curClasses . replace ( " " + className + " " , " " ) ;
}
}
}
this . extraClasses = $ . trim ( curClasses ) ;
// this.info("-> toggleClass('" + value + "', " + flag + "): '" + this.extraClasses + "'");
return wasAdded ;
} ,
/** Flip expanded status. */
toggleExpanded : function ( ) {
return this . tree . _callHook ( "nodeToggleExpanded" , this ) ;
} ,
/** Flip selection status. */
toggleSelected : function ( ) {
return this . tree . _callHook ( "nodeToggleSelected" , this ) ;
} ,
toString : function ( ) {
2018-05-24 18:59:32 +00:00
return "FancytreeNode@" + this . key + "[title='" + this . title + "']" ;
// return "<FancytreeNode(#" + this.key + ", '" + this.title + "')>";
2018-01-01 14:39:23 +00:00
} ,
/ * *
* Trigger ` modifyChild ` event on a parent to signal that a child was modified .
* @ param { string } operation Type of change : 'add' , 'remove' , 'rename' , 'move' , 'data' , ...
* @ param { FancytreeNode } [ childNode ]
* @ param { object } [ extra ]
* /
triggerModifyChild : function ( operation , childNode , extra ) {
var data ,
modifyChild = this . tree . options . modifyChild ;
if ( modifyChild ) {
if ( childNode && childNode . parent !== this ) {
$ . error ( "childNode " + childNode + " is not a child of " + this ) ;
}
data = {
node : this ,
tree : this . tree ,
operation : operation ,
childNode : childNode || null
} ;
if ( extra ) {
$ . extend ( data , extra ) ;
}
modifyChild ( { type : "modifyChild" } , data ) ;
}
} ,
/ * *
* Trigger ` modifyChild ` event on node . parent ( ! ) .
* @ param { string } operation Type of change : 'add' , 'remove' , 'rename' , 'move' , 'data' , ...
* @ param { object } [ extra ]
* /
triggerModify : function ( operation , extra ) {
this . parent . triggerModifyChild ( operation , this , extra ) ;
} ,
2018-05-24 18:59:32 +00:00
/ * * C a l l f n ( n o d e ) f o r a l l c h i l d n o d e s i n h i e r a r c h i c a l o r d e r ( d e p t h - f i r s t ) . < b r >
2018-01-01 14:39:23 +00:00
* Stop iteration , if fn ( ) returns false . Skip current branch , if fn ( ) returns "skip" . < br >
* Return false if iteration was stopped .
*
* @ param { function } fn the callback function .
* Return false to stop iteration , return "skip" to skip this node and
* its children only .
* @ param { boolean } [ includeSelf = false ]
* @ returns { boolean }
* /
visit : function ( fn , includeSelf ) {
var i , l ,
res = true ,
children = this . children ;
if ( includeSelf === true ) {
res = fn ( this ) ;
if ( res === false || res === "skip" ) {
return res ;
}
}
if ( children ) {
for ( i = 0 , l = children . length ; i < l ; i ++ ) {
res = children [ i ] . visit ( fn , true ) ;
if ( res === false ) {
break ;
}
}
}
return res ;
} ,
/ * * C a l l f n ( n o d e ) f o r a l l c h i l d n o d e s a n d r e c u r s i v e l y l o a d l a z y c h i l d r e n . < b r >
* < b > Note : < / b > I f y o u n e e d t h i s m e t h o d , y o u p r o b a b l y s h o u l d c o n s i d e r t o r e v i e w
* your architecture ! Recursivley loading nodes is a perfect way for lazy
* programmers to flood the server with requests ; - )
*
* @ param { function } [ fn ] optional callback function .
* Return false to stop iteration , return "skip" to skip this node and
* its children only .
* @ param { boolean } [ includeSelf = false ]
* @ returns { $ . Promise }
* @ since 2.4
* /
visitAndLoad : function ( fn , includeSelf , _recursion ) {
var dfd , res , loaders ,
node = this ;
// node.debug("visitAndLoad");
if ( fn && includeSelf === true ) {
res = fn ( node ) ;
if ( res === false || res === "skip" ) {
return _recursion ? res : _getResolvedPromise ( ) ;
}
}
if ( ! node . children && ! node . lazy ) {
return _getResolvedPromise ( ) ;
}
dfd = new $ . Deferred ( ) ;
loaders = [ ] ;
// node.debug("load()...");
node . load ( ) . done ( function ( ) {
// node.debug("load()... done.");
for ( var i = 0 , l = node . children . length ; i < l ; i ++ ) {
res = node . children [ i ] . visitAndLoad ( fn , true , true ) ;
if ( res === false ) {
dfd . reject ( ) ;
break ;
} else if ( res !== "skip" ) {
loaders . push ( res ) ; // Add promise to the list
}
}
$ . when . apply ( this , loaders ) . then ( function ( ) {
dfd . resolve ( ) ;
} ) ;
} ) ;
return dfd . promise ( ) ;
} ,
/ * * C a l l f n ( n o d e ) f o r a l l p a r e n t n o d e s , b o t t o m - u p , i n c l u d i n g i n v i s i b l e s y s t e m r o o t . < b r >
* Stop iteration , if fn ( ) returns false . < br >
* Return false if iteration was stopped .
*
* @ param { function } fn the callback function .
* Return false to stop iteration , return "skip" to skip this node and children only .
* @ param { boolean } [ includeSelf = false ]
* @ returns { boolean }
* /
visitParents : function ( fn , includeSelf ) {
// Visit parent nodes (bottom up)
if ( includeSelf && fn ( this ) === false ) {
return false ;
}
var p = this . parent ;
while ( p ) {
if ( fn ( p ) === false ) {
return false ;
}
p = p . parent ;
}
return true ;
} ,
/ * * C a l l f n ( n o d e ) f o r a l l s i b l i n g n o d e s . < b r >
* Stop iteration , if fn ( ) returns false . < br >
* Return false if iteration was stopped .
*
* @ param { function } fn the callback function .
* Return false to stop iteration .
* @ param { boolean } [ includeSelf = false ]
* @ returns { boolean }
* /
visitSiblings : function ( fn , includeSelf ) {
var i , l , n ,
ac = this . parent . children ;
for ( i = 0 , l = ac . length ; i < l ; i ++ ) {
n = ac [ i ] ;
if ( includeSelf || n !== this ) {
if ( fn ( n ) === false ) {
return false ;
}
}
}
return true ;
} ,
2018-05-24 18:59:32 +00:00
/ * * W r i t e w a r n i n g t o b r o w s e r c o n s o l e i f d e b u g L e v e l > = 2 ( p r e p e n d i n g n o d e i n f o )
2018-01-01 14:39:23 +00:00
*
* @ param { * } msg string or object or array of such
* /
warn : function ( msg ) {
2018-05-24 18:59:32 +00:00
if ( this . tree . options . debugLevel >= 2 ) {
Array . prototype . unshift . call ( arguments , this . toString ( ) ) ;
consoleApply ( "warn" , arguments ) ;
}
2018-01-01 14:39:23 +00:00
}
} ;
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Fancytree
* /
/ * *
* Construct a new tree object .
*
* @ class Fancytree
* @ classdesc The controller behind a fancytree .
* This class also contains 'hook methods' : see { @ link Fancytree _Hooks } .
*
* @ param { Widget } widget
*
* @ property { string } _id Automatically generated unique tree instance ID , e . g . "1" .
* @ property { string } _ns Automatically generated unique tree namespace , e . g . ".fancytree-1" .
* @ property { FancytreeNode } activeNode Currently active node or null .
* @ property { string } ariaPropName Property name of FancytreeNode that contains the element which will receive the aria attributes .
* Typically "li" , but "tr" for table extension .
* @ property { jQueryObject } $container Outer & lt ; ul > element ( or & lt ; table > element for ext - table ) .
* @ property { jQueryObject } $div A jQuery object containing the element used to instantiate the tree widget ( ` widget.element ` )
2018-05-24 18:59:32 +00:00
* @ property { object | array } columns Recommended place to store shared column meta data . @ since 2.27
2018-01-01 14:39:23 +00:00
* @ property { object } data Metadata , i . e . properties that may be passed to ` source ` in addition to a children array .
* @ property { object } ext Hash of all active plugin instances .
* @ property { FancytreeNode } focusNode Currently focused node or null .
* @ property { FancytreeNode } lastSelectedNode Used to implement selectMode 1 ( single select )
* @ property { string } nodeContainerAttrName Property name of FancytreeNode that contains the outer element of single nodes .
* Typically "li" , but "tr" for table extension .
* @ property { FancytreeOptions } options Current options , i . e . default options + options passed to constructor .
* @ property { FancytreeNode } rootNode Invisible system root node .
* @ property { string } statusClassPropName Property name of FancytreeNode that contains the element which will receive the status classes .
* Typically "span" , but "tr" for table extension .
2018-05-24 18:59:32 +00:00
* @ property { object } types Map for shared type specific meta data , used with node . type attribute . @ since 2.27
2018-01-01 14:39:23 +00:00
* @ property { object } widget Base widget instance .
* /
function Fancytree ( widget ) {
this . widget = widget ;
this . $div = widget . element ;
this . options = widget . options ;
if ( this . options ) {
if ( $ . isFunction ( this . options . lazyload ) && ! $ . isFunction ( this . options . lazyLoad ) ) {
this . options . lazyLoad = function ( ) {
FT . warn ( "The 'lazyload' event is deprecated since 2014-02-25. Use 'lazyLoad' (with uppercase L) instead." ) ;
return widget . options . lazyload . apply ( this , arguments ) ;
} ;
}
if ( $ . isFunction ( this . options . loaderror ) ) {
$ . error ( "The 'loaderror' event was renamed since 2014-07-03. Use 'loadError' (with uppercase E) instead." ) ;
}
if ( this . options . fx !== undefined ) {
FT . warn ( "The 'fx' option was replaced by 'toggleEffect' since 2014-11-30." ) ;
}
if ( this . options . removeNode !== undefined ) {
$ . error ( "The 'removeNode' event was replaced by 'modifyChild' since 2.20 (2016-09-10)." ) ;
}
}
this . ext = { } ; // Active extension instances
2018-05-24 18:59:32 +00:00
this . types = { } ;
this . columns = { } ;
2018-01-01 14:39:23 +00:00
// allow to init tree.data.foo from <div data-foo=''>
this . data = _getElementDataAsDict ( this . $div ) ;
// TODO: use widget.uuid instead?
this . _id = $ . ui . fancytree . _nextId ++ ;
// TODO: use widget.eventNamespace instead?
this . _ns = ".fancytree-" + this . _id ; // append for namespaced events
this . activeNode = null ;
this . focusNode = null ;
this . _hasFocus = null ;
this . _tempCache = { } ;
this . _lastMousedownNode = null ;
this . _enableUpdate = true ;
this . lastSelectedNode = null ;
this . systemFocusElement = null ;
this . lastQuicksearchTerm = "" ;
this . lastQuicksearchTime = 0 ;
this . statusClassPropName = "span" ;
this . ariaPropName = "li" ;
this . nodeContainerAttrName = "li" ;
// Remove previous markup if any
this . $div . find ( ">ul.fancytree-container" ) . remove ( ) ;
// Create a node without parent.
var fakeParent = { tree : this } ,
$ul ;
this . rootNode = new FancytreeNode ( fakeParent , {
title : "root" ,
key : "root_" + this . _id ,
children : null ,
expanded : true
} ) ;
this . rootNode . parent = null ;
// Create root markup
$ul = $ ( "<ul>" , {
"class" : "ui-fancytree fancytree-container fancytree-plain"
} ) . appendTo ( this . $div ) ;
this . $container = $ul ;
this . rootNode . ul = $ul [ 0 ] ;
if ( this . options . debugLevel == null ) {
this . options . debugLevel = FT . debugLevel ;
}
// // Add container to the TAB chain
// // See http://www.w3.org/TR/wai-aria-practices/#focus_activedescendant
// // #577: Allow to set tabindex to "0", "-1" and ""
// this.$container.attr("tabindex", this.options.tabindex);
// if( this.options.rtl ) {
// this.$container.attr("DIR", "RTL").addClass("fancytree-rtl");
// // }else{
// // this.$container.attr("DIR", null).removeClass("fancytree-rtl");
// }
// if(this.options.aria){
// this.$container.attr("role", "tree");
// if( this.options.selectMode !== 1 ) {
// this.$container.attr("aria-multiselectable", true);
// }
// }
}
Fancytree . prototype = /** @lends Fancytree# */ {
/ * R e t u r n a c o n t e x t o b j e c t t h a t c a n b e r e - u s e d f o r _ c a l l H o o k ( ) .
* @ param { Fancytree | FancytreeNode | EventData } obj
* @ param { Event } originalEvent
* @ param { Object } extra
* @ returns { EventData }
* /
_makeHookContext : function ( obj , originalEvent , extra ) {
var ctx , tree ;
if ( obj . node !== undefined ) {
// obj is already a context object
if ( originalEvent && obj . originalEvent !== originalEvent ) {
$ . error ( "invalid args" ) ;
}
ctx = obj ;
} else if ( obj . tree ) {
// obj is a FancytreeNode
tree = obj . tree ;
2018-05-24 18:59:32 +00:00
ctx = { node : obj , tree : tree , widget : tree . widget , options : tree . widget . options , originalEvent : originalEvent ,
typeInfo : tree . types [ obj . type ] || { } } ;
2018-01-01 14:39:23 +00:00
} else if ( obj . widget ) {
// obj is a Fancytree
ctx = { node : null , tree : obj , widget : obj . widget , options : obj . widget . options , originalEvent : originalEvent } ;
} else {
$ . error ( "invalid args" ) ;
}
if ( extra ) {
$ . extend ( ctx , extra ) ;
}
return ctx ;
} ,
/ * T r i g g e r a h o o k f u n c t i o n : f u n c N a m e ( c t x , [ . . . ] ) .
*
* @ param { string } funcName
* @ param { Fancytree | FancytreeNode | EventData } contextObject
* @ param { any } [ _extraArgs ] optional additional arguments
* @ returns { any }
* /
_callHook : function ( funcName , contextObject , _extraArgs ) {
var ctx = this . _makeHookContext ( contextObject ) ,
fn = this [ funcName ] ,
args = Array . prototype . slice . call ( arguments , 2 ) ;
if ( ! $ . isFunction ( fn ) ) {
$ . error ( "_callHook('" + funcName + "') is not a function" ) ;
}
args . unshift ( ctx ) ;
// this.debug("_hook", funcName, ctx.node && ctx.node.toString() || ctx.tree.toString(), args);
return fn . apply ( this , args ) ;
} ,
_setExpiringValue : function ( key , value , ms ) {
this . _tempCache [ key ] = { value : value , expire : Date . now ( ) + ( + ms || 50 ) } ;
} ,
_getExpiringValue : function ( key ) {
var entry = this . _tempCache [ key ] ;
2018-05-24 18:59:32 +00:00
if ( entry && entry . expire > Date . now ( ) ) {
2018-01-01 14:39:23 +00:00
return entry . value ;
}
delete this . _tempCache [ key ] ;
return null ;
} ,
/ * C h e c k i f c u r r e n t e x t e n s i o n s d e p e n d e n c i e s a r e m e t a n d t h r o w a n e r r o r i f n o t .
*
* This method may be called inside the ` treeInit ` hook for custom extensions .
*
* @ param { string } extension name of the required extension
* @ param { boolean } [ required = true ] pass ` false ` if the extension is optional , but we want to check for order if it is present
* @ param { boolean } [ before ] ` true ` if ` name ` must be included before this , ` false ` otherwise ( use ` null ` if order doesn ' t matter )
* @ param { string } [ message ] optional error message ( defaults to a descriptve error message )
* /
_requireExtension : function ( name , required , before , message ) {
before = ! ! before ;
var thisName = this . _local . name ,
extList = this . options . extensions ,
isBefore = $ . inArray ( name , extList ) < $ . inArray ( thisName , extList ) ,
isMissing = required && this . ext [ name ] == null ,
badOrder = ! isMissing && before != null && ( before !== isBefore ) ;
_assert ( thisName && thisName !== name , "invalid or same name" ) ;
if ( isMissing || badOrder ) {
if ( ! message ) {
if ( isMissing || required ) {
message = "'" + thisName + "' extension requires '" + name + "'" ;
if ( badOrder ) {
message += " to be registered " + ( before ? "before" : "after" ) + " itself" ;
}
} else {
message = "If used together, `" + name + "` must be registered " + ( before ? "before" : "after" ) + " `" + thisName + "`" ;
}
}
$ . error ( message ) ;
return false ;
}
return true ;
} ,
/ * * A c t i v a t e n o d e w i t h a g i v e n k e y a n d f i r e f o c u s a n d a c t i v a t e e v e n t s .
*
* A previously activated node will be deactivated .
* If activeVisible option is set , all parents will be expanded as necessary .
* Pass key = false , to deactivate the current node only .
* @ param { string } key
* @ returns { FancytreeNode } activated node ( null , if not found )
* /
activateKey : function ( key ) {
var node = this . getNodeByKey ( key ) ;
if ( node ) {
node . setActive ( ) ;
} else if ( this . activeNode ) {
this . activeNode . setActive ( false ) ;
}
return node ;
} ,
/ * * ( e x p e r i m e n t a l ) A d d c h i l d s t a t u s n o d e s t h a t i n d i c a t e ' M o r e . . . ' , . . . .
* @ param { boolean | object } node optional node definition . Pass ` false ` to remove all paging nodes .
* @ param { string } [ mode = 'append' ] 'child' | firstChild '
* @ since 2.15
* /
addPagingNode : function ( node , mode ) {
return this . rootNode . addPagingNode ( node , mode ) ;
} ,
/ * * ( e x p e r i m e n t a l ) M o d i f y e x i s t i n g d a t a m o d e l .
*
* @ param { Array } patchList array of [ key , NodePatch ] arrays
* @ returns { $ . Promise } resolved , when all patches have been applied
* @ see TreePatch
* /
applyPatch : function ( patchList ) {
var dfd , i , p2 , key , patch , node ,
patchCount = patchList . length ,
deferredList = [ ] ;
for ( i = 0 ; i < patchCount ; i ++ ) {
p2 = patchList [ i ] ;
_assert ( p2 . length === 2 , "patchList must be an array of length-2-arrays" ) ;
key = p2 [ 0 ] ;
patch = p2 [ 1 ] ;
node = ( key === null ) ? this . rootNode : this . getNodeByKey ( key ) ;
if ( node ) {
dfd = new $ . Deferred ( ) ;
deferredList . push ( dfd ) ;
node . applyPatch ( patch ) . always ( _makeResolveFunc ( dfd , node ) ) ;
} else {
this . warn ( "could not find node with key '" + key + "'" ) ;
}
}
// Return a promise that is resolved, when ALL patches were applied
return $ . when . apply ( $ , deferredList ) . promise ( ) ;
} ,
/ * T O D O : i m p l e m e n t i n d n d e x t e n s i o n
cancelDrag : function ( ) {
var dd = $ . ui . ddmanager . current ;
if ( dd ) {
dd . cancel ( ) ;
}
} ,
* /
/ * * R e m o v e a l l n o d e s .
* @ since 2.14
* /
clear : function ( source ) {
this . _callHook ( "treeClear" , this ) ;
} ,
/ * * R e t u r n t h e n u m b e r o f n o d e s .
* @ returns { integer }
* /
count : function ( ) {
return this . rootNode . countChildren ( ) ;
} ,
2018-05-24 18:59:32 +00:00
/ * * W r i t e t o b r o w s e r c o n s o l e i f d e b u g L e v e l > = 4 ( p r e p e n d i n g t r e e n a m e )
2018-01-01 14:39:23 +00:00
*
* @ param { * } msg string or object or array of such
* /
debug : function ( msg ) {
2018-05-24 18:59:32 +00:00
if ( this . options . debugLevel >= 4 ) {
2018-01-01 14:39:23 +00:00
Array . prototype . unshift . call ( arguments , this . toString ( ) ) ;
consoleApply ( "log" , arguments ) ;
}
} ,
// TODO: disable()
// TODO: enable()
/ * * T e m p o r a r i l y s u p p r e s s r e n d e r i n g t o i m p r o v e p e r f o r m a n c e o n b u l k - u p d a t e s .
*
* @ param { boolean } flag
* @ returns { boolean } previous status
* @ since 2.19
* /
enableUpdate : function ( flag ) {
flag = ( flag !== false ) ;
/*jshint -W018 */ // Confusing use of '!'
if ( ! ! this . _enableUpdate === ! ! flag ) {
return flag ;
}
/*jshint +W018 */
this . _enableUpdate = flag ;
if ( flag ) {
this . debug ( "enableUpdate(true): redraw " ) ; //, this._dirtyRoots);
this . render ( ) ;
} else {
// this._dirtyRoots = null;
this . debug ( "enableUpdate(false)..." ) ;
}
return ! flag ; // return previous value
} ,
/ * * F i n d a l l n o d e s t h a t m a t c h e s c o n d i t i o n .
*
* @ 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 )
* @ see FancytreeNode # findAll
* @ since 2.12
* /
findAll : function ( match ) {
return this . rootNode . findAll ( match ) ;
} ,
/ * * F i n d f i r s t n o d e t h a t m a t c h e s c o n d i t i o n .
*
* @ 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 # findFirst
* @ since 2.12
* /
findFirst : function ( match ) {
return this . rootNode . findFirst ( match ) ;
} ,
/ * * F i n d t h e n e x t v i s i b l e n o d e t h a t s t a r t s w i t h ` m a t c h ` , s t a r t i n g a t ` s t a r t N o d e `
* and wrap - around at the end .
*
* @ param { string | function } match
* @ param { FancytreeNode } [ startNode ] defaults to first node
* @ returns { FancytreeNode } matching node or null
* /
findNextNode : function ( match , startNode , visibleOnly ) {
2018-05-24 18:59:32 +00:00
match = ( typeof match === "string" ) ? _makeNodeTitleStartMatcher ( match ) : match ;
startNode = startNode || this . getFirstChild ( ) ;
2018-01-01 14:39:23 +00:00
var stopNode = null ,
parentChildren = startNode . parent . children ,
matchingNode = null ,
walkVisible = function ( parent , idx , fn ) {
var i , grandParent ,
parentChildren = parent . children ,
siblingCount = parentChildren . length ,
node = parentChildren [ idx ] ;
// visit node itself
if ( node && fn ( node ) === false ) {
return false ;
}
// visit descendants
if ( node && node . children && node . expanded ) {
if ( walkVisible ( node , 0 , fn ) === false ) {
return false ;
}
}
// visit subsequent siblings
for ( i = idx + 1 ; i < siblingCount ; i ++ ) {
if ( walkVisible ( parent , i , fn ) === false ) {
return false ;
}
}
// visit parent's subsequent siblings
grandParent = parent . parent ;
if ( grandParent ) {
return walkVisible ( grandParent , grandParent . children . indexOf ( parent ) + 1 , fn ) ;
} else {
// wrap-around: restart with first node
return walkVisible ( parent , 0 , fn ) ;
}
} ;
walkVisible ( startNode . parent , parentChildren . indexOf ( startNode ) , function ( node ) {
// Stop iteration if we see the start node a second time
if ( node === stopNode ) {
return false ;
}
stopNode = stopNode || node ;
// Ignore nodes hidden by a filter
if ( ! $ ( node . span ) . is ( ":visible" ) ) {
node . debug ( "quicksearch: skipping hidden node" ) ;
return ;
}
// Test if we found a match, but search for a second match if this
// was the currently active node
if ( match ( node ) ) {
// node.debug("quicksearch match " + node.title, startNode);
matchingNode = node ;
if ( matchingNode !== startNode ) {
return false ;
}
}
} ) ;
return matchingNode ;
} ,
// TODO: fromDict
/ * *
* Generate INPUT elements that can be submitted with html forms .
*
* In selectMode 3 only the topmost selected nodes are considered , unless
* ` opts.stopOnParents: false ` is passed .
*
* @ example
* // Generate input elements for active and selected nodes
* tree . generateFormElements ( ) ;
* // Generate input elements selected nodes, using a custom `name` attribute
* tree . generateFormElements ( "cust_sel" , false ) ;
* // Generate input elements using a custom filter
* tree . generateFormElements ( true , true , { filter : function ( node ) {
* return node . isSelected ( ) && node . data . yes ;
* } } ) ;
*
* @ param { boolean | string } [ selected = true ] Pass false to disable , pass a string to override the field name ( default : 'ft_ID[]' )
* @ param { boolean | string } [ active = true ] Pass false to disable , pass a string to override the field name ( default : 'ft_ID_active' )
* @ param { object } [ opts ] default { filter : null , stopOnParents : true }
* /
generateFormElements : function ( selected , active , opts ) {
opts = opts || { } ;
var nodeList ,
selectedName = ( typeof selected === "string" ) ? selected : "ft_" + this . _id + "[]" ,
activeName = ( typeof active === "string" ) ? active : "ft_" + this . _id + "_active" ,
id = "fancytree_result_" + this . _id ,
$result = $ ( "#" + id ) ,
stopOnParents = this . options . selectMode === 3 && opts . stopOnParents !== false ;
if ( $result . length ) {
$result . empty ( ) ;
} else {
$result = $ ( "<div>" , {
id : id
} ) . hide ( ) . insertAfter ( this . $container ) ;
}
if ( active !== false && this . activeNode ) {
$result . append ( $ ( "<input>" , {
type : "radio" ,
name : activeName ,
value : this . activeNode . key ,
checked : true
} ) ) ;
}
function _appender ( node ) {
$result . append ( $ ( "<input>" , {
type : "checkbox" ,
name : selectedName ,
value : node . key ,
checked : true
} ) ) ;
}
if ( opts . filter ) {
this . visit ( function ( node ) {
var res = opts . filter ( node ) ;
if ( res === "skip" ) { return res ; }
if ( res !== false ) {
_appender ( node ) ;
}
} ) ;
} else if ( selected !== false ) {
nodeList = this . getSelectedNodes ( stopOnParents ) ;
$ . each ( nodeList , function ( idx , node ) {
_appender ( node ) ;
} ) ;
}
} ,
/ * *
* Return the currently active node or null .
* @ returns { FancytreeNode }
* /
getActiveNode : function ( ) {
return this . activeNode ;
} ,
/ * * R e t u r n t h e f i r s t t o p l e v e l n o d e i f a n y ( n o t t h e i n v i s i b l e r o o t n o d e ) .
* @ returns { FancytreeNode | null }
* /
getFirstChild : function ( ) {
return this . rootNode . getFirstChild ( ) ;
} ,
/ * *
* Return node that has keyboard focus or null .
* @ returns { FancytreeNode }
* /
getFocusNode : function ( ) {
return this . focusNode ;
} ,
/ * *
* Return node with a given key or null if not found .
2018-05-24 18:59:32 +00:00
*
* Not
2018-01-01 14:39:23 +00:00
* @ param { string } key
* @ param { FancytreeNode } [ searchRoot ] only search below this node
* @ returns { FancytreeNode | null }
* /
getNodeByKey : function ( key , searchRoot ) {
// Search the DOM by element ID (assuming this is faster than traversing all nodes).
var el , match ;
2018-05-24 18:59:32 +00:00
// TODO: use tree.keyMap if available
// TODO: check opts.generateIds === true
2018-01-01 14:39:23 +00:00
if ( ! searchRoot ) {
el = document . getElementById ( this . options . idPrefix + key ) ;
if ( el ) {
return el . ftnode ? el . ftnode : null ;
}
}
// Not found in the DOM, but still may be in an unrendered part of tree
searchRoot = searchRoot || this . rootNode ;
match = null ;
searchRoot . visit ( function ( node ) {
if ( node . key === key ) {
match = node ;
2018-05-24 18:59:32 +00:00
return false ; // Stop iteration
2018-01-01 14:39:23 +00:00
}
} , true ) ;
return match ;
} ,
/ * * R e t u r n t h e i n v i s i b l e s y s t e m r o o t n o d e .
* @ returns { FancytreeNode }
* /
getRootNode : function ( ) {
return this . rootNode ;
} ,
/ * *
* Return an array of selected nodes .
* @ param { boolean } [ stopOnParents = false ] only return the topmost selected
* node ( useful with selectMode 3 )
* @ returns { FancytreeNode [ ] }
* /
getSelectedNodes : function ( stopOnParents ) {
return this . rootNode . getSelectedNodes ( stopOnParents ) ;
} ,
/ * * R e t u r n t r u e i f t h e t r e e c o n t r o l h a s k e y b o a r d f o c u s
* @ returns { boolean }
* /
hasFocus : function ( ) {
return ! ! this . _hasFocus ;
} ,
2018-05-24 18:59:32 +00:00
/ * * W r i t e t o b r o w s e r c o n s o l e i f d e b u g L e v e l > = 3 ( p r e p e n d i n g t r e e n a m e )
2018-01-01 14:39:23 +00:00
* @ param { * } msg string or object or array of such
* /
info : function ( msg ) {
2018-05-24 18:59:32 +00:00
if ( this . options . debugLevel >= 3 ) {
2018-01-01 14:39:23 +00:00
Array . prototype . unshift . call ( arguments , this . toString ( ) ) ;
consoleApply ( "info" , arguments ) ;
}
} ,
/ *
TODO : isInitializing : function ( ) {
return ( this . phase == "init" || this . phase == "postInit" ) ;
} ,
TODO : isReloading : function ( ) {
return ( this . phase == "init" || this . phase == "postInit" ) && this . options . persist && this . persistence . cookiesFound ;
} ,
TODO : isUserEvent : function ( ) {
return ( this . phase == "userEvent" ) ;
} ,
* /
/ * *
* Make sure that a node with a given ID is loaded , by traversing - and
2018-05-24 18:59:32 +00:00
* loading - its parents . This method is meant for lazy hierarchies .
2018-01-01 14:39:23 +00:00
* A callback is executed for every node as we go .
* @ example
2018-05-24 18:59:32 +00:00
* // Resolve using node.key:
2018-01-01 14:39:23 +00:00
* tree . loadKeyPath ( "/_3/_23/_26/_27" , function ( node , status ) {
* if ( status === "loaded" ) {
2018-05-24 18:59:32 +00:00
* console . log ( "loaded intermediate node " + node ) ;
2018-01-01 14:39:23 +00:00
* } else if ( status === "ok" ) {
* node . activate ( ) ;
* }
* } ) ;
2018-05-24 18:59:32 +00:00
* // Use deferred promise:
* tree . loadKeyPath ( "/_3/_23/_26/_27" ) . progress ( function ( data ) {
* if ( data . status === "loaded" ) {
* console . log ( "loaded intermediate node " + data . node ) ;
* } else if ( data . status === "ok" ) {
* node . activate ( ) ;
* }
* } ) . done ( function ( ) {
* ...
* } ) ;
* // Custom path segment resolver:
* tree . loadKeyPath ( "/321/431/21/2" , {
* matchKey : function ( node , key ) {
* return node . data . refKey === key ;
* } ,
* callback : function ( node , status ) {
* if ( status === "loaded" ) {
* console . log ( "loaded intermediate node " + node ) ;
* } else if ( status === "ok" ) {
* node . activate ( ) ;
* }
* }
* } ) ;
2018-01-01 14:39:23 +00:00
* @ param { string | string [ ] } keyPathList one or more key paths ( e . g . '/3/2_1/7' )
2018-05-24 18:59:32 +00:00
* @ param { function | object } optsOrCallback callback ( node , status ) is called for every visited node ( 'loading' , 'loaded' , 'ok' , 'error' ) .
* Pass an object to define custom key matchers for the path segments : { callback : function , matchKey : function } .
2018-01-01 14:39:23 +00:00
* @ returns { $ . Promise }
* /
2018-05-24 18:59:32 +00:00
loadKeyPath : function ( keyPathList , optsOrCallback ) {
var callback , i , path ,
self = this ,
dfd = new $ . Deferred ( ) ,
parent = this . getRootNode ( ) ,
2018-01-01 14:39:23 +00:00
sep = this . options . keyPathSeparator ,
2018-05-24 18:59:32 +00:00
pathSegList = [ ] ,
opts = $ . extend ( { } , optsOrCallback ) ;
2018-01-01 14:39:23 +00:00
2018-05-24 18:59:32 +00:00
// Prepare options
if ( typeof optsOrCallback === "function" ) {
callback = optsOrCallback ;
} else if ( optsOrCallback && optsOrCallback . callback ) {
callback = optsOrCallback . callback ;
}
opts . callback = function ( ctx , node , status ) {
if ( callback ) {
callback . call ( ctx , node , status ) ;
}
dfd . notifyWith ( ctx , [ { node : node , status : status } ] ) ;
} ;
if ( opts . matchKey == null ) {
opts . matchKey = function ( node , key ) { return node . key === key ; } ;
}
// Convert array of path strings to array of segment arrays
2018-01-01 14:39:23 +00:00
if ( ! $ . isArray ( keyPathList ) ) {
keyPathList = [ keyPathList ] ;
}
for ( i = 0 ; i < keyPathList . length ; i ++ ) {
path = keyPathList [ i ] ;
// strip leading slash
if ( path . charAt ( 0 ) === sep ) {
path = path . substr ( 1 ) ;
}
2018-05-24 18:59:32 +00:00
// segListMap[path] = { parent: parent, segList: path.split(sep) };
pathSegList . push ( path . split ( sep ) ) ;
// targetList.push({ parent: parent, segList: path.split(sep)/* , path: path*/});
}
// The timeout forces async behavior always (even if nodes are all loaded)
// This way a potential progress() event will fire.
setTimeout ( function ( ) {
self . _loadKeyPathImpl ( dfd , opts , parent , pathSegList ) . done ( function ( ) {
dfd . resolve ( ) ;
} ) ;
} , 0 ) ;
return dfd . promise ( ) ;
} ,
/ *
* Resolve a list of paths , relative to one parent node .
* /
_loadKeyPathImpl : function ( dfd , opts , parent , pathSegList ) {
var deferredList , i , key , node , remainMap , tmpParent , segList , subDfd ,
self = this ;
function _ _findChild ( parent , key ) {
// console.log("__findChild", key, parent);
var i , l ,
cl = parent . children ;
if ( cl ) {
for ( i = 0 , l = cl . length ; i < l ; i ++ ) {
if ( opts . matchKey ( cl [ i ] , key ) ) { return cl [ i ] ; }
}
}
return null ;
}
// console.log("_loadKeyPathImpl, parent=", parent, ", pathSegList=", pathSegList);
// Pass 1:
// Handle all path segments for nodes that are already loaded.
// Collect distinct top-most lazy nodes in a map.
// Note that we can use node.key to de-dupe entries, even if a custom matcher would
// look for other node attributes.
// map[node.key] => {node: node, pathList: [list of remaining rest-paths]}
remainMap = { } ;
for ( i = 0 ; i < pathSegList . length ; i ++ ) {
segList = pathSegList [ i ] ;
// target = targetList[i];
// Traverse and pop path segments (i.e. keys), until we hit a lazy, unloaded node
tmpParent = parent ;
2018-01-01 14:39:23 +00:00
while ( segList . length ) {
key = segList . shift ( ) ;
2018-05-24 18:59:32 +00:00
node = _ _findChild ( tmpParent , key ) ;
2018-01-01 14:39:23 +00:00
if ( ! node ) {
2018-05-24 18:59:32 +00:00
this . warn ( "loadKeyPath: key not found: " + key + " (parent: " + tmpParent + ")" ) ;
opts . callback ( this , key , "error" ) ;
2018-01-01 14:39:23 +00:00
break ;
} else if ( segList . length === 0 ) {
2018-05-24 18:59:32 +00:00
opts . callback ( this , node , "ok" ) ;
2018-01-01 14:39:23 +00:00
break ;
} else if ( ! node . lazy || ( node . hasChildren ( ) !== undefined ) ) {
2018-05-24 18:59:32 +00:00
opts . callback ( this , node , "loaded" ) ;
tmpParent = node ;
2018-01-01 14:39:23 +00:00
} else {
2018-05-24 18:59:32 +00:00
opts . callback ( this , node , "loaded" ) ;
key = node . key ; //target.segList.join(sep);
if ( remainMap [ key ] ) {
remainMap [ key ] . pathSegList . push ( segList ) ;
2018-01-01 14:39:23 +00:00
} else {
2018-05-24 18:59:32 +00:00
remainMap [ key ] = { parent : node , pathSegList : [ segList ] } ;
2018-01-01 14:39:23 +00:00
}
break ;
}
}
}
2018-05-24 18:59:32 +00:00
// console.log("_loadKeyPathImpl AFTER pass 1, remainMap=", remainMap);
// Now load all lazy nodes and continue iteration for remaining paths
2018-01-01 14:39:23 +00:00
deferredList = [ ] ;
2018-05-24 18:59:32 +00:00
2018-01-01 14:39:23 +00:00
// Avoid jshint warning 'Don't make functions within a loop.':
2018-05-24 18:59:32 +00:00
function _ _lazyload ( dfd , parent , pathSegList ) {
// console.log("__lazyload", parent, "pathSegList=", pathSegList);
opts . callback ( self , parent , "loading" ) ;
parent . load ( ) . done ( function ( ) {
self . _loadKeyPathImpl . call ( self , dfd , opts , parent , pathSegList )
. always ( _makeResolveFunc ( dfd , self ) ) ;
2018-01-01 14:39:23 +00:00
} ) . fail ( function ( errMsg ) {
2018-05-24 18:59:32 +00:00
self . warn ( "loadKeyPath: error loading lazy " + parent ) ;
opts . callback ( self , node , "error" ) ;
dfd . rejectWith ( self ) ;
2018-01-01 14:39:23 +00:00
} ) ;
}
2018-05-24 18:59:32 +00:00
// remainMap contains parent nodes, each with a list of relative sub-paths.
// We start loading all of them now, and pass the the list to each loader.
for ( var nodeKey in remainMap ) {
var remain = remainMap [ nodeKey ] ;
// console.log("for(): remain=", remain, "remainMap=", remainMap);
// key = remain.segList.shift();
// node = __findChild(remain.parent, key);
// if (node == null) { // #576
// // Issue #576, refactored for v2.27:
// // The root cause was, that sometimes the wrong parent was used here
// // to find the next segment.
// // Falling back to getNodeByKey() was a hack that no longer works if a custom
// // matcher is used, because we cannot assume that a single segment-key is unique
// // throughout the tree.
// self.error("loadKeyPath: error loading child by key '" + key + "' (parent: " + target.parent + ")", target);
// // node = self.getNodeByKey(key);
// continue;
// }
subDfd = new $ . Deferred ( ) ;
deferredList . push ( subDfd ) ;
_ _lazyload ( subDfd , remain . parent , remain . pathSegList ) ;
2018-01-01 14:39:23 +00:00
}
// Return a promise that is resolved, when ALL paths were loaded
return $ . when . apply ( $ , deferredList ) . promise ( ) ;
} ,
/ * * R e - f i r e b e f o r e A c t i v a t e , a c t i v a t e , a n d ( o p t i o n a l ) f o c u s e v e n t s .
* Calling this method in the ` init ` event , will activate the node that
* was marked 'active' in the source data , and optionally set the keyboard
* focus .
* @ param [ setFocus = false ]
* /
reactivate : function ( setFocus ) {
var res ,
node = this . activeNode ;
if ( ! node ) {
return _getResolvedPromise ( ) ;
}
this . activeNode = null ; // Force re-activating
res = node . setActive ( true , { noFocus : true } ) ;
if ( setFocus ) {
node . setFocus ( ) ;
}
return res ;
} ,
/ * * R e l o a d t r e e f r o m s o u r c e a n d r e t u r n a p r o m i s e .
* @ param [ source ] optional new source ( defaults to initial source data )
* @ returns { $ . Promise }
* /
reload : function ( source ) {
this . _callHook ( "treeClear" , this ) ;
return this . _callHook ( "treeLoad" , this , source ) ;
} ,
/ * * R e n d e r t r e e ( i . e . c r e a t e D O M e l e m e n t s f o r a l l t o p - l e v e l n o d e s ) .
* @ param { boolean } [ force = false ] create DOM elemnts , even if parent is collapsed
* @ param { boolean } [ deep = false ]
* /
render : function ( force , deep ) {
return this . rootNode . render ( force , deep ) ;
} ,
2018-05-24 18:59:32 +00:00
/ * * ( D e ) s e l e c t a l l n o d e s .
* @ param { boolean } [ flag = true ]
* @ since 2.28
* /
selectAll : function ( flag ) {
this . visit ( function ( node ) {
node . setSelected ( flag ) ;
} ) ;
} ,
2018-01-01 14:39:23 +00:00
// TODO: selectKey: function(key, select)
// TODO: serializeArray: function(stopOnParents)
/ * *
* @ param { boolean } [ flag = true ]
* /
setFocus : function ( flag ) {
return this . _callHook ( "treeSetFocus" , this , flag ) ;
} ,
/ * *
* Return all nodes as nested list of { @ link NodeData } .
*
* @ param { boolean } [ includeRoot = false ] Returns the hidden system root node ( and its children )
* @ param { function } [ callback ] callback ( dict , node ) is called for every node , in order to allow modifications
* @ returns { Array | object }
* @ see FancytreeNode # toDict
* /
toDict : function ( includeRoot , callback ) {
var res = this . rootNode . toDict ( true , callback ) ;
return includeRoot ? res : res . children ;
} ,
/ * I m p l i c i t l y c a l l e d f o r s t r i n g c o n v e r s i o n s .
* @ returns { string }
* /
toString : function ( ) {
2018-05-24 18:59:32 +00:00
return "Fancytree@" + this . _id ;
// return "<Fancytree(#" + this._id + ")>";
2018-01-01 14:39:23 +00:00
} ,
/ * _ t r i g g e r a w i d g e t e v e n t w i t h a d d i t i o n a l n o d e c t x .
* @ see EventData
* /
_triggerNodeEvent : function ( type , node , originalEvent , extra ) {
// this.debug("_trigger(" + type + "): '" + ctx.node.title + "'", ctx);
var ctx = this . _makeHookContext ( node , originalEvent , extra ) ,
res = this . widget . _trigger ( type , originalEvent , ctx ) ;
if ( res !== false && ctx . result !== undefined ) {
return ctx . result ;
}
return res ;
} ,
/* _trigger a widget event with additional tree data. */
_triggerTreeEvent : function ( type , originalEvent , extra ) {
// this.debug("_trigger(" + type + ")", ctx);
var ctx = this . _makeHookContext ( this , originalEvent , extra ) ,
res = this . widget . _trigger ( type , originalEvent , ctx ) ;
if ( res !== false && ctx . result !== undefined ) {
return ctx . result ;
}
return res ;
} ,
2018-05-24 18:59:32 +00:00
/ * * C a l l f n ( n o d e ) f o r a l l n o d e s i n h i e r a r c h i c a l o r d e r ( d e p t h - f i r s t ) .
2018-01-01 14:39:23 +00:00
*
* @ param { function } fn the callback function .
* Return false to stop iteration , return "skip" to skip this node and children only .
* @ returns { boolean } false , if the iterator was stopped .
* /
visit : function ( fn ) {
return this . rootNode . visit ( fn , false ) ;
} ,
2018-05-24 18:59:32 +00:00
/ * * C a l l f n ( n o d e ) f o r a l l n o d e s i n v e r t i c a l o r d e r , t o p d o w n ( o r b o t t o m u p ) . < b r >
* Stop iteration , if fn ( ) returns false . < br >
* Return false if iteration was stopped .
*
* @ param { function } fn the callback function .
* Return false to stop iteration , return "skip" to skip this node and children only .
* @ param { object } [ options ]
* Defaults :
* { start : First top node , reverse : false , includeSelf : true , includeHidden : false }
* @ returns { boolean }
* @ since 2.28
* /
visitRows : function ( fn , opts ) {
if ( opts && opts . reverse ) {
delete opts . reverse ;
return this . _visitRowsUp ( fn , opts ) ;
}
var i , nextIdx , parent , res , siblings ,
siblingOfs = 0 ,
skipFirstNode = ( opts . includeSelf === false ) ,
includeHidden = ! ! opts . includeHidden ,
node = opts . start || this . rootNode . children [ 0 ] ;
parent = node . parent ;
while ( parent ) {
// visit siblings
siblings = parent . children ;
nextIdx = siblings . indexOf ( node ) + siblingOfs ;
for ( i = nextIdx ; i < siblings . length ; i ++ ) {
node = siblings [ i ] ;
if ( ! skipFirstNode && fn ( node ) === false ) {
return false ;
}
skipFirstNode = false ;
// Dive into node's child nodes
if ( node . children && node . children . length && ( includeHidden || node . expanded ) ) {
// Disable warning: Functions declared within loops referencing an outer
// scoped variable may lead to confusing semantics:
/*jshint -W083 */
res = node . visit ( function ( n ) {
if ( fn ( n ) === false ) {
return false ;
}
if ( ! includeHidden && n . children && ! n . expanded ) {
return "skip" ;
}
} , false ) ;
/*jshint +W083 */
if ( res === false ) {
return false ;
}
}
}
// Visit parent nodes (bottom up)
node = parent ;
parent = parent . parent ;
siblingOfs = 1 ; //
}
return true ;
} ,
/ * C a l l f n ( n o d e ) f o r a l l n o d e s i n v e r t i c a l o r d e r , b o t t o m u p .
* /
_visitRowsUp : function ( fn , opts ) {
var children , idx , parent ,
includeHidden = ! ! opts . includeHidden ,
node = opts . start || this . rootNode . children [ 0 ] ;
while ( true ) {
parent = node . parent ;
children = parent . children ;
if ( children [ 0 ] === node ) {
// If this is already the first sibling, goto parent
node = parent ;
children = parent . children ;
} else {
// Otherwise, goto prev. sibling
idx = children . indexOf ( node ) ;
node = children [ idx - 1 ] ;
// If the prev. sibling has children, follow down to last descendant
while ( ( includeHidden || node . expanded ) && node . children && node . children . length ) {
children = node . children ;
parent = node ;
node = children [ children . length - 1 ] ;
}
}
// Skip invisible
if ( ! includeHidden && ! $ ( node . span ) . is ( ":visible" ) ) {
continue ;
}
if ( fn ( node ) === false ) {
return false ;
}
}
} ,
/ * * W r i t e w a r n i n g t o b r o w s e r c o n s o l e i f d e b u g L e v e l > = 2 ( p r e p e n d i n g t r e e i n f o )
2018-01-01 14:39:23 +00:00
*
* @ param { * } msg string or object or array of such
* /
warn : function ( msg ) {
2018-05-24 18:59:32 +00:00
if ( this . options . debugLevel >= 2 ) {
Array . prototype . unshift . call ( arguments , this . toString ( ) ) ;
consoleApply ( "warn" , arguments ) ;
}
2018-01-01 14:39:23 +00:00
}
} ;
/ * *
* These additional methods of the { @ link Fancytree } class are 'hook functions'
* that can be used and overloaded by extensions .
* ( See < a href = "https://github.com/mar10/fancytree/wiki/TutorialExtensions" > writing extensions < / a > . )
* @ mixin Fancytree _Hooks
* /
$ . extend ( Fancytree . prototype ,
/** @lends Fancytree_Hooks# */
{
/ * * D e f a u l t h a n d l i n g f o r m o u s e c l i c k e v e n t s .
*
* @ param { EventData } ctx
* /
nodeClick : function ( ctx ) {
var activate , expand ,
// event = ctx.originalEvent,
targetType = ctx . targetType ,
node = ctx . node ;
// this.debug("ftnode.onClick(" + event.type + "): ftnode:" + this + ", button:" + event.button + ", which: " + event.which, ctx);
// TODO: use switch
// TODO: make sure clicks on embedded <input> doesn't steal focus (see table sample)
if ( targetType === "expander" ) {
if ( node . isLoading ( ) ) {
// #495: we probably got a click event while a lazy load is pending.
// The 'expanded' state is not yet set, so 'toggle' would expand
// and trigger lazyLoad again.
// It would be better to allow to collapse/expand the status node
// while loading (instead of ignoring), but that would require some
// more work.
node . debug ( "Got 2nd click while loading: ignored" ) ;
return ;
}
// Clicking the expander icon always expands/collapses
this . _callHook ( "nodeToggleExpanded" , ctx ) ;
} else if ( targetType === "checkbox" ) {
// Clicking the checkbox always (de)selects
this . _callHook ( "nodeToggleSelected" , ctx ) ;
if ( ctx . options . focusOnSelect ) { // #358
this . _callHook ( "nodeSetFocus" , ctx , true ) ;
}
} else {
// Honor `clickFolderMode` for
expand = false ;
activate = true ;
if ( node . folder ) {
switch ( ctx . options . clickFolderMode ) {
case 2 : // expand only
expand = true ;
activate = false ;
break ;
case 3 : // expand and activate
activate = true ;
expand = true ; //!node.isExpanded();
break ;
// else 1 or 4: just activate
}
}
if ( activate ) {
this . nodeSetFocus ( ctx ) ;
this . _callHook ( "nodeSetActive" , ctx , true ) ;
}
if ( expand ) {
if ( ! activate ) {
// this._callHook("nodeSetFocus", ctx);
}
// this._callHook("nodeSetExpanded", ctx, true);
this . _callHook ( "nodeToggleExpanded" , ctx ) ;
}
}
// Make sure that clicks stop, otherwise <a href='#'> jumps to the top
// if(event.target.localName === "a" && event.target.className === "fancytree-title"){
// event.preventDefault();
// }
// TODO: return promise?
} ,
/ * * C o l l a p s e a l l o t h e r c h i l d r e n o f s a m e p a r e n t .
*
* @ param { EventData } ctx
* @ param { object } callOpts
* /
nodeCollapseSiblings : function ( ctx , callOpts ) {
// TODO: return promise?
var ac , i , l ,
node = ctx . node ;
if ( node . parent ) {
ac = node . parent . children ;
for ( i = 0 , l = ac . length ; i < l ; i ++ ) {
if ( ac [ i ] !== node && ac [ i ] . expanded ) {
this . _callHook ( "nodeSetExpanded" , ac [ i ] , false , callOpts ) ;
}
}
}
} ,
/ * * D e f a u l t h a n d l i n g f o r m o u s e d o u l e c l i c k e v e n t s .
* @ param { EventData } ctx
* /
nodeDblclick : function ( ctx ) {
// TODO: return promise?
if ( ctx . targetType === "title" && ctx . options . clickFolderMode === 4 ) {
// this.nodeSetFocus(ctx);
// this._callHook("nodeSetActive", ctx, true);
this . _callHook ( "nodeToggleExpanded" , ctx ) ;
}
// TODO: prevent text selection on dblclicks
if ( ctx . targetType === "title" ) {
ctx . originalEvent . preventDefault ( ) ;
}
} ,
/ * * D e f a u l t h a n d l i n g f o r m o u s e k e y d o w n e v e n t s .
*
* NOTE : this may be called with node == null if tree ( but no node ) has focus .
* @ param { EventData } ctx
* /
nodeKeydown : function ( ctx ) {
// TODO: return promise?
var matchNode , stamp , res , focusNode ,
event = ctx . originalEvent ,
node = ctx . node ,
tree = ctx . tree ,
opts = ctx . options ,
which = event . which ,
whichChar = String . fromCharCode ( which ) ,
clean = ! ( event . altKey || event . ctrlKey || event . metaKey || event . shiftKey ) ,
$target = $ ( event . target ) ,
handled = true ,
activate = ! ( event . ctrlKey || ! opts . autoActivate ) ;
// (node || FT).debug("ftnode.nodeKeydown(" + event.type + "): ftnode:" + this + ", charCode:" + event.charCode + ", keyCode: " + event.keyCode + ", which: " + event.which);
// FT.debug("eventToString", which, '"' + String.fromCharCode(which) + '"', '"' + FT.eventToString(event) + '"');
// Set focus to active (or first node) if no other node has the focus yet
if ( ! node ) {
focusNode = ( this . getActiveNode ( ) || this . getFirstChild ( ) ) ;
if ( focusNode ) {
focusNode . setFocus ( ) ;
node = ctx . node = this . focusNode ;
node . debug ( "Keydown force focus on active node" ) ;
}
}
if ( opts . quicksearch && clean && /\w/ . test ( whichChar ) &&
! SPECIAL _KEYCODES [ which ] && // #659
! $target . is ( ":input:enabled" ) ) {
// Allow to search for longer streaks if typed in quickly
2018-05-24 18:59:32 +00:00
stamp = Date . now ( ) ;
2018-01-01 14:39:23 +00:00
if ( stamp - tree . lastQuicksearchTime > 500 ) {
tree . lastQuicksearchTerm = "" ;
}
tree . lastQuicksearchTime = stamp ;
tree . lastQuicksearchTerm += whichChar ;
// tree.debug("quicksearch find", tree.lastQuicksearchTerm);
matchNode = tree . findNextNode ( tree . lastQuicksearchTerm , tree . getActiveNode ( ) ) ;
if ( matchNode ) {
matchNode . setActive ( ) ;
}
event . preventDefault ( ) ;
return ;
}
switch ( FT . eventToString ( event ) ) {
case "+" :
case "=" : // 187: '+' @ Chrome, Safari
tree . nodeSetExpanded ( ctx , true ) ;
break ;
case "-" :
tree . nodeSetExpanded ( ctx , false ) ;
break ;
case "space" :
if ( node . isPagingNode ( ) ) {
tree . _triggerNodeEvent ( "clickPaging" , ctx , event ) ;
} else if ( FT . evalOption ( "checkbox" , node , node , opts , false ) ) { // #768
tree . nodeToggleSelected ( ctx ) ;
} else {
tree . nodeSetActive ( ctx , true ) ;
}
break ;
case "return" :
tree . nodeSetActive ( ctx , true ) ;
break ;
case "home" :
case "end" :
case "backspace" :
case "left" :
case "right" :
case "up" :
case "down" :
2018-05-24 18:59:32 +00:00
res = node . navigate ( event . which , activate ) ;
2018-01-01 14:39:23 +00:00
break ;
default :
handled = false ;
}
if ( handled ) {
event . preventDefault ( ) ;
}
} ,
// /** Default handling for mouse keypress events. */
// nodeKeypress: function(ctx) {
// var event = ctx.originalEvent;
// },
// /** Trigger lazyLoad event (async). */
// nodeLazyLoad: function(ctx) {
// var node = ctx.node;
// if(this._triggerNodeEvent())
// },
/ * * L o a d c h i l d n o d e s ( a s y n c ) .
*
* @ param { EventData } ctx
* @ param { object [ ] | object | string | $ . Promise | function } source
* @ returns { $ . Promise } The deferred will be resolved as soon as the ( ajax )
* data was rendered .
* /
nodeLoadChildren : function ( ctx , source ) {
var ajax , delay , dfd ,
tree = ctx . tree ,
node = ctx . node ,
2018-05-24 18:59:32 +00:00
requestId = Date . now ( ) ;
2018-01-01 14:39:23 +00:00
if ( $ . isFunction ( source ) ) {
source = source . call ( tree , { type : "source" } , ctx ) ;
_assert ( ! $ . isFunction ( source ) , "source callback must not return another function" ) ;
}
if ( source . url ) {
if ( node . _requestId ) {
node . warn ( "Recursive load request #" + requestId + " while #" + node . _requestId + " is pending." ) ;
// } else {
// node.debug("Send load request #" + requestId);
}
// `source` is an Ajax options object
ajax = $ . extend ( { } , ctx . options . ajax , source ) ;
node . _requestId = requestId ;
if ( ajax . debugDelay ) {
// simulate a slow server
delay = ajax . debugDelay ;
if ( $ . isArray ( delay ) ) { // random delay range [min..max]
delay = delay [ 0 ] + Math . random ( ) * ( delay [ 1 ] - delay [ 0 ] ) ;
}
node . warn ( "nodeLoadChildren waiting debugDelay " + Math . round ( delay ) + " ms ..." ) ;
ajax . debugDelay = false ;
dfd = $ . Deferred ( function ( dfd ) {
setTimeout ( function ( ) {
$ . ajax ( ajax )
. done ( function ( ) { dfd . resolveWith ( this , arguments ) ; } )
. fail ( function ( ) { dfd . rejectWith ( this , arguments ) ; } ) ;
} , delay ) ;
} ) ;
} else {
dfd = $ . ajax ( ajax ) ;
}
// Defer the deferred: we want to be able to reject, even if ajax
// resolved ok.
source = new $ . Deferred ( ) ;
dfd . done ( function ( data , textStatus , jqXHR ) {
var errorObj , res ;
if ( ( this . dataType === "json" || this . dataType === "jsonp" ) && typeof data === "string" ) {
$ . error ( "Ajax request returned a string (did you get the JSON dataType wrong?)." ) ;
}
if ( node . _requestId && node . _requestId > requestId ) {
// The expected request time stamp is later than `requestId`
// (which was kept as as closure variable to this handler function)
// node.warn("Ignored load response for obsolete request #" + requestId + " (expected #" + node._requestId + ")");
source . rejectWith ( this , [ RECURSIVE _REQUEST _ERROR ] ) ;
return ;
// } else {
// node.debug("Response returned for load request #" + requestId);
}
// postProcess is similar to the standard ajax dataFilter hook,
// but it is also called for JSONP
if ( ctx . options . postProcess ) {
try {
res = tree . _triggerNodeEvent ( "postProcess" , ctx , ctx . originalEvent , {
response : data , error : null , dataType : this . dataType
} ) ;
} catch ( e ) {
res = { error : e , message : "" + e , details : "postProcess failed" } ;
}
if ( res . error ) {
errorObj = $ . isPlainObject ( res . error ) ? res . error : { message : res . error } ;
errorObj = tree . _makeHookContext ( node , null , errorObj ) ;
source . rejectWith ( this , [ errorObj ] ) ;
return ;
}
data = $ . isArray ( res ) ? res : data ;
} else if ( data && data . hasOwnProperty ( "d" ) && ctx . options . enableAspx ) {
// Process ASPX WebMethod JSON object inside "d" property
data = ( typeof data . d === "string" ) ? $ . parseJSON ( data . d ) : data . d ;
}
source . resolveWith ( this , [ data ] ) ;
} ) . fail ( function ( jqXHR , textStatus , errorThrown ) {
var errorObj = tree . _makeHookContext ( node , null , {
error : jqXHR ,
args : Array . prototype . slice . call ( arguments ) ,
message : errorThrown ,
details : jqXHR . status + ": " + errorThrown
} ) ;
source . rejectWith ( this , [ errorObj ] ) ;
} ) ;
}
// #383: accept and convert ECMAScript 6 Promise
if ( $ . isFunction ( source . then ) && $ . isFunction ( source [ "catch" ] ) ) {
dfd = source ;
source = new $ . Deferred ( ) ;
dfd . then ( function ( value ) {
source . resolve ( value ) ;
} , function ( reason ) {
source . reject ( reason ) ;
} ) ;
}
if ( $ . isFunction ( source . promise ) ) {
// `source` is a deferred, i.e. ajax request
// _assert(!node.isLoading(), "recursive load");
tree . nodeSetStatus ( ctx , "loading" ) ;
source . done ( function ( children ) {
tree . nodeSetStatus ( ctx , "ok" ) ;
node . _requestId = null ;
} ) . fail ( function ( error ) {
var ctxErr ;
if ( error === RECURSIVE _REQUEST _ERROR ) {
node . warn ( "Ignored response for obsolete load request #" + requestId + " (expected #" + node . _requestId + ")" ) ;
return ;
} else if ( error . node && error . error && error . message ) {
// error is already a context object
ctxErr = error ;
} else {
ctxErr = tree . _makeHookContext ( node , null , {
error : error , // it can be jqXHR or any custom error
args : Array . prototype . slice . call ( arguments ) ,
message : error ? ( error . message || error . toString ( ) ) : ""
} ) ;
if ( ctxErr . message === "[object Object]" ) {
ctxErr . message = "" ;
}
}
node . warn ( "Load children failed (" + ctxErr . message + ")" , ctxErr ) ;
if ( tree . _triggerNodeEvent ( "loadError" , ctxErr , null ) !== false ) {
tree . nodeSetStatus ( ctx , "error" , ctxErr . message , ctxErr . details ) ;
}
} ) ;
} else {
if ( ctx . options . postProcess ) {
// #792: Call postProcess for non-deferred source
2018-05-24 18:59:32 +00:00
var res = tree . _triggerNodeEvent ( "postProcess" , ctx , ctx . originalEvent , {
2018-01-01 14:39:23 +00:00
response : source , error : null , dataType : typeof source
} ) ;
2018-05-24 18:59:32 +00:00
source = $ . isArray ( res ) ? res : source ;
2018-01-01 14:39:23 +00:00
}
}
// $.when(source) resolves also for non-deferreds
return $ . when ( source ) . done ( function ( children ) {
var metaData ;
if ( $ . isPlainObject ( children ) ) {
// We got {foo: 'abc', children: [...]}
// Copy extra properties to tree.data.foo
_assert ( node . isRootNode ( ) , "source may only be an object for root nodes (expecting an array of child objects otherwise)" ) ;
_assert ( $ . isArray ( children . children ) , "if an object is passed as source, it must contain a 'children' array (all other properties are added to 'tree.data')" ) ;
metaData = children ;
children = children . children ;
delete metaData . children ;
2018-05-24 18:59:32 +00:00
// Copy some attributes to tree.data
$ . each ( TREE _ATTRS , function ( i , attr ) {
if ( metaData [ attr ] !== undefined ) {
tree [ attr ] = metaData [ attr ] ;
delete metaData [ attr ] ;
}
} ) ;
// Copy all other attributes to tree.data.NAME
2018-01-01 14:39:23 +00:00
$ . extend ( tree . data , metaData ) ;
}
_assert ( $ . isArray ( children ) , "expected array of children" ) ;
node . _setChildren ( children ) ;
// trigger fancytreeloadchildren
tree . _triggerNodeEvent ( "loadChildren" , node ) ;
} ) ;
} ,
/** [Not Implemented] */
nodeLoadKeyPath : function ( ctx , keyPathList ) {
// TODO: implement and improve
// http://code.google.com/p/dynatree/issues/detail?id=222
} ,
/ * *
* Remove a single direct child of ctx . node .
* @ param { EventData } ctx
* @ param { FancytreeNode } childNode dircect child of ctx . node
* /
nodeRemoveChild : function ( ctx , childNode ) {
var idx ,
node = ctx . node ,
// opts = ctx.options,
subCtx = $ . extend ( { } , ctx , { node : childNode } ) ,
children = node . children ;
// FT.debug("nodeRemoveChild()", node.toString(), childNode.toString());
if ( children . length === 1 ) {
_assert ( childNode === children [ 0 ] , "invalid single child" ) ;
return this . nodeRemoveChildren ( ctx ) ;
}
if ( this . activeNode && ( childNode === this . activeNode || this . activeNode . isDescendantOf ( childNode ) ) ) {
this . activeNode . setActive ( false ) ; // TODO: don't fire events
}
if ( this . focusNode && ( childNode === this . focusNode || this . focusNode . isDescendantOf ( childNode ) ) ) {
this . focusNode = null ;
}
// TODO: persist must take care to clear select and expand cookies
this . nodeRemoveMarkup ( subCtx ) ;
this . nodeRemoveChildren ( subCtx ) ;
idx = $ . inArray ( childNode , children ) ;
_assert ( idx >= 0 , "invalid child" ) ;
// Notify listeners
node . triggerModifyChild ( "remove" , childNode ) ;
// Unlink to support GC
childNode . visit ( function ( n ) {
n . parent = null ;
} , true ) ;
this . _callHook ( "treeRegisterNode" , this , false , childNode ) ;
// remove from child list
children . splice ( idx , 1 ) ;
} ,
/ * * R e m o v e H T M L m a r k u p f o r a l l d e s c e n d e n t s o f c t x . n o d e .
* @ param { EventData } ctx
* /
nodeRemoveChildMarkup : function ( ctx ) {
var node = ctx . node ;
// FT.debug("nodeRemoveChildMarkup()", node.toString());
// TODO: Unlink attr.ftnode to support GC
if ( node . ul ) {
if ( node . isRootNode ( ) ) {
$ ( node . ul ) . empty ( ) ;
} else {
$ ( node . ul ) . remove ( ) ;
node . ul = null ;
}
node . visit ( function ( n ) {
n . li = n . ul = null ;
} ) ;
}
} ,
/ * * R e m o v e a l l d e s c e n d a n t s o f c t x . n o d e .
* @ param { EventData } ctx
* /
nodeRemoveChildren : function ( ctx ) {
var subCtx ,
tree = ctx . tree ,
node = ctx . node ,
children = node . children ;
// opts = ctx.options;
// FT.debug("nodeRemoveChildren()", node.toString());
if ( ! children ) {
return ;
}
if ( this . activeNode && this . activeNode . isDescendantOf ( node ) ) {
this . activeNode . setActive ( false ) ; // TODO: don't fire events
}
if ( this . focusNode && this . focusNode . isDescendantOf ( node ) ) {
this . focusNode = null ;
}
// TODO: persist must take care to clear select and expand cookies
this . nodeRemoveChildMarkup ( ctx ) ;
// Unlink children to support GC
// TODO: also delete this.children (not possible using visit())
subCtx = $ . extend ( { } , ctx ) ;
node . triggerModifyChild ( "remove" , null ) ;
node . visit ( function ( n ) {
n . parent = null ;
tree . _callHook ( "treeRegisterNode" , tree , false , n ) ;
} ) ;
if ( node . lazy ) {
// 'undefined' would be interpreted as 'not yet loaded' for lazy nodes
node . children = [ ] ;
} else {
node . children = null ;
}
if ( ! node . isRootNode ( ) ) {
node . expanded = false ; // #449, #459
}
this . nodeRenderStatus ( ctx ) ;
} ,
/ * * R e m o v e H T M L m a r k u p f o r c t x . n o d e a n d a l l i t s d e s c e n d e n t s .
* @ param { EventData } ctx
* /
nodeRemoveMarkup : function ( ctx ) {
var node = ctx . node ;
// FT.debug("nodeRemoveMarkup()", node.toString());
// TODO: Unlink attr.ftnode to support GC
if ( node . li ) {
$ ( node . li ) . remove ( ) ;
node . li = null ;
}
this . nodeRemoveChildMarkup ( ctx ) ;
} ,
/ * *
* Create ` <li><span>..</span> .. </li> ` tags for this node .
*
* This method takes care that all HTML markup is created that is required
* to display this node in its current state .
*
* Call this method to create new nodes , or after the strucuture
* was changed ( e . g . after moving this node or adding / removing children )
* nodeRenderTitle ( ) and nodeRenderStatus ( ) are implied .
*
* & lt ; code >
* & lt ; li id = 'KEY' ftnode = NODE >
* & lt ; span class = 'fancytree-node fancytree-expanded fancytree-has-children fancytree-lastsib fancytree-exp-el fancytree-ico-e' >
* & lt ; span class = "fancytree-expander" > & lt ; / s p a n >
* & lt ; span class = "fancytree-checkbox" > & lt ; /span> / / only present in checkbox mode
* & lt ; span class = "fancytree-icon" > & lt ; / s p a n >
* & lt ; a href = "#" class = "fancytree-title" > Node 1 & lt ; / a >
* & lt ; / s p a n >
* & lt ; ul > // only present if node has children
* & lt ; li id = 'KEY' ftnode = NODE > child1 ... & lt ; / l i >
* & lt ; li id = 'KEY' ftnode = NODE > child2 ... & lt ; / l i >
* & lt ; / u l >
* & lt ; / l i >
* & lt ; / c o d e >
*
* @ param { EventData } ctx
* @ param { boolean } [ force = false ] re - render , even if html markup was already created
* @ param { boolean } [ deep = false ] also render all descendants , even if parent is collapsed
* @ param { boolean } [ collapsed = false ] force root node to be collapsed , so we can apply animated expand later
* /
nodeRender : function ( ctx , force , deep , collapsed , _recursive ) {
/ * T h i s m e t h o d m u s t t a k e c a r e o f a l l c a s e s w h e r e t h e c u r r e n t d a t a m o d e
* ( i . e . node hierarchy ) does not match the current markup .
*
* - node was not yet rendered :
* create markup
* - node was rendered : exit fast
* - children have been added
* - children have been removed
* /
var childLI , childNode1 , childNode2 , i , l , next , subCtx ,
node = ctx . node ,
tree = ctx . tree ,
opts = ctx . options ,
aria = opts . aria ,
firstTime = false ,
parent = node . parent ,
isRootNode = ! parent ,
children = node . children ,
successorLi = null ;
// FT.debug("nodeRender(" + !!force + ", " + !!deep + ")", node.toString());
if ( tree . _enableUpdate === false ) {
// tree.debug("no render", tree._enableUpdate);
return ;
}
if ( ! isRootNode && ! parent . ul ) {
// Calling node.collapse on a deep, unrendered node
return ;
}
_assert ( isRootNode || parent . ul , "parent UL must exist" ) ;
// Render the node
if ( ! isRootNode ) {
// Discard markup on force-mode, or if it is not linked to parent <ul>
if ( node . li && ( force || ( node . li . parentNode !== node . parent . ul ) ) ) {
if ( node . li . parentNode === node . parent . ul ) {
// #486: store following node, so we can insert the new markup there later
successorLi = node . li . nextSibling ;
} else {
// May happen, when a top-level node was dropped over another
this . debug ( "Unlinking " + node + " (must be child of " + node . parent + ")" ) ;
}
// this.debug("nodeRemoveMarkup...");
this . nodeRemoveMarkup ( ctx ) ;
}
// Create <li><span /> </li>
// node.debug("render...");
if ( ! node . li ) {
// node.debug("render... really");
firstTime = true ;
node . li = document . createElement ( "li" ) ;
node . li . ftnode = node ;
if ( node . key && opts . generateIds ) {
node . li . id = opts . idPrefix + node . key ;
}
node . span = document . createElement ( "span" ) ;
node . span . className = "fancytree-node" ;
if ( aria && ! node . tr ) {
$ ( node . li ) . attr ( "role" , "treeitem" ) ;
}
node . li . appendChild ( node . span ) ;
// Create inner HTML for the <span> (expander, checkbox, icon, and title)
this . nodeRenderTitle ( ctx ) ;
// Allow tweaking and binding, after node was created for the first time
if ( opts . createNode ) {
opts . createNode . call ( tree , { type : "createNode" } , ctx ) ;
}
} else {
// this.nodeRenderTitle(ctx);
this . nodeRenderStatus ( ctx ) ;
}
// Allow tweaking after node state was rendered
if ( opts . renderNode ) {
opts . renderNode . call ( tree , { type : "renderNode" } , ctx ) ;
}
}
// Visit child nodes
if ( children ) {
if ( isRootNode || node . expanded || deep === true ) {
// Create a UL to hold the children
if ( ! node . ul ) {
node . ul = document . createElement ( "ul" ) ;
if ( ( collapsed === true && ! _recursive ) || ! node . expanded ) {
// hide top UL, so we can use an animation to show it later
node . ul . style . display = "none" ;
}
if ( aria ) {
$ ( node . ul ) . attr ( "role" , "group" ) ;
}
if ( node . li ) { // issue #67
node . li . appendChild ( node . ul ) ;
} else {
node . tree . $div . append ( node . ul ) ;
}
}
// Add child markup
for ( i = 0 , l = children . length ; i < l ; i ++ ) {
subCtx = $ . extend ( { } , ctx , { node : children [ i ] } ) ;
this . nodeRender ( subCtx , force , deep , false , true ) ;
}
// Remove <li> if nodes have moved to another parent
childLI = node . ul . firstChild ;
while ( childLI ) {
childNode2 = childLI . ftnode ;
if ( childNode2 && childNode2 . parent !== node ) {
node . debug ( "_fixParent: remove missing " + childNode2 , childLI ) ;
next = childLI . nextSibling ;
childLI . parentNode . removeChild ( childLI ) ;
childLI = next ;
} else {
childLI = childLI . nextSibling ;
}
}
// Make sure, that <li> order matches node.children order.
childLI = node . ul . firstChild ;
for ( i = 0 , l = children . length - 1 ; i < l ; i ++ ) {
childNode1 = children [ i ] ;
childNode2 = childLI . ftnode ;
if ( childNode1 !== childNode2 ) {
// node.debug("_fixOrder: mismatch at index " + i + ": " + childNode1 + " != " + childNode2);
node . ul . insertBefore ( childNode1 . li , childNode2 . li ) ;
} else {
childLI = childLI . nextSibling ;
}
}
}
} else {
// No children: remove markup if any
if ( node . ul ) {
// alert("remove child markup for " + node);
this . warn ( "remove child markup for " + node ) ;
this . nodeRemoveChildMarkup ( ctx ) ;
}
}
if ( ! isRootNode ) {
// Update element classes according to node state
// this.nodeRenderStatus(ctx);
// Finally add the whole structure to the DOM, so the browser can render
if ( firstTime ) {
// #486: successorLi is set, if we re-rendered (i.e. discarded)
// existing markup, which we want to insert at the same position.
// (null is equivalent to append)
// parent.ul.appendChild(node.li);
parent . ul . insertBefore ( node . li , successorLi ) ;
}
}
} ,
/ * * C r e a t e H T M L i n s i d e t h e n o d e ' s o u t e r & l t ; s p a n > ( i . e . e x p a n d e r , c h e c k b o x ,
* icon , and title ) .
*
* nodeRenderStatus ( ) is implied .
* @ param { EventData } ctx
* @ param { string } [ title ] optinal new title
* /
nodeRenderTitle : function ( ctx , title ) {
// set node connector images, links and text
2018-05-24 18:59:32 +00:00
var checkbox , className , icon , nodeTitle , role , tabindex , tooltip , iconTooltip ,
2018-01-01 14:39:23 +00:00
node = ctx . node ,
tree = ctx . tree ,
opts = ctx . options ,
aria = opts . aria ,
level = node . getLevel ( ) ,
ares = [ ] ;
if ( title !== undefined ) {
node . title = title ;
}
if ( ! node . span || tree . _enableUpdate === false ) {
// Silently bail out if node was not rendered yet, assuming
// node.render() will be called as the node becomes visible
return ;
}
// Connector (expanded, expandable or simple)
role = ( aria && node . hasChildren ( ) !== false ) ? " role='button'" : "" ;
if ( level < opts . minExpandLevel ) {
if ( ! node . lazy ) {
node . expanded = true ;
}
if ( level > 1 ) {
ares . push ( "<span " + role + " class='fancytree-expander fancytree-expander-fixed'></span>" ) ;
}
// .. else (i.e. for root level) skip expander/connector alltogether
} else {
ares . push ( "<span " + role + " class='fancytree-expander'></span>" ) ;
}
// Checkbox mode
checkbox = FT . evalOption ( "checkbox" , node , node , opts , false ) ;
if ( checkbox && ! node . isStatusNode ( ) ) {
role = aria ? " role='checkbox'" : "" ;
className = "fancytree-checkbox" ;
if ( checkbox === "radio" || ( node . parent && node . parent . radiogroup ) ) {
className += " fancytree-radio" ;
}
ares . push ( "<span " + role + " class='" + className + "'></span>" ) ;
}
// Folder or doctype icon
if ( node . data . iconClass !== undefined ) { // 2015-11-16
// Handle / warn about backward compatibility
if ( node . icon ) {
$ . error ( "'iconClass' node option is deprecated since v2.14.0: use 'icon' only instead" ) ;
} else {
node . warn ( "'iconClass' node option is deprecated since v2.14.0: use 'icon' instead" ) ;
node . icon = node . data . iconClass ;
}
}
// If opts.icon is a callback and returns something other than undefined, use that
// else if node.icon is a boolean or string, use that
// else if opts.icon is a boolean or string, use that
// else show standard icon (which may be different for folders or documents)
icon = FT . evalOption ( "icon" , node , node , opts , true ) ;
2018-05-24 18:59:32 +00:00
// if( typeof icon !== "boolean" ) {
// // icon is defined, but not true/false: must be a string
// icon = "" + icon;
// }
2018-01-01 14:39:23 +00:00
if ( icon !== false ) {
role = aria ? " role='presentation'" : "" ;
2018-05-24 18:59:32 +00:00
iconTooltip = FT . evalOption ( "iconTooltip" , node , node , opts , null ) ;
iconTooltip = iconTooltip ? " title='" + _escapeTooltip ( iconTooltip ) + "'" : "" ;
2018-01-01 14:39:23 +00:00
if ( typeof icon === "string" ) {
if ( TEST _IMG . test ( icon ) ) {
// node.icon is an image url. Prepend imagePath
icon = ( icon . charAt ( 0 ) === "/" ) ? icon : ( ( opts . imagePath || "" ) + icon ) ;
2018-05-24 18:59:32 +00:00
ares . push ( "<img src='" + icon + "' class='fancytree-icon'" + iconTooltip + " alt='' />" ) ;
2018-01-01 14:39:23 +00:00
} else {
2018-05-24 18:59:32 +00:00
ares . push ( "<span " + role + " class='fancytree-custom-icon " + icon + "'" + iconTooltip + "></span>" ) ;
}
} else if ( icon . text ) {
ares . push ( "<span " + role + " class='fancytree-custom-icon " +
( icon . addClass || "" ) + "'" + iconTooltip + ">" + FT . escapeHtml ( icon . text ) + "</span>" ) ;
} else if ( icon . html ) {
ares . push ( "<span " + role + " class='fancytree-custom-icon " +
( icon . addClass || "" ) + "'" + iconTooltip + ">" + icon . html + "</span>" ) ;
2018-01-01 14:39:23 +00:00
} else {
// standard icon: theme css will take care of this
2018-05-24 18:59:32 +00:00
ares . push ( "<span " + role + " class='fancytree-icon'" + iconTooltip + "></span>" ) ;
2018-01-01 14:39:23 +00:00
}
}
// Node title
nodeTitle = "" ;
if ( opts . renderTitle ) {
nodeTitle = opts . renderTitle . call ( tree , { type : "renderTitle" } , ctx ) || "" ;
}
if ( ! nodeTitle ) {
tooltip = FT . evalOption ( "tooltip" , node , node , opts , null ) ;
if ( tooltip === true ) {
tooltip = node . title ;
}
// if( node.tooltip ) {
// tooltip = node.tooltip;
// } else if ( opts.tooltip ) {
// tooltip = opts.tooltip === true ? node.title : opts.tooltip.call(tree, node);
// }
tooltip = tooltip ? " title='" + _escapeTooltip ( tooltip ) + "'" : "" ;
tabindex = opts . titlesTabbable ? " tabindex='0'" : "" ;
nodeTitle = "<span class='fancytree-title'" +
tooltip + tabindex + ">" +
( opts . escapeTitles ? FT . escapeHtml ( node . title ) : node . title ) +
"</span>" ;
}
ares . push ( nodeTitle ) ;
// Note: this will trigger focusout, if node had the focus
//$(node.span).html(ares.join("")); // it will cleanup the jQuery data currently associated with SPAN (if any), but it executes more slowly
node . span . innerHTML = ares . join ( "" ) ;
// Update CSS classes
this . nodeRenderStatus ( ctx ) ;
if ( opts . enhanceTitle ) {
ctx . $title = $ ( ">span.fancytree-title" , node . span ) ;
nodeTitle = opts . enhanceTitle . call ( tree , { type : "enhanceTitle" } , ctx ) || "" ;
}
} ,
/ * * U p d a t e e l e m e n t c l a s s e s a c c o r d i n g t o n o d e s t a t e .
* @ param { EventData } ctx
* /
nodeRenderStatus : function ( ctx ) {
// Set classes for current status
var $ariaElem ,
node = ctx . node ,
tree = ctx . tree ,
opts = ctx . options ,
// nodeContainer = node[tree.nodeContainerAttrName],
hasChildren = node . hasChildren ( ) ,
isLastSib = node . isLastSibling ( ) ,
aria = opts . aria ,
cn = opts . _classNames ,
cnList = [ ] ,
statusElem = node [ tree . statusClassPropName ] ;
if ( ! statusElem || tree . _enableUpdate === false ) {
// if this function is called for an unrendered node, ignore it (will be updated on nect render anyway)
return ;
}
if ( aria ) {
$ariaElem = $ ( node . tr || node . li ) ;
}
// Build a list of class names that we will add to the node <span>
cnList . push ( cn . node ) ;
if ( tree . activeNode === node ) {
cnList . push ( cn . active ) ;
// $(">span.fancytree-title", statusElem).attr("tabindex", "0");
// tree.$container.removeAttr("tabindex");
// }else{
// $(">span.fancytree-title", statusElem).removeAttr("tabindex");
// tree.$container.attr("tabindex", "0");
}
if ( tree . focusNode === node ) {
cnList . push ( cn . focused ) ;
}
if ( node . expanded ) {
cnList . push ( cn . expanded ) ;
}
if ( aria ) {
if ( hasChildren !== false ) {
$ariaElem . attr ( "aria-expanded" , Boolean ( node . expanded ) ) ;
}
else {
$ariaElem . removeAttr ( "aria-expanded" ) ;
}
}
if ( node . folder ) {
cnList . push ( cn . folder ) ;
}
if ( hasChildren !== false ) {
cnList . push ( cn . hasChildren ) ;
}
// TODO: required?
if ( isLastSib ) {
cnList . push ( cn . lastsib ) ;
}
if ( node . lazy && node . children == null ) {
cnList . push ( cn . lazy ) ;
}
if ( node . partload ) {
cnList . push ( cn . partload ) ;
}
if ( node . partsel ) {
cnList . push ( cn . partsel ) ;
}
if ( FT . evalOption ( "unselectable" , node , node , opts , false ) ) {
cnList . push ( cn . unselectable ) ;
}
if ( node . _isLoading ) {
cnList . push ( cn . loading ) ;
}
if ( node . _error ) {
cnList . push ( cn . error ) ;
}
if ( node . statusNodeType ) {
cnList . push ( cn . statusNodePrefix + node . statusNodeType ) ;
}
if ( node . selected ) {
cnList . push ( cn . selected ) ;
if ( aria ) {
$ariaElem . attr ( "aria-selected" , true ) ;
}
} else if ( aria ) {
$ariaElem . attr ( "aria-selected" , false ) ;
}
if ( node . extraClasses ) {
cnList . push ( node . extraClasses ) ;
}
// IE6 doesn't correctly evaluate multiple class names,
// so we create combined class names that can be used in the CSS
if ( hasChildren === false ) {
cnList . push ( cn . combinedExpanderPrefix + "n" +
( isLastSib ? "l" : "" )
) ;
} else {
cnList . push ( cn . combinedExpanderPrefix +
( node . expanded ? "e" : "c" ) +
( node . lazy && node . children == null ? "d" : "" ) +
( isLastSib ? "l" : "" )
) ;
}
cnList . push ( cn . combinedIconPrefix +
( node . expanded ? "e" : "c" ) +
( node . folder ? "f" : "" )
) ;
// node.span.className = cnList.join(" ");
statusElem . className = cnList . join ( " " ) ;
// TODO: we should not set this in the <span> tag also, if we set it here:
// Maybe most (all) of the classes should be set in LI instead of SPAN?
if ( node . li ) {
// #719: we have to consider that there may be already other classes:
$ ( node . li ) . toggleClass ( cn . lastsib , isLastSib ) ;
}
} ,
/ * * A c t i v a t e n o d e .
* flag defaults to true .
* If flag is true , the node is activated ( must be a synchronous operation )
* If flag is false , the node is deactivated ( must be a synchronous operation )
* @ param { EventData } ctx
* @ param { boolean } [ flag = true ]
* @ param { object } [ opts ] additional options . Defaults to { noEvents : false , noFocus : false }
* @ returns { $ . Promise }
* /
nodeSetActive : function ( ctx , flag , callOpts ) {
// Handle user click / [space] / [enter], according to clickFolderMode.
callOpts = callOpts || { } ;
var subCtx ,
node = ctx . node ,
tree = ctx . tree ,
opts = ctx . options ,
noEvents = ( callOpts . noEvents === true ) ,
noFocus = ( callOpts . noFocus === true ) ,
isActive = ( node === tree . activeNode ) ;
// flag defaults to true
flag = ( flag !== false ) ;
// node.debug("nodeSetActive", flag);
if ( isActive === flag ) {
// Nothing to do
return _getResolvedPromise ( node ) ;
} else if ( flag && ! noEvents && this . _triggerNodeEvent ( "beforeActivate" , node , ctx . originalEvent ) === false ) {
// Callback returned false
return _getRejectedPromise ( node , [ "rejected" ] ) ;
}
if ( flag ) {
if ( tree . activeNode ) {
_assert ( tree . activeNode !== node , "node was active (inconsistency)" ) ;
subCtx = $ . extend ( { } , ctx , { node : tree . activeNode } ) ;
tree . nodeSetActive ( subCtx , false ) ;
_assert ( tree . activeNode === null , "deactivate was out of sync?" ) ;
}
if ( opts . activeVisible ) {
// If no focus is set (noFocus: true) and there is no focused node, this node is made visible.
node . makeVisible ( { scrollIntoView : noFocus && tree . focusNode == null } ) ;
}
tree . activeNode = node ;
tree . nodeRenderStatus ( ctx ) ;
if ( ! noFocus ) {
tree . nodeSetFocus ( ctx ) ;
}
if ( ! noEvents ) {
tree . _triggerNodeEvent ( "activate" , node , ctx . originalEvent ) ;
}
} else {
_assert ( tree . activeNode === node , "node was not active (inconsistency)" ) ;
tree . activeNode = null ;
this . nodeRenderStatus ( ctx ) ;
if ( ! noEvents ) {
ctx . tree . _triggerNodeEvent ( "deactivate" , node , ctx . originalEvent ) ;
}
}
return _getResolvedPromise ( node ) ;
} ,
/ * * E x p a n d o r c o l l a p s e n o d e , r e t u r n D e f e r r e d . p r o m i s e .
*
* @ param { EventData } ctx
* @ param { boolean } [ flag = true ]
* @ param { object } [ opts ] additional options . Defaults to { noAnimation : false , noEvents : false }
* @ returns { $ . Promise } The deferred will be resolved as soon as the ( lazy )
* data was retrieved , rendered , and the expand animation finshed .
* /
nodeSetExpanded : function ( ctx , flag , callOpts ) {
callOpts = callOpts || { } ;
var _afterLoad , dfd , i , l , parents , prevAC ,
node = ctx . node ,
tree = ctx . tree ,
opts = ctx . options ,
noAnimation = ( callOpts . noAnimation === true ) ,
noEvents = ( callOpts . noEvents === true ) ;
// flag defaults to true
flag = ( flag !== false ) ;
// node.debug("nodeSetExpanded(" + flag + ")");
if ( ( node . expanded && flag ) || ( ! node . expanded && ! flag ) ) {
// Nothing to do
// node.debug("nodeSetExpanded(" + flag + "): nothing to do");
return _getResolvedPromise ( node ) ;
} else if ( flag && ! node . lazy && ! node . hasChildren ( ) ) {
// Prevent expanding of empty nodes
// return _getRejectedPromise(node, ["empty"]);
return _getResolvedPromise ( node ) ;
} else if ( ! flag && node . getLevel ( ) < opts . minExpandLevel ) {
// Prevent collapsing locked levels
return _getRejectedPromise ( node , [ "locked" ] ) ;
} else if ( ! noEvents && this . _triggerNodeEvent ( "beforeExpand" , node , ctx . originalEvent ) === false ) {
// Callback returned false
return _getRejectedPromise ( node , [ "rejected" ] ) ;
}
// If this node inside a collpased node, no animation and scrolling is needed
if ( ! noAnimation && ! node . isVisible ( ) ) {
noAnimation = callOpts . noAnimation = true ;
}
dfd = new $ . Deferred ( ) ;
// Auto-collapse mode: collapse all siblings
if ( flag && ! node . expanded && opts . autoCollapse ) {
parents = node . getParentList ( false , true ) ;
prevAC = opts . autoCollapse ;
try {
opts . autoCollapse = false ;
for ( i = 0 , l = parents . length ; i < l ; i ++ ) {
// TODO: should return promise?
this . _callHook ( "nodeCollapseSiblings" , parents [ i ] , callOpts ) ;
}
} finally {
opts . autoCollapse = prevAC ;
}
}
// Trigger expand/collapse after expanding
dfd . done ( function ( ) {
var lastChild = node . getLastChild ( ) ;
if ( flag && opts . autoScroll && ! noAnimation && lastChild ) {
// Scroll down to last child, but keep current node visible
lastChild . scrollIntoView ( true , { topNode : node } ) . always ( function ( ) {
if ( ! noEvents ) {
ctx . tree . _triggerNodeEvent ( flag ? "expand" : "collapse" , ctx ) ;
}
} ) ;
} else {
if ( ! noEvents ) {
ctx . tree . _triggerNodeEvent ( flag ? "expand" : "collapse" , ctx ) ;
}
}
} ) ;
// vvv Code below is executed after loading finished:
_afterLoad = function ( callback ) {
var cn = opts . _classNames ,
isVisible , isExpanded ,
effect = opts . toggleEffect ;
node . expanded = flag ;
// Create required markup, but make sure the top UL is hidden, so we
// can animate later
tree . _callHook ( "nodeRender" , ctx , false , false , true ) ;
// Hide children, if node is collapsed
if ( node . ul ) {
isVisible = ( node . ul . style . display !== "none" ) ;
isExpanded = ! ! node . expanded ;
if ( isVisible === isExpanded ) {
node . warn ( "nodeSetExpanded: UL.style.display already set" ) ;
} else if ( ! effect || noAnimation ) {
node . ul . style . display = ( node . expanded || ! parent ) ? "" : "none" ;
} else {
// The UI toggle() effect works with the ext-wide extension,
// while jQuery.animate() has problems when the title span
// has positon: absolute.
// Since jQuery UI 1.12, the blind effect requires the parent
// element to have 'position: relative'.
// See #716, #717
$ ( node . li ) . addClass ( cn . animating ) ; // #717
// node.info("fancytree-animating start: " + node.li.className);
$ ( node . ul )
. addClass ( cn . animating ) // # 716
. toggle ( effect . effect , effect . options , effect . duration , function ( ) {
// node.info("fancytree-animating end: " + node.li.className);
$ ( this ) . removeClass ( cn . animating ) ; // #716
$ ( node . li ) . removeClass ( cn . animating ) ; // #717
callback ( ) ;
} ) ;
return ;
}
}
callback ( ) ;
} ;
// ^^^ Code above is executed after loading finshed.
// Load lazy nodes, if any. Then continue with _afterLoad()
if ( flag && node . lazy && node . hasChildren ( ) === undefined ) {
// node.debug("nodeSetExpanded: load start...");
node . load ( ) . done ( function ( ) {
// node.debug("nodeSetExpanded: load done");
if ( dfd . notifyWith ) { // requires jQuery 1.6+
dfd . notifyWith ( node , [ "loaded" ] ) ;
}
_afterLoad ( function ( ) { dfd . resolveWith ( node ) ; } ) ;
} ) . fail ( function ( errMsg ) {
_afterLoad ( function ( ) { dfd . rejectWith ( node , [ "load failed (" + errMsg + ")" ] ) ; } ) ;
} ) ;
/ *
var source = tree . _triggerNodeEvent ( "lazyLoad" , node , ctx . originalEvent ) ;
_assert ( typeof source !== "boolean" , "lazyLoad event must return source in data.result" ) ;
node . debug ( "nodeSetExpanded: load start..." ) ;
this . _callHook ( "nodeLoadChildren" , ctx , source ) . done ( function ( ) {
node . debug ( "nodeSetExpanded: load done" ) ;
if ( dfd . notifyWith ) { // requires jQuery 1.6+
dfd . notifyWith ( node , [ "loaded" ] ) ;
}
_afterLoad . call ( tree ) ;
} ) . fail ( function ( errMsg ) {
dfd . rejectWith ( node , [ "load failed (" + errMsg + ")" ] ) ;
} ) ;
* /
} else {
_afterLoad ( function ( ) { dfd . resolveWith ( node ) ; } ) ;
}
// node.debug("nodeSetExpanded: returns");
return dfd . promise ( ) ;
} ,
/ * * F o c u s o r b l u r t h i s n o d e .
* @ param { EventData } ctx
* @ param { boolean } [ flag = true ]
* /
nodeSetFocus : function ( ctx , flag ) {
// ctx.node.debug("nodeSetFocus(" + flag + ")");
var ctx2 ,
tree = ctx . tree ,
node = ctx . node ,
opts = tree . options ,
// et = ctx.originalEvent && ctx.originalEvent.type,
isInput = ctx . originalEvent ? $ ( ctx . originalEvent . target ) . is ( ":input" ) : false ;
flag = ( flag !== false ) ;
// (node || tree).debug("nodeSetFocus(" + flag + "), event: " + et + ", isInput: "+ isInput);
// Blur previous node if any
if ( tree . focusNode ) {
if ( tree . focusNode === node && flag ) {
// node.debug("nodeSetFocus(" + flag + "): nothing to do");
return ;
}
ctx2 = $ . extend ( { } , ctx , { node : tree . focusNode } ) ;
tree . focusNode = null ;
this . _triggerNodeEvent ( "blur" , ctx2 ) ;
this . _callHook ( "nodeRenderStatus" , ctx2 ) ;
}
// Set focus to container and node
if ( flag ) {
if ( ! this . hasFocus ( ) ) {
node . debug ( "nodeSetFocus: forcing container focus" ) ;
this . _callHook ( "treeSetFocus" , ctx , true , { calledByNode : true } ) ;
}
node . makeVisible ( { scrollIntoView : false } ) ;
tree . focusNode = node ;
if ( opts . titlesTabbable ) {
if ( ! isInput ) { // #621
$ ( node . span ) . find ( ".fancytree-title" ) . focus ( ) ;
}
} else {
// We cannot set KB focus to a node, so use the tree container
// #563, #570: IE scrolls on every call to .focus(), if the container
// is partially outside the viewport. So do it only, when absolutely
// neccessary:
if ( $ ( document . activeElement ) . closest ( ".fancytree-container" ) . length === 0 ) {
$ ( tree . $container ) . focus ( ) ;
}
}
if ( opts . aria ) {
// Set active descendant to node's span ID (create one, if needed)
$ ( tree . $container ) . attr ( "aria-activedescendant" ,
$ ( node . tr || node . li ) . uniqueId ( ) . attr ( "id" ) ) ;
// "ftal_" + opts.idPrefix + node.key);
}
// $(node.span).find(".fancytree-title").focus();
this . _triggerNodeEvent ( "focus" , ctx ) ;
// if( opts.autoActivate ){
// tree.nodeSetActive(ctx, true);
// }
if ( opts . autoScroll ) {
node . scrollIntoView ( ) ;
}
this . _callHook ( "nodeRenderStatus" , ctx ) ;
}
} ,
/ * * ( D e ) S e l e c t n o d e , r e t u r n n e w s t a t u s ( s y n c ) .
*
* @ param { EventData } ctx
* @ param { boolean } [ flag = true ]
* @ param { object } [ opts ] additional options . Defaults to { noEvents : false ,
* propagateDown : null , propagateUp : null ,
* callback : null ,
* }
* @ returns { boolean } previous status
* /
nodeSetSelected : function ( ctx , flag , callOpts ) {
callOpts = callOpts || { } ;
var node = ctx . node ,
tree = ctx . tree ,
opts = ctx . options ,
noEvents = ( callOpts . noEvents === true ) ,
parent = node . parent ;
// flag defaults to true
flag = ( flag !== false ) ;
// node.debug("nodeSetSelected(" + flag + ")", ctx);
// Cannot (de)select unselectable nodes directly (only by propagation or
// by setting the `.selected` property)
if ( FT . evalOption ( "unselectable" , node , node , opts , false ) ) {
return ;
}
// Remember the user's intent, in case down -> up propagation prevents
// applying it to node.selected
node . _lastSelectIntent = flag ;
// Nothing to do?
/*jshint -W018 */ // Confusing use of '!'
if ( ! ! node . selected === flag ) {
if ( opts . selectMode === 3 && node . partsel && ! flag ) {
// If propagation prevented selecting this node last time, we still
// want to allow to apply setSelected(false) now
} else {
return flag ;
}
}
/*jshint +W018 */
if ( ! noEvents &&
this . _triggerNodeEvent ( "beforeSelect" , node , ctx . originalEvent ) === false ) {
return ! ! node . selected ;
}
if ( flag && opts . selectMode === 1 ) {
// single selection mode (we don't uncheck all tree nodes, for performance reasons)
if ( tree . lastSelectedNode ) {
tree . lastSelectedNode . setSelected ( false ) ;
}
node . selected = flag ;
} else if ( opts . selectMode === 3 && parent && ! parent . radiogroup && ! node . radiogroup ) {
// multi-hierarchical selection mode
node . selected = flag ;
node . fixSelection3AfterClick ( callOpts ) ;
} else if ( parent && parent . radiogroup ) {
node . visitSiblings ( function ( n ) {
n . _changeSelectStatusAttrs ( flag && n === node ) ;
} , true ) ;
} else {
// default: selectMode: 2, multi selection mode
node . selected = flag ;
}
this . nodeRenderStatus ( ctx ) ;
tree . lastSelectedNode = flag ? node : null ;
if ( ! noEvents ) {
tree . _triggerNodeEvent ( "select" , ctx ) ;
}
} ,
/ * * S h o w n o d e s t a t u s ( o k , l o a d i n g , e r r o r , n o d a t a ) u s i n g s t y l e s a n d a d u m m y c h i l d n o d e .
*
* @ param { EventData } ctx
* @ param status
* @ param message
* @ param details
* @ since 2.3
* /
nodeSetStatus : function ( ctx , status , message , details ) {
var node = ctx . node ,
tree = ctx . tree ;
function _clearStatusNode ( ) {
// Remove dedicated dummy node, if any
var firstChild = ( node . children ? node . children [ 0 ] : null ) ;
if ( firstChild && firstChild . isStatusNode ( ) ) {
try {
// I've seen exceptions here with loadKeyPath...
if ( node . ul ) {
node . ul . removeChild ( firstChild . li ) ;
firstChild . li = null ; // avoid leaks (DT issue 215)
}
} catch ( e ) { }
if ( node . children . length === 1 ) {
node . children = [ ] ;
} else {
node . children . shift ( ) ;
}
}
}
function _setStatusNode ( data , type ) {
// Create/modify the dedicated dummy node for 'loading...' or
// 'error!' status. (only called for direct child of the invisible
// system root)
var firstChild = ( node . children ? node . children [ 0 ] : null ) ;
if ( firstChild && firstChild . isStatusNode ( ) ) {
$ . extend ( firstChild , data ) ;
firstChild . statusNodeType = type ;
tree . _callHook ( "nodeRenderTitle" , firstChild ) ;
} else {
node . _setChildren ( [ data ] ) ;
node . children [ 0 ] . statusNodeType = type ;
tree . render ( ) ;
}
return node . children [ 0 ] ;
}
switch ( status ) {
case "ok" :
_clearStatusNode ( ) ;
node . _isLoading = false ;
node . _error = null ;
node . renderStatus ( ) ;
break ;
case "loading" :
if ( ! node . parent ) {
_setStatusNode ( {
title : tree . options . strings . loading + ( message ? " (" + message + ")" : "" ) ,
// icon: true, // needed for 'loding' icon
checkbox : false ,
tooltip : details
} , status ) ;
}
node . _isLoading = true ;
node . _error = null ;
node . renderStatus ( ) ;
break ;
case "error" :
_setStatusNode ( {
title : tree . options . strings . loadError + ( message ? " (" + message + ")" : "" ) ,
// icon: false,
checkbox : false ,
tooltip : details
} , status ) ;
node . _isLoading = false ;
node . _error = { message : message , details : details } ;
node . renderStatus ( ) ;
break ;
case "nodata" :
_setStatusNode ( {
title : tree . options . strings . noData ,
// icon: false,
checkbox : false ,
tooltip : details
} , status ) ;
node . _isLoading = false ;
node . _error = null ;
node . renderStatus ( ) ;
break ;
default :
$ . error ( "invalid node status " + status ) ;
}
} ,
/ * *
*
* @ param { EventData } ctx
* /
nodeToggleExpanded : function ( ctx ) {
return this . nodeSetExpanded ( ctx , ! ctx . node . expanded ) ;
} ,
/ * *
* @ param { EventData } ctx
* /
nodeToggleSelected : function ( ctx ) {
var node = ctx . node ,
flag = ! node . selected ;
// In selectMode: 3 this node may be unselected+partsel, even if
// setSelected(true) was called before, due to `unselectable` children.
// In this case, we now toggle as `setSelected(false)`
if ( node . partsel && ! node . selected && node . _lastSelectIntent === true ) {
flag = false ;
node . selected = true ; // so it is not considered 'nothing to do'
}
node . _lastSelectIntent = flag ;
return this . nodeSetSelected ( ctx , flag ) ;
} ,
/ * * R e m o v e a l l n o d e s .
* @ param { EventData } ctx
* /
treeClear : function ( ctx ) {
var tree = ctx . tree ;
tree . activeNode = null ;
tree . focusNode = null ;
tree . $div . find ( ">ul.fancytree-container" ) . empty ( ) ;
// TODO: call destructors and remove reference loops
tree . rootNode . children = null ;
} ,
/ * * W i d g e t w a s c r e a t e d ( c a l l e d o n l y o n c e , e v e n i t r e - i n i t i a l i z e d ) .
* @ param { EventData } ctx
* /
treeCreate : function ( ctx ) {
} ,
/ * * W i d g e t w a s d e s t r o y e d .
* @ param { EventData } ctx
* /
treeDestroy : function ( ctx ) {
this . $div . find ( ">ul.fancytree-container" ) . remove ( ) ;
2018-05-24 18:59:32 +00:00
this . $source && this . $source . removeClass ( "fancytree-helper-hidden" ) ;
2018-01-01 14:39:23 +00:00
} ,
/ * * W i d g e t w a s ( r e - ) i n i t i a l i z e d .
* @ param { EventData } ctx
* /
treeInit : function ( ctx ) {
var tree = ctx . tree ,
opts = tree . options ;
//this.debug("Fancytree.treeInit()");
// Add container to the TAB chain
// See http://www.w3.org/TR/wai-aria-practices/#focus_activedescendant
// #577: Allow to set tabindex to "0", "-1" and ""
tree . $container . attr ( "tabindex" , opts . tabindex ) ;
2018-05-24 18:59:32 +00:00
// Copy some attributes to tree.data
$ . each ( TREE _ATTRS , function ( i , attr ) {
if ( opts [ attr ] !== undefined ) {
tree . info ( "Move option " + attr + " to tree" ) ;
tree [ attr ] = opts [ attr ] ;
delete opts [ attr ] ;
}
} ) ;
2018-01-01 14:39:23 +00:00
if ( opts . rtl ) {
tree . $container . attr ( "DIR" , "RTL" ) . addClass ( "fancytree-rtl" ) ;
} else {
tree . $container . removeAttr ( "DIR" ) . removeClass ( "fancytree-rtl" ) ;
}
if ( opts . aria ) {
tree . $container . attr ( "role" , "tree" ) ;
if ( opts . selectMode !== 1 ) {
tree . $container . attr ( "aria-multiselectable" , true ) ;
}
}
this . treeLoad ( ctx ) ;
} ,
/ * * P a r s e F a n c y t r e e f r o m s o u r c e , a s c o n f i g u r e d i n t h e o p t i o n s .
* @ param { EventData } ctx
* @ param { object } [ source ] optional new source ( use last data otherwise )
* /
treeLoad : function ( ctx , source ) {
var metaData , type , $ul ,
tree = ctx . tree ,
$container = ctx . widget . element ,
dfd ,
// calling context for root node
rootCtx = $ . extend ( { } , ctx , { node : this . rootNode } ) ;
if ( tree . rootNode . children ) {
this . treeClear ( ctx ) ;
}
source = source || this . options . source ;
if ( ! source ) {
type = $container . data ( "type" ) || "html" ;
switch ( type ) {
case "html" :
$ul = $container . find ( ">ul:first" ) ;
2018-05-24 18:59:32 +00:00
$ul . addClass ( "ui-fancytree-source fancytree-helper-hidden" ) ;
2018-01-01 14:39:23 +00:00
source = $ . ui . fancytree . parseHtml ( $ul ) ;
// allow to init tree.data.foo from <ul data-foo=''>
this . data = $ . extend ( this . data , _getElementDataAsDict ( $ul ) ) ;
break ;
case "json" :
source = $ . parseJSON ( $container . text ( ) ) ;
// $container already contains the <ul>, but we remove the plain (json) text
// $container.empty();
$container . contents ( ) . filter ( function ( ) {
return ( this . nodeType === 3 ) ;
} ) . remove ( ) ;
if ( $ . isPlainObject ( source ) ) {
// We got {foo: 'abc', children: [...]}
_assert ( $ . isArray ( source . children ) , "if an object is passed as source, it must contain a 'children' array (all other properties are added to 'tree.data')" ) ;
metaData = source ;
source = source . children ;
delete metaData . children ;
2018-05-24 18:59:32 +00:00
// Copy some attributes to tree.data
$ . each ( TREE _ATTRS , function ( i , attr ) {
if ( metaData [ attr ] !== undefined ) {
tree [ attr ] = metaData [ attr ] ;
delete metaData [ attr ] ;
}
} ) ;
// Copy extra properties to tree.data.foo
2018-01-01 14:39:23 +00:00
$ . extend ( tree . data , metaData ) ;
}
break ;
default :
$ . error ( "Invalid data-type: " + type ) ;
}
} else if ( typeof source === "string" ) {
// TODO: source is an element ID
$ . error ( "Not implemented" ) ;
}
// Trigger fancytreeinit after nodes have been loaded
dfd = this . nodeLoadChildren ( rootCtx , source ) . done ( function ( ) {
tree . render ( ) ;
if ( ctx . options . selectMode === 3 ) {
tree . rootNode . fixSelection3FromEndNodes ( ) ;
}
if ( tree . activeNode && tree . options . activeVisible ) {
tree . activeNode . makeVisible ( ) ;
}
tree . _triggerTreeEvent ( "init" , null , { status : true } ) ;
} ) . fail ( function ( ) {
tree . render ( ) ;
tree . _triggerTreeEvent ( "init" , null , { status : false } ) ;
} ) ;
return dfd ;
} ,
/ * * N o d e w a s i n s e r t e d i n t o o r r e m o v e d f r o m t h e t r e e .
* @ param { EventData } ctx
* @ param { boolean } add
* @ param { FancytreeNode } node
* /
treeRegisterNode : function ( ctx , add , node ) {
} ,
/ * * W i d g e t g o t f o c u s .
* @ param { EventData } ctx
* @ param { boolean } [ flag = true ]
* /
treeSetFocus : function ( ctx , flag , callOpts ) {
var targetNode ;
flag = ( flag !== false ) ;
// this.debug("treeSetFocus(" + flag + "), callOpts: ", callOpts, this.hasFocus());
// this.debug(" focusNode: " + this.focusNode);
// this.debug(" activeNode: " + this.activeNode);
if ( flag !== this . hasFocus ( ) ) {
this . _hasFocus = flag ;
if ( ! flag && this . focusNode ) {
// Node also looses focus if widget blurs
this . focusNode . setFocus ( false ) ;
} else if ( flag && ( ! callOpts || ! callOpts . calledByNode ) ) {
$ ( this . $container ) . focus ( ) ;
}
this . $container . toggleClass ( "fancytree-treefocus" , flag ) ;
this . _triggerTreeEvent ( flag ? "focusTree" : "blurTree" ) ;
if ( flag && ! this . activeNode ) {
// #712: Use last mousedowned node ('click' event fires after focusin)
targetNode = this . _lastMousedownNode || this . getFirstChild ( ) ;
targetNode && targetNode . setFocus ( ) ;
}
}
} ,
/ * * W i d g e t o p t i o n w a s s e t u s i n g ` $ ( ) . f a n c y t r e e ( " o p t i o n " , " f o o " , " b a r " ) ` .
* @ param { EventData } ctx
* @ param { string } key option name
* @ param { any } value option value
* /
treeSetOption : function ( ctx , key , value ) {
var tree = ctx . tree ,
callDefault = true ,
callCreate = false ,
callRender = false ;
switch ( key ) {
case "aria" :
case "checkbox" :
case "icon" :
case "minExpandLevel" :
case "tabindex" :
// tree._callHook("treeCreate", tree);
callCreate = true ;
callRender = true ;
break ;
case "escapeTitles" :
case "tooltip" :
callRender = true ;
break ;
case "rtl" :
if ( value === false ) {
tree . $container . removeAttr ( "DIR" ) . removeClass ( "fancytree-rtl" ) ;
} else {
tree . $container . attr ( "DIR" , "RTL" ) . addClass ( "fancytree-rtl" ) ;
}
callRender = true ;
break ;
case "source" :
callDefault = false ;
tree . _callHook ( "treeLoad" , tree , value ) ;
callRender = true ;
break ;
}
tree . debug ( "set option " + key + "=" + value + " <" + typeof ( value ) + ">" ) ;
if ( callDefault ) {
if ( this . widget . _super ) {
// jQuery UI 1.9+
this . widget . _super . call ( this . widget , key , value ) ;
} else {
// jQuery UI <= 1.8, we have to manually invoke the _setOption method from the base widget
$ . Widget . prototype . _setOption . call ( this . widget , key , value ) ;
}
}
if ( callCreate ) {
tree . _callHook ( "treeCreate" , tree ) ;
}
if ( callRender ) {
tree . render ( true , false ) ; // force, not-deep
}
}
} ) ;
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* jQuery UI widget boilerplate
* /
/ * *
* The plugin ( derrived from < a href = " http://api.jqueryui.com/jQuery.widget/" > jQuery . Widget < / a > ) . < b r >
* This constructor is not called directly . Use ` $ (selector).fancytree({}) `
* to initialize the plugin instead . < br >
* < pre class = "sh_javascript sunlight-highlight-javascript" > // Access widget methods and members:
* var tree = $ ( "#tree" ) . fancytree ( "getTree" ) ;
* var node = $ ( "#tree" ) . fancytree ( "getActiveNode" , "1234" ) ;
* < / p r e >
*
* @ mixin Fancytree _Widget
* /
$ . widget ( "ui.fancytree" ,
/** @lends Fancytree_Widget# */
{
/ * * T h e s e o p t i o n s w i l l b e u s e d a s d e f a u l t s
* @ type { FancytreeOptions }
* /
options :
{
activeVisible : true ,
ajax : {
type : "GET" ,
cache : false , // false: Append random '_' argument to the request url to prevent caching.
// timeout: 0, // >0: Make sure we get an ajax error if server is unreachable
dataType : "json" // Expect json format and pass json object to callbacks.
} , //
aria : true ,
autoActivate : true ,
autoCollapse : false ,
autoScroll : false ,
checkbox : false ,
clickFolderMode : 4 ,
2018-05-24 18:59:32 +00:00
debugLevel : null , // 0..4 (null: use global setting $.ui.fancytree.debugInfo)
2018-01-01 14:39:23 +00:00
disabled : false , // TODO: required anymore?
enableAspx : true ,
escapeTitles : false ,
extensions : [ ] ,
// fx: { height: "toggle", duration: 200 },
// toggleEffect: { effect: "drop", options: {direction: "left"}, duration: 200 },
// toggleEffect: { effect: "slide", options: {direction: "up"}, duration: 200 },
toggleEffect : { effect : "blind" , options : { direction : "vertical" , scale : "box" } , duration : 200 } ,
generateIds : false ,
icon : true ,
idPrefix : "ft_" ,
focusOnSelect : false ,
keyboard : true ,
keyPathSeparator : "/" ,
minExpandLevel : 1 ,
quicksearch : false ,
rtl : false ,
scrollOfs : { top : 0 , bottom : 0 } ,
scrollParent : null ,
selectMode : 2 ,
strings : {
loading : "Loading..." , // … would be escaped when escapeTitles is true
loadError : "Load error!" ,
moreData : "More..." ,
noData : "No data."
} ,
tabindex : "0" ,
titlesTabbable : false ,
tooltip : false ,
_classNames : {
node : "fancytree-node" ,
folder : "fancytree-folder" ,
animating : "fancytree-animating" ,
combinedExpanderPrefix : "fancytree-exp-" ,
combinedIconPrefix : "fancytree-ico-" ,
hasChildren : "fancytree-has-children" ,
active : "fancytree-active" ,
selected : "fancytree-selected" ,
expanded : "fancytree-expanded" ,
lazy : "fancytree-lazy" ,
focused : "fancytree-focused" ,
partload : "fancytree-partload" ,
partsel : "fancytree-partsel" ,
radio : "fancytree-radio" ,
// radiogroup: "fancytree-radiogroup",
unselectable : "fancytree-unselectable" ,
lastsib : "fancytree-lastsib" ,
loading : "fancytree-loading" ,
error : "fancytree-error" ,
statusNodePrefix : "fancytree-statusnode-"
} ,
// events
lazyLoad : null ,
postProcess : null
} ,
/* Set up the widget, Called on first $().fancytree() */
_create : function ( ) {
this . tree = new Fancytree ( this ) ;
this . $source = this . source || this . element . data ( "type" ) === "json" ? this . element
: this . element . find ( ">ul:first" ) ;
// Subclass Fancytree instance with all enabled extensions
var extension , extName , i ,
opts = this . options ,
extensions = opts . extensions ,
base = this . tree ;
for ( i = 0 ; i < extensions . length ; i ++ ) {
extName = extensions [ i ] ;
extension = $ . ui . fancytree . _extensions [ extName ] ;
if ( ! extension ) {
$ . error ( "Could not apply extension '" + extName + "' (it is not registered, did you forget to include it?)" ) ;
}
// Add extension options as tree.options.EXTENSION
// _assert(!this.tree.options[extName], "Extension name must not exist as option name: " + extName);
this . tree . options [ extName ] = $ . extend ( true , { } , extension . options , this . tree . options [ extName ] ) ;
// Add a namespace tree.ext.EXTENSION, to hold instance data
_assert ( this . tree . ext [ extName ] === undefined , "Extension name must not exist as Fancytree.ext attribute: '" + extName + "'" ) ;
// this.tree[extName] = extension;
this . tree . ext [ extName ] = { } ;
// Subclass Fancytree methods using proxies.
_subclassObject ( this . tree , base , extension , extName ) ;
// current extension becomes base for the next extension
base = extension ;
}
//
if ( opts . icons !== undefined ) { // 2015-11-16
if ( opts . icon !== true ) {
$ . error ( "'icons' tree option is deprecated since v2.14.0: use 'icon' only instead" ) ;
} else {
this . tree . warn ( "'icons' tree option is deprecated since v2.14.0: use 'icon' instead" ) ;
opts . icon = opts . icons ;
}
}
if ( opts . iconClass !== undefined ) { // 2015-11-16
if ( opts . icon ) {
$ . error ( "'iconClass' tree option is deprecated since v2.14.0: use 'icon' only instead" ) ;
} else {
this . tree . warn ( "'iconClass' tree option is deprecated since v2.14.0: use 'icon' instead" ) ;
opts . icon = opts . iconClass ;
}
}
if ( opts . tabbable !== undefined ) { // 2016-04-04
opts . tabindex = opts . tabbable ? "0" : "-1" ;
this . tree . warn ( "'tabbable' tree option is deprecated since v2.17.0: use 'tabindex='" + opts . tabindex + "' instead" ) ;
}
//
this . tree . _callHook ( "treeCreate" , this . tree ) ;
// Note: 'fancytreecreate' event is fired by widget base class
// this.tree._triggerTreeEvent("create");
} ,
/* Called on every $().fancytree() */
_init : function ( ) {
this . tree . _callHook ( "treeInit" , this . tree ) ;
// TODO: currently we call bind after treeInit, because treeInit
// might change tree.$container.
2018-05-24 18:59:32 +00:00
// It would be better, to move event binding into hooks altogether
2018-01-01 14:39:23 +00:00
this . _bind ( ) ;
} ,
/* Use the _setOption method to respond to changes to options */
_setOption : function ( key , value ) {
return this . tree . _callHook ( "treeSetOption" , this . tree , key , value ) ;
} ,
/** Use the destroy method to clean up any modifications your widget has made to the DOM */
destroy : function ( ) {
this . _unbind ( ) ;
this . tree . _callHook ( "treeDestroy" , this . tree ) ;
// In jQuery UI 1.8, you must invoke the destroy method from the base widget
$ . Widget . prototype . destroy . call ( this ) ;
// TODO: delete tree and nodes to make garbage collect easier?
// TODO: In jQuery UI 1.9 and above, you would define _destroy instead of destroy and not call the base method
} ,
// -------------------------------------------------------------------------
/* Remove all event handlers for our namespace */
_unbind : function ( ) {
var ns = this . tree . _ns ;
this . element . off ( ns ) ;
this . tree . $container . off ( ns ) ;
$ ( document ) . off ( ns ) ;
} ,
/* Add mouse and kyboard handlers to the container */
_bind : function ( ) {
var that = this ,
opts = this . options ,
tree = this . tree ,
ns = tree . _ns
// selstartEvent = ( $.support.selectstart ? "selectstart" : "mousedown" )
;
// Remove all previuous handlers for this tree
this . _unbind ( ) ;
//alert("keydown" + ns + "foc=" + tree.hasFocus() + tree.$container);
// tree.debug("bind events; container: ", tree.$container);
tree . $container . on ( "focusin" + ns + " focusout" + ns , function ( event ) {
var node = FT . getNode ( event ) ,
flag = ( event . type === "focusin" ) ;
2018-05-24 18:59:32 +00:00
if ( ! flag && node && $ ( event . target ) . is ( "a" ) ) {
// #764
node . debug ( "Ignored focusout on embedded <a> element." ) ;
2018-01-01 14:39:23 +00:00
return ;
}
2018-05-24 18:59:32 +00:00
// tree.treeOnFocusInOut.call(tree, event);
// tree.debug("Tree container got event " + event.type, node, event, FT.getEventTarget(event));
if ( flag ) {
if ( tree . _getExpiringValue ( "focusin" ) ) {
// #789: IE 11 may send duplicate focusin events
FT . info ( "Ignored double focusin." ) ;
return ;
}
tree . _setExpiringValue ( "focusin" , true , 50 ) ;
2018-01-01 14:39:23 +00:00
2018-05-24 18:59:32 +00:00
if ( ! node ) {
// #789: IE 11 may send focusin before mousdown(?)
node = tree . _getExpiringValue ( "mouseDownNode" ) ;
if ( node ) { FT . info ( "Reconstruct mouse target for focusin from recent event." ) ; }
}
2018-01-01 14:39:23 +00:00
}
if ( node ) {
// For example clicking into an <input> that is part of a node
tree . _callHook ( "nodeSetFocus" , tree . _makeHookContext ( node , event ) , flag ) ;
} else {
if ( tree . tbody && $ ( event . target ) . parents ( "table.fancytree-container > thead" ) . length ) {
// #767: ignore events in the table's header
tree . debug ( "Ignore focus event outside table body." , event ) ;
} else {
tree . _callHook ( "treeSetFocus" , tree , flag ) ;
}
}
} ) . on ( "selectstart" + ns , "span.fancytree-title" , function ( event ) {
// prevent mouse-drags to select text ranges
// tree.debug("<span title> got event " + event.type);
event . preventDefault ( ) ;
} ) . on ( "keydown" + ns , function ( event ) {
// TODO: also bind keyup and keypress
// tree.debug("got event " + event.type + ", hasFocus:" + tree.hasFocus());
// if(opts.disabled || opts.keyboard === false || !tree.hasFocus() ){
if ( opts . disabled || opts . keyboard === false ) {
return true ;
}
var res ,
node = tree . focusNode , // node may be null
ctx = tree . _makeHookContext ( node || tree , event ) ,
prevPhase = tree . phase ;
try {
tree . phase = "userEvent" ;
// If a 'fancytreekeydown' handler returns false, skip the default
// handling (implemented by tree.nodeKeydown()).
if ( node ) {
res = tree . _triggerNodeEvent ( "keydown" , node , event ) ;
} else {
res = tree . _triggerTreeEvent ( "keydown" , event ) ;
}
if ( res === "preventNav" ) {
res = true ; // prevent keyboard navigation, but don't prevent default handling of embedded input controls
} else if ( res !== false ) {
res = tree . _callHook ( "nodeKeydown" , ctx ) ;
}
return res ;
} finally {
tree . phase = prevPhase ;
}
} ) . on ( "mousedown" + ns , function ( event ) {
var et = FT . getEventTarget ( event ) ;
// that.tree.debug("event(" + event.type + "): node: ", et.node);
// #712: Store the clicked node, so we can use it when we get a focusin event
// ('click' event fires after focusin)
// tree.debug("event(" + event.type + "): node: ", et.node);
tree . _lastMousedownNode = et ? et . node : null ;
// #789: Store the node also for a short period, so we can use it
// in a *resulting* focusin event
tree . _setExpiringValue ( "mouseDownNode" , tree . _lastMousedownNode ) ;
} ) . on ( "click" + ns + " dblclick" + ns , function ( event ) {
if ( opts . disabled ) {
return true ;
}
var ctx ,
et = FT . getEventTarget ( event ) ,
node = et . node ,
tree = that . tree ,
prevPhase = tree . phase ;
// that.tree.debug("event(" + event.type + "): node: ", node);
if ( ! node ) {
return true ; // Allow bubbling of other events
}
ctx = tree . _makeHookContext ( node , event ) ;
// that.tree.debug("event(" + event.type + "): node: ", node);
try {
tree . phase = "userEvent" ;
switch ( event . type ) {
case "click" :
ctx . targetType = et . type ;
if ( node . isPagingNode ( ) ) {
return tree . _triggerNodeEvent ( "clickPaging" , ctx , event ) === true ;
}
return ( tree . _triggerNodeEvent ( "click" , ctx , event ) === false ) ? false : tree . _callHook ( "nodeClick" , ctx ) ;
case "dblclick" :
ctx . targetType = et . type ;
return ( tree . _triggerNodeEvent ( "dblclick" , ctx , event ) === false ) ? false : tree . _callHook ( "nodeDblclick" , ctx ) ;
}
} finally {
tree . phase = prevPhase ;
}
} ) ;
} ,
/ * * R e t u r n t h e a c t i v e n o d e o r n u l l .
* @ returns { FancytreeNode }
* /
getActiveNode : function ( ) {
return this . tree . activeNode ;
} ,
/ * * R e t u r n t h e m a t c h i n g n o d e o r n u l l .
* @ param { string } key
* @ returns { FancytreeNode }
* /
getNodeByKey : function ( key ) {
return this . tree . getNodeByKey ( key ) ;
} ,
/ * * R e t u r n t h e i n v i s i b l e s y s t e m r o o t n o d e .
* @ returns { FancytreeNode }
* /
getRootNode : function ( ) {
return this . tree . rootNode ;
} ,
/ * * R e t u r n t h e c u r r e n t t r e e i n s t a n c e .
* @ returns { Fancytree }
* /
getTree : function ( ) {
return this . tree ;
}
} ) ;
// $.ui.fancytree was created by the widget factory. Create a local shortcut:
FT = $ . ui . fancytree ;
/ * *
* Static members in the ` $ .ui.fancytree ` namespace . < br >
* < br >
* < pre class = "sh_javascript sunlight-highlight-javascript" > // Access static members:
* var node = $ . ui . fancytree . getNode ( element ) ;
* alert ( $ . ui . fancytree . version ) ;
* < / p r e >
*
* @ mixin Fancytree _Static
* /
$ . extend ( $ . ui . fancytree ,
/** @lends Fancytree_Static# */
{
/** @type {string} */
2018-05-24 18:59:32 +00:00
version : "2.28.1" , // Set to semver by 'grunt release'
2018-01-01 14:39:23 +00:00
/** @type {string} */
buildType : "production" , // Set to 'production' by 'grunt build'
/** @type {int} */
2018-05-24 18:59:32 +00:00
debugLevel : 3 , // Set to 3 by 'grunt build'
2018-01-01 14:39:23 +00:00
// Used by $.ui.fancytree.debug() and as default for tree.options.debugLevel
_nextId : 1 ,
_nextNodeKey : 1 ,
_extensions : { } ,
// focusTree: null,
/** Expose class object as $.ui.fancytree._FancytreeClass */
_FancytreeClass : Fancytree ,
/** Expose class object as $.ui.fancytree._FancytreeNodeClass */
_FancytreeNodeClass : FancytreeNode ,
/* Feature checks to provide backwards compatibility */
jquerySupports : {
// http://jqueryui.com/upgrade-guide/1.9/#deprecated-offset-option-merged-into-my-and-at
positionMyOfs : isVersionAtLeast ( $ . ui . version , 1 , 9 )
} ,
/ * * T h r o w a n e r r o r i f c o n d i t i o n f a i l s ( d e b u g m e t h o d ) .
* @ param { boolean } cond
* @ param { string } msg
* /
assert : function ( cond , msg ) {
return _assert ( cond , msg ) ;
} ,
/ * * C r e a t e a n e w F a n c y t r e e i n s t a n c e o n a t a r g e t e l e m e n t .
*
* @ param { Element | jQueryObject | string } el Target DOM element or selector
* @ param { FancytreeOptions } [ opts ] Fancytree options
* @ returns { Fancytree } new tree instance
* @ example
* var tree = $ . ui . fancytree . createTree ( "#tree" , {
* source : { url : "my/webservice" }
* } ) ; // Create tree for this matching element
*
* @ since 2.25
* /
createTree : function ( el , opts ) {
var tree = $ ( el ) . fancytree ( opts ) . fancytree ( "getTree" ) ;
return tree ;
} ,
/ * * R e t u r n a f u n c t i o n t h a t e x e c u t e s * f n * a t m o s t e v e r y * t i m e o u t * m s .
* @ param { integer } timeout
* @ param { function } fn
* @ param { boolean } [ invokeAsap = false ]
* @ param { any } [ ctx ]
* /
debounce : function ( timeout , fn , invokeAsap , ctx ) {
var timer ;
if ( arguments . length === 3 && typeof invokeAsap !== "boolean" ) {
ctx = invokeAsap ;
invokeAsap = false ;
}
return function ( ) {
var args = arguments ;
ctx = ctx || this ;
invokeAsap && ! timer && fn . apply ( ctx , args ) ;
clearTimeout ( timer ) ;
timer = setTimeout ( function ( ) {
invokeAsap || fn . apply ( ctx , args ) ;
timer = null ;
} , timeout ) ;
} ;
} ,
2018-05-24 18:59:32 +00:00
/ * * W r i t e m e s s a g e t o c o n s o l e i f d e b u g L e v e l > = 4
2018-01-01 14:39:23 +00:00
* @ param { string } msg
* /
debug : function ( msg ) {
/*jshint expr:true */
2018-05-24 18:59:32 +00:00
( $ . ui . fancytree . debugLevel >= 4 ) && consoleApply ( "log" , arguments ) ;
2018-01-01 14:39:23 +00:00
} ,
2018-05-24 18:59:32 +00:00
/ * * W r i t e e r r o r m e s s a g e t o c o n s o l e i f d e b u g L e v e l > = 1 .
2018-01-01 14:39:23 +00:00
* @ param { string } msg
* /
error : function ( msg ) {
2018-05-24 18:59:32 +00:00
( $ . ui . fancytree . debugLevel >= 1 ) && consoleApply ( "error" , arguments ) ;
2018-01-01 14:39:23 +00:00
} ,
/ * * C o n v e r t & l t ; , & g t ; , & a m p ; , & q u o t ; , & # 3 9 ; , & # x 2 F ; t o t h e e q u i v a l e n t e n t i t i e s .
*
* @ param { string } s
* @ returns { string }
* /
escapeHtml : function ( s ) {
return ( "" + s ) . replace ( REX _HTML , function ( s ) {
return ENTITY _MAP [ s ] ;
} ) ;
} ,
/ * * M a k e j Q u e r y . p o s i t i o n ( ) a r g u m e n t s b a c k w a r d s c o m p a t i b l e , i . e . i f
* jQuery UI version <= 1.8 , convert
* { my : "left+3 center" , at : "left bottom" , of : $target }
* to
* { my : "left center" , at : "left bottom" , of : $target , offset : "3 0" }
*
* See http : //jqueryui.com/upgrade-guide/1.9/#deprecated-offset-option-merged-into-my-and-at
* and http : //jsfiddle.net/mar10/6xtu9a4e/
2018-05-24 18:59:32 +00:00
*
* @ param { object } opts
* @ returns { object } the ( potentially modified ) original opts hash object
2018-01-01 14:39:23 +00:00
* /
fixPositionOptions : function ( opts ) {
if ( opts . offset || ( "" + opts . my + opts . at ) . indexOf ( "%" ) >= 0 ) {
$ . error ( "expected new position syntax (but '%' is not supported)" ) ;
}
if ( ! $ . ui . fancytree . jquerySupports . positionMyOfs ) {
var // parse 'left+3 center' into ['left+3 center', 'left', '+3', 'center', undefined]
myParts = /(\w+)([+-]?\d+)?\s+(\w+)([+-]?\d+)?/ . exec ( opts . my ) ,
atParts = /(\w+)([+-]?\d+)?\s+(\w+)([+-]?\d+)?/ . exec ( opts . at ) ,
// convert to numbers
dx = ( myParts [ 2 ] ? ( + myParts [ 2 ] ) : 0 ) + ( atParts [ 2 ] ? ( + atParts [ 2 ] ) : 0 ) ,
dy = ( myParts [ 4 ] ? ( + myParts [ 4 ] ) : 0 ) + ( atParts [ 4 ] ? ( + atParts [ 4 ] ) : 0 ) ;
opts = $ . extend ( { } , opts , { // make a copy and overwrite
my : myParts [ 1 ] + " " + myParts [ 3 ] ,
at : atParts [ 1 ] + " " + atParts [ 3 ]
} ) ;
if ( dx || dy ) {
opts . offset = "" + dx + " " + dy ;
}
}
return opts ;
} ,
/ * * R e t u r n a { n o d e : F a n c y t r e e N o d e , t y p e : T Y P E } o b j e c t f o r a m o u s e e v e n t .
*
* @ param { Event } event Mouse event , e . g . click , ...
* @ returns { object } Return a { node : FancytreeNode , type : TYPE } object
* TYPE : 'title' | 'prefix' | 'expander' | 'checkbox' | 'icon' | undefined
* /
getEventTarget : function ( event ) {
2018-05-24 18:59:32 +00:00
var $target ,
tcn = event && event . target ? event . target . className : "" ,
2018-01-01 14:39:23 +00:00
res = { node : this . getNode ( event . target ) , type : undefined } ;
// We use a fast version of $(res.node).hasClass()
// See http://jsperf.com/test-for-classname/2
if ( /\bfancytree-title\b/ . test ( tcn ) ) {
res . type = "title" ;
} else if ( /\bfancytree-expander\b/ . test ( tcn ) ) {
res . type = ( res . node . hasChildren ( ) === false ? "prefix" : "expander" ) ;
// }else if( /\bfancytree-checkbox\b/.test(tcn) || /\bfancytree-radio\b/.test(tcn) ){
} else if ( /\bfancytree-checkbox\b/ . test ( tcn ) ) {
res . type = "checkbox" ;
} else if ( /\bfancytree(-custom)?-icon\b/ . test ( tcn ) ) {
res . type = "icon" ;
} else if ( /\bfancytree-node\b/ . test ( tcn ) ) {
// Somewhere near the title
res . type = "title" ;
2018-05-24 18:59:32 +00:00
} else if ( event && event . target ) {
$target = $ ( event . target ) ;
if ( $target . is ( "ul[role=group]" ) ) {
// #nnn: Clicking right to a node may hit the surrounding UL
FT . info ( "Ignoring click on outer UL." ) ;
res . node = null ;
} else if ( $target . closest ( ".fancytree-title" ) . length ) {
// #228: clicking an embedded element inside a title
res . type = "title" ;
} else if ( $target . closest ( ".fancytree-checkbox" ) . length ) {
// E.g. <svg> inside checkbox span
res . type = "checkbox" ;
} else if ( $target . closest ( ".fancytree-expander" ) . length ) {
res . type = "expander" ;
}
2018-01-01 14:39:23 +00:00
}
return res ;
} ,
/ * * R e t u r n a s t r i n g d e s c r i b i n g t h e a f f e c t e d n o d e r e g i o n f o r a m o u s e e v e n t .
*
* @ param { Event } event Mouse event , e . g . click , mousemove , ...
* @ returns { string } 'title' | 'prefix' | 'expander' | 'checkbox' | 'icon' | undefined
* /
getEventTargetType : function ( event ) {
return this . getEventTarget ( event ) . type ;
} ,
/ * * R e t u r n a F a n c y t r e e N o d e i n s t a n c e f r o m e l e m e n t , e v e n t , o r j Q u e r y o b j e c t .
*
* @ param { Element | jQueryObject | Event } el
* @ returns { FancytreeNode } matching node or null
* /
getNode : function ( el ) {
if ( el instanceof FancytreeNode ) {
return el ; // el already was a FancytreeNode
} else if ( el instanceof $ ) {
el = el [ 0 ] ; // el was a jQuery object: use the DOM element
} else if ( el . originalEvent !== undefined ) {
el = el . target ; // el was an Event
}
while ( el ) {
if ( el . ftnode ) {
return el . ftnode ;
}
el = el . parentNode ;
}
return null ;
} ,
/ * * R e t u r n a F a n c y t r e e i n s t a n c e , f r o m e l e m e n t , i n d e x , e v e n t , o r j Q u e r y O b j e c t .
*
* @ param { Element | jQueryObject | Event | integer | string } [ el ]
* @ returns { Fancytree } matching tree or null
* @ example
* $ . ui . fancytree . getTree ( ) ; // Get first Fancytree instance on page
* $ . ui . fancytree . getTree ( 1 ) ; // Get second Fancytree instance on page
* $ . ui . fancytree . getTree ( "#tree" ) ; // Get tree for this matching element
*
* @ since 2.13
* /
getTree : function ( el ) {
var widget ;
if ( el instanceof Fancytree ) {
return el ; // el already was a Fancytree
}
if ( el === undefined ) {
el = 0 ; // get first tree
}
if ( typeof el === "number" ) {
el = $ ( ".fancytree-container" ) . eq ( el ) ; // el was an integer: return nth instance
} else if ( typeof el === "string" ) {
el = $ ( el ) . eq ( 0 ) ; // el was a selector: use first match
} else if ( el . selector !== undefined ) {
el = el . eq ( 0 ) ; // el was a jQuery object: use the first DOM element
} else if ( el . originalEvent !== undefined ) {
el = $ ( el . target ) ; // el was an Event
}
el = el . closest ( ":ui-fancytree" ) ;
widget = el . data ( "ui-fancytree" ) || el . data ( "fancytree" ) ; // the latter is required by jQuery <= 1.8
return widget ? widget . tree : null ;
} ,
/ * * R e t u r n a n o p t i o n v a l u e t h a t h a s a d e f a u l t , b u t m a y b e o v e r r i d d e n b y a
* callback or a node instance attribute .
*
* Evaluation sequence : < br >
*
* If tree . options . < optionName > is a callback that returns something , use that . < br >
* Else if node . < optionName > is defined , use that . < br >
* Else if tree . options . < optionName > is a value , use that . < br >
* Else use ` defaultValue ` .
*
* @ param { string } optionName name of the option property ( on node and tree )
* @ param { FancytreeNode } node passed to the callback
* @ param { object } nodeObject where to look for the local option property , e . g . ` node ` or ` node.data `
* @ param { object } treeOption where to look for the tree option , e . g . ` tree.options ` or ` tree.options.dnd5 `
* @ param { any } [ defaultValue ]
* @ returns { any }
*
* @ example
* // Check for node.foo, tree,options.foo(), and tree.options.foo:
* $ . ui . fancytree . evalOption ( "foo" , node , node , tree . options ) ;
* // Check for node.data.bar, tree,options.qux.bar(), and tree.options.qux.bar:
* $ . ui . fancytree . evalOption ( "bar" , node , node . data , tree . options . qux ) ;
*
* @ since 2.22
* /
evalOption : function ( optionName , node , nodeObject , treeOptions , defaultValue ) {
var ctx , res ,
tree = node . tree ,
treeOpt = treeOptions [ optionName ] ,
nodeOpt = nodeObject [ optionName ] ;
if ( $ . isFunction ( treeOpt ) ) {
2018-05-24 18:59:32 +00:00
ctx = {
node : node , tree : tree , widget : tree . widget , options : tree . widget . options ,
typeInfo : tree . types [ node . type ] || { }
} ;
2018-01-01 14:39:23 +00:00
res = treeOpt . call ( tree , { type : optionName } , ctx ) ;
if ( res == null ) {
res = nodeOpt ;
}
} else {
res = ( nodeOpt != null ) ? nodeOpt : treeOpt ;
}
if ( res == null ) {
res = defaultValue ; // no option set at all: return default
}
return res ;
} ,
2018-05-24 18:59:32 +00:00
/ * * S e t e x p a n d e r , c h e c k b o x , o r n o d e i c o n , s u p p o r t i n g s t r i n g a n d o b j e c t f o r m a t .
*
* @ param { Element | jQueryObject } span
* @ param { string } baseClass
* @ param { string | object } icon
* @ since 2.27
* /
setSpanIcon : function ( span , baseClass , icon ) {
var $span = $ ( span ) ;
if ( typeof icon === "string" ) {
$span . attr ( "class" , baseClass + " " + icon ) ;
} else { // support object syntax: { text: ligature, addClasse: classname }
if ( icon . text ) {
$span . text ( "" + icon . text ) ;
} else if ( icon . html ) {
span . innerHTML = icon . html ;
}
$span . attr ( "class" , baseClass + " " + ( icon . addClass || "" ) ) ;
}
} ,
2018-01-01 14:39:23 +00:00
/ * * C o n v e r t a k e y d o w n o r m o u s e e v e n t t o a c a n o n i c a l s t r i n g l i k e ' c t r l + a ' ,
* 'ctrl+shift+f2' , 'shift+leftdblclick' .
*
* This is especially handy for switch - statements in event handlers .
*
* @ param { event }
* @ returns { string }
*
* @ example
switch ( $ . ui . fancytree . eventToString ( event ) ) {
case "-" :
tree . nodeSetExpanded ( ctx , false ) ;
break ;
case "shift+return" :
tree . nodeSetActive ( ctx , true ) ;
break ;
case "down" :
2018-05-24 18:59:32 +00:00
res = node . navigate ( event . which , activate ) ;
2018-01-01 14:39:23 +00:00
break ;
default :
handled = false ;
}
if ( handled ) {
event . preventDefault ( ) ;
}
* /
eventToString : function ( event ) {
// Poor-man's hotkeys. See here for a complete implementation:
// https://github.com/jeresig/jquery.hotkeys
var which = event . which ,
et = event . type ,
s = [ ] ;
if ( event . altKey ) { s . push ( "alt" ) ; }
if ( event . ctrlKey ) { s . push ( "ctrl" ) ; }
if ( event . metaKey ) { s . push ( "meta" ) ; }
if ( event . shiftKey ) { s . push ( "shift" ) ; }
if ( et === "click" || et === "dblclick" ) {
s . push ( MOUSE _BUTTONS [ event . button ] + et ) ;
} else {
if ( ! IGNORE _KEYCODES [ which ] ) {
s . push ( SPECIAL _KEYCODES [ which ] || String . fromCharCode ( which ) . toLowerCase ( ) ) ;
}
}
return s . join ( "+" ) ;
} ,
2018-05-24 18:59:32 +00:00
/ * * W r i t e m e s s a g e t o c o n s o l e i f d e b u g L e v e l > = 3
2018-01-01 14:39:23 +00:00
* @ param { string } msg
* /
info : function ( msg ) {
/*jshint expr:true */
2018-05-24 18:59:32 +00:00
( $ . ui . fancytree . debugLevel >= 3 ) && consoleApply ( "info" , arguments ) ;
2018-01-01 14:39:23 +00:00
} ,
/ * @ d e p r e c a t e d : u s e e v e n t T o S t r i n g ( e v e n t ) i n s t e a d .
* /
keyEventToString : function ( event ) {
this . warn ( "keyEventToString() is deprecated: use eventToString()" ) ;
return this . eventToString ( event ) ;
} ,
/ * * R e t u r n a w r a p p e d h a n d l e r m e t h o d , t h a t p r o v i d e s ` t h i s . s u p e r ` .
*
* @ example
// Implement `opts.createNode` event to add the 'draggable' attribute
$ . ui . fancytree . overrideMethod ( ctx . options , "createNode" , function ( event , data ) {
// Default processing if any
this . _super . apply ( this , arguments ) ;
// Add 'draggable' attribute
data . node . span . draggable = true ;
} ) ;
*
* @ param { object } instance
* @ param { string } methodName
* @ param { function } handler
* /
overrideMethod : function ( instance , methodName , handler ) {
var prevSuper ,
_super = instance [ methodName ] || $ . noop ;
// context = context || this;
instance [ methodName ] = function ( ) {
try {
prevSuper = this . _super ;
this . _super = _super ;
return handler . apply ( this , arguments ) ;
} finally {
this . _super = prevSuper ;
}
} ;
} ,
/ * *
* Parse tree data from HTML < ul > markup
*
* @ param { jQueryObject } $ul
* @ returns { NodeData [ ] }
* /
parseHtml : function ( $ul ) {
// TODO: understand this:
/*jshint validthis:true */
var classes , className , extraClasses , i , iPos , l , tmp , tmp2 ,
$children = $ul . find ( ">li" ) ,
children = [ ] ;
$children . each ( function ( ) {
var allData , lowerCaseAttr ,
$li = $ ( this ) ,
$liSpan = $li . find ( ">span:first" , this ) ,
$liA = $liSpan . length ? null : $li . find ( ">a:first" ) ,
d = { tooltip : null , data : { } } ;
if ( $liSpan . length ) {
d . title = $liSpan . html ( ) ;
} else if ( $liA && $liA . length ) {
// If a <li><a> tag is specified, use it literally and extract href/target.
d . title = $liA . html ( ) ;
d . data . href = $liA . attr ( "href" ) ;
d . data . target = $liA . attr ( "target" ) ;
d . tooltip = $liA . attr ( "title" ) ;
} else {
// If only a <li> tag is specified, use the trimmed string up to
// the next child <ul> tag.
d . title = $li . html ( ) ;
iPos = d . title . search ( /<ul/i ) ;
if ( iPos >= 0 ) {
d . title = d . title . substring ( 0 , iPos ) ;
}
}
d . title = $ . trim ( d . title ) ;
// Make sure all fields exist
for ( i = 0 , l = CLASS _ATTRS . length ; i < l ; i ++ ) {
d [ CLASS _ATTRS [ i ] ] = undefined ;
}
// Initialize to `true`, if class is set and collect extraClasses
classes = this . className . split ( " " ) ;
extraClasses = [ ] ;
for ( i = 0 , l = classes . length ; i < l ; i ++ ) {
className = classes [ i ] ;
if ( CLASS _ATTR _MAP [ className ] ) {
d [ className ] = true ;
} else {
extraClasses . push ( className ) ;
}
}
d . extraClasses = extraClasses . join ( " " ) ;
// Parse node options from ID, title and class attributes
tmp = $li . attr ( "title" ) ;
if ( tmp ) {
d . tooltip = tmp ; // overrides <a title='...'>
}
tmp = $li . attr ( "id" ) ;
if ( tmp ) {
d . key = tmp ;
}
// Translate hideCheckbox -> checkbox:false
if ( $li . attr ( "hideCheckbox" ) ) {
d . checkbox = false ;
}
// Add <li data-NAME='...'> as node.data.NAME
allData = _getElementDataAsDict ( $li ) ;
if ( allData && ! $ . isEmptyObject ( allData ) ) {
// #507: convert data-hidecheckbox (lower case) to hideCheckbox
for ( lowerCaseAttr in NODE _ATTR _LOWERCASE _MAP ) {
if ( allData . hasOwnProperty ( lowerCaseAttr ) ) {
allData [ NODE _ATTR _LOWERCASE _MAP [ lowerCaseAttr ] ] = allData [ lowerCaseAttr ] ;
delete allData [ lowerCaseAttr ] ;
}
}
// #56: Allow to set special node.attributes from data-...
for ( i = 0 , l = NODE _ATTRS . length ; i < l ; i ++ ) {
tmp = NODE _ATTRS [ i ] ;
tmp2 = allData [ tmp ] ;
if ( tmp2 != null ) {
delete allData [ tmp ] ;
d [ tmp ] = tmp2 ;
}
}
// All other data-... goes to node.data...
$ . extend ( d . data , allData ) ;
}
// Recursive reading of child nodes, if LI tag contains an UL tag
$ul = $li . find ( ">ul:first" ) ;
if ( $ul . length ) {
d . children = $ . ui . fancytree . parseHtml ( $ul ) ;
} else {
d . children = d . lazy ? undefined : null ;
}
children . push ( d ) ;
// FT.debug("parse ", d, children);
} ) ;
return children ;
} ,
/ * * A d d F a n c y t r e e e x t e n s i o n d e f i n i t i o n t o t h e l i s t o f g l o b a l l y a v a i l a b l e e x t e n s i o n s .
*
* @ param { object } definition
* /
registerExtension : function ( definition ) {
_assert ( definition . name != null , "extensions must have a `name` property." ) ;
_assert ( definition . version != null , "extensions must have a `version` property." ) ;
$ . ui . fancytree . _extensions [ definition . name ] = definition ;
} ,
/ * * I n v e r s e o f e s c a p e H t m l ( ) .
*
* @ param { string } s
* @ returns { string }
* /
unescapeHtml : function ( s ) {
var e = document . createElement ( "div" ) ;
e . innerHTML = s ;
return e . childNodes . length === 0 ? "" : e . childNodes [ 0 ] . nodeValue ;
} ,
2018-05-24 18:59:32 +00:00
/ * * W r i t e w a r n i n g m e s s a g e t o c o n s o l e i f d e b u g L e v e l > = 2 .
2018-01-01 14:39:23 +00:00
* @ param { string } msg
* /
warn : function ( msg ) {
2018-05-24 18:59:32 +00:00
( $ . ui . fancytree . debugLevel >= 2 ) && consoleApply ( "warn" , arguments ) ;
2018-01-01 14:39:23 +00:00
}
} ) ;
// Value returned by `require('jquery.fancytree')`
return $ . ui . fancytree ;
} ) ) ; // End of closure
/*! Extension 'jquery.fancytree.childcounter.js' */ // Extending Fancytree
// ===================
//
// See also the [live demo](http://wwwendt.de/tech/fancytree/demo/sample-ext-childcounter.html) of this code.
//
// Every extension should have a comment header containing some information
// about the author, copyright and licensing. Also a pointer to the latest
// source code.
// Prefix with `/*!` so the comment is not removed by the minifier.
/ * !
* jquery . fancytree . childcounter . js
*
* Add a child counter bubble to tree nodes .
* ( Extension module for jquery . fancytree . js : https : //github.com/mar10/fancytree/)
*
2018-05-24 18:59:32 +00:00
* Copyright ( c ) 2008 - 2018 , Martin Wendt ( http : //wwWendt.de)
2018-01-01 14:39:23 +00:00
*
* Released under the MIT license
* https : //github.com/mar10/fancytree/wiki/LicenseInfo
*
2018-05-24 18:59:32 +00:00
* @ version 2.28 . 1
* @ date 2018 - 03 - 19 T06 : 47 : 37 Z
2018-01-01 14:39:23 +00:00
* /
// To keep the global namespace clean, we wrap everything in a closure.
// The UMD wrapper pattern defines the dependencies on jQuery and the
// Fancytree core module, and makes sure that we can use the `require()`
// syntax with package loaders.
; ( function ( factory ) {
if ( typeof define === "function" && define . amd ) {
// AMD. Register as an anonymous module.
define ( [ "jquery" , "./jquery.fancytree" ] , factory ) ;
} else if ( typeof module === "object" && module . exports ) {
// Node/CommonJS
2018-05-24 18:59:32 +00:00
require ( "./jquery.fancytree" ) ;
2018-01-01 14:39:23 +00:00
module . exports = factory ( require ( "jquery" ) ) ;
} else {
// Browser globals
factory ( jQuery ) ;
}
} ( function ( $ ) {
// Consider to use [strict mode](http://ejohn.org/blog/ecmascript-5-strict-mode-json-and-more/)
"use strict" ;
// The [coding guidelines](http://contribute.jquery.org/style-guide/js/)
// require jshint compliance.
// But for this sample, we want to allow unused variables for demonstration purpose.
/*jshint unused:false */
// Adding methods
// --------------
// New member functions can be added to the `Fancytree` class.
// This function will be available for every tree instance:
//
// var tree = $("#tree").fancytree("getTree");
// tree.countSelected(false);
$ . ui . fancytree . _FancytreeClass . prototype . countSelected = function ( topOnly ) {
var tree = this ,
treeOptions = tree . options ;
return tree . getSelectedNodes ( topOnly ) . length ;
} ;
// The `FancytreeNode` class can also be easily extended. This would be called
// like
// node.updateCounters();
//
// It is also good practice to add a docstring comment.
/ * *
* [ ext - childcounter ] Update counter badges for ` node ` and its parents .
* May be called in the ` loadChildren ` event , to update parents of lazy loaded
* nodes .
* @ alias FancytreeNode # updateCounters
* @ requires jquery . fancytree . childcounters . js
* /
$ . ui . fancytree . _FancytreeNodeClass . prototype . updateCounters = function ( ) {
var node = this ,
$badge = $ ( "span.fancytree-childcounter" , node . span ) ,
extOpts = node . tree . options . childcounter ,
count = node . countChildren ( extOpts . deep ) ;
node . data . childCounter = count ;
if ( ( count || ! extOpts . hideZeros ) && ( ! node . isExpanded ( ) || ! extOpts . hideExpanded ) ) {
if ( ! $badge . length ) {
$badge = $ ( "<span class='fancytree-childcounter'/>" ) . appendTo ( $ ( "span.fancytree-icon" , node . span ) ) ;
}
$badge . text ( count ) ;
} else {
$badge . remove ( ) ;
}
if ( extOpts . deep && ! node . isTopLevel ( ) && ! node . isRoot ( ) ) {
node . parent . updateCounters ( ) ;
}
} ;
// Finally, we can extend the widget API and create functions that are called
// like so:
//
// $("#tree").fancytree("widgetMethod1", "abc");
$ . ui . fancytree . prototype . widgetMethod1 = function ( arg1 ) {
var tree = this . tree ;
return arg1 ;
} ;
// Register a Fancytree extension
// ------------------------------
// A full blown extension, extension is available for all trees and can be
// enabled like so (see also the [live demo](http://wwwendt.de/tech/fancytree/demo/sample-ext-childcounter.html)):
//
// <script src="../src/jquery.fancytree.js"></script>
// <script src="../src/jquery.fancytree.childcounter.js"></script>
// ...
//
// $("#tree").fancytree({
// extensions: ["childcounter"],
// childcounter: {
// hideExpanded: true
// },
// ...
// });
//
/* 'childcounter' extension */
$ . ui . fancytree . registerExtension ( {
// Every extension must be registered by a unique name.
name : "childcounter" ,
// Version information should be compliant with [semver](http://semver.org)
2018-05-24 18:59:32 +00:00
version : "2.28.1" ,
2018-01-01 14:39:23 +00:00
// Extension specific options and their defaults.
// This options will be available as `tree.options.childcounter.hideExpanded`
options : {
deep : true ,
hideZeros : true ,
hideExpanded : false
} ,
// Attributes other than `options` (or functions) can be defined here, and
// will be added to the tree.ext.EXTNAME namespace, in this case `tree.ext.childcounter.foo`.
// They can also be accessed as `this._local.foo` from within the extension
// methods.
foo : 42 ,
// Local functions are prefixed with an underscore '_'.
// Callable as `this._local._appendCounter()`.
_appendCounter : function ( bar ) {
var tree = this ;
} ,
// **Override virtual methods for this extension.**
//
// Fancytree implements a number of 'hook methods', prefixed by 'node...' or 'tree...'.
// with a `ctx` argument (see [EventData](http://www.wwwendt.de/tech/fancytree/doc/jsdoc/global.html#EventData)
// for details) and an extended calling context:<br>
// `this` : the Fancytree instance<br>
// `this._local`: the namespace that contains extension attributes and private methods (same as this.ext.EXTNAME)<br>
// `this._super`: the virtual function that was overridden (member of previous extension or Fancytree)
//
// See also the [complete list of available hook functions](http://www.wwwendt.de/tech/fancytree/doc/jsdoc/Fancytree_Hooks.html).
/* Init */
// `treeInit` is triggered when a tree is initalized. We can set up classes or
// bind event handlers here...
treeInit : function ( ctx ) {
var tree = this , // same as ctx.tree,
opts = ctx . options ,
extOpts = ctx . options . childcounter ;
// Optionally check for dependencies with other extensions
/* this._requireExtension("glyph", false, false); */
// Call the base implementation
this . _superApply ( arguments ) ;
// Add a class to the tree container
this . $container . addClass ( "fancytree-ext-childcounter" ) ;
} ,
// Destroy this tree instance (we only call the default implementation, so
// this method could as well be omitted).
treeDestroy : function ( ctx ) {
this . _superApply ( arguments ) ;
} ,
// Overload the `renderTitle` hook, to append a counter badge
nodeRenderTitle : function ( ctx , title ) {
var node = ctx . node ,
extOpts = ctx . options . childcounter ,
count = ( node . data . childCounter == null ) ? node . countChildren ( extOpts . deep ) : + node . data . childCounter ;
// Let the base implementation render the title
// We use `_super()` instead of `_superApply()` here, since it is a little bit
// more performant when called often
this . _super ( ctx , title ) ;
// Append a counter badge
if ( ( count || ! extOpts . hideZeros ) && ( ! node . isExpanded ( ) || ! extOpts . hideExpanded ) ) {
$ ( "span.fancytree-icon" , node . span ) . append ( $ ( "<span class='fancytree-childcounter'/>" ) . text ( count ) ) ;
}
} ,
// Overload the `setExpanded` hook, so the counters are updated
nodeSetExpanded : function ( ctx , flag , callOpts ) {
var tree = ctx . tree ,
node = ctx . node ;
// Let the base implementation expand/collapse the node, then redraw the title
// after the animation has finished
return this . _superApply ( arguments ) . always ( function ( ) {
tree . nodeRenderTitle ( ctx ) ;
} ) ;
}
// End of extension definition
} ) ;
// Value returned by `require('jquery.fancytree..')`
return $ . ui . fancytree ;
} ) ) ; // End of closure
/*! Extension 'jquery.fancytree.clones.js' */ / * !
*
* jquery . fancytree . clones . js
* Support faster lookup of nodes by key and shared ref - ids .
* ( Extension module for jquery . fancytree . js : https : //github.com/mar10/fancytree/)
*
2018-05-24 18:59:32 +00:00
* Copyright ( c ) 2008 - 2018 , Martin Wendt ( http : //wwWendt.de)
2018-01-01 14:39:23 +00:00
*
* Released under the MIT license
* https : //github.com/mar10/fancytree/wiki/LicenseInfo
*
2018-05-24 18:59:32 +00:00
* @ version 2.28 . 1
* @ date 2018 - 03 - 19 T06 : 47 : 37 Z
2018-01-01 14:39:23 +00:00
* /
; ( function ( factory ) {
if ( typeof define === "function" && define . amd ) {
// AMD. Register as an anonymous module.
define ( [ "jquery" , "./jquery.fancytree" ] , factory ) ;
} else if ( typeof module === "object" && module . exports ) {
// Node/CommonJS
2018-05-24 18:59:32 +00:00
require ( "./jquery.fancytree" ) ;
2018-01-01 14:39:23 +00:00
module . exports = factory ( require ( "jquery" ) ) ;
} else {
// Browser globals
factory ( jQuery ) ;
}
} ( function ( $ ) {
"use strict" ;
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Private functions and variables
* /
function _assert ( cond , msg ) {
// TODO: see qunit.js extractStacktrace()
if ( ! cond ) {
msg = msg ? ": " + msg : "" ;
$ . error ( "Assertion failed" + msg ) ;
}
}
/* Return first occurrence of member from array. */
function _removeArrayMember ( arr , elem ) {
// TODO: use Array.indexOf for IE >= 9
var i ;
for ( i = arr . length - 1 ; i >= 0 ; i -- ) {
if ( arr [ i ] === elem ) {
arr . splice ( i , 1 ) ;
return true ;
}
}
return false ;
}
// /**
// * Calculate a 32 bit FNV-1a hash
// * Found here: https://gist.github.com/vaiorabbit/5657561
// * Ref.: http://isthe.com/chongo/tech/comp/fnv/
// *
// * @param {string} str the input value
// * @param {boolean} [asString=false] set to true to return the hash value as
// * 8-digit hex string instead of an integer
// * @param {integer} [seed] optionally pass the hash of the previous chunk
// * @returns {integer | string}
// */
// function hashFnv32a(str, asString, seed) {
// /*jshint bitwise:false */
// var i, l,
// hval = (seed === undefined) ? 0x811c9dc5 : seed;
// for (i = 0, l = str.length; i < l; i++) {
// hval ^= str.charCodeAt(i);
// hval += (hval << 1) + (hval << 4) + (hval << 7) + (hval << 8) + (hval << 24);
// }
// if( asString ){
// // Convert to 8 digit hex string
// return ("0000000" + (hval >>> 0).toString(16)).substr(-8);
// }
// return hval >>> 0;
// }
/ * *
* JS Implementation of MurmurHash3 ( r136 ) ( as of May 20 , 2011 )
*
* @ author < a href = "mailto:gary.court@gmail.com" > Gary Court < / a >
* @ see http : //github.com/garycourt/murmurhash-js
* @ author < a href = "mailto:aappleby@gmail.com" > Austin Appleby < / a >
* @ see http : //sites.google.com/site/murmurhash/
*
* @ param { string } key ASCII only
* @ param { boolean } [ asString = false ]
* @ param { number } seed Positive integer only
* @ return { number } 32 - bit positive integer hash
* /
function hashMurmur3 ( key , asString , seed ) {
/*jshint bitwise:false */
var h1b , k1 ,
remainder = key . length & 3 ,
bytes = key . length - remainder ,
h1 = seed ,
c1 = 0xcc9e2d51 ,
c2 = 0x1b873593 ,
i = 0 ;
while ( i < bytes ) {
k1 =
( ( key . charCodeAt ( i ) & 0xff ) ) |
( ( key . charCodeAt ( ++ i ) & 0xff ) << 8 ) |
( ( key . charCodeAt ( ++ i ) & 0xff ) << 16 ) |
( ( key . charCodeAt ( ++ i ) & 0xff ) << 24 ) ;
++ i ;
k1 = ( ( ( ( k1 & 0xffff ) * c1 ) + ( ( ( ( k1 >>> 16 ) * c1 ) & 0xffff ) << 16 ) ) ) & 0xffffffff ;
k1 = ( k1 << 15 ) | ( k1 >>> 17 ) ;
k1 = ( ( ( ( k1 & 0xffff ) * c2 ) + ( ( ( ( k1 >>> 16 ) * c2 ) & 0xffff ) << 16 ) ) ) & 0xffffffff ;
h1 ^= k1 ;
h1 = ( h1 << 13 ) | ( h1 >>> 19 ) ;
h1b = ( ( ( ( h1 & 0xffff ) * 5 ) + ( ( ( ( h1 >>> 16 ) * 5 ) & 0xffff ) << 16 ) ) ) & 0xffffffff ;
h1 = ( ( ( h1b & 0xffff ) + 0x6b64 ) + ( ( ( ( h1b >>> 16 ) + 0xe654 ) & 0xffff ) << 16 ) ) ;
}
k1 = 0 ;
switch ( remainder ) {
/*jshint -W086:true */
case 3 : k1 ^= ( key . charCodeAt ( i + 2 ) & 0xff ) << 16 ;
case 2 : k1 ^= ( key . charCodeAt ( i + 1 ) & 0xff ) << 8 ;
case 1 : k1 ^= ( key . charCodeAt ( i ) & 0xff ) ;
k1 = ( ( ( k1 & 0xffff ) * c1 ) + ( ( ( ( k1 >>> 16 ) * c1 ) & 0xffff ) << 16 ) ) & 0xffffffff ;
k1 = ( k1 << 15 ) | ( k1 >>> 17 ) ;
k1 = ( ( ( k1 & 0xffff ) * c2 ) + ( ( ( ( k1 >>> 16 ) * c2 ) & 0xffff ) << 16 ) ) & 0xffffffff ;
h1 ^= k1 ;
}
h1 ^= key . length ;
h1 ^= h1 >>> 16 ;
h1 = ( ( ( h1 & 0xffff ) * 0x85ebca6b ) + ( ( ( ( h1 >>> 16 ) * 0x85ebca6b ) & 0xffff ) << 16 ) ) & 0xffffffff ;
h1 ^= h1 >>> 13 ;
h1 = ( ( ( ( h1 & 0xffff ) * 0xc2b2ae35 ) + ( ( ( ( h1 >>> 16 ) * 0xc2b2ae35 ) & 0xffff ) << 16 ) ) ) & 0xffffffff ;
h1 ^= h1 >>> 16 ;
if ( asString ) {
// Convert to 8 digit hex string
return ( "0000000" + ( h1 >>> 0 ) . toString ( 16 ) ) . substr ( - 8 ) ;
}
return h1 >>> 0 ;
}
// console.info(hashMurmur3("costarring"));
// console.info(hashMurmur3("costarring", true));
// console.info(hashMurmur3("liquid"));
// console.info(hashMurmur3("liquid", true));
/ *
* Return a unique key for node by calculationg the hash of the parents refKey - list
* /
function calcUniqueKey ( node ) {
var key ,
path = $ . map ( node . getParentList ( false , true ) , function ( e ) { return e . refKey || e . key ; } ) ;
path = path . join ( "/" ) ;
key = "id_" + hashMurmur3 ( path , true ) ;
// node.debug(path + " -> " + key);
return key ;
}
/ * *
* [ ext - clones ] Return a list of clone - nodes or null .
* @ param { boolean } [ includeSelf = false ]
* @ returns { FancytreeNode [ ] | null }
*
* @ alias FancytreeNode # getCloneList
* @ requires jquery . fancytree . clones . js
* /
$ . ui . fancytree . _FancytreeNodeClass . prototype . getCloneList = function ( includeSelf ) {
var key ,
tree = this . tree ,
refList = tree . refMap [ this . refKey ] || null ,
keyMap = tree . keyMap ;
if ( refList ) {
key = this . key ;
// Convert key list to node list
if ( includeSelf ) {
refList = $ . map ( refList , function ( val ) { return keyMap [ val ] ; } ) ;
} else {
refList = $ . map ( refList , function ( val ) { return val === key ? null : keyMap [ val ] ; } ) ;
if ( refList . length < 1 ) {
refList = null ;
}
}
}
return refList ;
} ;
/ * *
* [ ext - clones ] Return true if this node has at least another clone with same refKey .
* @ returns { boolean }
*
* @ alias FancytreeNode # isClone
* @ requires jquery . fancytree . clones . js
* /
$ . ui . fancytree . _FancytreeNodeClass . prototype . isClone = function ( ) {
var refKey = this . refKey || null ,
refList = refKey && this . tree . refMap [ refKey ] || null ;
return ! ! ( refList && refList . length > 1 ) ;
} ;
/ * *
* [ ext - clones ] Update key and / or refKey for an existing node .
* @ param { string } key
* @ param { string } refKey
* @ returns { boolean }
*
* @ alias FancytreeNode # reRegister
* @ requires jquery . fancytree . clones . js
* /
$ . ui . fancytree . _FancytreeNodeClass . prototype . reRegister = function ( key , refKey ) {
key = ( key == null ) ? null : "" + key ;
refKey = ( refKey == null ) ? null : "" + refKey ;
// this.debug("reRegister", key, refKey);
var tree = this . tree ,
prevKey = this . key ,
prevRefKey = this . refKey ,
keyMap = tree . keyMap ,
refMap = tree . refMap ,
refList = refMap [ prevRefKey ] || null ,
// curCloneKeys = refList ? node.getCloneList(true),
modified = false ;
// Key has changed: update all references
if ( key != null && key !== this . key ) {
if ( keyMap [ key ] ) {
$ . error ( "[ext-clones] reRegister(" + key + "): already exists: " + this ) ;
}
// Update keyMap
delete keyMap [ prevKey ] ;
keyMap [ key ] = this ;
// Update refMap
if ( refList ) {
refMap [ prevRefKey ] = $ . map ( refList , function ( e ) {
return e === prevKey ? key : e ;
} ) ;
}
this . key = key ;
modified = true ;
}
// refKey has changed
if ( refKey != null && refKey !== this . refKey ) {
// Remove previous refKeys
if ( refList ) {
if ( refList . length === 1 ) {
delete refMap [ prevRefKey ] ;
} else {
refMap [ prevRefKey ] = $ . map ( refList , function ( e ) {
return e === prevKey ? null : e ;
} ) ;
}
}
// Add refKey
if ( refMap [ refKey ] ) {
refMap [ refKey ] . append ( key ) ;
} else {
refMap [ refKey ] = [ this . key ] ;
}
this . refKey = refKey ;
modified = true ;
}
return modified ;
} ;
/ * *
* [ ext - clones ] Define a refKey for an existing node .
* @ param { string } refKey
* @ returns { boolean }
*
* @ alias FancytreeNode # setRefKey
* @ requires jquery . fancytree . clones . js
* @ since 2.16
* /
$ . ui . fancytree . _FancytreeNodeClass . prototype . setRefKey = function ( refKey ) {
return this . reRegister ( null , refKey ) ;
} ;
/ * *
* [ ext - clones ] Return all nodes with a given refKey ( null if not found ) .
* @ param { string } refKey
* @ param { FancytreeNode } [ rootNode ] optionally restrict results to descendants of this node
* @ returns { FancytreeNode [ ] | null }
* @ alias Fancytree # getNodesByRef
* @ requires jquery . fancytree . clones . js
* /
$ . ui . fancytree . _FancytreeClass . prototype . getNodesByRef = function ( refKey , rootNode ) {
var keyMap = this . keyMap ,
refList = this . refMap [ refKey ] || null ;
if ( refList ) {
// Convert key list to node list
if ( rootNode ) {
refList = $ . map ( refList , function ( val ) {
var node = keyMap [ val ] ;
return node . isDescendantOf ( rootNode ) ? node : null ;
} ) ;
} else {
refList = $ . map ( refList , function ( val ) { return keyMap [ val ] ; } ) ;
}
if ( refList . length < 1 ) {
refList = null ;
}
}
return refList ;
} ;
/ * *
* [ ext - clones ] Replace a refKey with a new one .
* @ param { string } oldRefKey
* @ param { string } newRefKey
* @ alias Fancytree # changeRefKey
* @ requires jquery . fancytree . clones . js
* /
$ . ui . fancytree . _FancytreeClass . prototype . changeRefKey = function ( oldRefKey , newRefKey ) {
var i , node ,
keyMap = this . keyMap ,
refList = this . refMap [ oldRefKey ] || null ;
if ( refList ) {
for ( i = 0 ; i < refList . length ; i ++ ) {
node = keyMap [ refList [ i ] ] ;
node . refKey = newRefKey ;
}
delete this . refMap [ oldRefKey ] ;
this . refMap [ newRefKey ] = refList ;
}
} ;
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Extension code
* /
$ . ui . fancytree . registerExtension ( {
name : "clones" ,
2018-05-24 18:59:32 +00:00
version : "2.28.1" ,
2018-01-01 14:39:23 +00:00
// Default options for this extension.
options : {
highlightActiveClones : true , // set 'fancytree-active-clone' on active clones and all peers
highlightClones : false // set 'fancytree-clone' class on any node that has at least one clone
} ,
treeCreate : function ( ctx ) {
this . _superApply ( arguments ) ;
ctx . tree . refMap = { } ;
ctx . tree . keyMap = { } ;
} ,
treeInit : function ( ctx ) {
this . $container . addClass ( "fancytree-ext-clones" ) ;
_assert ( ctx . options . defaultKey == null ) ;
// Generate unique / reproducible default keys
ctx . options . defaultKey = function ( node ) {
return calcUniqueKey ( node ) ;
} ;
// The default implementation loads initial data
this . _superApply ( arguments ) ;
} ,
treeClear : function ( ctx ) {
ctx . tree . refMap = { } ;
ctx . tree . keyMap = { } ;
return this . _superApply ( arguments ) ;
} ,
treeRegisterNode : function ( ctx , add , node ) {
var refList , len ,
tree = ctx . tree ,
keyMap = tree . keyMap ,
refMap = tree . refMap ,
key = node . key ,
refKey = ( node && node . refKey != null ) ? "" + node . refKey : null ;
// ctx.tree.debug("clones.treeRegisterNode", add, node);
if ( node . isStatusNode ( ) ) {
return this . _super ( ctx , add , node ) ;
}
if ( add ) {
if ( keyMap [ node . key ] != null ) {
$ . error ( "clones.treeRegisterNode: node.key already exists: " + node ) ;
}
keyMap [ key ] = node ;
if ( refKey ) {
refList = refMap [ refKey ] ;
if ( refList ) {
refList . push ( key ) ;
if ( refList . length === 2 && ctx . options . clones . highlightClones ) {
// Mark peer node, if it just became a clone (no need to
// mark current node, since it will be rendered later anyway)
keyMap [ refList [ 0 ] ] . renderStatus ( ) ;
}
} else {
refMap [ refKey ] = [ key ] ;
}
// node.debug("clones.treeRegisterNode: add clone =>", refMap[refKey]);
}
} else {
if ( keyMap [ key ] == null ) {
$ . error ( "clones.treeRegisterNode: node.key not registered: " + node . key ) ;
}
delete keyMap [ key ] ;
if ( refKey ) {
refList = refMap [ refKey ] ;
// node.debug("clones.treeRegisterNode: remove clone BEFORE =>", refMap[refKey]);
if ( refList ) {
len = refList . length ;
if ( len <= 1 ) {
_assert ( len === 1 ) ;
_assert ( refList [ 0 ] === key ) ;
delete refMap [ refKey ] ;
} else {
_removeArrayMember ( refList , key ) ;
// Unmark peer node, if this was the only clone
if ( len === 2 && ctx . options . clones . highlightClones ) {
// node.debug("clones.treeRegisterNode: last =>", node.getCloneList());
keyMap [ refList [ 0 ] ] . renderStatus ( ) ;
}
}
// node.debug("clones.treeRegisterNode: remove clone =>", refMap[refKey]);
}
}
}
return this . _super ( ctx , add , node ) ;
} ,
nodeRenderStatus : function ( ctx ) {
var $span , res ,
node = ctx . node ;
res = this . _super ( ctx ) ;
if ( ctx . options . clones . highlightClones ) {
$span = $ ( node [ ctx . tree . statusClassPropName ] ) ;
// Only if span already exists
if ( $span . length && node . isClone ( ) ) {
// node.debug("clones.nodeRenderStatus: ", ctx.options.clones.highlightClones);
$span . addClass ( "fancytree-clone" ) ;
}
}
return res ;
} ,
nodeSetActive : function ( ctx , flag , callOpts ) {
var res ,
scpn = ctx . tree . statusClassPropName ,
node = ctx . node ;
res = this . _superApply ( arguments ) ;
if ( ctx . options . clones . highlightActiveClones && node . isClone ( ) ) {
$ . each ( node . getCloneList ( true ) , function ( idx , n ) {
// n.debug("clones.nodeSetActive: ", flag !== false);
$ ( n [ scpn ] ) . toggleClass ( "fancytree-active-clone" , flag !== false ) ;
} ) ;
}
return res ;
}
} ) ;
// Value returned by `require('jquery.fancytree..')`
return $ . ui . fancytree ;
} ) ) ; // End of closure
/*! Extension 'jquery.fancytree.dnd5.js' */ / * !
* jquery . fancytree . dnd5 . js
*
* Drag - and - drop support ( native HTML5 ) .
* ( Extension module for jquery . fancytree . js : https : //github.com/mar10/fancytree/)
*
2018-05-24 18:59:32 +00:00
* Copyright ( c ) 2008 - 2018 , Martin Wendt ( http : //wwWendt.de)
2018-01-01 14:39:23 +00:00
*
* Released under the MIT license
* https : //github.com/mar10/fancytree/wiki/LicenseInfo
*
2018-05-24 18:59:32 +00:00
* @ version 2.28 . 1
* @ date 2018 - 03 - 19 T06 : 47 : 37 Z
2018-01-01 14:39:23 +00:00
* /
/ *
# TODO
Compatiblity when dragging between * separate * windows :
Drag from Chrome Edge FF IE11 Safari
To Chrome ok ok ok NO ?
Edge ok ok ok NO ?
FF ok ok ok NO ?
IE 11 ok ok ok ok ?
Safari ? ? ? ? ok
* /
; ( function ( factory ) {
if ( typeof define === "function" && define . amd ) {
// AMD. Register as an anonymous module.
define ( [ "jquery" , "./jquery.fancytree" ] , factory ) ;
} else if ( typeof module === "object" && module . exports ) {
// Node/CommonJS
2018-05-24 18:59:32 +00:00
require ( "./jquery.fancytree" ) ;
2018-01-01 14:39:23 +00:00
module . exports = factory ( require ( "jquery" ) ) ;
} else {
// Browser globals
factory ( jQuery ) ;
}
} ( function ( $ ) {
"use strict" ;
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Private functions and variables
* /
2018-05-24 18:59:32 +00:00
var FT = $ . ui . fancytree ,
isMac = /Mac/ . test ( navigator . platform ) ,
2018-01-01 14:39:23 +00:00
classDragSource = "fancytree-drag-source" ,
classDragRemove = "fancytree-drag-remove" ,
classDropAccept = "fancytree-drop-accept" ,
classDropAfter = "fancytree-drop-after" ,
classDropBefore = "fancytree-drop-before" ,
classDropOver = "fancytree-drop-over" ,
classDropReject = "fancytree-drop-reject" ,
classDropTarget = "fancytree-drop-target" ,
nodeMimeType = "application/x-fancytree-node" ,
$dropMarker = null ,
SOURCE _NODE = null ,
2018-05-24 18:59:32 +00:00
SOURCE _NODE _LIST = null ,
$sourceList = null ,
2018-01-01 14:39:23 +00:00
DRAG _ENTER _RESPONSE = null ,
LAST _HIT _MODE = null ;
2018-05-24 18:59:32 +00:00
/* */
function _clearGlobals ( ) {
SOURCE _NODE = null ;
SOURCE _NODE _LIST = null ;
$sourceList = null ;
DRAG _ENTER _RESPONSE = null ;
}
2018-01-01 14:39:23 +00:00
/* Convert number to string and prepend +/-; return empty string for 0.*/
function offsetString ( n ) {
return n === 0 ? "" : ( ( n > 0 ) ? ( "+" + n ) : ( "" + n ) ) ;
}
/ * C o n v e r t a d r a g E n t e r ( ) o r d r a g O v e r ( ) r e s p o n s e t o a c a n o n i c a l f o r m .
* Return false or plain object
* @ param { string | object | boolean } r
* @ return { object | false }
* /
function normalizeDragEnterResponse ( r ) {
var res ;
if ( ! r ) {
return false ;
}
if ( $ . isPlainObject ( r ) ) {
res = {
over : ! ! r . over ,
before : ! ! r . before ,
after : ! ! r . after
} ;
} else if ( $ . isArray ( r ) ) {
res = {
over : ( $ . inArray ( "over" , r ) >= 0 ) ,
before : ( $ . inArray ( "before" , r ) >= 0 ) ,
after : ( $ . inArray ( "after" , r ) >= 0 )
} ;
} else {
res = {
over : ( ( r === true ) || ( r === "over" ) ) ,
before : ( ( r === true ) || ( r === "before" ) ) ,
after : ( ( r === true ) || ( r === "after" ) )
} ;
}
if ( Object . keys ( res ) . length === 0 ) {
return false ;
}
// if( Object.keys(res).length === 1 ) {
// res.unique = res[0];
// }
return res ;
}
/* Implement auto scrolling when drag cursor is in top/bottom area of scroll parent. */
function autoScroll ( tree , event ) {
var spOfs , scrollTop , delta ,
dndOpts = tree . options . dnd5 ,
sp = tree . $scrollParent [ 0 ] ,
sensitivity = dndOpts . scrollSensitivity ,
speed = dndOpts . scrollSpeed ,
scrolled = 0 ;
if ( sp !== document && sp . tagName !== "HTML" ) {
spOfs = tree . $scrollParent . offset ( ) ;
scrollTop = sp . scrollTop ;
if ( spOfs . top + sp . offsetHeight - event . pageY < sensitivity ) {
delta = ( sp . scrollHeight - tree . $scrollParent . innerHeight ( ) - scrollTop ) ;
// console.log ("sp.offsetHeight: " + sp.offsetHeight
// + ", spOfs.top: " + spOfs.top
// + ", scrollTop: " + scrollTop
// + ", innerHeight: " + tree.$scrollParent.innerHeight()
// + ", scrollHeight: " + sp.scrollHeight
// + ", delta: " + delta
// );
if ( delta > 0 ) {
sp . scrollTop = scrolled = scrollTop + speed ;
}
} else if ( scrollTop > 0 && event . pageY - spOfs . top < sensitivity ) {
sp . scrollTop = scrolled = scrollTop - speed ;
}
} else {
scrollTop = $ ( document ) . scrollTop ( ) ;
if ( scrollTop > 0 && event . pageY - scrollTop < sensitivity ) {
scrolled = scrollTop - speed ;
$ ( document ) . scrollTop ( scrolled ) ;
} else if ( $ ( window ) . height ( ) - ( event . pageY - scrollTop ) < sensitivity ) {
scrolled = scrollTop + speed ;
$ ( document ) . scrollTop ( scrolled ) ;
}
}
if ( scrolled ) {
tree . debug ( "autoScroll: " + scrolled + "px" ) ;
}
return scrolled ;
}
/* Handle dragover event (fired every x ms) and return hitMode. */
function handleDragOver ( event , data ) {
// Implement auto-scrolling
if ( data . options . dnd5 . scroll ) {
autoScroll ( data . tree , event ) ;
}
// Bail out with previous response if we get an invalid dragover
if ( ! data . node ) {
data . tree . warn ( "Ignore dragover for non-node" ) ; //, event, data);
return LAST _HIT _MODE ;
}
2018-05-24 18:59:32 +00:00
var markerOffsetX , nodeOfs , pos , relPosY ,
2018-01-01 14:39:23 +00:00
hitMode = null ,
tree = data . tree ,
options = tree . options ,
dndOpts = options . dnd5 ,
targetNode = data . node ,
sourceNode = data . otherNode ,
markerAt = "center" ,
$target = $ ( targetNode . span ) ,
$targetTitle = $target . find ( "span.fancytree-title" ) ;
if ( DRAG _ENTER _RESPONSE === false ) {
tree . info ( "Ignore dragover, since dragenter returned false" ) ; //, event, data);
// $.error("assert failed: dragenter returned false");
return false ;
} else if ( typeof DRAG _ENTER _RESPONSE === "string" ) {
$ . error ( "assert failed: dragenter returned string" ) ;
// Use hitMode from onEnter if provided.
// hitMode = DRAG_ENTER_RESPONSE;
} else {
// Calculate hitMode from relative cursor position.
nodeOfs = $target . offset ( ) ;
relPosY = ( event . pageY - nodeOfs . top ) / $target . height ( ) ;
if ( DRAG _ENTER _RESPONSE . after && relPosY > 0.75 ) {
hitMode = "after" ;
} else if ( ! DRAG _ENTER _RESPONSE . over && DRAG _ENTER _RESPONSE . after && relPosY > 0.5 ) {
hitMode = "after" ;
} else if ( DRAG _ENTER _RESPONSE . before && relPosY <= 0.25 ) {
hitMode = "before" ;
} else if ( ! DRAG _ENTER _RESPONSE . over && DRAG _ENTER _RESPONSE . before && relPosY <= 0.5 ) {
hitMode = "before" ;
} else if ( DRAG _ENTER _RESPONSE . over ) {
hitMode = "over" ;
}
// Prevent no-ops like 'before source node'
// TODO: these are no-ops when moving nodes, but not in copy mode
if ( dndOpts . preventVoidMoves ) {
if ( targetNode === sourceNode ) {
2018-05-24 18:59:32 +00:00
targetNode . debug ( "Drop over source node prevented." ) ;
2018-01-01 14:39:23 +00:00
hitMode = null ;
} else if ( hitMode === "before" && sourceNode && targetNode === sourceNode . getNextSibling ( ) ) {
2018-05-24 18:59:32 +00:00
targetNode . debug ( "Drop after source node prevented." ) ;
2018-01-01 14:39:23 +00:00
hitMode = null ;
} else if ( hitMode === "after" && sourceNode && targetNode === sourceNode . getPrevSibling ( ) ) {
2018-05-24 18:59:32 +00:00
targetNode . debug ( "Drop before source node prevented." ) ;
2018-01-01 14:39:23 +00:00
hitMode = null ;
} else if ( hitMode === "over" && sourceNode && sourceNode . parent === targetNode && sourceNode . isLastSibling ( ) ) {
2018-05-24 18:59:32 +00:00
targetNode . debug ( "Drop last child over own parent prevented." ) ;
2018-01-01 14:39:23 +00:00
hitMode = null ;
}
}
}
// Let callback modify the calculated hitMode
data . hitMode = hitMode ;
if ( hitMode && dndOpts . dragOver ) {
// TODO: http://code.google.com/p/dynatree/source/detail?r=625
dndOpts . dragOver ( targetNode , data ) ;
hitMode = data . hitMode ;
}
// LAST_DROP_EFFECT = data.dataTransfer.dropEffect;
// LAST_EFFECT_ALLOWED = data.dataTransfer.effectAllowed;
LAST _HIT _MODE = hitMode ;
//
if ( hitMode === "after" || hitMode === "before" || hitMode === "over" ) {
markerOffsetX = dndOpts . dropMarkerOffsetX || 0 ;
switch ( hitMode ) {
case "before" :
markerAt = "top" ;
markerOffsetX += ( dndOpts . dropMarkerInsertOffsetX || 0 ) ;
break ;
case "after" :
markerAt = "bottom" ;
markerOffsetX += ( dndOpts . dropMarkerInsertOffsetX || 0 ) ;
break ;
}
2018-05-24 18:59:32 +00:00
pos = {
my : "left" + offsetString ( markerOffsetX ) + " center" ,
at : "left " + markerAt ,
of : $targetTitle
} ;
if ( options . rtl ) {
pos . my = "right" + offsetString ( - markerOffsetX ) + " center" ;
pos . at = "right " + markerAt ;
// console.log("rtl", pos);
}
2018-01-01 14:39:23 +00:00
$dropMarker
. toggleClass ( classDropAfter , hitMode === "after" )
. toggleClass ( classDropOver , hitMode === "over" )
. toggleClass ( classDropBefore , hitMode === "before" )
. show ( )
2018-05-24 18:59:32 +00:00
. position ( FT . fixPositionOptions ( pos ) ) ;
2018-01-01 14:39:23 +00:00
} else {
$dropMarker . hide ( ) ;
// console.log("hide dropmarker")
}
2018-05-24 18:59:32 +00:00
2018-01-01 14:39:23 +00:00
$ ( targetNode . span )
. toggleClass ( classDropTarget , hitMode === "after" || hitMode === "before" || hitMode === "over" )
. toggleClass ( classDropAfter , hitMode === "after" )
. toggleClass ( classDropBefore , hitMode === "before" )
. toggleClass ( classDropAccept , hitMode === "over" )
. toggleClass ( classDropReject , hitMode === false ) ;
return hitMode ;
}
2018-05-24 18:59:32 +00:00
/ * G u e s s d r o p E f f e c t f r o m m o d i f i e r k e y s .
* Safari :
* It seems that ` dataTransfer.dropEffect ` can only be set on dragStart , and will remain
* even if the cursor changes when [ Alt ] or [ Ctrl ] are pressed ( ? )
* Using rules suggested here :
* https : //ux.stackexchange.com/a/83769
* @ returns
* 'copy' , 'link' , 'move' , or 'none'
* /
function getDropEffect ( event , data ) {
var dndOpts = data . options . dnd5 ,
res = dndOpts . dropEffectDefault
// dataTransfer = event.dataTransfer || event.originalEvent.dataTransfer,
;
// Use callback if any:
if ( dndOpts . dropEffect ) {
return dndOpts . dropEffect ( event , data ) ;
}
if ( isMac ) {
if ( event . metaKey && event . altKey ) { // Mac: [Control] + [Option]
return "link" ;
} else if ( event . metaKey ) { // Mac: [Command]
return "move" ;
} else if ( event . altKey ) { // Mac: [Option]
return "copy" ;
}
} else {
if ( event . ctrlKey ) { // Windows: [Ctrl]
return "copy" ;
} else if ( event . shiftKey ) { // Windows: [Shift]
return "move" ;
} else if ( event . altKey ) { // Windows: [Alt]
return "link" ;
}
}
// data.tree.debug("getDropEffect: " + res);
return res ;
}
2018-01-01 14:39:23 +00:00
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* /
$ . ui . fancytree . registerExtension ( {
name : "dnd5" ,
2018-05-24 18:59:32 +00:00
version : "2.28.1" ,
2018-01-01 14:39:23 +00:00
// Default options for this extension.
options : {
autoExpandMS : 1500 , // Expand nodes after n milliseconds of hovering
2018-05-24 18:59:32 +00:00
dropMarkerInsertOffsetX : - 16 , // Additional offset for drop-marker with hitMode = "before"/"after"
dropMarkerOffsetX : - 24 , // Absolute position offset for .fancytree-drop-marker relatively to ..fancytree-title (icon/img near a node accepting drop)
multiSource : false , // true: Drag multiple (i.e. selected) nodes.
dragImage : null , // Callback(node, data) that can be used to call dataTransfer.setDragImage().
dropEffect : null , // Callback(node, data) that returns 'copy', 'link', 'move', or 'none'.
dropEffectDefault : "move" , // Default dropEffect ('copy', 'link', or 'move').
2018-01-01 14:39:23 +00:00
preventForeignNodes : false , // Prevent dropping nodes from different Fancytrees
preventNonNodes : false , // Prevent dropping items other than Fancytree nodes
preventRecursiveMoves : true , // Prevent dropping nodes on own descendants
preventVoidMoves : true , // Prevent dropping nodes 'before self', etc.
scroll : true , // Enable auto-scrolling while dragging
scrollSensitivity : 20 , // Active top/bottom margin in pixel
scrollSpeed : 5 , // Pixel per event
2018-05-24 18:59:32 +00:00
setTextTypeJson : false , // Allow dragging of nodes to different IE windows
2018-01-01 14:39:23 +00:00
// Events (drag support)
dragStart : null , // Callback(sourceNode, data), return true, to enable dnd drag
dragDrag : $ . noop , // Callback(sourceNode, data)
dragEnd : $ . noop , // Callback(sourceNode, data)
// Events (drop support)
dragEnter : null , // Callback(targetNode, data), return true, to enable dnd drop
dragOver : $ . noop , // Callback(targetNode, data)
dragExpand : $ . noop , // Callback(targetNode, data), return false to prevent autoExpand
dragDrop : $ . noop , // Callback(targetNode, data)
dragLeave : $ . noop // Callback(targetNode, data)
} ,
treeInit : function ( ctx ) {
2018-05-24 18:59:32 +00:00
var $dragImage , $extraHelper , $temp ,
2018-01-01 14:39:23 +00:00
tree = ctx . tree ,
opts = ctx . options ,
glyph = opts . glyph || null ,
dndOpts = opts . dnd5 ,
2018-05-24 18:59:32 +00:00
getNode = FT . getNode ;
2018-01-01 14:39:23 +00:00
if ( $ . inArray ( "dnd" , opts . extensions ) >= 0 ) {
$ . error ( "Extensions 'dnd' and 'dnd5' are mutually exclusive." ) ;
}
if ( dndOpts . dragStop ) {
$ . error ( "dragStop is not used by ext-dnd5. Use dragEnd instead." ) ;
}
// Implement `opts.createNode` event to add the 'draggable' attribute
// #680: this must happen before calling super.treeInit()
if ( dndOpts . dragStart ) {
2018-05-24 18:59:32 +00:00
FT . overrideMethod ( ctx . options , "createNode" , function ( event , data ) {
2018-01-01 14:39:23 +00:00
// Default processing if any
this . _super . apply ( this , arguments ) ;
data . node . span . draggable = true ;
} ) ;
}
this . _superApply ( arguments ) ;
this . $container . addClass ( "fancytree-ext-dnd5" ) ;
// Store the current scroll parent, which may be the tree
// container, any enclosing div, or the document.
// #761: scrollParent() always needs a container child
$temp = $ ( "<span>" ) . appendTo ( this . $container ) ;
this . $scrollParent = $temp . scrollParent ( ) ;
$temp . remove ( ) ;
$dropMarker = $ ( "#fancytree-drop-marker" ) ;
if ( ! $dropMarker . length ) {
$dropMarker = $ ( "<div id='fancytree-drop-marker'></div>" )
. hide ( )
. css ( {
"z-index" : 1000 ,
// Drop marker should not steal dragenter/dragover events:
"pointer-events" : "none"
} ) . prependTo ( "body" ) ;
if ( glyph ) {
2018-05-24 18:59:32 +00:00
FT . setSpanIcon ( $dropMarker [ 0 ] , glyph . map . _addClass , glyph . map . dropMarker ) ;
// $dropMarker.addClass(glyph.map._addClass + " " + glyph.map.dropMarker);
2018-01-01 14:39:23 +00:00
}
}
2018-05-24 18:59:32 +00:00
$dropMarker . toggleClass ( "fancytree-rtl" , ! ! opts . rtl ) ;
2018-01-01 14:39:23 +00:00
// Enable drag support if dragStart() is specified:
if ( dndOpts . dragStart ) {
// Bind drag event handlers
tree . $container . on ( "dragstart drag dragend" , function ( event ) {
var json ,
node = getNode ( event ) ,
dataTransfer = event . dataTransfer || event . originalEvent . dataTransfer ,
data = {
node : node ,
tree : tree ,
options : tree . options ,
originalEvent : event ,
dataTransfer : dataTransfer ,
// dropEffect: undefined, // set by dragend
isCancelled : undefined // set by dragend
2018-05-24 18:59:32 +00:00
} ,
dropEffect = getDropEffect ( event , data ) ,
isMove = dropEffect === "move" ;
2018-01-01 14:39:23 +00:00
2018-05-24 18:59:32 +00:00
// console.log(event.type, "dropEffect: " + dropEffect);
2018-01-01 14:39:23 +00:00
switch ( event . type ) {
case "dragstart" :
// Store current source node in different formats
SOURCE _NODE = node ;
2018-05-24 18:59:32 +00:00
// Also optionally store selected nodes
if ( dndOpts . multiSource === false ) {
SOURCE _NODE _LIST = [ node ] ;
} else if ( dndOpts . multiSource === true ) {
SOURCE _NODE _LIST = tree . getSelectedNodes ( ) ;
if ( ! node . isSelected ( ) ) {
SOURCE _NODE _LIST . unshift ( node ) ;
}
} else {
SOURCE _NODE _LIST = dndOpts . multiSource ( node , data ) ;
}
// Cache as array of jQuery objects for faster access:
$sourceList = $ ( $ . map ( SOURCE _NODE _LIST , function ( n ) {
return n . span ;
} ) ) ;
// Set visual feedback
$sourceList . addClass ( classDragSource ) ;
2018-01-01 14:39:23 +00:00
// Set payload
// Note:
// Transfer data is only accessible on dragstart and drop!
// For all other events the formats and kinds in the drag
// data store list of items representing dragged data can be
// enumerated, but the data itself is unavailable and no new
// data can be added.
json = JSON . stringify ( node . toDict ( ) ) ;
try {
dataTransfer . setData ( nodeMimeType , json ) ;
dataTransfer . setData ( "text/html" , $ ( node . span ) . html ( ) ) ;
dataTransfer . setData ( "text/plain" , node . title ) ;
} catch ( ex ) {
// IE only accepts 'text' type
tree . warn ( "Could not set data (IE only accepts 'text') - " + ex ) ;
}
// We always need to set the 'text' type if we want to drag
// Because IE 11 only accepts this single type.
// If we pass JSON here, IE can can access all node properties,
// even when the source lives in another window. (D'n'd inside
// the same window will always work.)
// The drawback is, that in this case ALL browsers will see
// the JSON representation as 'text', so dragging
// to a text field will insert the JSON string instead of
// the node title.
if ( dndOpts . setTextTypeJson ) {
dataTransfer . setData ( "text" , json ) ;
} else {
dataTransfer . setData ( "text" , node . title ) ;
}
// Set the allowed and current drag mode (move, copy, or link)
dataTransfer . effectAllowed = "all" ; // "copyMove"
2018-05-24 18:59:32 +00:00
// dropEffect = "move";
$extraHelper = null ;
2018-01-01 14:39:23 +00:00
2018-05-24 18:59:32 +00:00
if ( dndOpts . dragImage ) {
// Let caller set a custom drag image using dataTransfer.setDragImage()
// and/or modify visual feedback otherwise.
dndOpts . dragImage ( node , data ) ;
} else {
// Set the title as drag image (otherwise it would contain the expander)
$dragImage = $ ( node . span ) . find ( ".fancytree-title" ) ;
if ( SOURCE _NODE _LIST && SOURCE _NODE _LIST . length > 1 ) {
// Add a counter badge to node title if dragging more than one node.
// We want this, because the element that is used as drag image
// must be *visible* in the DOM, so we cannot create some hidden
// custom markup.
// See https://kryogenix.org/code/browser/custom-drag-image.html
// Also, since IE 11 and Edge don't support setDragImage() alltogether,
// it gives som feedback to the user.
// The badge will be removed later on drag end.
$extraHelper = $ ( "<span class='fancytree-childcounter'/>" )
. text ( "+" + ( SOURCE _NODE _LIST . length - 1 ) )
. appendTo ( $dragImage ) ;
}
if ( dataTransfer . setDragImage ) {
// IE 11 and Edge do not support this
dataTransfer . setDragImage ( $dragImage [ 0 ] , - 10 , - 10 ) ;
}
2018-01-01 14:39:23 +00:00
}
// Let user modify above settings
return dndOpts . dragStart ( node , data ) !== false ;
case "drag" :
// Called every few miliseconds
2018-05-24 18:59:32 +00:00
// data.tree.info("drag", SOURCE_NODE)
$sourceList . toggleClass ( classDragRemove , isMove ) ;
2018-01-01 14:39:23 +00:00
dndOpts . dragDrag ( node , data ) ;
break ;
case "dragend" :
2018-05-24 18:59:32 +00:00
$sourceList . removeClass ( classDragSource + " " + classDragRemove ) ;
_clearGlobals ( ) ;
// data.dropEffect = dropEffect;
data . isCancelled = ( dropEffect === "none" ) ;
2018-01-01 14:39:23 +00:00
$dropMarker . hide ( ) ;
2018-05-24 18:59:32 +00:00
// Take this badge off of me - I can't use it anymore:
if ( $extraHelper ) {
$extraHelper . remove ( ) ;
$extraHelper = null ;
}
2018-01-01 14:39:23 +00:00
dndOpts . dragEnd ( node , data ) ;
break ;
}
} ) ;
}
// Enable drop support if dragEnter() is specified:
if ( dndOpts . dragEnter ) {
// Bind drop event handlers
tree . $container . on ( "dragenter dragover dragleave drop" , function ( event ) {
var json , nodeData , r , res ,
allowDrop = null ,
node = getNode ( event ) ,
dataTransfer = event . dataTransfer || event . originalEvent . dataTransfer ,
2018-05-24 18:59:32 +00:00
// dropEffect = getDropEffect(dataTransfer, opts),
2018-01-01 14:39:23 +00:00
data = {
node : node ,
tree : tree ,
options : tree . options ,
hitMode : DRAG _ENTER _RESPONSE ,
originalEvent : event ,
dataTransfer : dataTransfer ,
otherNode : SOURCE _NODE || null ,
2018-05-24 18:59:32 +00:00
otherNodeList : SOURCE _NODE _LIST || null ,
2018-01-01 14:39:23 +00:00
otherNodeData : null , // set by drop event
dropEffect : undefined , // set by drop event
isCancelled : undefined // set by drop event
} ;
switch ( event . type ) {
case "dragenter" :
// The dragenter event is fired when a dragged element or
// text selection enters a valid drop target.
if ( ! node ) {
// Sometimes we get dragenter for the container element
tree . debug ( "Ignore non-node " + event . type + ": " + event . target . tagName + "." + event . target . className ) ;
DRAG _ENTER _RESPONSE = false ;
break ;
}
$ ( node . span )
. addClass ( classDropOver )
. removeClass ( classDropAccept + " " + classDropReject ) ;
if ( dndOpts . preventNonNodes && ! nodeData ) {
2018-05-24 18:59:32 +00:00
node . debug ( "Reject dropping a non-node." ) ;
2018-01-01 14:39:23 +00:00
DRAG _ENTER _RESPONSE = false ;
break ;
} else if ( dndOpts . preventForeignNodes && ( ! SOURCE _NODE || SOURCE _NODE . tree !== node . tree ) ) {
2018-05-24 18:59:32 +00:00
node . debug ( "Reject dropping a foreign node." ) ;
2018-01-01 14:39:23 +00:00
DRAG _ENTER _RESPONSE = false ;
break ;
}
// NOTE: dragenter is fired BEFORE the dragleave event
// of the previous element!
// https://www.w3.org/Bugs/Public/show_bug.cgi?id=19041
setTimeout ( function ( ) {
// node.info("DELAYED " + event.type, event.target, DRAG_ENTER_RESPONSE);
// Auto-expand node (only when 'over' the node, not 'before', or 'after')
if ( dndOpts . autoExpandMS &&
node . hasChildren ( ) !== false && ! node . expanded &&
( ! dndOpts . dragExpand || dndOpts . dragExpand ( node , data ) !== false )
) {
node . scheduleAction ( "expand" , dndOpts . autoExpandMS ) ;
}
} , 0 ) ;
$dropMarker . show ( ) ;
// Call dragEnter() to figure out if (and where) dropping is allowed
if ( dndOpts . preventRecursiveMoves && node . isDescendantOf ( data . otherNode ) ) {
2018-05-24 18:59:32 +00:00
node . debug ( "Reject dropping below own ancestor." ) ;
2018-01-01 14:39:23 +00:00
res = false ;
} else {
r = dndOpts . dragEnter ( node , data ) ;
res = normalizeDragEnterResponse ( r ) ;
}
DRAG _ENTER _RESPONSE = res ;
allowDrop = res && ( res . over || res . before || res . after ) ;
break ;
case "dragover" :
// The dragover event is fired when an element or text
// selection is being dragged over a valid drop target
// (every few hundred milliseconds).
2018-05-24 18:59:32 +00:00
// console.log(event.type, "dropEffect: " + dataTransfer.dropEffect)
2018-01-01 14:39:23 +00:00
LAST _HIT _MODE = handleDragOver ( event , data ) ;
allowDrop = ! ! LAST _HIT _MODE ;
break ;
case "dragleave" :
// NOTE: dragleave is fired AFTER the dragenter event of the
// FOLLOWING element.
if ( ! node ) {
tree . debug ( "Ignore non-node " + event . type + ": " + event . target . tagName + "." + event . target . className ) ;
break ;
}
if ( ! $ ( node . span ) . hasClass ( classDropOver ) ) {
node . debug ( "Ignore dragleave (multi)" ) ; //, event.currentTarget);
break ;
}
$ ( node . span ) . removeClass ( classDropOver + " " + classDropAccept + " " + classDropReject ) ;
node . scheduleAction ( "cancel" ) ;
dndOpts . dragLeave ( node , data ) ;
$dropMarker . hide ( ) ;
break ;
case "drop" :
// Data is only readable in the (dragenter and) drop event:
if ( $ . inArray ( nodeMimeType , dataTransfer . types ) >= 0 ) {
nodeData = dataTransfer . getData ( nodeMimeType ) ;
tree . info ( event . type + ": getData('application/x-fancytree-node'): '" + nodeData + "'" ) ;
}
if ( ! nodeData ) {
// 1. Source is not a Fancytree node, or
// 2. If the FT mime type was set, but returns '', this
// is probably IE 11 (which only supports 'text')
nodeData = dataTransfer . getData ( "text" ) ;
tree . info ( event . type + ": getData('text'): '" + nodeData + "'" ) ;
}
if ( nodeData ) {
try {
// 'text' type may contain JSON if IE is involved
// and setTextTypeJson option was set
json = JSON . parse ( nodeData ) ;
if ( json . title !== undefined ) {
data . otherNodeData = json ;
}
} catch ( ex ) {
// assume 'text' type contains plain text, so `otherNodeData`
// should not be set
}
}
tree . debug ( event . type + ": nodeData: '" + nodeData + "', otherNodeData: " , data . otherNodeData ) ;
$ ( node . span ) . removeClass ( classDropOver + " " + classDropAccept + " " + classDropReject ) ;
$dropMarker . hide ( ) ;
data . hitMode = LAST _HIT _MODE ;
data . dropEffect = dataTransfer . dropEffect ;
data . isCancelled = data . dropEffect === "none" ;
// Let user implement the actual drop operation
dndOpts . dragDrop ( node , data ) ;
// Prevent browser's default drop handling
event . preventDefault ( ) ;
2018-05-24 18:59:32 +00:00
_clearGlobals ( ) ;
2018-01-01 14:39:23 +00:00
break ;
}
// Dnd API madness: we must PREVENT default handling to enable dropping
if ( allowDrop ) {
event . preventDefault ( ) ;
return false ;
}
} ) ;
}
}
} ) ;
// Value returned by `require('jquery.fancytree..')`
return $ . ui . fancytree ;
} ) ) ; // End of closure
/*! Extension 'jquery.fancytree.edit.js' */ / * !
* jquery . fancytree . edit . js
*
* Make node titles editable .
* ( Extension module for jquery . fancytree . js : https : //github.com/mar10/fancytree/)
*
2018-05-24 18:59:32 +00:00
* Copyright ( c ) 2008 - 2018 , Martin Wendt ( http : //wwWendt.de)
2018-01-01 14:39:23 +00:00
*
* Released under the MIT license
* https : //github.com/mar10/fancytree/wiki/LicenseInfo
*
2018-05-24 18:59:32 +00:00
* @ version 2.28 . 1
* @ date 2018 - 03 - 19 T06 : 47 : 37 Z
2018-01-01 14:39:23 +00:00
* /
; ( function ( factory ) {
if ( typeof define === "function" && define . amd ) {
// AMD. Register as an anonymous module.
define ( [ "jquery" , "./jquery.fancytree" ] , factory ) ;
} else if ( typeof module === "object" && module . exports ) {
// Node/CommonJS
2018-05-24 18:59:32 +00:00
require ( "./jquery.fancytree" ) ;
2018-01-01 14:39:23 +00:00
module . exports = factory ( require ( "jquery" ) ) ;
} else {
// Browser globals
factory ( jQuery ) ;
}
} ( function ( $ ) {
"use strict" ;
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Private functions and variables
* /
var isMac = /Mac/ . test ( navigator . platform ) ,
escapeHtml = $ . ui . fancytree . escapeHtml ,
unescapeHtml = $ . ui . fancytree . unescapeHtml ;
/ * *
* [ ext - edit ] Start inline editing of current node title .
*
* @ alias FancytreeNode # editStart
* @ requires Fancytree
* /
$ . ui . fancytree . _FancytreeNodeClass . prototype . editStart = function ( ) {
var $input ,
node = this ,
tree = this . tree ,
local = tree . ext . edit ,
instOpts = tree . options . edit ,
$title = $ ( ".fancytree-title" , node . span ) ,
eventData = {
node : node ,
tree : tree ,
options : tree . options ,
isNew : $ ( node [ tree . statusClassPropName ] ) . hasClass ( "fancytree-edit-new" ) ,
orgTitle : node . title ,
input : null ,
dirty : false
} ;
// beforeEdit may want to modify the title before editing
if ( instOpts . beforeEdit . call ( node , { type : "beforeEdit" } , eventData ) === false ) {
return false ;
}
$ . ui . fancytree . assert ( ! local . currentNode , "recursive edit" ) ;
local . currentNode = this ;
local . eventData = eventData ;
// Disable standard Fancytree mouse- and key handling
tree . widget . _unbind ( ) ;
// #116: ext-dnd prevents the blur event, so we have to catch outer clicks
$ ( document ) . on ( "mousedown.fancytree-edit" , function ( event ) {
if ( ! $ ( event . target ) . hasClass ( "fancytree-edit-input" ) ) {
node . editEnd ( true , event ) ;
}
} ) ;
// Replace node with <input>
$input = $ ( "<input />" , {
"class" : "fancytree-edit-input" ,
type : "text" ,
value : tree . options . escapeTitles ? eventData . orgTitle : unescapeHtml ( eventData . orgTitle )
} ) ;
local . eventData . input = $input ;
if ( instOpts . adjustWidthOfs != null ) {
$input . width ( $title . width ( ) + instOpts . adjustWidthOfs ) ;
}
if ( instOpts . inputCss != null ) {
$input . css ( instOpts . inputCss ) ;
}
$title . html ( $input ) ;
// Focus <input> and bind keyboard handler
$input
. focus ( )
. change ( function ( event ) {
$input . addClass ( "fancytree-edit-dirty" ) ;
} ) . keydown ( function ( event ) {
switch ( event . which ) {
case $ . ui . keyCode . ESCAPE :
node . editEnd ( false , event ) ;
break ;
case $ . ui . keyCode . ENTER :
node . editEnd ( true , event ) ;
return false ; // so we don't start editmode on Mac
}
event . stopPropagation ( ) ;
} ) . blur ( function ( event ) {
return node . editEnd ( true , event ) ;
} ) ;
instOpts . edit . call ( node , { type : "edit" } , eventData ) ;
} ;
/ * *
* [ ext - edit ] Stop inline editing .
* @ param { Boolean } [ applyChanges = false ] false : cancel edit , true : save ( if modified )
* @ alias FancytreeNode # editEnd
* @ requires jquery . fancytree . edit . js
* /
$ . ui . fancytree . _FancytreeNodeClass . prototype . editEnd = function ( applyChanges , _event ) {
var newVal ,
node = this ,
tree = this . tree ,
local = tree . ext . edit ,
eventData = local . eventData ,
instOpts = tree . options . edit ,
$title = $ ( ".fancytree-title" , node . span ) ,
$input = $title . find ( "input.fancytree-edit-input" ) ;
if ( instOpts . trim ) {
$input . val ( $ . trim ( $input . val ( ) ) ) ;
}
newVal = $input . val ( ) ;
eventData . dirty = ( newVal !== node . title ) ;
eventData . originalEvent = _event ;
// Find out, if saving is required
if ( applyChanges === false ) {
// If true/false was passed, honor this (except in rename mode, if unchanged)
eventData . save = false ;
} else if ( eventData . isNew ) {
2018-05-24 18:59:32 +00:00
// In create mode, we save everything, except for empty text
2018-01-01 14:39:23 +00:00
eventData . save = ( newVal !== "" ) ;
} else {
// In rename mode, we save everyting, except for empty or unchanged text
eventData . save = eventData . dirty && ( newVal !== "" ) ;
}
// Allow to break (keep editor open), modify input, or re-define data.save
if ( instOpts . beforeClose . call ( node , { type : "beforeClose" } , eventData ) === false ) {
return false ;
}
if ( eventData . save && instOpts . save . call ( node , { type : "save" } , eventData ) === false ) {
return false ;
}
$input
. removeClass ( "fancytree-edit-dirty" )
. off ( ) ;
// Unbind outer-click handler
$ ( document ) . off ( ".fancytree-edit" ) ;
if ( eventData . save ) {
// # 171: escape user input (not required if global escaping is on)
node . setTitle ( tree . options . escapeTitles ? newVal : escapeHtml ( newVal ) ) ;
node . setFocus ( ) ;
} else {
if ( eventData . isNew ) {
node . remove ( ) ;
node = eventData . node = null ;
local . relatedNode . setFocus ( ) ;
} else {
node . renderTitle ( ) ;
node . setFocus ( ) ;
}
}
local . eventData = null ;
local . currentNode = null ;
local . relatedNode = null ;
// Re-enable mouse and keyboard handling
tree . widget . _bind ( ) ;
// Set keyboard focus, even if setFocus() claims 'nothing to do'
$ ( tree . $container ) . focus ( ) ;
eventData . input = null ;
instOpts . close . call ( node , { type : "close" } , eventData ) ;
return true ;
} ;
/ * *
* [ ext - edit ] Create a new child or sibling node and start edit mode .
*
* @ param { String } [ mode = 'child' ] 'before' , 'after' , or 'child'
* @ param { Object } [ init ] NodeData ( or simple title string )
* @ alias FancytreeNode # editCreateNode
* @ requires jquery . fancytree . edit . js
* @ since 2.4
* /
$ . ui . fancytree . _FancytreeNodeClass . prototype . editCreateNode = function ( mode , init ) {
var newNode ,
tree = this . tree ,
self = this ;
mode = mode || "child" ;
if ( init == null ) {
init = { title : "" } ;
} else if ( typeof init === "string" ) {
init = { title : init } ;
} else {
$ . ui . fancytree . assert ( $ . isPlainObject ( init ) ) ;
}
// Make sure node is expanded (and loaded) in 'child' mode
if ( mode === "child" && ! this . isExpanded ( ) && this . hasChildren ( ) !== false ) {
this . setExpanded ( ) . done ( function ( ) {
self . editCreateNode ( mode , init ) ;
} ) ;
return ;
}
newNode = this . addNode ( init , mode ) ;
// #644: Don't filter new nodes.
newNode . match = true ;
$ ( newNode [ tree . statusClassPropName ] )
. removeClass ( "fancytree-hide" )
. addClass ( "fancytree-match" ) ;
newNode . makeVisible ( /*{noAnimation: true}*/ ) . done ( function ( ) {
$ ( newNode [ tree . statusClassPropName ] ) . addClass ( "fancytree-edit-new" ) ;
self . tree . ext . edit . relatedNode = self ;
newNode . editStart ( ) ;
} ) ;
} ;
/ * *
* [ ext - edit ] Check if any node in this tree in edit mode .
*
* @ returns { FancytreeNode | null }
* @ alias Fancytree # isEditing
* @ requires jquery . fancytree . edit . js
* /
$ . ui . fancytree . _FancytreeClass . prototype . isEditing = function ( ) {
return this . ext . edit ? this . ext . edit . currentNode : null ;
} ;
/ * *
* [ ext - edit ] Check if this node is in edit mode .
* @ returns { Boolean } true if node is currently beeing edited
* @ alias FancytreeNode # isEditing
* @ requires jquery . fancytree . edit . js
* /
$ . ui . fancytree . _FancytreeNodeClass . prototype . isEditing = function ( ) {
return this . tree . ext . edit ? this . tree . ext . edit . currentNode === this : false ;
} ;
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Extension code
* /
$ . ui . fancytree . registerExtension ( {
name : "edit" ,
2018-05-24 18:59:32 +00:00
version : "2.28.1" ,
2018-01-01 14:39:23 +00:00
// Default options for this extension.
options : {
adjustWidthOfs : 4 , // null: don't adjust input size to content
allowEmpty : false , // Prevent empty input
inputCss : { minWidth : "3em" } ,
// triggerCancel: ["esc", "tab", "click"],
2018-05-24 18:59:32 +00:00
triggerStart : [ "f2" , "mac+enter" , "shift+click" ] ,
2018-01-01 14:39:23 +00:00
trim : true , // Trim whitespace before save
// Events:
beforeClose : $ . noop , // Return false to prevent cancel/save (data.input is available)
beforeEdit : $ . noop , // Return false to prevent edit mode
close : $ . noop , // Editor was removed
edit : $ . noop , // Editor was opened (available as data.input)
// keypress: $.noop, // Not yet implemented
save : $ . noop // Save data.input.val() or return false to keep editor open
} ,
// Local attributes
currentNode : null ,
treeInit : function ( ctx ) {
this . _superApply ( arguments ) ;
this . $container . addClass ( "fancytree-ext-edit" ) ;
} ,
nodeClick : function ( ctx ) {
if ( $ . inArray ( "shift+click" , ctx . options . edit . triggerStart ) >= 0 ) {
if ( ctx . originalEvent . shiftKey ) {
ctx . node . editStart ( ) ;
return false ;
}
}
2018-05-24 18:59:32 +00:00
if ( $ . inArray ( "clickActive" , ctx . options . edit . triggerStart ) >= 0 ) {
// Only when click was inside title text (not aynwhere else in the row)
if ( ctx . node . isActive ( ) && ! ctx . node . isEditing ( ) &&
$ ( ctx . originalEvent . target ) . hasClass ( "fancytree-title" )
) {
ctx . node . editStart ( ) ;
return false ;
}
}
2018-01-01 14:39:23 +00:00
return this . _superApply ( arguments ) ;
} ,
nodeDblclick : function ( ctx ) {
if ( $ . inArray ( "dblclick" , ctx . options . edit . triggerStart ) >= 0 ) {
ctx . node . editStart ( ) ;
return false ;
}
return this . _superApply ( arguments ) ;
} ,
nodeKeydown : function ( ctx ) {
switch ( ctx . originalEvent . which ) {
case 113 : // [F2]
if ( $ . inArray ( "f2" , ctx . options . edit . triggerStart ) >= 0 ) {
ctx . node . editStart ( ) ;
return false ;
}
break ;
case $ . ui . keyCode . ENTER :
if ( $ . inArray ( "mac+enter" , ctx . options . edit . triggerStart ) >= 0 && isMac ) {
ctx . node . editStart ( ) ;
return false ;
}
break ;
}
return this . _superApply ( arguments ) ;
}
} ) ;
// Value returned by `require('jquery.fancytree..')`
return $ . ui . fancytree ;
} ) ) ; // End of closure
/*! Extension 'jquery.fancytree.filter.js' */ / * !
* jquery . fancytree . filter . js
*
* Remove or highlight tree nodes , based on a filter .
* ( Extension module for jquery . fancytree . js : https : //github.com/mar10/fancytree/)
*
2018-05-24 18:59:32 +00:00
* Copyright ( c ) 2008 - 2018 , Martin Wendt ( http : //wwWendt.de)
2018-01-01 14:39:23 +00:00
*
* Released under the MIT license
* https : //github.com/mar10/fancytree/wiki/LicenseInfo
*
2018-05-24 18:59:32 +00:00
* @ version 2.28 . 1
* @ date 2018 - 03 - 19 T06 : 47 : 37 Z
2018-01-01 14:39:23 +00:00
* /
; ( function ( factory ) {
if ( typeof define === "function" && define . amd ) {
// AMD. Register as an anonymous module.
define ( [ "jquery" , "./jquery.fancytree" ] , factory ) ;
} else if ( typeof module === "object" && module . exports ) {
// Node/CommonJS
2018-05-24 18:59:32 +00:00
require ( "./jquery.fancytree" ) ;
2018-01-01 14:39:23 +00:00
module . exports = factory ( require ( "jquery" ) ) ;
} else {
// Browser globals
factory ( jQuery ) ;
}
} ( function ( $ ) {
"use strict" ;
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Private functions and variables
* /
var KeyNoData = "__not_found__" ,
escapeHtml = $ . ui . fancytree . escapeHtml ;
function _escapeRegex ( str ) {
/*jshint regexdash:true */
return ( str + "" ) . replace ( /([.?*+\^\$\[\]\\(){}|-])/g , "\\$1" ) ;
}
function extractHtmlText ( s ) {
if ( s . indexOf ( ">" ) >= 0 ) {
return $ ( "<div/>" ) . html ( s ) . text ( ) ;
}
return s ;
}
$ . ui . fancytree . _FancytreeClass . prototype . _applyFilterImpl = function ( filter , branchMode , _opts ) {
var match , statusNode , re , reHighlight , temp ,
count = 0 ,
treeOpts = this . options ,
escapeTitles = treeOpts . escapeTitles ,
prevAutoCollapse = treeOpts . autoCollapse ,
opts = $ . extend ( { } , treeOpts . filter , _opts ) ,
hideMode = opts . mode === "hide" ,
leavesOnly = ! ! opts . leavesOnly && ! branchMode ;
// Default to 'match title substring (not case sensitive)'
if ( typeof filter === "string" ) {
if ( filter === "" ) {
this . warn ( "Fancytree passing an empty string as a filter is handled as clearFilter()." ) ;
this . clearFilter ( ) ;
return ;
}
if ( opts . fuzzy ) {
// See https://codereview.stackexchange.com/questions/23899/faster-javascript-fuzzy-string-matching-function/23905#23905
// and http://www.quora.com/How-is-the-fuzzy-search-algorithm-in-Sublime-Text-designed
// and http://www.dustindiaz.com/autocomplete-fuzzy-matching
match = filter . split ( "" ) . reduce ( function ( a , b ) {
return a + "[^" + b + "]*" + b ;
} ) ;
} else {
match = _escapeRegex ( filter ) ; // make sure a '.' is treated literally
}
re = new RegExp ( ".*" + match + ".*" , "i" ) ;
reHighlight = new RegExp ( _escapeRegex ( filter ) , "gi" ) ;
filter = function ( node ) {
2018-05-24 18:59:32 +00:00
if ( ! node . title ) {
return false ;
}
2018-01-01 14:39:23 +00:00
var text = escapeTitles ? node . title : extractHtmlText ( node . title ) ,
res = ! ! re . test ( text ) ;
if ( res && opts . highlight ) {
if ( escapeTitles ) {
// #740: we must not apply the marks to escaped entity names, e.g. `"`
// Use some exotic characters to mark matches:
temp = text . replace ( reHighlight , function ( s ) {
return "\uFFF7" + s + "\uFFF8" ;
} ) ;
// now we can escape the title...
node . titleWithHighlight = escapeHtml ( temp )
// ... and finally insert the desired `<mark>` tags
. replace ( /\uFFF7/g , "<mark>" )
. replace ( /\uFFF8/g , "</mark>" ) ;
} else {
node . titleWithHighlight = text . replace ( reHighlight , function ( s ) {
return "<mark>" + s + "</mark>" ;
} ) ;
}
// node.debug("filter", escapeTitles, text, node.titleWithHighlight);
}
return res ;
} ;
}
this . enableFilter = true ;
this . lastFilterArgs = arguments ;
this . $div . addClass ( "fancytree-ext-filter" ) ;
if ( hideMode ) {
this . $div . addClass ( "fancytree-ext-filter-hide" ) ;
} else {
this . $div . addClass ( "fancytree-ext-filter-dimm" ) ;
}
this . $div . toggleClass ( "fancytree-ext-filter-hide-expanders" , ! ! opts . hideExpanders ) ;
// Reset current filter
this . visit ( function ( node ) {
delete node . match ;
delete node . titleWithHighlight ;
node . subMatchCount = 0 ;
} ) ;
statusNode = this . getRootNode ( ) . _findDirectChild ( KeyNoData ) ;
if ( statusNode ) {
statusNode . remove ( ) ;
}
// Adjust node.hide, .match, and .subMatchCount properties
treeOpts . autoCollapse = false ; // #528
this . visit ( function ( node ) {
if ( leavesOnly && node . children != null ) {
return ;
}
var res = filter ( node ) ,
matchedByBranch = false ;
if ( res === "skip" ) {
node . visit ( function ( c ) {
c . match = false ;
} , true ) ;
return "skip" ;
}
if ( ! res && ( branchMode || res === "branch" ) && node . parent . match ) {
res = true ;
matchedByBranch = true ;
}
if ( res ) {
count ++ ;
node . match = true ;
node . visitParents ( function ( p ) {
p . subMatchCount += 1 ;
// Expand match (unless this is no real match, but only a node in a matched branch)
if ( opts . autoExpand && ! matchedByBranch && ! p . expanded ) {
p . setExpanded ( true , { noAnimation : true , noEvents : true , scrollIntoView : false } ) ;
p . _filterAutoExpanded = true ;
}
} ) ;
}
} ) ;
treeOpts . autoCollapse = prevAutoCollapse ;
if ( count === 0 && opts . nodata && hideMode ) {
statusNode = opts . nodata ;
if ( $ . isFunction ( statusNode ) ) {
statusNode = statusNode ( ) ;
}
if ( statusNode === true ) {
statusNode = { } ;
} else if ( typeof statusNode === "string" ) {
statusNode = { title : statusNode } ;
}
statusNode = $ . extend ( {
statusNodeType : "nodata" ,
key : KeyNoData ,
title : this . options . strings . noData
} , statusNode ) ;
this . getRootNode ( ) . addNode ( statusNode ) . match = true ;
}
// Redraw whole tree
this . render ( ) ;
return count ;
} ;
/ * *
* [ ext - filter ] Dimm or hide nodes .
*
* @ param { function | string } filter
* @ param { boolean } [ opts = { autoExpand : false , leavesOnly : false } ]
* @ returns { integer } count
* @ alias Fancytree # filterNodes
* @ requires jquery . fancytree . filter . js
* /
$ . ui . fancytree . _FancytreeClass . prototype . filterNodes = function ( filter , opts ) {
if ( typeof opts === "boolean" ) {
opts = { leavesOnly : opts } ;
this . warn ( "Fancytree.filterNodes() leavesOnly option is deprecated since 2.9.0 / 2015-04-19. Use opts.leavesOnly instead." ) ;
}
return this . _applyFilterImpl ( filter , false , opts ) ;
} ;
/ * *
* @ deprecated
* /
$ . ui . fancytree . _FancytreeClass . prototype . applyFilter = function ( filter ) {
this . warn ( "Fancytree.applyFilter() is deprecated since 2.1.0 / 2014-05-29. Use .filterNodes() instead." ) ;
return this . filterNodes . apply ( this , arguments ) ;
} ;
/ * *
* [ ext - filter ] Dimm or hide whole branches .
*
* @ param { function | string } filter
* @ param { boolean } [ opts = { autoExpand : false } ]
* @ returns { integer } count
* @ alias Fancytree # filterBranches
* @ requires jquery . fancytree . filter . js
* /
$ . ui . fancytree . _FancytreeClass . prototype . filterBranches = function ( filter , opts ) {
return this . _applyFilterImpl ( filter , true , opts ) ;
} ;
/ * *
* [ ext - filter ] Reset the filter .
*
* @ alias Fancytree # clearFilter
* @ requires jquery . fancytree . filter . js
* /
$ . ui . fancytree . _FancytreeClass . prototype . clearFilter = function ( ) {
var $title ,
statusNode = this . getRootNode ( ) . _findDirectChild ( KeyNoData ) ,
escapeTitles = this . options . escapeTitles ,
enhanceTitle = this . options . enhanceTitle ;
if ( statusNode ) {
statusNode . remove ( ) ;
}
this . visit ( function ( node ) {
if ( node . match && node . span ) { // #491, #601
$title = $ ( node . span ) . find ( ">span.fancytree-title" ) ;
if ( escapeTitles ) {
$title . text ( node . title ) ;
} else {
$title . html ( node . title ) ;
}
if ( enhanceTitle ) {
enhanceTitle ( { type : "enhanceTitle" } , { node : node , $title : $title } ) ;
}
}
delete node . match ;
delete node . subMatchCount ;
delete node . titleWithHighlight ;
if ( node . $subMatchBadge ) {
node . $subMatchBadge . remove ( ) ;
delete node . $subMatchBadge ;
}
if ( node . _filterAutoExpanded && node . expanded ) {
node . setExpanded ( false , { noAnimation : true , noEvents : true , scrollIntoView : false } ) ;
}
delete node . _filterAutoExpanded ;
} ) ;
this . enableFilter = false ;
this . lastFilterArgs = null ;
this . $div . removeClass ( "fancytree-ext-filter fancytree-ext-filter-dimm fancytree-ext-filter-hide" ) ;
this . render ( ) ;
} ;
/ * *
* [ ext - filter ] Return true if a filter is currently applied .
*
* @ returns { Boolean }
* @ alias Fancytree # isFilterActive
* @ requires jquery . fancytree . filter . js
* @ since 2.13
* /
$ . ui . fancytree . _FancytreeClass . prototype . isFilterActive = function ( ) {
return ! ! this . enableFilter ;
} ;
/ * *
* [ ext - filter ] Return true if this node is matched by current filter ( or no filter is active ) .
*
* @ returns { Boolean }
* @ alias FancytreeNode # isMatched
* @ requires jquery . fancytree . filter . js
* @ since 2.13
* /
$ . ui . fancytree . _FancytreeNodeClass . prototype . isMatched = function ( ) {
return ! ( this . tree . enableFilter && ! this . match ) ;
} ;
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Extension code
* /
$ . ui . fancytree . registerExtension ( {
name : "filter" ,
2018-05-24 18:59:32 +00:00
version : "2.28.1" ,
2018-01-01 14:39:23 +00:00
// Default options for this extension.
options : {
autoApply : true , // Re-apply last filter if lazy data is loaded
autoExpand : false , // Expand all branches that contain matches while filtered
counter : true , // Show a badge with number of matching child nodes near parent icons
fuzzy : false , // Match single characters in order, e.g. 'fb' will match 'FooBar'
hideExpandedCounter : true , // Hide counter badge if parent is expanded
hideExpanders : false , // Hide expanders if all child nodes are hidden by filter
highlight : true , // Highlight matches by wrapping inside <mark> tags
leavesOnly : false , // Match end nodes only
nodata : true , // Display a 'no data' status node if result is empty
mode : "dimm" // Grayout unmatched nodes (pass "hide" to remove unmatched node instead)
} ,
nodeLoadChildren : function ( ctx , source ) {
return this . _superApply ( arguments ) . done ( function ( ) {
if ( ctx . tree . enableFilter && ctx . tree . lastFilterArgs && ctx . options . filter . autoApply ) {
ctx . tree . _applyFilterImpl . apply ( ctx . tree , ctx . tree . lastFilterArgs ) ;
}
} ) ;
} ,
nodeSetExpanded : function ( ctx , flag , callOpts ) {
delete ctx . node . _filterAutoExpanded ;
// Make sure counter badge is displayed again, when node is beeing collapsed
if ( ! flag && ctx . options . filter . hideExpandedCounter && ctx . node . $subMatchBadge ) {
ctx . node . $subMatchBadge . show ( ) ;
}
return this . _superApply ( arguments ) ;
} ,
nodeRenderStatus : function ( ctx ) {
// Set classes for current status
var res ,
node = ctx . node ,
tree = ctx . tree ,
opts = ctx . options . filter ,
$title = $ ( node . span ) . find ( "span.fancytree-title" ) ,
$span = $ ( node [ tree . statusClassPropName ] ) ,
enhanceTitle = ctx . options . enhanceTitle ,
escapeTitles = ctx . options . escapeTitles ;
res = this . _super ( ctx ) ;
// nothing to do, if node was not yet rendered
if ( ! $span . length || ! tree . enableFilter ) {
return res ;
}
$span
. toggleClass ( "fancytree-match" , ! ! node . match )
. toggleClass ( "fancytree-submatch" , ! ! node . subMatchCount )
. toggleClass ( "fancytree-hide" , ! ( node . match || node . subMatchCount ) ) ;
// Add/update counter badge
if ( opts . counter && node . subMatchCount && ( ! node . isExpanded ( ) || ! opts . hideExpandedCounter ) ) {
if ( ! node . $subMatchBadge ) {
node . $subMatchBadge = $ ( "<span class='fancytree-childcounter'/>" ) ;
$ ( "span.fancytree-icon, span.fancytree-custom-icon" , node . span ) . append ( node . $subMatchBadge ) ;
}
node . $subMatchBadge . show ( ) . text ( node . subMatchCount ) ;
} else if ( node . $subMatchBadge ) {
node . $subMatchBadge . hide ( ) ;
}
// node.debug("nodeRenderStatus", node.titleWithHighlight, node.title)
// #601: also chek for $title.length, because we don't need to render
// if node.span is null (i.e. not rendered)
if ( node . span && ( ! node . isEditing || ! node . isEditing . call ( node ) ) ) {
if ( node . titleWithHighlight ) {
$title . html ( node . titleWithHighlight ) ;
} else if ( escapeTitles ) {
$title . text ( node . title ) ;
} else {
$title . html ( node . title ) ;
}
if ( enhanceTitle ) {
enhanceTitle ( { type : "enhanceTitle" } , { node : node , $title : $title } ) ;
}
}
return res ;
}
} ) ;
// Value returned by `require('jquery.fancytree..')`
return $ . ui . fancytree ;
} ) ) ; // End of closure
/*! Extension 'jquery.fancytree.glyph.js' */ / * !
* jquery . fancytree . glyph . js
*
2018-05-24 18:59:32 +00:00
* Use glyph - fonts , ligature - fonts , or SVG icons instead of icon sprites .
2018-01-01 14:39:23 +00:00
* ( Extension module for jquery . fancytree . js : https : //github.com/mar10/fancytree/)
*
2018-05-24 18:59:32 +00:00
* Copyright ( c ) 2008 - 2018 , Martin Wendt ( http : //wwWendt.de)
2018-01-01 14:39:23 +00:00
*
* Released under the MIT license
* https : //github.com/mar10/fancytree/wiki/LicenseInfo
*
2018-05-24 18:59:32 +00:00
* @ version 2.28 . 1
* @ date 2018 - 03 - 19 T06 : 47 : 37 Z
2018-01-01 14:39:23 +00:00
* /
; ( function ( factory ) {
if ( typeof define === "function" && define . amd ) {
// AMD. Register as an anonymous module.
define ( [ "jquery" , "./jquery.fancytree" ] , factory ) ;
} else if ( typeof module === "object" && module . exports ) {
// Node/CommonJS
2018-05-24 18:59:32 +00:00
require ( "./jquery.fancytree" ) ;
2018-01-01 14:39:23 +00:00
module . exports = factory ( require ( "jquery" ) ) ;
} else {
// Browser globals
factory ( jQuery ) ;
}
} ( function ( $ ) {
"use strict" ;
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Private functions and variables
* /
var FT = $ . ui . fancytree ,
PRESETS = {
2018-05-24 18:59:32 +00:00
"awesome3" : { // Outdated!
_addClass : "" ,
checkbox : "icon-check-empty" ,
checkboxSelected : "icon-check" ,
checkboxUnknown : "icon-check icon-muted" ,
dragHelper : "icon-caret-right" ,
dropMarker : "icon-caret-right" ,
error : "icon-exclamation-sign" ,
expanderClosed : "icon-caret-right" ,
expanderLazy : "icon-angle-right" ,
expanderOpen : "icon-caret-down" ,
loading : "icon-refresh icon-spin" ,
nodata : "icon-meh" ,
noExpander : "" ,
radio : "icon-circle-blank" ,
radioSelected : "icon-circle" ,
// radioUnknown: "icon-circle icon-muted",
// Default node icons.
// (Use tree.options.icon callback to define custom icons based on node data)
doc : "icon-file-alt" ,
docOpen : "icon-file-alt" ,
folder : "icon-folder-close-alt" ,
folderOpen : "icon-folder-open-alt"
} ,
"awesome4" : {
_addClass : "fa" ,
checkbox : "fa-square-o" ,
checkboxSelected : "fa-check-square-o" ,
checkboxUnknown : "fa-square fancytree-helper-indeterminate-cb" ,
dragHelper : "fa-arrow-right" ,
dropMarker : "fa-long-arrow-right" ,
error : "fa-warning" ,
expanderClosed : "fa-caret-right" ,
expanderLazy : "fa-angle-right" ,
expanderOpen : "fa-caret-down" ,
// We may prevent wobbling rotations on FF by creating a separate sub element:
loading : { html : "<span class='fa fa-spinner fa-pulse' />" } ,
nodata : "fa-meh-o" ,
noExpander : "" ,
radio : "fa-circle-thin" , // "fa-circle-o"
radioSelected : "fa-circle" ,
// radioUnknown: "fa-dot-circle-o",
// Default node icons.
// (Use tree.options.icon callback to define custom icons based on node data)
doc : "fa-file-o" ,
docOpen : "fa-file-o" ,
folder : "fa-folder-o" ,
folderOpen : "fa-folder-open-o"
} ,
"awesome5" : {
// fontawesome 5 have several different base classes
// "far, fas, fal and fab" The rendered svg puts that prefix
// in a different location so we have to keep them separate here
_addClass : "" ,
checkbox : "far fa-square" ,
checkboxSelected : "far fa-check-square" ,
// checkboxUnknown: "far fa-window-close",
checkboxUnknown : "fas fa-square fancytree-helper-indeterminate-cb" ,
radio : "far fa-circle" ,
radioSelected : "fas fa-circle" ,
radioUnknown : "far fa-dot-circle" ,
dragHelper : "fas fa-arrow-right" ,
dropMarker : "fas fa-long-arrow-right" ,
error : "fas fa-exclamation-triangle" ,
expanderClosed : "fas fa-caret-right" ,
expanderLazy : "fas fa-angle-right" ,
expanderOpen : "fas fa-caret-down" ,
loading : "fas fa-spinner fa-pulse" ,
nodata : "far fa-meh" ,
noExpander : "" ,
// Default node icons.
// (Use tree.options.icon callback to define custom icons based on node data)
doc : "far fa-file" ,
docOpen : "far fa-file" ,
folder : "far fa-folder" ,
folderOpen : "far fa-folder-open"
} ,
"bootstrap3" : {
_addClass : "glyphicon" ,
checkbox : "glyphicon-unchecked" ,
checkboxSelected : "glyphicon-check" ,
checkboxUnknown : "glyphicon-expand fancytree-helper-indeterminate-cb" , // "glyphicon-share",
dragHelper : "glyphicon-play" ,
dropMarker : "glyphicon-arrow-right" ,
error : "glyphicon-warning-sign" ,
expanderClosed : "glyphicon-menu-right" , // glyphicon-plus-sign
expanderLazy : "glyphicon-menu-right" , // glyphicon-plus-sign
expanderOpen : "glyphicon-menu-down" , // glyphicon-minus-sign
loading : "glyphicon-refresh fancytree-helper-spin" ,
nodata : "glyphicon-info-sign" ,
noExpander : "" ,
radio : "glyphicon-remove-circle" , // "glyphicon-unchecked",
radioSelected : "glyphicon-ok-circle" , // "glyphicon-check",
// radioUnknown: "glyphicon-ban-circle",
// Default node icons.
// (Use tree.options.icon callback to define custom icons based on node data)
doc : "glyphicon-file" ,
docOpen : "glyphicon-file" ,
folder : "glyphicon-folder-close" ,
folderOpen : "glyphicon-folder-open"
} ,
"material" : {
_addClass : "material-icons" ,
checkbox : { text : "check_box_outline_blank" } ,
checkboxSelected : { text : "check_box" } ,
checkboxUnknown : { text : "indeterminate_check_box" } ,
dragHelper : { text : "play_arrow" } ,
dropMarker : { text : "arrow-forward" } ,
error : { text : "warning" } ,
expanderClosed : { text : "chevron_right" } ,
expanderLazy : { text : "last_page" } ,
expanderOpen : { text : "expand_more" } ,
loading : { text : "autorenew" , addClass : "fancytree-helper-spin" } ,
nodata : { text : "info" } ,
noExpander : { text : "" } ,
radio : { text : "radio_button_unchecked" } ,
radioSelected : { text : "radio_button_checked" } ,
// Default node icons.
// (Use tree.options.icon callback to define custom icons based on node data)
doc : { text : "insert_drive_file" } ,
docOpen : { text : "insert_drive_file" } ,
folder : { text : "folder" } ,
folderOpen : { text : "folder_open" }
}
2018-01-01 14:39:23 +00:00
} ;
2018-05-24 18:59:32 +00:00
function setIcon ( span , baseClass , opts , type ) {
var map = opts . map ,
icon = map [ type ] ,
$span = $ ( span ) ,
setClass = baseClass + " " + ( map . _addClass || "" ) ;
if ( typeof icon === "string" ) {
$span . attr ( "class" , setClass + " " + icon ) ;
} else if ( icon ) {
if ( icon . text ) {
// $span.text( "" + icon.text );
span . textContent = "" + icon . text ;
} else if ( icon . html ) {
// $(span).append($(icon.html));
span . innerHTML = icon . html ;
}
$span . attr ( "class" , setClass + " " + ( icon . addClass || "" ) ) ;
}
2018-01-01 14:39:23 +00:00
}
$ . ui . fancytree . registerExtension ( {
name : "glyph" ,
2018-05-24 18:59:32 +00:00
version : "2.28.1" ,
2018-01-01 14:39:23 +00:00
// Default options for this extension.
options : {
2018-05-24 18:59:32 +00:00
preset : null , // 'awesome3', 'awesome4', 'bootstrap3', 'material'
2018-01-01 14:39:23 +00:00
map : {
}
} ,
treeInit : function ( ctx ) {
var tree = ctx . tree ,
opts = ctx . options . glyph ;
if ( opts . preset ) {
FT . assert ( ! ! PRESETS [ opts . preset ] ,
"Invalid value for `options.glyph.preset`: " + opts . preset ) ;
opts . map = $ . extend ( { } , PRESETS [ opts . preset ] , opts . map ) ;
} else {
tree . warn ( "ext-glyph: missing `preset` option." ) ;
}
this . _superApply ( arguments ) ;
tree . $container . addClass ( "fancytree-ext-glyph" ) ;
} ,
nodeRenderStatus : function ( ctx ) {
2018-05-24 18:59:32 +00:00
var checkbox , icon , res , span ,
2018-01-01 14:39:23 +00:00
node = ctx . node ,
2018-05-24 18:59:32 +00:00
$span = $ ( node . span ) ,
opts = ctx . options . glyph ;
2018-01-01 14:39:23 +00:00
res = this . _super ( ctx ) ;
if ( node . isRoot ( ) ) {
return res ;
}
span = $span . children ( "span.fancytree-expander" ) . get ( 0 ) ;
if ( span ) {
// if( node.isLoading() ){
// icon = "loading";
if ( node . expanded && node . hasChildren ( ) ) {
icon = "expanderOpen" ;
} else if ( node . isUndefined ( ) ) {
icon = "expanderLazy" ;
} else if ( node . hasChildren ( ) ) {
icon = "expanderClosed" ;
} else {
icon = "noExpander" ;
}
2018-05-24 18:59:32 +00:00
// span.className = "fancytree-expander " + map[icon];
setIcon ( span , "fancytree-expander" , opts , icon ) ;
2018-01-01 14:39:23 +00:00
}
if ( node . tr ) {
span = $ ( "td" , node . tr ) . find ( "span.fancytree-checkbox" ) . get ( 0 ) ;
} else {
span = $span . children ( "span.fancytree-checkbox" ) . get ( 0 ) ;
}
2018-05-24 18:59:32 +00:00
if ( span ) {
checkbox = FT . evalOption ( "checkbox" , node , node , opts , false ) ;
if ( ( node . parent && node . parent . radiogroup ) || checkbox === "radio" ) {
icon = node . selected ? "radioSelected" : "radio" ;
setIcon ( span , "fancytree-checkbox fancytree-radio" , opts , icon ) ;
} else {
icon = node . selected ? "checkboxSelected" : ( node . partsel ? "checkboxUnknown" : "checkbox" ) ;
// span.className = "fancytree-checkbox " + map[icon];
setIcon ( span , "fancytree-checkbox" , opts , icon ) ;
2018-01-01 14:39:23 +00:00
}
}
// Standard icon (note that this does not match .fancytree-custom-icon,
// that might be set by opts.icon callbacks)
span = $span . children ( "span.fancytree-icon" ) . get ( 0 ) ;
if ( span ) {
if ( node . statusNodeType ) {
2018-05-24 18:59:32 +00:00
icon = node . statusNodeType ; // loading, error
2018-01-01 14:39:23 +00:00
} else if ( node . folder ) {
2018-05-24 18:59:32 +00:00
icon = ( node . expanded && node . hasChildren ( ) ) ? "folderOpen" : "folder" ;
2018-01-01 14:39:23 +00:00
} else {
2018-05-24 18:59:32 +00:00
icon = node . expanded ? "docOpen" : "doc" ;
2018-01-01 14:39:23 +00:00
}
2018-05-24 18:59:32 +00:00
setIcon ( span , "fancytree-icon" , opts , icon ) ;
2018-01-01 14:39:23 +00:00
}
return res ;
} ,
nodeSetStatus : function ( ctx , status , message , details ) {
var res , span ,
opts = ctx . options . glyph ,
node = ctx . node ;
res = this . _superApply ( arguments ) ;
if ( status === "error" || status === "loading" || status === "nodata" ) {
if ( node . parent ) {
span = $ ( "span.fancytree-expander" , node . span ) . get ( 0 ) ;
if ( span ) {
2018-05-24 18:59:32 +00:00
setIcon ( span , "fancytree-expander" , opts , status ) ;
2018-01-01 14:39:23 +00:00
}
} else { //
span = $ ( ".fancytree-statusnode-" + status , node [ this . nodeContainerAttrName ] )
. find ( "span.fancytree-icon" ) . get ( 0 ) ;
if ( span ) {
2018-05-24 18:59:32 +00:00
setIcon ( span , "fancytree-icon" , opts , status ) ;
2018-01-01 14:39:23 +00:00
}
}
}
return res ;
}
} ) ;
// Value returned by `require('jquery.fancytree..')`
return $ . ui . fancytree ;
} ) ) ; // End of closure
/*! Extension 'jquery.fancytree.gridnav.js' */ / * !
* jquery . fancytree . gridnav . js
*
* Support keyboard navigation for trees with embedded input controls .
* ( Extension module for jquery . fancytree . js : https : //github.com/mar10/fancytree/)
*
2018-05-24 18:59:32 +00:00
* Copyright ( c ) 2008 - 2018 , Martin Wendt ( http : //wwWendt.de)
2018-01-01 14:39:23 +00:00
*
* Released under the MIT license
* https : //github.com/mar10/fancytree/wiki/LicenseInfo
*
2018-05-24 18:59:32 +00:00
* @ version 2.28 . 1
* @ date 2018 - 03 - 19 T06 : 47 : 37 Z
2018-01-01 14:39:23 +00:00
* /
; ( function ( factory ) {
if ( typeof define === "function" && define . amd ) {
// AMD. Register as an anonymous module.
define ( [
"jquery" ,
"./jquery.fancytree" ,
"./jquery.fancytree.table"
] , factory ) ;
} else if ( typeof module === "object" && module . exports ) {
// Node/CommonJS
2018-05-24 18:59:32 +00:00
require ( "./jquery.fancytree.table" ) ; // core + table
2018-01-01 14:39:23 +00:00
module . exports = factory ( require ( "jquery" ) ) ;
} else {
// Browser globals
factory ( jQuery ) ;
}
} ( function ( $ ) {
"use strict" ;
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Private functions and variables
* /
// Allow these navigation keys even when input controls are focused
var KC = $ . ui . keyCode ,
// which keys are *not* handled by embedded control, but passed to tree
// navigation handler:
NAV _KEYS = {
"text" : [ KC . UP , KC . DOWN ] ,
"checkbox" : [ KC . UP , KC . DOWN , KC . LEFT , KC . RIGHT ] ,
"link" : [ KC . UP , KC . DOWN , KC . LEFT , KC . RIGHT ] ,
"radiobutton" : [ KC . UP , KC . DOWN , KC . LEFT , KC . RIGHT ] ,
"select-one" : [ KC . LEFT , KC . RIGHT ] ,
"select-multiple" : [ KC . LEFT , KC . RIGHT ]
} ;
/* Calculate TD column index (considering colspans).*/
function getColIdx ( $tr , $td ) {
var colspan ,
td = $td . get ( 0 ) ,
idx = 0 ;
$tr . children ( ) . each ( function ( ) {
if ( this === td ) {
return false ;
}
colspan = $ ( this ) . prop ( "colspan" ) ;
idx += colspan ? colspan : 1 ;
} ) ;
return idx ;
}
/* Find TD at given column index (considering colspans).*/
function findTdAtColIdx ( $tr , colIdx ) {
var colspan ,
res = null ,
idx = 0 ;
$tr . children ( ) . each ( function ( ) {
if ( idx >= colIdx ) {
res = $ ( this ) ;
return false ;
}
colspan = $ ( this ) . prop ( "colspan" ) ;
idx += colspan ? colspan : 1 ;
} ) ;
return res ;
}
/* Find adjacent cell for a given direction. Skip empty cells and consider merged cells */
function findNeighbourTd ( $target , keyCode ) {
var $tr , colIdx ,
$td = $target . closest ( "td" ) ,
$tdNext = null ;
switch ( keyCode ) {
case KC . LEFT :
$tdNext = $td . prev ( ) ;
break ;
case KC . RIGHT :
$tdNext = $td . next ( ) ;
break ;
case KC . UP :
case KC . DOWN :
$tr = $td . parent ( ) ;
colIdx = getColIdx ( $tr , $td ) ;
while ( true ) {
$tr = ( keyCode === KC . UP ) ? $tr . prev ( ) : $tr . next ( ) ;
if ( ! $tr . length ) {
break ;
}
// Skip hidden rows
if ( $tr . is ( ":hidden" ) ) {
continue ;
}
// Find adjacent cell in the same column
$tdNext = findTdAtColIdx ( $tr , colIdx ) ;
// Skip cells that don't conatain a focusable element
if ( $tdNext && $tdNext . find ( ":input,a" ) . length ) {
break ;
}
}
break ;
}
return $tdNext ;
}
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Extension code
* /
$ . ui . fancytree . registerExtension ( {
name : "gridnav" ,
2018-05-24 18:59:32 +00:00
version : "2.28.1" ,
2018-01-01 14:39:23 +00:00
// Default options for this extension.
options : {
autofocusInput : false , // Focus first embedded input if node gets activated
handleCursorKeys : true // Allow UP/DOWN in inputs to move to prev/next node
} ,
treeInit : function ( ctx ) {
// gridnav requires the table extension to be loaded before itself
this . _requireExtension ( "table" , true , true ) ;
this . _superApply ( arguments ) ;
this . $container . addClass ( "fancytree-ext-gridnav" ) ;
// Activate node if embedded input gets focus (due to a click)
this . $container . on ( "focusin" , function ( event ) {
var ctx2 ,
node = $ . ui . fancytree . getNode ( event . target ) ;
if ( node && ! node . isActive ( ) ) {
// Call node.setActive(), but also pass the event
ctx2 = ctx . tree . _makeHookContext ( node , event ) ;
ctx . tree . _callHook ( "nodeSetActive" , ctx2 , true ) ;
}
} ) ;
} ,
nodeSetActive : function ( ctx , flag , callOpts ) {
var $outer ,
opts = ctx . options . gridnav ,
node = ctx . node ,
event = ctx . originalEvent || { } ,
triggeredByInput = $ ( event . target ) . is ( ":input" ) ;
flag = ( flag !== false ) ;
this . _superApply ( arguments ) ;
if ( flag ) {
if ( ctx . options . titlesTabbable ) {
if ( ! triggeredByInput ) {
$ ( node . span ) . find ( "span.fancytree-title" ) . focus ( ) ;
node . setFocus ( ) ;
}
// If one node is tabbable, the container no longer needs to be
ctx . tree . $container . attr ( "tabindex" , "-1" ) ;
// ctx.tree.$container.removeAttr("tabindex");
} else if ( opts . autofocusInput && ! triggeredByInput ) {
// Set focus to input sub input (if node was clicked, but not
// when TAB was pressed )
$outer = $ ( node . tr || node . span ) ;
$outer . find ( ":input:enabled:first" ) . focus ( ) ;
}
}
} ,
nodeKeydown : function ( ctx ) {
var inputType , handleKeys , $td ,
opts = ctx . options . gridnav ,
event = ctx . originalEvent ,
$target = $ ( event . target ) ;
if ( $target . is ( ":input:enabled" ) ) {
inputType = $target . prop ( "type" ) ;
} else if ( $target . is ( "a" ) ) {
inputType = "link" ;
}
// ctx.tree.debug("ext-gridnav nodeKeydown", event, inputType);
if ( inputType && opts . handleCursorKeys ) {
handleKeys = NAV _KEYS [ inputType ] ;
if ( handleKeys && $ . inArray ( event . which , handleKeys ) >= 0 ) {
$td = findNeighbourTd ( $target , event . which ) ;
if ( $td && $td . length ) {
// ctx.node.debug("ignore keydown in input", event.which, handleKeys);
$td . find ( ":input:enabled,a" ) . focus ( ) ;
// Prevent Fancytree default navigation
return false ;
}
}
return true ;
}
// ctx.tree.debug("ext-gridnav NOT HANDLED", event, inputType);
return this . _superApply ( arguments ) ;
}
} ) ;
// Value returned by `require('jquery.fancytree..')`
return $ . ui . fancytree ;
} ) ) ; // End of closure
/*! Extension 'jquery.fancytree.persist.js' */ / * !
* jquery . fancytree . persist . js
*
* Persist tree status in cookiesRemove or highlight tree nodes , based on a filter .
* ( Extension module for jquery . fancytree . js : https : //github.com/mar10/fancytree/)
*
* @ depends : js - cookie or jquery - cookie
*
2018-05-24 18:59:32 +00:00
* Copyright ( c ) 2008 - 2018 , Martin Wendt ( http : //wwWendt.de)
2018-01-01 14:39:23 +00:00
*
* Released under the MIT license
* https : //github.com/mar10/fancytree/wiki/LicenseInfo
*
2018-05-24 18:59:32 +00:00
* @ version 2.28 . 1
* @ date 2018 - 03 - 19 T06 : 47 : 37 Z
2018-01-01 14:39:23 +00:00
* /
; ( function ( factory ) {
if ( typeof define === "function" && define . amd ) {
// AMD. Register as an anonymous module.
define ( [ "jquery" , "./jquery.fancytree" ] , factory ) ;
} else if ( typeof module === "object" && module . exports ) {
// Node/CommonJS
2018-05-24 18:59:32 +00:00
require ( "./jquery.fancytree" ) ;
2018-01-01 14:39:23 +00:00
module . exports = factory ( require ( "jquery" ) ) ;
} else {
// Browser globals
factory ( jQuery ) ;
}
} ( function ( $ ) {
"use strict" ;
/* global Cookies:false */
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Private functions and variables
* /
2018-05-24 18:59:32 +00:00
var cookieStore = null ,
localStorageStore = window . localStorage ? {
get : function ( key ) { return window . localStorage . getItem ( key ) ; } ,
set : function ( key , value ) { window . localStorage . setItem ( key , value ) ; } ,
remove : function ( key ) { window . localStorage . removeItem ( key ) ; }
} : null ,
sessionStorageStore = window . sessionStorage ? {
get : function ( key ) { return window . sessionStorage . getItem ( key ) ; } ,
set : function ( key , value ) { window . sessionStorage . setItem ( key , value ) ; } ,
remove : function ( key ) { window . sessionStorage . removeItem ( key ) ; }
} : null ,
2018-01-01 14:39:23 +00:00
_assert = $ . ui . fancytree . assert ,
ACTIVE = "active" ,
EXPANDED = "expanded" ,
FOCUS = "focus" ,
SELECTED = "selected" ;
if ( typeof Cookies === "function" ) {
// Assume https://github.com/js-cookie/js-cookie
2018-05-24 18:59:32 +00:00
cookieStore = {
get : Cookies . get ,
set : function ( key , value ) {
Cookies . set ( key , value , this . options . persist . cookie ) ;
} ,
remove : Cookies . remove
} ;
} else if ( $ && typeof $ . cookie === "function" ) {
2018-01-01 14:39:23 +00:00
// Fall back to https://github.com/carhartl/jquery-cookie
2018-05-24 18:59:32 +00:00
cookieStore = {
get : $ . cookie ,
set : function ( key , value ) {
$ . cookie . set ( key , value , this . options . persist . cookie ) ;
} ,
remove : $ . removeCookie
} ;
2018-01-01 14:39:23 +00:00
}
/ * R e c u r s i v e l y l o a d l a z y n o d e s
* @ param { string } mode 'load' , 'expand' , false
* /
function _loadLazyNodes ( tree , local , keyList , mode , dfd ) {
var i , key , l , node ,
foundOne = false ,
expandOpts = tree . options . persist . expandOpts ,
deferredList = [ ] ,
missingKeyList = [ ] ;
keyList = keyList || [ ] ;
dfd = dfd || $ . Deferred ( ) ;
for ( i = 0 , l = keyList . length ; i < l ; i ++ ) {
key = keyList [ i ] ;
node = tree . getNodeByKey ( key ) ;
if ( node ) {
if ( mode && node . isUndefined ( ) ) {
foundOne = true ;
tree . debug ( "_loadLazyNodes: " + node + " is lazy: loading..." ) ;
if ( mode === "expand" ) {
deferredList . push ( node . setExpanded ( true , expandOpts ) ) ;
} else {
deferredList . push ( node . load ( ) ) ;
}
} else {
tree . debug ( "_loadLazyNodes: " + node + " already loaded." ) ;
node . setExpanded ( true , expandOpts ) ;
}
} else {
missingKeyList . push ( key ) ;
tree . debug ( "_loadLazyNodes: " + node + " was not yet found." ) ;
}
}
$ . when . apply ( $ , deferredList ) . always ( function ( ) {
// All lazy-expands have finished
if ( foundOne && missingKeyList . length > 0 ) {
// If we read new nodes from server, try to resolve yet-missing keys
_loadLazyNodes ( tree , local , missingKeyList , mode , dfd ) ;
} else {
if ( missingKeyList . length ) {
tree . warn ( "_loadLazyNodes: could not load those keys: " , missingKeyList ) ;
for ( i = 0 , l = missingKeyList . length ; i < l ; i ++ ) {
key = keyList [ i ] ;
local . _appendKey ( EXPANDED , keyList [ i ] , false ) ;
}
}
dfd . resolve ( ) ;
}
} ) ;
return dfd ;
}
/ * *
2018-05-24 18:59:32 +00:00
* [ ext - persist ] Remove persistence data of the given type ( s ) .
2018-01-01 14:39:23 +00:00
* Called like
* $ ( "#tree" ) . fancytree ( "getTree" ) . clearCookies ( "active expanded focus selected" ) ;
*
2018-05-24 18:59:32 +00:00
* @ alias Fancytree # clearPersistData
2018-01-01 14:39:23 +00:00
* @ requires jquery . fancytree . persist . js
* /
2018-05-24 18:59:32 +00:00
$ . ui . fancytree . _FancytreeClass . prototype . clearPersistData = function ( types ) {
2018-01-01 14:39:23 +00:00
var local = this . ext . persist ,
prefix = local . cookiePrefix ;
types = types || "active expanded focus selected" ;
if ( types . indexOf ( ACTIVE ) >= 0 ) {
local . _data ( prefix + ACTIVE , null ) ;
}
if ( types . indexOf ( EXPANDED ) >= 0 ) {
local . _data ( prefix + EXPANDED , null ) ;
}
if ( types . indexOf ( FOCUS ) >= 0 ) {
local . _data ( prefix + FOCUS , null ) ;
}
if ( types . indexOf ( SELECTED ) >= 0 ) {
local . _data ( prefix + SELECTED , null ) ;
}
} ;
2018-05-24 18:59:32 +00:00
$ . ui . fancytree . _FancytreeClass . prototype . clearCookies = function ( types ) {
this . warn ( "'tree.clearCookies()' is deprecated since v2.27.0: use 'clearPersistData()' instead." ) ;
return this . clearPersistData ( types ) ;
} ;
2018-01-01 14:39:23 +00:00
/ * *
* [ ext - persist ] Return persistence information from cookies
*
* Called like
* $ ( "#tree" ) . fancytree ( "getTree" ) . getPersistData ( ) ;
*
* @ alias Fancytree # getPersistData
* @ requires jquery . fancytree . persist . js
* /
$ . ui . fancytree . _FancytreeClass . prototype . getPersistData = function ( ) {
var local = this . ext . persist ,
prefix = local . cookiePrefix ,
delim = local . cookieDelimiter ,
res = { } ;
res [ ACTIVE ] = local . _data ( prefix + ACTIVE ) ;
res [ EXPANDED ] = ( local . _data ( prefix + EXPANDED ) || "" ) . split ( delim ) ;
res [ SELECTED ] = ( local . _data ( prefix + SELECTED ) || "" ) . split ( delim ) ;
res [ FOCUS ] = local . _data ( prefix + FOCUS ) ;
return res ;
} ;
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Extension code
* /
$ . ui . fancytree . registerExtension ( {
name : "persist" ,
2018-05-24 18:59:32 +00:00
version : "2.28.1" ,
2018-01-01 14:39:23 +00:00
// Default options for this extension.
options : {
cookieDelimiter : "~" ,
cookiePrefix : undefined , // 'fancytree-<treeId>-' by default
cookie : {
raw : false ,
expires : "" ,
path : "" ,
domain : "" ,
secure : false
} ,
expandLazy : false , // true: recursively expand and load lazy nodes
expandOpts : undefined , // optional `opts` argument passed to setExpanded()
fireActivate : true , // false: suppress `activate` event after active node was restored
overrideSource : true , // true: cookie takes precedence over `source` data attributes.
store : "auto" , // 'cookie': force cookie, 'local': force localStore, 'session': force sessionStore
types : "active expanded focus selected"
} ,
/* Generic read/write string data to cookie, sessionStorage or localStorage. */
_data : function ( key , value ) {
2018-05-24 18:59:32 +00:00
var store = this . _local . store ;
2018-01-01 14:39:23 +00:00
if ( value === undefined ) {
2018-05-24 18:59:32 +00:00
return store . get . call ( this , key ) ;
2018-01-01 14:39:23 +00:00
} else if ( value === null ) {
2018-05-24 18:59:32 +00:00
store . remove . call ( this , key ) ;
2018-01-01 14:39:23 +00:00
} else {
2018-05-24 18:59:32 +00:00
store . set . call ( this , key , value ) ;
2018-01-01 14:39:23 +00:00
}
} ,
/* Append `key` to a cookie. */
_appendKey : function ( type , key , flag ) {
key = "" + key ; // #90
var local = this . _local ,
instOpts = this . options . persist ,
delim = instOpts . cookieDelimiter ,
cookieName = local . cookiePrefix + type ,
data = local . _data ( cookieName ) ,
keyList = data ? data . split ( delim ) : [ ] ,
idx = $ . inArray ( key , keyList ) ;
// Remove, even if we add a key, so the key is always the last entry
if ( idx >= 0 ) {
keyList . splice ( idx , 1 ) ;
}
// Append key to cookie
if ( flag ) {
keyList . push ( key ) ;
}
local . _data ( cookieName , keyList . join ( delim ) ) ;
} ,
treeInit : function ( ctx ) {
var tree = ctx . tree ,
opts = ctx . options ,
local = this . _local ,
instOpts = this . options . persist ;
2018-05-24 18:59:32 +00:00
// // For 'auto' or 'cookie' mode, the cookie plugin must be available
// _assert((instOpts.store !== "auto" && instOpts.store !== "cookie") || cookieStore,
// "Missing required plugin for 'persist' extension: js.cookie.js or jquery.cookie.js");
2018-01-01 14:39:23 +00:00
local . cookiePrefix = instOpts . cookiePrefix || ( "fancytree-" + tree . _id + "-" ) ;
local . storeActive = instOpts . types . indexOf ( ACTIVE ) >= 0 ;
local . storeExpanded = instOpts . types . indexOf ( EXPANDED ) >= 0 ;
local . storeSelected = instOpts . types . indexOf ( SELECTED ) >= 0 ;
local . storeFocus = instOpts . types . indexOf ( FOCUS ) >= 0 ;
2018-05-24 18:59:32 +00:00
local . store = null ;
if ( instOpts . store === "auto" ) {
instOpts . store = localStorageStore ? "local" : "cookie" ;
}
if ( $ . isPlainObject ( instOpts . store ) ) {
local . store = instOpts . store ;
} else if ( instOpts . store === "cookie" ) {
local . store = cookieStore ;
} else if ( instOpts . store === "local" ) {
local . store = ( instOpts . store === "local" ) ? localStorageStore : sessionStorageStore ;
} else if ( instOpts . store === "session" ) {
local . store = ( instOpts . store === "local" ) ? localStorageStore : sessionStorageStore ;
2018-01-01 14:39:23 +00:00
}
2018-05-24 18:59:32 +00:00
_assert ( local . store , "Need a valid store." ) ;
2018-01-01 14:39:23 +00:00
// Bind init-handler to apply cookie state
tree . $div . on ( "fancytreeinit" , function ( event ) {
if ( tree . _triggerTreeEvent ( "beforeRestore" , null , { } ) === false ) {
return ;
}
var cookie , dfd , i , keyList , node ,
prevFocus = local . _data ( local . cookiePrefix + FOCUS ) , // record this before node.setActive() overrides it;
noEvents = instOpts . fireActivate === false ;
// tree.debug("document.cookie:", document.cookie);
cookie = local . _data ( local . cookiePrefix + EXPANDED ) ;
keyList = cookie && cookie . split ( instOpts . cookieDelimiter ) ;
if ( local . storeExpanded ) {
// Recursively load nested lazy nodes if expandLazy is 'expand' or 'load'
// Also remove expand-cookies for unmatched nodes
dfd = _loadLazyNodes ( tree , local , keyList , instOpts . expandLazy ? "expand" : false , null ) ;
} else {
// nothing to do
dfd = new $ . Deferred ( ) . resolve ( ) ;
}
dfd . done ( function ( ) {
if ( local . storeSelected ) {
cookie = local . _data ( local . cookiePrefix + SELECTED ) ;
if ( cookie ) {
keyList = cookie . split ( instOpts . cookieDelimiter ) ;
for ( i = 0 ; i < keyList . length ; i ++ ) {
node = tree . getNodeByKey ( keyList [ i ] ) ;
if ( node ) {
if ( node . selected === undefined || instOpts . overrideSource && ( node . selected === false ) ) {
// node.setSelected();
node . selected = true ;
node . renderStatus ( ) ;
}
} else {
// node is no longer member of the tree: remove from cookie also
local . _appendKey ( SELECTED , keyList [ i ] , false ) ;
}
}
}
// In selectMode 3 we have to fix the child nodes, since we
// only stored the selected *top* nodes
if ( tree . options . selectMode === 3 ) {
tree . visit ( function ( n ) {
if ( n . selected ) {
n . fixSelection3AfterClick ( ) ;
return "skip" ;
}
} ) ;
}
}
if ( local . storeActive ) {
cookie = local . _data ( local . cookiePrefix + ACTIVE ) ;
if ( cookie && ( opts . persist . overrideSource || ! tree . activeNode ) ) {
node = tree . getNodeByKey ( cookie ) ;
if ( node ) {
node . debug ( "persist: set active" , cookie ) ;
// We only want to set the focus if the container
// had the keyboard focus before
node . setActive ( true , {
noFocus : true ,
noEvents : noEvents
} ) ;
}
}
}
if ( local . storeFocus && prevFocus ) {
node = tree . getNodeByKey ( prevFocus ) ;
if ( node ) {
// node.debug("persist: set focus", cookie);
if ( tree . options . titlesTabbable ) {
$ ( node . span ) . find ( ".fancytree-title" ) . focus ( ) ;
} else {
$ ( tree . $container ) . focus ( ) ;
}
// node.setFocus();
}
}
tree . _triggerTreeEvent ( "restore" , null , { } ) ;
} ) ;
} ) ;
// Init the tree
return this . _superApply ( arguments ) ;
} ,
nodeSetActive : function ( ctx , flag , callOpts ) {
var res ,
local = this . _local ;
flag = ( flag !== false ) ;
res = this . _superApply ( arguments ) ;
if ( local . storeActive ) {
local . _data ( local . cookiePrefix + ACTIVE , this . activeNode ? this . activeNode . key : null ) ;
}
return res ;
} ,
nodeSetExpanded : function ( ctx , flag , callOpts ) {
var res ,
node = ctx . node ,
local = this . _local ;
flag = ( flag !== false ) ;
res = this . _superApply ( arguments ) ;
if ( local . storeExpanded ) {
local . _appendKey ( EXPANDED , node . key , flag ) ;
}
return res ;
} ,
nodeSetFocus : function ( ctx , flag ) {
var res ,
local = this . _local ;
flag = ( flag !== false ) ;
res = this . _superApply ( arguments ) ;
if ( local . storeFocus ) {
local . _data ( local . cookiePrefix + FOCUS , this . focusNode ? this . focusNode . key : null ) ;
}
return res ;
} ,
nodeSetSelected : function ( ctx , flag , callOpts ) {
var res , selNodes ,
tree = ctx . tree ,
node = ctx . node ,
local = this . _local ;
flag = ( flag !== false ) ;
res = this . _superApply ( arguments ) ;
if ( local . storeSelected ) {
if ( tree . options . selectMode === 3 ) {
// In selectMode 3 we only store the the selected *top* nodes.
// De-selecting a node may also de-select some parents, so we
// calculate the current status again
selNodes = $ . map ( tree . getSelectedNodes ( true ) , function ( n ) {
return n . key ;
} ) ;
selNodes = selNodes . join ( ctx . options . persist . cookieDelimiter ) ;
local . _data ( local . cookiePrefix + SELECTED , selNodes ) ;
} else {
// beforeSelect can prevent the change - flag doesn't reflect the node.selected state
local . _appendKey ( SELECTED , node . key , node . selected ) ;
}
}
return res ;
}
} ) ;
// Value returned by `require('jquery.fancytree..')`
return $ . ui . fancytree ;
} ) ) ; // End of closure
/*! Extension 'jquery.fancytree.table.js' */ / * !
* jquery . fancytree . table . js
*
* Render tree as table ( aka 'tree grid' , 'table tree' ) .
* ( Extension module for jquery . fancytree . js : https : //github.com/mar10/fancytree/)
*
2018-05-24 18:59:32 +00:00
* Copyright ( c ) 2008 - 2018 , Martin Wendt ( http : //wwWendt.de)
2018-01-01 14:39:23 +00:00
*
* Released under the MIT license
* https : //github.com/mar10/fancytree/wiki/LicenseInfo
*
2018-05-24 18:59:32 +00:00
* @ version 2.28 . 1
* @ date 2018 - 03 - 19 T06 : 47 : 37 Z
2018-01-01 14:39:23 +00:00
* /
; ( function ( factory ) {
if ( typeof define === "function" && define . amd ) {
// AMD. Register as an anonymous module.
define ( [ "jquery" , "./jquery.fancytree" ] , factory ) ;
} else if ( typeof module === "object" && module . exports ) {
// Node/CommonJS
2018-05-24 18:59:32 +00:00
require ( "./jquery.fancytree" ) ;
2018-01-01 14:39:23 +00:00
module . exports = factory ( require ( "jquery" ) ) ;
} else {
// Browser globals
factory ( jQuery ) ;
}
} ( function ( $ ) {
"use strict" ;
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Private functions and variables
* /
function _assert ( cond , msg ) {
msg = msg || "" ;
if ( ! cond ) {
$ . error ( "Assertion failed " + msg ) ;
}
}
function insertFirstChild ( referenceNode , newNode ) {
referenceNode . insertBefore ( newNode , referenceNode . firstChild ) ;
}
function insertSiblingAfter ( referenceNode , newNode ) {
referenceNode . parentNode . insertBefore ( newNode , referenceNode . nextSibling ) ;
}
/* Show/hide all rows that are structural descendants of `parent`. */
function setChildRowVisibility ( parent , flag ) {
parent . visit ( function ( node ) {
var tr = node . tr ;
// currentFlag = node.hide ? false : flag; // fix for ext-filter
if ( tr ) {
tr . style . display = ( node . hide || ! flag ) ? "none" : "" ;
}
if ( ! node . expanded ) {
return "skip" ;
}
} ) ;
}
/* Find node that is rendered in previous row. */
function findPrevRowNode ( node ) {
var i , last , prev ,
parent = node . parent ,
siblings = parent ? parent . children : null ;
if ( siblings && siblings . length > 1 && siblings [ 0 ] !== node ) {
// use the lowest descendant of the preceeding sibling
i = $ . inArray ( node , siblings ) ;
prev = siblings [ i - 1 ] ;
_assert ( prev . tr ) ;
// descend to lowest child (with a <tr> tag)
while ( prev . children && prev . children . length ) {
last = prev . children [ prev . children . length - 1 ] ;
if ( ! last . tr ) {
break ;
}
prev = last ;
}
} else {
// if there is no preceding sibling, use the direct parent
prev = parent ;
}
return prev ;
}
/* Render callback for 'wide' mode. */
// function _renderStatusNodeWide(event, data) {
// var node = data.node,
// nodeColumnIdx = data.options.table.nodeColumnIdx,
// $tdList = $(node.tr).find(">td");
// $tdList.eq(nodeColumnIdx).attr("colspan", data.tree.columnCount);
// $tdList.not(":eq(" + nodeColumnIdx + ")").remove();
// }
$ . ui . fancytree . registerExtension ( {
name : "table" ,
2018-05-24 18:59:32 +00:00
version : "2.28.1" ,
2018-01-01 14:39:23 +00:00
// Default options for this extension.
options : {
checkboxColumnIdx : null , // render the checkboxes into the this column index (default: nodeColumnIdx)
// customStatus: false, // true: generate renderColumns events for status nodes
indentation : 16 , // indent every node level by 16px
nodeColumnIdx : 0 // render node expander, icon, and title to this column (default: #0)
} ,
// Overide virtual methods for this extension.
// `this` : is this extension object
// `this._super`: the virtual function that was overriden (member of prev. extension or Fancytree)
treeInit : function ( ctx ) {
var i , columnCount , n , $row , $tbody ,
tree = ctx . tree ,
opts = ctx . options ,
tableOpts = opts . table ,
$table = tree . widget . element ;
if ( tableOpts . customStatus != null ) {
if ( opts . renderStatusColumns != null ) {
$ . error ( "The 'customStatus' option is deprecated since v2.15.0. Use 'renderStatusColumns' only instead." ) ;
} else {
tree . warn ( "The 'customStatus' option is deprecated since v2.15.0. Use 'renderStatusColumns' instead." ) ;
opts . renderStatusColumns = tableOpts . customStatus ;
}
}
if ( opts . renderStatusColumns ) {
if ( opts . renderStatusColumns === true ) {
opts . renderStatusColumns = opts . renderColumns ;
// } else if( opts.renderStatusColumns === "wide" ) {
// opts.renderStatusColumns = _renderStatusNodeWide;
}
}
$table . addClass ( "fancytree-container fancytree-ext-table" ) ;
2018-05-24 18:59:32 +00:00
$tbody = $table . find ( ">tbody" ) ;
if ( ! $tbody . length ) {
// TODO: not sure if we can rely on browsers to insert missing <tbody> before <tr>s:
if ( $table . find ( ">tr" ) . length ) {
$ . error ( "Expected table > tbody > tr. If you see this please open an issue." ) ;
}
$tbody = $ ( "<tbody>" ) . appendTo ( $table ) ;
}
tree . tbody = $tbody [ 0 ] ;
2018-01-01 14:39:23 +00:00
// Prepare row templates:
// Determine column count from table header if any
columnCount = $ ( "thead >tr:last >th" , $table ) . length ;
// Read TR templates from tbody if any
$row = $tbody . children ( "tr:first" ) ;
if ( $row . length ) {
n = $row . children ( "td" ) . length ;
if ( columnCount && n !== columnCount ) {
tree . warn ( "Column count mismatch between thead (" + columnCount + ") and tbody (" + n + "): using tbody." ) ;
columnCount = n ;
}
$row = $row . clone ( ) ;
} else {
// Only thead is defined: create default row markup
_assert ( columnCount >= 1 , "Need either <thead> or <tbody> with <td> elements to determine column count." ) ;
$row = $ ( "<tr />" ) ;
for ( i = 0 ; i < columnCount ; i ++ ) {
$row . append ( "<td />" ) ;
}
}
$row . find ( ">td" ) . eq ( tableOpts . nodeColumnIdx )
. html ( "<span class='fancytree-node' />" ) ;
if ( opts . aria ) {
$row . attr ( "role" , "row" ) ;
$row . find ( "td" ) . attr ( "role" , "gridcell" ) ;
}
tree . rowFragment = document . createDocumentFragment ( ) ;
tree . rowFragment . appendChild ( $row . get ( 0 ) ) ;
// // If tbody contains a second row, use this as status node template
// $row = $tbody.children("tr:eq(1)");
// if( $row.length === 0 ) {
// tree.statusRowFragment = tree.rowFragment;
// } else {
// $row = $row.clone();
// tree.statusRowFragment = document.createDocumentFragment();
// tree.statusRowFragment.appendChild($row.get(0));
// }
//
$tbody . empty ( ) ;
// Make sure that status classes are set on the node's <tr> elements
tree . statusClassPropName = "tr" ;
tree . ariaPropName = "tr" ;
this . nodeContainerAttrName = "tr" ;
// #489: make sure $container is set to <table>, even if ext-dnd is listed before ext-table
tree . $container = $table ;
this . _superApply ( arguments ) ;
// standard Fancytree created a root UL
$ ( tree . rootNode . ul ) . remove ( ) ;
tree . rootNode . ul = null ;
// Add container to the TAB chain
// #577: Allow to set tabindex to "0", "-1" and ""
this . $container . attr ( "tabindex" , opts . tabindex ) ;
// this.$container.attr("tabindex", opts.tabbable ? "0" : "-1");
if ( opts . aria ) {
tree . $container
. attr ( "role" , "treegrid" )
. attr ( "aria-readonly" , true ) ;
}
} ,
nodeRemoveChildMarkup : function ( ctx ) {
var node = ctx . node ;
// node.debug("nodeRemoveChildMarkup()");
node . visit ( function ( n ) {
if ( n . tr ) {
$ ( n . tr ) . remove ( ) ;
n . tr = null ;
}
} ) ;
} ,
nodeRemoveMarkup : function ( ctx ) {
var node = ctx . node ;
// node.debug("nodeRemoveMarkup()");
if ( node . tr ) {
$ ( node . tr ) . remove ( ) ;
node . tr = null ;
}
this . nodeRemoveChildMarkup ( ctx ) ;
} ,
/* Override standard render. */
nodeRender : function ( ctx , force , deep , collapsed , _recursive ) {
var children , firstTr , i , l , newRow , prevNode , prevTr , subCtx ,
tree = ctx . tree ,
node = ctx . node ,
opts = ctx . options ,
isRootNode = ! node . parent ;
if ( tree . _enableUpdate === false ) {
// $.ui.fancytree.debug("*** nodeRender _enableUpdate: false");
return ;
}
if ( ! _recursive ) {
ctx . hasCollapsedParents = node . parent && ! node . parent . expanded ;
}
// $.ui.fancytree.debug("*** nodeRender " + node + ", isRoot=" + isRootNode, "tr=" + node.tr, "hcp=" + ctx.hasCollapsedParents, "parent.tr=" + (node.parent && node.parent.tr));
if ( ! isRootNode ) {
if ( node . tr && force ) {
this . nodeRemoveMarkup ( ctx ) ;
}
if ( ! node . tr ) {
if ( ctx . hasCollapsedParents && ! deep ) {
// #166: we assume that the parent will be (recursively) rendered
// later anyway.
// node.debug("nodeRender ignored due to unrendered parent");
return ;
}
// Create new <tr> after previous row
// if( node.isStatusNode() ) {
// newRow = tree.statusRowFragment.firstChild.cloneNode(true);
// } else {
newRow = tree . rowFragment . firstChild . cloneNode ( true ) ;
// }
prevNode = findPrevRowNode ( node ) ;
// $.ui.fancytree.debug("*** nodeRender " + node + ": prev: " + prevNode.key);
_assert ( prevNode ) ;
if ( collapsed === true && _recursive ) {
// hide all child rows, so we can use an animation to show it later
newRow . style . display = "none" ;
} else if ( deep && ctx . hasCollapsedParents ) {
// also hide this row if deep === true but any parent is collapsed
newRow . style . display = "none" ;
// newRow.style.color = "red";
}
if ( ! prevNode . tr ) {
_assert ( ! prevNode . parent , "prev. row must have a tr, or be system root" ) ;
// tree.tbody.appendChild(newRow);
insertFirstChild ( tree . tbody , newRow ) ; // #675
} else {
insertSiblingAfter ( prevNode . tr , newRow ) ;
}
node . tr = newRow ;
if ( node . key && opts . generateIds ) {
node . tr . id = opts . idPrefix + node . key ;
}
node . tr . ftnode = node ;
// if(opts.aria){
// $(node.tr).attr("aria-labelledby", "ftal_" + opts.idPrefix + node.key);
// }
node . span = $ ( "span.fancytree-node" , node . tr ) . get ( 0 ) ;
// Set icon, link, and title (normally this is only required on initial render)
this . nodeRenderTitle ( ctx ) ;
// Allow tweaking, binding, after node was created for the first time
// tree._triggerNodeEvent("createNode", ctx);
if ( opts . createNode ) {
opts . createNode . call ( tree , { type : "createNode" } , ctx ) ;
}
} else {
if ( force ) {
// Set icon, link, and title (normally this is only required on initial render)
this . nodeRenderTitle ( ctx ) ; // triggers renderColumns()
} else {
// Update element classes according to node state
this . nodeRenderStatus ( ctx ) ;
}
}
}
// Allow tweaking after node state was rendered
// tree._triggerNodeEvent("renderNode", ctx);
if ( opts . renderNode ) {
opts . renderNode . call ( tree , { type : "renderNode" } , ctx ) ;
}
// Visit child nodes
// Add child markup
children = node . children ;
if ( children && ( isRootNode || deep || node . expanded ) ) {
for ( i = 0 , l = children . length ; i < l ; i ++ ) {
subCtx = $ . extend ( { } , ctx , { node : children [ i ] } ) ;
subCtx . hasCollapsedParents = subCtx . hasCollapsedParents || ! node . expanded ;
this . nodeRender ( subCtx , force , deep , collapsed , true ) ;
}
}
// Make sure, that <tr> order matches node.children order.
if ( children && ! _recursive ) { // we only have to do it once, for the root branch
prevTr = node . tr || null ;
firstTr = tree . tbody . firstChild ;
// Iterate over all descendants
node . visit ( function ( n ) {
if ( n . tr ) {
if ( ! n . parent . expanded && n . tr . style . display !== "none" ) {
// fix after a node was dropped over a collapsed
n . tr . style . display = "none" ;
setChildRowVisibility ( n , false ) ;
}
if ( n . tr . previousSibling !== prevTr ) {
node . debug ( "_fixOrder: mismatch at node: " + n ) ;
var nextTr = prevTr ? prevTr . nextSibling : firstTr ;
tree . tbody . insertBefore ( n . tr , nextTr ) ;
}
prevTr = n . tr ;
}
} ) ;
}
// Update element classes according to node state
// if(!isRootNode){
// this.nodeRenderStatus(ctx);
// }
} ,
nodeRenderTitle : function ( ctx , title ) {
var $cb , res ,
node = ctx . node ,
opts = ctx . options ,
isStatusNode = node . isStatusNode ( ) ;
res = this . _super ( ctx , title ) ;
if ( node . isRootNode ( ) ) {
return res ;
}
// Move checkbox to custom column
if ( opts . checkbox && ! isStatusNode && opts . table . checkboxColumnIdx != null ) {
$cb = $ ( "span.fancytree-checkbox" , node . span ) ; //.detach();
$ ( node . tr ) . find ( "td" ) . eq ( + opts . table . checkboxColumnIdx ) . html ( $cb ) ;
}
// Update element classes according to node state
this . nodeRenderStatus ( ctx ) ;
if ( isStatusNode ) {
if ( opts . renderStatusColumns ) {
// Let user code write column content
opts . renderStatusColumns . call ( ctx . tree , { type : "renderStatusColumns" } , ctx ) ;
} // else: default rendering for status node: leave other cells empty
} else if ( opts . renderColumns ) {
opts . renderColumns . call ( ctx . tree , { type : "renderColumns" } , ctx ) ;
}
return res ;
} ,
nodeRenderStatus : function ( ctx ) {
var indent ,
node = ctx . node ,
opts = ctx . options ;
this . _super ( ctx ) ;
$ ( node . tr ) . removeClass ( "fancytree-node" ) ;
// indent
indent = ( node . getLevel ( ) - 1 ) * opts . table . indentation ;
2018-05-24 18:59:32 +00:00
if ( opts . rtl ) {
$ ( node . span ) . css ( { paddingRight : indent + "px" } ) ;
} else {
$ ( node . span ) . css ( { paddingLeft : indent + "px" } ) ;
}
2018-01-01 14:39:23 +00:00
} ,
/* Expand node, return Deferred.promise. */
nodeSetExpanded : function ( ctx , flag , callOpts ) {
// flag defaults to true
flag = ( flag !== false ) ;
if ( ( ctx . node . expanded && flag ) || ( ! ctx . node . expanded && ! flag ) ) {
// Expanded state isn't changed - just call base implementation
return this . _superApply ( arguments ) ;
}
var dfd = new $ . Deferred ( ) ,
subOpts = $ . extend ( { } , callOpts , { noEvents : true , noAnimation : true } ) ;
callOpts = callOpts || { } ;
function _afterExpand ( ok ) {
setChildRowVisibility ( ctx . node , flag ) ;
if ( ok ) {
if ( flag && ctx . options . autoScroll && ! callOpts . noAnimation && ctx . node . hasChildren ( ) ) {
// Scroll down to last child, but keep current node visible
ctx . node . getLastChild ( ) . scrollIntoView ( true , { topNode : ctx . node } ) . always ( function ( ) {
if ( ! callOpts . noEvents ) {
ctx . tree . _triggerNodeEvent ( flag ? "expand" : "collapse" , ctx ) ;
}
dfd . resolveWith ( ctx . node ) ;
} ) ;
} else {
if ( ! callOpts . noEvents ) {
ctx . tree . _triggerNodeEvent ( flag ? "expand" : "collapse" , ctx ) ;
}
dfd . resolveWith ( ctx . node ) ;
}
} else {
if ( ! callOpts . noEvents ) {
ctx . tree . _triggerNodeEvent ( flag ? "expand" : "collapse" , ctx ) ;
}
dfd . rejectWith ( ctx . node ) ;
}
}
// Call base-expand with disabled events and animation
this . _super ( ctx , flag , subOpts ) . done ( function ( ) {
_afterExpand ( true ) ;
} ) . fail ( function ( ) {
_afterExpand ( false ) ;
} ) ;
return dfd . promise ( ) ;
} ,
nodeSetStatus : function ( ctx , status , message , details ) {
if ( status === "ok" ) {
var node = ctx . node ,
firstChild = ( node . children ? node . children [ 0 ] : null ) ;
if ( firstChild && firstChild . isStatusNode ( ) ) {
$ ( firstChild . tr ) . remove ( ) ;
}
}
return this . _superApply ( arguments ) ;
} ,
treeClear : function ( ctx ) {
this . nodeRemoveChildMarkup ( this . _makeHookContext ( this . rootNode ) ) ;
return this . _superApply ( arguments ) ;
} ,
treeDestroy : function ( ctx ) {
this . $container . find ( "tbody" ) . empty ( ) ;
2018-05-24 18:59:32 +00:00
this . $source && this . $source . removeClass ( "fancytree-helper-hidden" ) ;
2018-01-01 14:39:23 +00:00
return this . _superApply ( arguments ) ;
}
/ * ,
treeSetFocus : function ( ctx , flag ) {
// alert("treeSetFocus" + ctx.tree.$container);
ctx . tree . $container . focus ( ) ;
$ . ui . fancytree . focusTree = ctx . tree ;
} * /
} ) ;
// Value returned by `require('jquery.fancytree..')`
return $ . ui . fancytree ;
} ) ) ; // End of closure
/*! Extension 'jquery.fancytree.themeroller.js' */ / * !
* jquery . fancytree . themeroller . js
*
* Enable jQuery UI ThemeRoller styles .
* ( Extension module for jquery . fancytree . js : https : //github.com/mar10/fancytree/)
*
* @ see http : //jqueryui.com/themeroller/
*
2018-05-24 18:59:32 +00:00
* Copyright ( c ) 2008 - 2018 , Martin Wendt ( http : //wwWendt.de)
2018-01-01 14:39:23 +00:00
*
* Released under the MIT license
* https : //github.com/mar10/fancytree/wiki/LicenseInfo
*
2018-05-24 18:59:32 +00:00
* @ version 2.28 . 1
* @ date 2018 - 03 - 19 T06 : 47 : 37 Z
2018-01-01 14:39:23 +00:00
* /
; ( function ( factory ) {
if ( typeof define === "function" && define . amd ) {
// AMD. Register as an anonymous module.
define ( [ "jquery" , "./jquery.fancytree" ] , factory ) ;
} else if ( typeof module === "object" && module . exports ) {
// Node/CommonJS
2018-05-24 18:59:32 +00:00
require ( "./jquery.fancytree" ) ;
2018-01-01 14:39:23 +00:00
module . exports = factory ( require ( "jquery" ) ) ;
} else {
// Browser globals
factory ( jQuery ) ;
}
} ( function ( $ ) {
"use strict" ;
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Extension code
* /
$ . ui . fancytree . registerExtension ( {
name : "themeroller" ,
2018-05-24 18:59:32 +00:00
version : "2.28.1" ,
2018-01-01 14:39:23 +00:00
// Default options for this extension.
options : {
activeClass : "ui-state-active" , // Class added to active node
// activeClass: "ui-state-highlight",
addClass : "ui-corner-all" , // Class added to all nodes
focusClass : "ui-state-focus" , // Class added to focused node
hoverClass : "ui-state-hover" , // Class added to hovered node
selectedClass : "ui-state-highlight" // Class added to selected nodes
// selectedClass: "ui-state-active"
} ,
treeInit : function ( ctx ) {
var $el = ctx . widget . element ,
opts = ctx . options . themeroller ;
this . _superApply ( arguments ) ;
if ( $el [ 0 ] . nodeName === "TABLE" ) {
$el . addClass ( "ui-widget ui-corner-all" ) ;
$el . find ( ">thead tr" ) . addClass ( "ui-widget-header" ) ;
$el . find ( ">tbody" ) . addClass ( "ui-widget-conent" ) ;
} else {
$el . addClass ( "ui-widget ui-widget-content ui-corner-all" ) ;
}
$el . delegate ( ".fancytree-node" , "mouseenter mouseleave" , function ( event ) {
var node = $ . ui . fancytree . getNode ( event . target ) ,
flag = ( event . type === "mouseenter" ) ;
$ ( node . tr ? node . tr : node . span )
. toggleClass ( opts . hoverClass + " " + opts . addClass , flag ) ;
} ) ;
} ,
treeDestroy : function ( ctx ) {
this . _superApply ( arguments ) ;
ctx . widget . element . removeClass ( "ui-widget ui-widget-content ui-corner-all" ) ;
} ,
nodeRenderStatus : function ( ctx ) {
var classes = { } ,
node = ctx . node ,
$el = $ ( node . tr ? node . tr : node . span ) ,
opts = ctx . options . themeroller ;
this . _super ( ctx ) ;
/ *
. 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 .
. 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 .
. 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 .
. 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 .
. 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 .
. 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 .
. 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 .
* /
// Set ui-state-* class (handle the case that the same class is assigned
// to different states)
classes [ opts . activeClass ] = false ;
classes [ opts . focusClass ] = false ;
classes [ opts . selectedClass ] = false ;
if ( node . isActive ( ) ) { classes [ opts . activeClass ] = true ; }
if ( node . hasFocus ( ) ) { classes [ opts . focusClass ] = true ; }
// activeClass takes precedence before selectedClass:
if ( node . isSelected ( ) && ! node . isActive ( ) ) { classes [ opts . selectedClass ] = true ; }
$el . toggleClass ( opts . activeClass , classes [ opts . activeClass ] ) ;
$el . toggleClass ( opts . focusClass , classes [ opts . focusClass ] ) ;
$el . toggleClass ( opts . selectedClass , classes [ opts . selectedClass ] ) ;
// Additional classes (e.g. 'ui-corner-all')
$el . addClass ( opts . addClass ) ;
}
} ) ;
// Value returned by `require('jquery.fancytree..')`
return $ . ui . fancytree ;
} ) ) ; // End of closure
/*! Extension 'jquery.fancytree.wide.js' */ / * !
* jquery . fancytree . wide . js
* Support for 100 % wide selection bars .
* ( Extension module for jquery . fancytree . js : https : //github.com/mar10/fancytree/)
*
2018-05-24 18:59:32 +00:00
* Copyright ( c ) 2008 - 2018 , Martin Wendt ( http : //wwWendt.de)
2018-01-01 14:39:23 +00:00
*
* Released under the MIT license
* https : //github.com/mar10/fancytree/wiki/LicenseInfo
*
2018-05-24 18:59:32 +00:00
* @ version 2.28 . 1
* @ date 2018 - 03 - 19 T06 : 47 : 37 Z
2018-01-01 14:39:23 +00:00
* /
; ( function ( factory ) {
if ( typeof define === "function" && define . amd ) {
// AMD. Register as an anonymous module.
define ( [ "jquery" , "./jquery.fancytree" ] , factory ) ;
} else if ( typeof module === "object" && module . exports ) {
// Node/CommonJS
2018-05-24 18:59:32 +00:00
require ( "./jquery.fancytree" ) ;
2018-01-01 14:39:23 +00:00
module . exports = factory ( require ( "jquery" ) ) ;
} else {
// Browser globals
factory ( jQuery ) ;
}
} ( function ( $ ) {
"use strict" ;
var reNumUnit = /^([+-]?(?:\d+|\d*\.\d+))([a-z]*|%)$/ ; // split "1.5em" to ["1.5", "em"]
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Private functions and variables
* /
// var _assert = $.ui.fancytree.assert;
/* Calculate inner width without scrollbar */
// function realInnerWidth($el) {
// // http://blog.jquery.com/2012/08/16/jquery-1-8-box-sizing-width-csswidth-and-outerwidth/
// // inst.contWidth = parseFloat(this.$container.css("width"), 10);
// // 'Client width without scrollbar' - 'padding'
// return $el[0].clientWidth - ($el.innerWidth() - parseFloat($el.css("width"), 10));
// }
/* Create a global embedded CSS style for the tree. */
function defineHeadStyleElement ( id , cssText ) {
id = "fancytree-style-" + id ;
var $headStyle = $ ( "#" + id ) ;
if ( ! cssText ) {
$headStyle . remove ( ) ;
return null ;
}
if ( ! $headStyle . length ) {
$headStyle = $ ( "<style />" )
. attr ( "id" , id )
. addClass ( "fancytree-style" )
. prop ( "type" , "text/css" )
. appendTo ( "head" ) ;
}
try {
$headStyle . html ( cssText ) ;
} catch ( e ) {
// fix for IE 6-8
$headStyle [ 0 ] . styleSheet . cssText = cssText ;
}
return $headStyle ;
}
/* Calculate the CSS rules that indent title spans. */
function renderLevelCss ( containerId , depth , levelOfs , lineOfs , labelOfs , measureUnit )
{
var i ,
prefix = "#" + containerId + " span.fancytree-level-" ,
rules = [ ] ;
for ( i = 0 ; i < depth ; i ++ ) {
rules . push ( prefix + ( i + 1 ) + " span.fancytree-title { padding-left: " +
( i * levelOfs + lineOfs ) + measureUnit + "; }" ) ;
}
// Some UI animations wrap the UL inside a DIV and set position:relative on both.
// This breaks the left:0 and padding-left:nn settings of the title
rules . push (
"#" + containerId + " div.ui-effects-wrapper ul li span.fancytree-title, " +
2018-05-24 18:59:32 +00:00
"#" + containerId + " li.fancytree-animating span.fancytree-title " + // #716
2018-01-01 14:39:23 +00:00
"{ padding-left: " + labelOfs + measureUnit + "; position: static; width: auto; }" ) ;
return rules . join ( "\n" ) ;
}
// /**
// * [ext-wide] Recalculate the width of the selection bar after the tree container
// * was resized.<br>
// * May be called explicitly on container resize, since there is no resize event
// * for DIV tags.
// *
// * @alias Fancytree#wideUpdate
// * @requires jquery.fancytree.wide.js
// */
// $.ui.fancytree._FancytreeClass.prototype.wideUpdate = function(){
// var inst = this.ext.wide,
// prevCw = inst.contWidth,
// prevLo = inst.lineOfs;
// inst.contWidth = realInnerWidth(this.$container);
// // Each title is precceeded by 2 or 3 icons (16px + 3 margin)
// // + 1px title border and 3px title padding
// // TODO: use code from treeInit() below
// inst.lineOfs = (this.options.checkbox ? 3 : 2) * 19;
// if( prevCw !== inst.contWidth || prevLo !== inst.lineOfs ) {
// this.debug("wideUpdate: " + inst.contWidth);
// this.visit(function(node){
// node.tree._callHook("nodeRenderTitle", node);
// });
// }
// };
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Extension code
* /
$ . ui . fancytree . registerExtension ( {
name : "wide" ,
2018-05-24 18:59:32 +00:00
version : "2.28.1" ,
2018-01-01 14:39:23 +00:00
// Default options for this extension.
options : {
iconWidth : null , // Adjust this if @fancy-icon-width != "16px"
iconSpacing : null , // Adjust this if @fancy-icon-spacing != "3px"
labelSpacing : null , // Adjust this if padding between icon and label != "3px"
levelOfs : null // Adjust this if ul padding != "16px"
} ,
treeCreate : function ( ctx ) {
this . _superApply ( arguments ) ;
this . $container . addClass ( "fancytree-ext-wide" ) ;
var containerId , cssText , iconSpacingUnit , labelSpacingUnit , iconWidthUnit , levelOfsUnit ,
instOpts = ctx . options . wide ,
// css sniffing
$dummyLI = $ ( "<li id='fancytreeTemp'><span class='fancytree-node'><span class='fancytree-icon' /><span class='fancytree-title' /></span><ul />" )
. appendTo ( ctx . tree . $container ) ,
$dummyIcon = $dummyLI . find ( ".fancytree-icon" ) ,
$dummyUL = $dummyLI . find ( "ul" ) ,
// $dummyTitle = $dummyLI.find(".fancytree-title"),
iconSpacing = instOpts . iconSpacing || $dummyIcon . css ( "margin-left" ) ,
iconWidth = instOpts . iconWidth || $dummyIcon . css ( "width" ) ,
labelSpacing = instOpts . labelSpacing || "3px" ,
levelOfs = instOpts . levelOfs || $dummyUL . css ( "padding-left" ) ;
$dummyLI . remove ( ) ;
iconSpacingUnit = iconSpacing . match ( reNumUnit ) [ 2 ] ;
iconSpacing = parseFloat ( iconSpacing , 10 ) ;
labelSpacingUnit = labelSpacing . match ( reNumUnit ) [ 2 ] ;
labelSpacing = parseFloat ( labelSpacing , 10 ) ;
iconWidthUnit = iconWidth . match ( reNumUnit ) [ 2 ] ;
iconWidth = parseFloat ( iconWidth , 10 ) ;
levelOfsUnit = levelOfs . match ( reNumUnit ) [ 2 ] ;
if ( iconSpacingUnit !== iconWidthUnit || levelOfsUnit !== iconWidthUnit || labelSpacingUnit !== iconWidthUnit ) {
$ . error ( "iconWidth, iconSpacing, and levelOfs must have the same css measure unit" ) ;
}
this . _local . measureUnit = iconWidthUnit ;
this . _local . levelOfs = parseFloat ( levelOfs ) ;
this . _local . lineOfs = ( 1 + ( ctx . options . checkbox ? 1 : 0 ) +
( ctx . options . icon === false ? 0 : 1 ) ) * ( iconWidth + iconSpacing ) +
iconSpacing ;
this . _local . labelOfs = labelSpacing ;
this . _local . maxDepth = 10 ;
// Get/Set a unique Id on the container (if not already exists)
containerId = this . $container . uniqueId ( ) . attr ( "id" ) ;
// Generated css rules for some levels (extended on demand)
cssText = renderLevelCss ( containerId , this . _local . maxDepth ,
this . _local . levelOfs , this . _local . lineOfs , this . _local . labelOfs ,
this . _local . measureUnit ) ;
defineHeadStyleElement ( containerId , cssText ) ;
} ,
treeDestroy : function ( ctx ) {
// Remove generated css rules
defineHeadStyleElement ( this . $container . attr ( "id" ) , null ) ;
return this . _superApply ( arguments ) ;
} ,
nodeRenderStatus : function ( ctx ) {
var containerId , cssText , res ,
node = ctx . node ,
level = node . getLevel ( ) ;
res = this . _super ( ctx ) ;
// Generate some more level-n rules if required
if ( level > this . _local . maxDepth ) {
containerId = this . $container . attr ( "id" ) ;
this . _local . maxDepth *= 2 ;
node . debug ( "Define global ext-wide css up to level " + this . _local . maxDepth ) ;
cssText = renderLevelCss ( containerId , this . _local . maxDepth ,
this . _local . levelOfs , this . _local . lineOfs , this . _local . labelSpacing ,
this . _local . measureUnit ) ;
defineHeadStyleElement ( containerId , cssText ) ;
}
// Add level-n class to apply indentation padding.
// (Setting element style would not work, since it cannot easily be
// overriden while animations run)
$ ( node . span ) . addClass ( "fancytree-level-" + level ) ;
return res ;
}
} ) ;
// Value returned by `require('jquery.fancytree..')`
return $ . ui . fancytree ;
} ) ) ; // End of closure
// Value returned by `require('jquery.fancytree')`
return $ . ui . fancytree ;
} ) ) ; // End of closure