2017-05-23 08:32:19 +08:00
/ * !
* jquery . fancytree . js
* Tree view control with support for lazy loading and much more .
* https : //github.com/mar10/fancytree/
*
* Copyright ( c ) 2008 - 2017 , Martin Wendt ( http : //wwWendt.de)
* Released under the MIT license
* https : //github.com/mar10/fancytree/wiki/LicenseInfo
*
2017-08-17 10:06:14 +08:00
* @ version 2.23 . 0
* @ date 2017 - 05 - 27 T20 : 09 : 38 Z
2017-05-23 08:32:19 +08:00
* /
/ * * C o r e F a n c y t r e e m o d u l e .
* /
// Start of local namespace
; ( function ( $ , window , document , undefined ) {
"use strict" ;
// prevent duplicate loading
if ( $ . ui && $ . ui . fancytree ) {
$ . ui . fancytree . warn ( "Fancytree: ignored duplicate include" ) ;
return ;
}
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Private functions and variables
* /
var i , attr ,
FT = null , // initialized below
TEST _IMG = new RegExp ( /\.|\// ) , // strings are considered image urls if they contain '.' or '/'
REX _HTML = /[&<>"'\/]/g ,
REX _TOOLTIP = /[<>"'\/]/g ,
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
2017-08-17 10:06:14 +08:00
// Note: v2.23: checkbox and hideCheckbox are *not* in this list
CLASS _ATTRS = "active expanded focus folder lazy radiogroup selected unselectable unselectableIgnore" . split ( " " ) ,
2017-05-23 08:32:19 +08:00
CLASS _ATTR _MAP = { } ,
// Top-level Fancytree node attributes, that can be set by dict
2017-08-17 10:06:14 +08:00
NODE _ATTRS = "checkbox expanded extraClasses folder icon key lazy radiogroup refKey selected statusNodeType title tooltip unselectable unselectableIgnore unselectableStatus" . split ( " " ) ,
2017-05-23 08:32:19 +08: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 ;
}
}
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 ) ;
}
}
}
/*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 _escapeHtml ( s ) {
return ( "" + s ) . replace ( REX _HTML , function ( s ) {
return ENTITY _MAP [ s ] ;
} ) ;
}
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
* /
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 ] ;
}
2017-08-17 10:06:14 +08:00
// 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'" ) ;
}
2017-05-23 08:32:19 +08:00
// node.data += obj.data
if ( obj . data ) {
$ . extend ( this . data , obj . data ) ;
}
// copy all other attributes to this.data.NAME
for ( name in obj ) {
if ( ! NODE _ATTR _MAP [ name ] && ! $ . isFunction ( obj [ name ] ) && ! NONE _NODE _DATA _MAP [ name ] ) {
// node.data.NAME = obj.NAME
this . data [ name ] = obj [ name ] ;
}
}
// Fix missing key
if ( this . key == null ) { // test for null OR undefined
if ( this . tree . options . defaultKey ) {
this . key = this . tree . options . defaultKey ( this ) ;
_assert ( this . key , "defaultKey() must return a unique key" ) ;
} else {
this . key = "_" + ( FT . _nextNodeKey ++ ) ;
}
} else {
this . key = "" + this . key ; // Convert to string (#217)
}
// Fix tree.activeNode
// TODO: not elegant: we use obj.active as marker to set tree.activeNode
// when loading from a dictionary.
if ( obj . active ) {
_assert ( this . tree . activeNode === null , "only one active node allowed" ) ;
this . tree . activeNode = this ;
}
if ( obj . selected ) { // #186
this . tree . lastSelectedNode = this ;
}
// TODO: handle obj.focus = true
2017-08-17 10:06:14 +08:00
2017-05-23 08:32:19 +08:00
// 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 {
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()
/ * * 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 > = 2 ( p r e p e n d i n g n o d e i n f o )
*
* @ param { * } msg string or object or array of such
* /
debug : function ( msg ) {
if ( this . tree . options . debugLevel >= 2 ) {
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 ) ;
} ,
/ * * 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) */
2017-08-17 10:06:14 +08:00
_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 ) ;
2017-05-23 08:32:19 +08:00
2017-08-17 10:06:14 +08:00
if ( unselectable && unselectableStatus != null ) {
state = unselectableStatus ;
}
2017-05-23 08:32:19 +08:00
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 .
* /
2017-08-17 10:06:14 +08:00
fixSelection3AfterClick : function ( callOpts ) {
2017-05-23 08:32:19 +08:00
var flag = this . isSelected ( ) ;
// this.debug("fixSelection3AfterClick()");
this . visit ( function ( node ) {
node . _changeSelectStatusAttrs ( flag ) ;
} ) ;
2017-08-17 10:06:14 +08:00
this . fixSelection3FromEndNodes ( callOpts ) ;
2017-05-23 08:32:19 +08:00
} ,
/ * *
* 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 .
* /
2017-08-17 10:06:14 +08:00
fixSelection3FromEndNodes : function ( callOpts ) {
var opts = this . tree . options ;
2017-05-23 08:32:19 +08:00
// this.debug("fixSelection3FromEndNodes()");
2017-08-17 10:06:14 +08:00
_assert ( opts . selectMode === 3 , "expected selectMode 3" ) ;
2017-05-23 08:32:19 +08:00
// Visit all end nodes and adjust their parent's `selected` and `partsel`
// attributes. Return selection state true, false, or undefined.
function _walk ( node ) {
2017-08-17 10:06:14 +08:00
var i , l , child , s , state , allSelected , someSelected , unselIgnore , unselState ,
2017-05-23 08:32:19 +08:00
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 ) ;
2017-08-17 10:06:14 +08:00
// if( !child.unselectableIgnore ) {
unselIgnore = FT . evalOption ( "unselectableIgnore" , child , child , opts , false ) ;
if ( ! unselIgnore ) {
if ( s !== false ) {
someSelected = true ;
}
if ( s !== true ) {
allSelected = false ;
}
2017-05-23 08:32:19 +08:00
}
}
state = allSelected ? true : ( someSelected ? undefined : false ) ;
} else {
// This is an end-node: simply report the status
2017-08-17 10:06:14 +08:00
unselState = FT . evalOption ( "unselectableStatus" , node , node , opts , undefined ) ;
state = ( unselState == null ) ? ! ! node . selected : ! ! unselState ;
2017-05-23 08:32:19 +08:00
}
node . _changeSelectStatusAttrs ( state ) ;
return state ;
}
_walk ( this ) ;
// Update parent's state
this . visitParents ( function ( node ) {
2017-08-17 10:06:14 +08:00
var i , l , child , state , unselIgnore , unselState ,
2017-05-23 08:32:19 +08:00
children = node . children ,
allSelected = true ,
someSelected = false ;
for ( i = 0 , l = children . length ; i < l ; i ++ ) {
child = children [ i ] ;
2017-08-17 10:06:14 +08:00
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 ;
}
2017-05-23 08:32:19 +08:00
}
}
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 ) ;
} ,
/ * * 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 > = 1 ( p r e p e n d i n g n o d e i n f o )
*
* @ param { * } msg string or object or array of such
* /
info : function ( msg ) {
if ( this . tree . options . debugLevel >= 1 ) {
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 ) ;
} ,
/ * * 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 ;
}
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 ( ) ;
} ,
2017-08-17 10:06:14 +08:00
/ * * 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 ;
} ,
2017-05-23 08:32:19 +08:00
/ * * ( 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
2017-08-17 10:06:14 +08:00
* @ param { object } [ opts ] additional options . Defaults to { noEvents : false , p
* propagateDown : null , propagateUp : null , callback : null }
2017-05-23 08:32:19 +08:00
* /
2017-08-17 10:06:14 +08:00
setSelected : function ( flag , opts ) {
return this . tree . _callHook ( "nodeSetSelected" , this , flag , opts ) ;
2017-05-23 08:32:19 +08:00
} ,
/ * * 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 ;
} ,
2017-08-17 10:06:14 +08:00
/** Flip expanded status. */
2017-05-23 08:32:19 +08:00
toggleExpanded : function ( ) {
return this . tree . _callHook ( "nodeToggleExpanded" , this ) ;
} ,
2017-08-17 10:06:14 +08:00
/** Flip selection status. */
2017-05-23 08:32:19 +08:00
toggleSelected : function ( ) {
return this . tree . _callHook ( "nodeToggleSelected" , this ) ;
} ,
toString : function ( ) {
return "<FancytreeNode(#" + this . key + ", '" + this . title + "')>" ;
} ,
/ * *
* 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 ) ;
} ,
/ * * 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 . < b r >
* 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 ;
} ,
2017-08-17 10:06:14 +08:00
/ * * 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 ;
} ,
2017-05-23 08:32:19 +08: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 ( p r e p e n d i n g n o d e i n f o )
*
* @ param { * } msg string or object or array of such
* /
warn : function ( msg ) {
Array . prototype . unshift . call ( arguments , this . toString ( ) ) ;
consoleApply ( "warn" , arguments ) ;
}
} ;
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* 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 ` )
* @ 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 .
* @ 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
// 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 . _enableUpdate = true ;
// this._dirtyRoots = null;
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 ;
ctx = { node : obj , tree : tree , widget : tree . widget , options : tree . widget . options , originalEvent : originalEvent } ;
} 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 ) ;
} ,
/ * 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 prevously 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 ( ) ;
} ,
/ * * 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 > = 2 ( p r e p e n d i n g t r e e n a m e )
*
* @ param { * } msg string or object or array of such
* /
debug : function ( msg ) {
if ( this . options . debugLevel >= 2 ) {
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 ) {
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 ) ;
}
} ;
match = ( typeof match === "string" ) ? _makeNodeTitleStartMatcher ( match ) : match ;
startNode = startNode || this . getFirstChild ( ) ;
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 .
* @ 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).
// $("#...") has problems, if the key contains '.', so we use getElementById()
var el , match ;
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
// TODO: optimize with specialized loop
// TODO: consider keyMap?
searchRoot = searchRoot || this . rootNode ;
match = null ;
searchRoot . visit ( function ( node ) {
// window.console.log("getNodeByKey(" + key + "): ", node.key);
if ( node . key === key ) {
match = node ;
return false ;
}
} , 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 ;
} ,
/ * * 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 > = 1 ( p r e p e n d i n g t r e e n a m e )
* @ param { * } msg string or object or array of such
* /
info : function ( msg ) {
if ( this . options . debugLevel >= 1 ) {
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
* loading - its parents . This method is ment for lazy hierarchies .
* A callback is executed for every node as we go .
* @ example
* tree . loadKeyPath ( "/_3/_23/_26/_27" , function ( node , status ) {
* if ( status === "loaded" ) {
* console . log ( "loaded intermiediate node " + node ) ;
* } else if ( status === "ok" ) {
* node . activate ( ) ;
* }
* } ) ;
*
* @ param { string | string [ ] } keyPathList one or more key paths ( e . g . '/3/2_1/7' )
* @ param { function } callback callback ( node , status ) is called for every visited node ( 'loading' , 'loaded' , 'ok' , 'error' )
* @ returns { $ . Promise }
* /
loadKeyPath : function ( keyPathList , callback , _rootNode ) {
var deferredList , dfd , i , path , key , loadMap , node , root , segList ,
sep = this . options . keyPathSeparator ,
self = this ;
callback = callback || $ . noop ;
if ( ! $ . isArray ( keyPathList ) ) {
keyPathList = [ keyPathList ] ;
}
// Pass 1: handle all path segments for nodes that are already loaded
// Collect distinct top-most lazy nodes in a map
loadMap = { } ;
for ( i = 0 ; i < keyPathList . length ; i ++ ) {
root = _rootNode || this . rootNode ;
path = keyPathList [ i ] ;
// strip leading slash
if ( path . charAt ( 0 ) === sep ) {
path = path . substr ( 1 ) ;
}
// traverse and strip keys, until we hit a lazy, unloaded node
segList = path . split ( sep ) ;
while ( segList . length ) {
key = segList . shift ( ) ;
// node = _findDirectChild(root, key);
node = root . _findDirectChild ( key ) ;
if ( ! node ) {
this . warn ( "loadKeyPath: key not found: " + key + " (parent: " + root + ")" ) ;
callback . call ( this , key , "error" ) ;
break ;
} else if ( segList . length === 0 ) {
callback . call ( this , node , "ok" ) ;
break ;
} else if ( ! node . lazy || ( node . hasChildren ( ) !== undefined ) ) {
callback . call ( this , node , "loaded" ) ;
root = node ;
} else {
callback . call ( this , node , "loaded" ) ;
// segList.unshift(key);
if ( loadMap [ key ] ) {
loadMap [ key ] . push ( segList . join ( sep ) ) ;
} else {
loadMap [ key ] = [ segList . join ( sep ) ] ;
}
break ;
}
}
}
// alert("loadKeyPath: loadMap=" + JSON.stringify(loadMap));
// Now load all lazy nodes and continue itearation for remaining paths
deferredList = [ ] ;
// Avoid jshint warning 'Don't make functions within a loop.':
function _ _lazyload ( key , node , dfd ) {
callback . call ( self , node , "loading" ) ;
node . load ( ) . done ( function ( ) {
self . loadKeyPath . call ( self , loadMap [ key ] , callback , node ) . always ( _makeResolveFunc ( dfd , self ) ) ;
} ) . fail ( function ( errMsg ) {
self . warn ( "loadKeyPath: error loading: " + key + " (parent: " + root + ")" ) ;
callback . call ( self , node , "error" ) ;
dfd . reject ( ) ;
} ) ;
}
for ( key in loadMap ) {
node = root . _findDirectChild ( key ) ;
if ( node == null ) { // #576
node = self . getNodeByKey ( key ) ;
}
dfd = new $ . Deferred ( ) ;
deferredList . push ( dfd ) ;
_ _lazyload ( key , node , dfd ) ;
}
// 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 ) ;
} ,
// 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 ( ) {
return "<Fancytree(#" + this . _id + ")>" ;
} ,
/ * _ 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 ;
} ,
/ * * C a l l f n ( n o d e ) f o r a l l n o d e s .
*
* @ 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 ) ;
} ,
/ * * 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 ( 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
* /
warn : function ( msg ) {
Array . prototype . unshift . call ( arguments , this . toString ( ) ) ;
consoleApply ( "warn" , arguments ) ;
}
} ;
/ * *
* 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
stamp = new Date ( ) . getTime ( ) ;
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 ( opts . checkbox ) {
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" :
res = node . navigate ( event . which , activate , true ) ;
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 ,
requestId = new Date ( ) . getTime ( ) ;
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 ) ;
}
} ) ;
}
// $.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 ;
$ . 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
2017-08-17 10:06:14 +08:00
var checkbox , className , icon , nodeTitle , role , tabindex , tooltip ,
2017-05-23 08:32:19 +08: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
2017-08-17 10:06:14 +08:00
checkbox = FT . evalOption ( "checkbox" , node , node , opts , false ) ;
if ( checkbox && ! node . isStatusNode ( ) ) {
2017-05-23 08:32:19 +08:00
role = aria ? " role='checkbox'" : "" ;
2017-08-17 10:06:14 +08:00
className = "fancytree-checkbox" ;
if ( checkbox === "radio" || ( node . parent && node . parent . radiogroup ) ) {
className += " fancytree-radio" ;
}
ares . push ( "<span " + role + " class='" + className + "'></span>" ) ;
2017-05-23 08:32:19 +08:00
}
// 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 ) ;
if ( typeof icon !== "boolean" ) {
// icon is defined, but not true/false: must be a string
icon = "" + icon ;
}
if ( icon !== false ) {
role = aria ? " role='presentation'" : "" ;
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 ) ;
ares . push ( "<img src='" + icon + "' class='fancytree-icon' alt='' />" ) ;
} else {
ares . push ( "<span " + role + " class='fancytree-custom-icon " + icon + "'></span>" ) ;
}
} else {
// standard icon: theme css will take care of this
ares . push ( "<span " + role + " class='fancytree-icon'></span>" ) ;
}
}
// Node title
nodeTitle = "" ;
if ( opts . renderTitle ) {
nodeTitle = opts . renderTitle . call ( tree , { type : "renderTitle" } , ctx ) || "" ;
}
if ( ! nodeTitle ) {
2017-08-17 10:06:14 +08:00
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);
// }
2017-05-23 08:32:19 +08:00
tooltip = tooltip ? " title='" + _escapeTooltip ( tooltip ) + "'" : "" ;
tabindex = opts . titlesTabbable ? " tabindex='0'" : "" ;
nodeTitle = "<span class='fancytree-title'" +
tooltip + tabindex + ">" +
( opts . escapeTitles ? _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 ) ;
}
2017-08-17 10:06:14 +08:00
if ( FT . evalOption ( "unselectable" , node , node , opts , false ) ) {
2017-05-23 08:32:19 +08:00
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 ]
2017-08-17 10:06:14 +08:00
* @ param { object } [ opts ] additional options . Defaults to { noEvents : false ,
* propagateDown : null , propagateUp : null ,
* callback : null ,
* }
* @ returns { boolean } previous status
2017-05-23 08:32:19 +08:00
* /
2017-08-17 10:06:14 +08:00
nodeSetSelected : function ( ctx , flag , callOpts ) {
callOpts = callOpts || { } ;
2017-05-23 08:32:19 +08:00
var node = ctx . node ,
tree = ctx . tree ,
2017-08-17 10:06:14 +08:00
opts = ctx . options ,
noEvents = ( callOpts . noEvents === true ) ;
2017-05-23 08:32:19 +08:00
// flag defaults to true
flag = ( flag !== false ) ;
// node.debug("nodeSetSelected(" + flag + ")", ctx);
2017-08-17 10:06:14 +08:00
// Cannot (de)select unselectable nodes directly (only by propagation or
// by setting the `.selected` property)
if ( FT . evalOption ( "unselectable" , node , node , opts , false ) ) {
2017-05-23 08:32:19 +08:00
return ;
}
2017-08-17 10:06:14 +08:00
// 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 ;
2017-05-23 08:32:19 +08:00
}
if ( flag && opts . selectMode === 1 ) {
2017-08-17 10:06:14 +08:00
// single selection mode (we don't uncheck all tree nodes, for performance reasons)
2017-05-23 08:32:19 +08:00
if ( tree . lastSelectedNode ) {
tree . lastSelectedNode . setSelected ( false ) ;
}
node . selected = flag ;
2017-08-17 10:06:14 +08:00
} else if ( opts . selectMode === 3 && ! node . parent . radiogroup && ! node . radiogroup ) {
// multi-hierarchical selection mode
node . selected = flag ;
node . fixSelection3AfterClick ( callOpts ) ;
} else if ( node . parent . radiogroup ) {
node . visitSiblings ( function ( n ) {
n . _changeSelectStatusAttrs ( flag && n === node ) ;
} , true ) ;
} else {
// default: selectMode: 2, multi selection mode
node . selected = flag ;
2017-05-23 08:32:19 +08:00
}
this . nodeRenderStatus ( ctx ) ;
tree . lastSelectedNode = flag ? node : null ;
2017-08-17 10:06:14 +08:00
if ( ! noEvents ) {
tree . _triggerNodeEvent ( "select" , ctx ) ;
}
2017-05-23 08:32:19 +08:00
} ,
/ * * 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 ) {
2017-08-17 10:06:14 +08:00
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 ) ;
2017-05-23 08:32:19 +08:00
} ,
/ * * 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 ( ) ;
this . $source && this . $source . removeClass ( "ui-helper-hidden" ) ;
} ,
/ * * 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 ) ;
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" ) ;
$ul . addClass ( "ui-fancytree-source ui-helper-hidden" ) ;
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: [...]}
// Copy extra properties to tree.data.foo
_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 ;
$ . 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 ) {
function ensureTreeFocus ( thisTree ) {
if ( ! thisTree . activeNode && thisTree . getFirstChild ( ) ) {
thisTree . getFirstChild ( ) . setFocus ( ) ;
}
}
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 ) {
// Check after timeout to ensure mousedown processing is complete
// and the clicked node is already activated
var thisTree = this ;
setTimeout ( function ( ) { ensureTreeFocus ( thisTree ) ; } , 0 ) ;
}
}
} ,
/ * * 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 ,
rerender = false ;
switch ( key ) {
case "aria" :
case "checkbox" :
case "icon" :
case "minExpandLevel" :
case "tabindex" :
tree . _callHook ( "treeCreate" , tree ) ;
rerender = true ;
break ;
case "escapeTitles" :
case "tooltip" :
rerender = true ;
break ;
case "rtl" :
if ( value === false ) {
tree . $container . removeAttr ( "DIR" ) . removeClass ( "fancytree-rtl" ) ;
} else {
tree . $container . attr ( "DIR" , "RTL" ) . addClass ( "fancytree-rtl" ) ;
}
rerender = true ;
break ;
case "source" :
callDefault = false ;
tree . _callHook ( "treeLoad" , tree , value ) ;
rerender = 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 ( rerender ) {
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.
} , //
2017-08-17 10:06:14 +08:00
aria : true ,
2017-05-23 08:32:19 +08:00
autoActivate : true ,
autoCollapse : false ,
autoScroll : false ,
checkbox : false ,
clickFolderMode : 4 ,
debugLevel : null , // 0..2 (null: use global setting $.ui.fancytree.debugInfo)
disabled : false , // TODO: required anymore?
enableAspx : true , // TODO: document
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" ,
2017-08-17 10:06:14 +08:00
radio : "fancytree-radio" ,
// radiogroup: "fancytree-radiogroup",
2017-05-23 08:32:19 +08:00
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.
// It would be better, to move ebent binding into hooks altogether
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" ) ;
// tree.debug("Tree container got event " + event.type, node, event);
// tree.treeOnFocusInOut.call(tree, event);
if ( node ) {
// For example clicking into an <input> that is part of a node
tree . _callHook ( "nodeSetFocus" , tree . _makeHookContext ( node , event ) , flag ) ;
// tree._callHook("nodeSetFocus", node, flag);
} 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 + " dblclick" + ns , function ( event ) {
// that.tree.debug("event(" + 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 "mousedown" :
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 ) ;
}
// } catch(e) {
// // var _ = null; // DT issue 117 // TODO
// $.error(e);
} 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} */
2017-08-17 10:06:14 +08:00
version : "2.23.0" , // Set to semver by 'grunt release'
2017-05-23 08:32:19 +08:00
/** @type {string} */
buildType : "production" , // Set to 'production' by 'grunt build'
/** @type {int} */
debugLevel : 1 , // Set to 1 by 'grunt build'
// 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 ) ;
} ,
/ * * 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 ) ;
} ;
} ,
/ * * 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 > = 2
* @ param { string } msg
* /
debug : function ( msg ) {
/*jshint expr:true */
( $ . ui . fancytree . debugLevel >= 2 ) && consoleApply ( "log" , arguments ) ;
} ,
/ * * 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 .
* @ param { string } msg
* /
error : function ( msg ) {
consoleApply ( "error" , arguments ) ;
} ,
/ * * 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 : _escapeHtml ,
/ * * 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/
* /
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 { string } 'title' | 'prefix' | 'expander' | 'checkbox' | 'icon' | undefined
* /
getEventTargetType : function ( event ) {
return this . getEventTarget ( event ) . type ;
} ,
/ * * 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 ) {
var tcn = event && event . target ? event . target . className : "" ,
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" ) ;
2017-08-17 10:06:14 +08:00
// }else if( /\bfancytree-checkbox\b/.test(tcn) || /\bfancytree-radio\b/.test(tcn) ){
} else if ( /\bfancytree-checkbox\b/ . test ( tcn ) ) {
2017-05-23 08:32:19 +08:00
res . type = "checkbox" ;
} else if ( /\bfancytree-icon\b/ . test ( tcn ) ) {
res . type = "icon" ;
} else if ( /\bfancytree-node\b/ . test ( tcn ) ) {
// Somewhere near the title
res . type = "title" ;
} else if ( event && event . target && $ ( event . target ) . closest ( ".fancytree-title" ) . length ) {
// #228: clicking an embedded element inside a title
res . type = "title" ;
}
return res ;
} ,
/ * * 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 jQuery ) {
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 ) ) {
ctx = { node : node , tree : tree , widget : tree . widget , options : tree . widget . options } ;
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 ;
} ,
/ * * 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" :
res = node . navigate ( event . which , activate , true ) ;
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 ( "+" ) ;
} ,
/ * * 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 > = 1
* @ param { string } msg
* /
info : function ( msg ) {
/*jshint expr:true */
( $ . ui . fancytree . debugLevel >= 1 ) && consoleApply ( "info" , arguments ) ;
} ,
/ * @ 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 ;
}
2017-08-17 10:06:14 +08:00
// Translate hideCheckbox -> checkbox:false
if ( $li . attr ( "hideCheckbox" ) ) {
d . checkbox = false ;
}
2017-05-23 08:32:19 +08:00
// 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 ;
} ,
/ * * 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 .
* @ param { string } msg
* /
warn : function ( msg ) {
consoleApply ( "warn" , arguments ) ;
}
} ) ;
} ( jQuery , window , document ) ) ;