2012-03-07 01:30:47 +01:00
/ * *
2013-02-08 14:23:58 +01:00
* EGroupware eTemplate2 - JS Tree object
2012-03-07 01:30:47 +01:00
*
2016-04-07 12:38:05 +02:00
* @ link http : //community.egroupware.org/egroupware/api/js/dhtmlxtree/docsExplorer/dhtmlxtree/
2012-03-07 01:30:47 +01:00
* @ license http : //opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @ package etemplate
* @ subpackage api
2021-06-07 17:33:53 +02:00
* @ link https : //www.egroupware.org
2012-03-07 01:30:47 +01:00
* @ author Nathan Gray
2013-02-08 14:23:58 +01:00
* @ author Ralf Becker
2012-03-07 01:30:47 +01:00
* @ copyright Nathan Gray 2011
* /
/ * e g w : u s e s
2020-02-12 11:29:03 +01:00
et2 _core _inputWidget ;
/ a p i / j s / e g w _ a c t i o n / e g w _ d r a g d r o p _ d h t m l x _ t r e e . j s ;
/ a p i / j s / d h t m l x t r e e / c o d e b a s e / d h t m l x c o m m o n . j s ;
2016-04-07 12:38:05 +02:00
// using debugable and fixed source of dhtmltree instead: /api/js/dhtmlxtree/js/dhtmlXTree.js;
2020-02-12 11:29:03 +01:00
/ a p i / j s / d h t m l x t r e e / s o u r c e s / d h t m l x t r e e . j s ;
/ a p i / j s / d h t m l x t r e e / s o u r c e s / e x t / d h t m l x t r e e _ j s o n . j s ;
2016-04-07 12:38:05 +02:00
// /api/js/dhtmlxtree/sources/ext/dhtmlxtree_start.js;
2012-03-07 01:30:47 +01:00
* /
2021-06-07 17:33:53 +02:00
import { et2 _register _widget } from "./et2_core_widget" ;
import { et2 _inputWidget } from "./et2_core_inputWidget" ;
import { ClassWithAttributes } from "./et2_core_inheritance" ;
import { et2 _no _init } from "./et2_core_common" ;
import { egw , framework } from "../jsapi/egw_global" ;
2021-06-10 14:07:02 +02:00
import { EGW _AO _FLAG _IS _CONTAINER , egw _getAppObjectManager , egw _getObjectManager , egwActionObject } from "../egw_action/egw_action.js" ;
2021-06-10 14:54:16 +02:00
import { dhtmlxtreeItemAOI } from "../egw_action/egw_dragdrop_dhtmlx_tree.js" ;
import { egwIsMobile } from "../egw_action/egw_action_common.js" ;
2021-06-10 13:53:07 +02:00
/ * n o m o d u l e , b u t e g w : u s e s i s i g n o r e d , s o a d d i n g i t h e r e c o m m e n t e d o u t
import '../../../api/js/dhtmlxtree/sources/dhtmlxtree.js' ;
import '../../../api/js/dhtmlxtree/sources/ext/dhtmlxtree_json.js' ;
import '../../../api/js/dhtmlxtree/sources/ext/dhtmlxtree_start.js' ;
* /
2013-04-13 21:00:13 +02:00
/ * *
* Tree widget
2014-01-21 14:34:02 +01:00
*
2021-06-10 13:53:07 +02:00
* For syntax of nodes supplied via sel _options or autoloading refer to Etemplate \ Widget \ Tree class .
2014-01-21 14:34:02 +01:00
*
2013-04-13 21:00:13 +02:00
* @ augments et2 _inputWidget
* /
2021-06-07 17:33:53 +02:00
export class et2 _tree extends et2 _inputWidget {
2020-02-12 11:29:03 +01:00
/ * *
* Constructor
*
* @ memberOf et2 _tree
* /
2021-06-07 17:33:53 +02:00
constructor ( _parent , _attrs , _child ) {
2020-02-12 11:29:03 +01:00
// Call the inherited constructor
2021-06-07 17:33:53 +02:00
super ( _parent , _attrs , ClassWithAttributes . extendAttributes ( et2 _tree . _attributes , _child || { } ) ) ;
this . input = null ;
2020-02-12 11:29:03 +01:00
/ * *
* Regexp used by _htmlencode
* /
2021-06-07 17:33:53 +02:00
this . _lt _regexp = /</g ;
this . input = null ;
this . div = jQuery ( document . createElement ( "div" ) ) . addClass ( "dhtmlxTree" ) ;
this . setDOMNode ( this . div [ 0 ] ) ;
2020-02-12 11:29:03 +01:00
}
2021-06-07 17:33:53 +02:00
destroy ( ) {
2020-02-12 11:29:03 +01:00
if ( this . input ) {
this . input . destructor ( ) ;
}
this . input = null ;
2021-06-07 17:33:53 +02:00
super . destroy ( ) ;
}
2020-02-12 11:29:03 +01:00
/ * *
* Get tree items from the sel _options data array
*
* @ param { object } _attrs
* /
2021-06-07 17:33:53 +02:00
transformAttributes ( _attrs ) {
super . transformAttributes ( _attrs ) ;
2020-02-12 11:29:03 +01:00
// If select_options are already known, skip the rest
if ( this . options && this . options . select _options && ! jQuery . isEmptyObject ( this . options . select _options ) ) {
return ;
}
2021-06-07 17:33:53 +02:00
let name _parts = this . id . replace ( /]/g , '' ) . split ( '[' ) ;
2020-02-12 11:29:03 +01:00
// Try to find the options inside the "sel-options" array
if ( this . getArrayMgr ( "sel_options" ) ) {
// Select options tend to be defined once, at the top level, so try that first
2021-06-07 17:33:53 +02:00
let content _options = this . getArrayMgr ( "sel_options" ) . getRoot ( ) . getEntry ( name _parts [ name _parts . length - 1 ] ) ;
2020-02-12 11:29:03 +01:00
// Try again according to ID
if ( ! content _options )
content _options = this . getArrayMgr ( "sel_options" ) . getEntry ( this . id ) ;
if ( _attrs [ "select_options" ] && ! jQuery . isEmptyObject ( _attrs [ "select_options" ] ) && content _options ) {
_attrs [ "select_options" ] = jQuery . extend ( { } , _attrs [ "select_options" ] , content _options ) ;
}
else if ( content _options ) {
_attrs [ "select_options" ] = content _options ;
}
}
// Check whether the options entry was found, if not read it from the
// content array.
if ( _attrs [ "select_options" ] == null ) {
// Again, try last name part at top level
2021-06-07 17:33:53 +02:00
let content _options = this . getArrayMgr ( 'content' ) . getRoot ( ) . getEntry ( name _parts [ name _parts . length - 1 ] ) ;
2020-02-12 11:29:03 +01:00
// If that didn't work, check according to ID
_attrs [ "select_options" ] = content _options ? content _options : this . getArrayMgr ( 'content' )
. getEntry ( "options-" + this . id ) ;
}
// Default to an empty object
if ( _attrs [ "select_options" ] == null ) {
_attrs [ "select_options" ] = { } ;
}
2021-06-07 17:33:53 +02:00
}
2020-02-12 11:29:03 +01:00
// overwrite default onclick to do nothing, as we install onclick via dhtmlxtree
2021-06-07 17:33:53 +02:00
click ( _node ) { }
createTree ( widget ) {
2020-02-12 11:29:03 +01:00
widget . input = new dhtmlXTreeObject ( {
parent : widget . div [ 0 ] ,
width : '100%' ,
height : '100%' ,
image _path : widget . options . image _path ,
checkbox : widget . options . multiple
} ) ;
// to allow "," in value, eg. folder-names, IF value is specified as array
widget . input . dlmtr = ':}-*(' ;
if ( widget . options . std _images ) {
widget . setImages . apply ( widget , widget . options . std _images . split ( ',' ) ) ;
}
else {
// calling setImages to get our png or svg default images
widget . setImages ( ) ;
}
// Add in the callback so we can keep the two in sync
widget . input . AJAX _callback = function ( dxmlObject ) {
widget . _dhtmlxtree _json _callback ( JSON . parse ( dxmlObject . xmlDoc . responseText ) , widget . input . lastLoadedXMLId ) ;
// Call this in case we added some options that were already selected, but missing
if ( widget . options . multiple ) {
widget . set _value ( widget . value ) ;
}
} ;
if ( widget . options . autoloading ) {
2021-06-07 17:33:53 +02:00
let url = widget . options . autoloading ;
2020-02-12 11:29:03 +01:00
//Set escaping mode to utf8, as url in
//autoloading needs to be utf8 encoded.
//For instance item id with umlaut.
widget . input . setEscapingMode ( 'utf8' ) ;
if ( url . charAt ( 0 ) != '/' && url . substr ( 0 , 4 ) != 'http' ) {
url = '/json.php?menuaction=' + url ;
}
this . autoloading _url = url ;
widget . input . setXMLAutoLoading ( egw . link ( url ) ) ;
widget . input . setDataMode ( 'JSON' ) ;
}
if ( widget . options . multimarking ) {
widget . input . enableMultiselection ( ! ! widget . options . multimarking , widget . options . multimarking === 'strict' ) ;
}
// Enable/Disable highlighting
widget . input . enableHighlighting ( ! ! widget . options . highlighting ) ;
// if templates supplies open/close right/down arrows, show no more lines and use them instead of plus/minus
2021-06-07 17:33:53 +02:00
let open = egw . image ( 'dhtmlxtree/open' ) ;
let close = egw . image ( 'dhtmlxtree/close' ) ;
2020-02-12 11:29:03 +01:00
if ( open && close ) {
widget . input . enableTreeLines ( false ) ;
open = this . _rel _url ( open ) ;
widget . input . setImageArrays ( 'plus' , open , open , open , open , open ) ;
close = this . _rel _url ( close ) ;
widget . input . setImageArrays ( 'minus' , close , close , close , close , close ) ;
}
this . _install _handler ( 'onBeforeCheck' , function ( ) {
return ! this . options . readonly ;
} . bind ( this ) ) ;
2021-06-07 17:33:53 +02:00
}
2020-02-12 11:29:03 +01:00
/ * *
* Install event handlers on tree
*
* @ param _name
* @ param _handler
* /
2021-06-07 17:33:53 +02:00
_install _handler ( _name , _handler ) {
2020-02-12 11:29:03 +01:00
if ( typeof _handler == 'function' ) {
if ( this . input == null )
this . createTree ( this ) ;
// automatic convert onChange event to oncheck or onSelect depending on multiple is used or not
if ( _name == 'onchange' )
_name = this . options . multiple ? 'oncheck' : 'onselect' ;
2021-06-07 17:33:53 +02:00
let handler = _handler ;
let widget = this ;
2020-02-12 11:29:03 +01:00
this . input . attachEvent ( _name , function ( _id ) {
2021-06-07 17:33:53 +02:00
let args = jQuery . makeArray ( arguments ) ;
2020-02-12 11:29:03 +01:00
// splice in widget as 2. parameter, 1. is new node-id, now 3. is old node id
2021-06-07 17:33:53 +02:00
args . splice ( 1 , 0 , widget ) ;
2020-02-12 11:29:03 +01:00
// try to close mobile sidemenu after clicking on node
if ( egwIsMobile ( ) && typeof args [ 2 ] == 'string' )
framework . toggleMenu ( 'on' ) ;
2021-06-07 17:33:53 +02:00
return handler . apply ( this , args ) ;
2020-02-12 11:29:03 +01:00
} ) ;
}
2021-06-07 17:33:53 +02:00
}
set _onchange ( _handler ) { this . _install _handler ( 'onchange' , _handler ) ; }
set _onclick ( _handler ) { this . _install _handler ( 'onclick' , _handler ) ; }
set _onselect ( _handler ) { this . _install _handler ( 'onselect' , _handler ) ; }
set _onopenstart ( _handler ) { this . _install _handler ( 'onOpenStart' , _handler ) ; }
set _onopenend ( _handler ) { this . _install _handler ( 'onOpenEnd' , _handler ) ; }
set _select _options ( options ) {
let custom _images = false ;
2020-02-12 11:29:03 +01:00
this . options . select _options = options ;
if ( this . input == null ) {
this . createTree ( this ) ;
}
// Structure data for category tree
if ( this . getType ( ) == 'tree-cat' ) {
2021-06-07 17:33:53 +02:00
let data = { id : 0 , item : [ ] } ;
let stack = { } ;
for ( let key = 0 ; key < options . length ; key ++ ) {
2020-02-12 11:29:03 +01:00
// See if item has an icon
if ( options [ key ] . data && typeof options [ key ] . data . icon !== 'undefined' && options [ key ] . data . icon ) {
2021-06-07 17:33:53 +02:00
let img = this . egw ( ) . image ( options [ key ] . data . icon , options [ key ] . appname ) ;
2020-02-12 11:29:03 +01:00
if ( img ) {
custom _images = true ;
options [ key ] . im0 = options [ key ] . im1 = options [ key ] . im2 = img ;
}
}
// Item color - not working
if ( options [ key ] . data && typeof options [ key ] . data . color !== 'undefined' && options [ key ] . data . color ) {
options [ key ] . style = options [ key ] . style || "" + "background-color:'" + options [ key ] . data . color + "';" ;
}
// Tooltip
if ( options [ key ] . description && ! options [ key ] . tooltip ) {
options [ key ] . tooltip = options [ key ] . description ;
}
2021-06-07 17:33:53 +02:00
let parent _id = parseInt ( options [ key ] [ 'parent' ] ) ;
2020-02-12 11:29:03 +01:00
if ( isNaN ( parent _id ) )
parent _id = 0 ;
2021-06-07 17:33:53 +02:00
if ( ! stack [ parent _id ] )
stack [ parent _id ] = [ ] ;
stack [ parent _id ] . push ( options [ key ] ) ;
2020-02-12 11:29:03 +01:00
}
if ( custom _images ) {
2021-06-07 17:33:53 +02:00
let path = this . input . iconURL ;
2020-02-12 11:29:03 +01:00
this . input . setIconPath ( "" ) ;
2021-06-07 17:33:53 +02:00
for ( let k = 0 ; k < this . input . imageArray . length ; k ++ )
2020-02-12 11:29:03 +01:00
this . input . imageArray [ k ] = path + this . input . imageArray [ k ] ;
}
2021-06-07 17:33:53 +02:00
let f = function ( data , _f ) {
if ( stack [ data . id ] ) {
data . item = stack [ data . id ] ;
for ( let j = 0 ; j < data . item . length ; j ++ ) {
f ( data . item [ j ] , _f ) ;
2020-02-12 11:29:03 +01:00
}
}
} ;
2021-06-07 17:33:53 +02:00
f ( data , f ) ;
2020-02-12 11:29:03 +01:00
options = data ;
}
// if no options given, but autoloading url, use that to load initial nodes
if ( typeof options . id == 'undefined' && this . input . XMLsource )
this . input . loadJSON ( this . input . XMLsource ) ;
else
this . input . loadJSONObject ( this . _htmlencode _node ( options ) ) ;
2021-06-07 17:33:53 +02:00
}
2020-02-12 11:29:03 +01:00
/ * *
* html encoding of text of node
*
* We only do a minimal html encoding by replacing opening bracket < with & lt ;
* as tree seems not to need more and we dont want to waste time .
*
* @ param { string } _text text to encode
* @ return { string }
* /
2021-06-07 17:33:53 +02:00
_htmlencode ( _text ) {
2020-02-12 11:29:03 +01:00
if ( _text && _text . indexOf ( '<' ) >= 0 ) {
_text = _text . replace ( this . _lt _regexp , '<' ) ;
}
return _text ;
2021-06-07 17:33:53 +02:00
}
2020-02-12 11:29:03 +01:00
/ * *
* html encoding of text of node incl . all children
*
* @ param { object } _item with required attributes text , id and optional tooltip and item
* @ return { object } encoded node
* /
2021-06-07 17:33:53 +02:00
_htmlencode _node ( _item ) {
2020-02-12 11:29:03 +01:00
_item . text = this . _htmlencode ( _item . text ) ;
if ( _item . item && jQuery . isArray ( _item . item ) ) {
2021-06-07 17:33:53 +02:00
for ( let i = 0 ; i < _item . item . length ; ++ i ) {
2020-02-12 11:29:03 +01:00
this . _htmlencode _node ( _item . item [ i ] ) ;
}
}
return _item ;
2021-06-07 17:33:53 +02:00
}
set _value ( new _value ) {
2020-02-12 11:29:03 +01:00
this . value = this . _oldValue = ( typeof new _value === 'string' && this . options . multiple ? new _value . split ( ',' ) : new _value ) ;
if ( this . input == null )
return ;
if ( this . options . multiple ) {
// Clear all checked
2021-06-07 17:33:53 +02:00
let checked = this . input . getAllChecked ( ) . split ( this . input . dlmtr ) ;
for ( let i = 0 ; i < checked . length ; i ++ ) {
2020-02-12 11:29:03 +01:00
this . input . setCheck ( checked [ i ] , false ) ;
}
// Check selected
2021-06-07 17:33:53 +02:00
for ( let i = 0 ; i < this . value . length ; i ++ ) {
2020-02-12 11:29:03 +01:00
this . input . setCheck ( this . value [ i ] , true ) ;
// autoloading openning needs to be absolutely based on user interaction
// or open flag in folder structure, therefore, We should
// not force it to open the node
if ( ! this . options . autoloading )
this . input . openItem ( this . value [ i ] ) ;
}
}
else {
this . input . selectItem ( this . value , false ) ; // false = do not trigger onSelect
this . input . focusItem ( this . value ) ;
this . input . openItem ( this . value ) ;
}
2021-06-07 17:33:53 +02:00
}
2020-02-12 11:29:03 +01:00
/ * *
* Links actions to tree nodes
*
* @ param { object } actions [ { ID : attributes . . } + ] as for set _actions
* /
2021-06-07 17:33:53 +02:00
_link _actions ( actions ) {
2020-02-12 11:29:03 +01:00
// Get the top level element for the tree
// Only look 1 level deep for application object manager
2021-06-07 17:33:53 +02:00
let objectManager = egw _getObjectManager ( this . egw ( ) . app _name ( ) , true , 1 ) ;
let treeObj = objectManager . getObjectById ( this . id ) ;
2020-02-12 11:29:03 +01:00
if ( treeObj == null ) {
// Add a new container to the object manager which will hold the tree
// objects
treeObj = objectManager . addObject ( new egwActionObject ( this . id , objectManager , null , this . _actionManager , EGW _AO _FLAG _IS _CONTAINER ) , null , EGW _AO _FLAG _IS _CONTAINER ) ;
}
// Delete all old objects
treeObj . clear ( ) ;
// Go over the tree parts & add links
2021-06-07 17:33:53 +02:00
let action _links = this . _get _action _links ( actions ) ;
2020-02-12 11:29:03 +01:00
if ( typeof this . options . select _options != 'undefined' ) {
// Iterate over the options (leaves) and add action to each one
2021-06-07 17:33:53 +02:00
let apply _actions = function ( treeObj , option ) {
2020-02-12 11:29:03 +01:00
// Add a new action object to the object manager
// @ts-ignore
2021-06-07 17:33:53 +02:00
let obj = treeObj . addObject ( ( typeof option . id == 'number' ? String ( option . id ) : option . id ) , new dhtmlxtreeItemAOI ( this . input , option . id ) ) ;
2020-02-12 11:29:03 +01:00
obj . updateActionLinks ( action _links ) ;
if ( option . item && option . item . length > 0 ) {
2021-06-07 17:33:53 +02:00
for ( let i = 0 ; i < option . item . length ; i ++ ) {
apply _actions . call ( this , treeObj , option . item [ i ] ) ;
2020-02-12 11:29:03 +01:00
}
}
} ;
2021-06-07 17:33:53 +02:00
apply _actions . call ( this , treeObj , this . options . select _options ) ;
2020-02-12 11:29:03 +01:00
}
2021-06-07 17:33:53 +02:00
}
2020-02-12 11:29:03 +01:00
/ * *
* getValue , retrieves the Id of the selected Item
* @ return string or object or null
* /
2021-06-07 17:33:53 +02:00
getValue ( ) {
2020-02-12 11:29:03 +01:00
if ( this . input == null )
return null ;
if ( this . options . multiple ) {
2021-06-07 17:33:53 +02:00
let allChecked = this . input . getAllChecked ( ) . split ( this . input . dlmtr ) ;
let allUnchecked = this . input . getAllUnchecked ( ) . split ( this . input . dlmtr ) ;
2020-02-12 11:29:03 +01:00
if ( this . options . autoloading ) {
2021-06-07 17:33:53 +02:00
let res = { } ;
for ( let i = 0 ; i < allChecked . length ; i ++ ) {
2020-02-12 11:29:03 +01:00
res [ allChecked [ i ] ] = { value : true } ;
}
2021-06-07 17:33:53 +02:00
for ( let i = 0 ; i < allUnchecked . length ; i ++ ) {
2020-02-12 11:29:03 +01:00
res [ allUnchecked [ i ] ] = { value : false } ;
}
return res ;
}
else {
return allChecked ;
}
}
return this . input . getSelectedItemId ( ) ;
2021-06-07 17:33:53 +02:00
}
2020-02-12 11:29:03 +01:00
/ * *
* getSelectedLabel , retrieves the Label of the selected Item
* @ return string or null
* /
2021-06-07 17:33:53 +02:00
getSelectedLabel ( ) {
2020-02-12 11:29:03 +01:00
if ( this . input == null )
return null ;
if ( this . options . multiple ) {
/ *
var out = [ ] ;
var checked = this . input . getAllChecked ( ) . split ( this . input . dlmtr ) ;
for ( var i = 0 ; i < checked . length ; i ++ )
{
out . push ( this . input . getItemText ( checked [ i ] ) ) ;
}
return out ;
* /
return null ; // not supported yet
}
else {
return this . input . getSelectedItemText ( ) ;
}
2021-06-07 17:33:53 +02:00
}
2020-02-12 11:29:03 +01:00
/ * *
* renameItem , renames an item by id
*
* @ param { string } _id ID of the node
* @ param { string } _newItemId ID of the node
* @ param { string } _label label to set
* /
2021-06-07 17:33:53 +02:00
renameItem ( _id , _newItemId , _label ) {
2020-02-12 11:29:03 +01:00
if ( this . input == null )
return null ;
this . input . changeItemId ( _id , _newItemId ) ;
// Update action
// since the action ID has to = this.id, getObjectById() won't work
var treeObj = egw _getAppObjectManager ( ) . getObjectById ( this . id ) ;
for ( var i = 0 ; i < treeObj . children . length ; i ++ ) {
if ( treeObj . children [ i ] . id == _id ) {
treeObj . children [ i ] . id = _newItemId ;
if ( treeObj . children [ i ] . iface )
treeObj . children [ i ] . iface . id = _newItemId ;
break ;
}
}
if ( typeof _label != 'undefined' )
this . setLabel ( _newItemId , _label ) ;
2021-06-07 17:33:53 +02:00
}
2020-02-12 11:29:03 +01:00
/ * *
* deleteItem , deletes an item by id
* @ param _id ID of the node
* @ param _selectParent select the parent node true / false
* @ return void
* /
2021-06-07 17:33:53 +02:00
deleteItem ( _id , _selectParent ) {
2020-02-12 11:29:03 +01:00
if ( this . input == null )
return null ;
this . input . deleteItem ( _id , _selectParent ) ;
// Update action
// since the action ID has to = this.id, getObjectById() won't work
2021-06-07 17:33:53 +02:00
let treeObj = egw _getAppObjectManager ( ) . getObjectById ( this . id ) ;
for ( let i = 0 ; i < treeObj . children . length ; i ++ ) {
2020-02-12 11:29:03 +01:00
if ( treeObj . children [ i ] . id == _id ) {
treeObj . children . splice ( i , 1 ) ;
}
}
2021-06-07 17:33:53 +02:00
}
2020-02-12 11:29:03 +01:00
/ * *
* Updates a leaf of the tree by requesting new information from the server using the
* autoloading attribute .
*
* @ param { string } _id ID of the node
* @ param { Object } [ data ] If provided , the item is refreshed directly with
* the provided data instead of asking the server
* @ return void
* /
2021-06-07 17:33:53 +02:00
refreshItem ( _id , data ) {
2020-02-12 11:29:03 +01:00
if ( this . input == null )
return null ;
this . input . deleteChildItems ( _id ) ;
this . input . setDataMode ( 'JSON' ) ;
/ * C a n ' t u s e t h i s , i t d o e s n ' t a l l o w a c a l l b a c k
this . input . refreshItem ( _id ) ;
* /
2021-06-07 17:33:53 +02:00
let self = this ;
2020-02-12 11:29:03 +01:00
if ( typeof data != 'undefined' && data != null ) {
this . input . loadJSONObject ( data , function ( ) { self . _dhtmlxtree _json _callback ( data , _id ) ; } ) ;
}
else {
this . input . loadJSON ( this . egw ( ) . link ( this . autoloading _url , { id : _id } ) , function ( dxmlObject ) { self . _dhtmlxtree _json _callback ( JSON . parse ( dxmlObject . xmlDoc . responseText ) , _id ) ; } ) ;
}
2021-06-07 17:33:53 +02:00
}
2020-02-12 11:29:03 +01:00
/ * *
* focus the item , and scrolls it into view
*
* @ param _id ID of the node
* @ return void
* /
2021-06-07 17:33:53 +02:00
focusItem ( _id ) {
2020-02-12 11:29:03 +01:00
if ( this . input == null )
return null ;
this . input . focusItem ( _id ) ;
2021-06-07 17:33:53 +02:00
}
2020-02-12 11:29:03 +01:00
/ * *
* hasChildren
*
* @ param _id ID of the node
* @ return the number of childelements
* /
2021-06-07 17:33:53 +02:00
hasChildren ( _id ) {
2020-02-12 11:29:03 +01:00
if ( this . input == null )
return null ;
return this . input . hasChildren ( _id ) ;
2021-06-07 17:33:53 +02:00
}
2020-02-12 11:29:03 +01:00
/ * *
* Callback for after using dhtmlxtree ' s AJAX loading
* The tree has visually already been updated at this point , we just need
* to update the internal data .
*
* @ param { object } new _data Fresh data for the tree
* @ param { string } update _option _id optional If provided , only update that node ( and children ) with the
* provided data instead of the whole thing . Allows for partial updates .
* @ return void
* /
2021-06-07 17:33:53 +02:00
_dhtmlxtree _json _callback ( new _data , update _option _id ) {
2020-02-12 11:29:03 +01:00
// not sure if it makes sense to try update_option_id, so far I only seen it to be -1
2021-06-07 17:33:53 +02:00
let parent _id = typeof update _option _id != 'undefined' && update _option _id != - 1 ? update _option _id : new _data . id ;
2020-02-12 11:29:03 +01:00
// find root of loaded data to merge it there
2021-06-07 17:33:53 +02:00
let option = this . _find _in _item ( parent _id , this . options . select _options ) ;
2020-02-12 11:29:03 +01:00
// if we found it, merge it
if ( option ) {
jQuery . extend ( option , new _data || { } ) ;
}
else // else store it in root
{
this . options . select _options = new _data ;
}
// Update actions by just re-setting them
this . set _actions ( this . options . actions || { } ) ;
2021-06-07 17:33:53 +02:00
}
2020-02-12 11:29:03 +01:00
/ * *
* Recursive search item object for given id
*
* @ param { string } _id
* @ param { object } _item
* @ returns
* /
2021-06-07 17:33:53 +02:00
_find _in _item ( _id , _item ) {
2020-02-12 11:29:03 +01:00
if ( _item && _item . id == _id ) {
return _item ;
}
if ( _item && typeof _item . item != 'undefined' ) {
2021-06-07 17:33:53 +02:00
for ( let i = 0 ; i < _item . item . length ; ++ i ) {
let found = this . _find _in _item ( _id , _item . item [ i ] ) ;
2020-02-12 11:29:03 +01:00
if ( found )
return found ;
}
}
return null ;
2021-06-07 17:33:53 +02:00
}
2020-02-12 11:29:03 +01:00
/ * *
* Get node data by id
*
* @ param { string } _id id of node
* @ return { object } object with attributes id , im0 - 2 , text , tooltip , ... as set via select _options or autoload url
* /
2021-06-07 17:33:53 +02:00
getNode ( _id ) {
2020-02-12 11:29:03 +01:00
return this . _find _in _item ( _id , this . options . select _options ) ;
2021-06-07 17:33:53 +02:00
}
2020-02-12 11:29:03 +01:00
/ * *
* Sets label of an item by id
*
* @ param _id ID of the node
* @ param _label label to set
* @ param _tooltip new tooltip , default is previous set tooltip
* @ return void
* /
2021-06-07 17:33:53 +02:00
setLabel ( _id , _label , _tooltip ) {
2020-02-12 11:29:03 +01:00
if ( this . input == null )
return null ;
2021-06-07 17:33:53 +02:00
let tooltip = _tooltip || ( this . getNode ( _id ) && this . getNode ( _id ) . tooltip ?
2020-02-12 11:29:03 +01:00
this . getNode ( _id ) . tooltip : "" ) ;
this . input . setItemText ( _id , this . _htmlencode ( _label ) , tooltip ) ;
2021-06-07 17:33:53 +02:00
}
2020-02-12 11:29:03 +01:00
/ * *
* Sets a style for an item by id
*
* @ param { string } _id ID of node
* @ param { string } _style style to set
* @ return void
* /
2021-06-07 17:33:53 +02:00
setStyle ( _id , _style ) {
2020-02-12 11:29:03 +01:00
if ( this . input == null )
return null ;
this . input . setItemStyle ( _id , _style ) ;
2021-06-07 17:33:53 +02:00
}
2020-02-12 11:29:03 +01:00
/ * *
* getLabel , gets the Label of of an item by id
* @ param _id ID of the node
* @ return _label
* /
2021-06-07 17:33:53 +02:00
getLabel ( _id ) {
2020-02-12 11:29:03 +01:00
if ( this . input == null )
return null ;
return this . input . getItemText ( _id ) ;
2021-06-07 17:33:53 +02:00
}
2020-02-12 11:29:03 +01:00
/ * *
* getSelectedNode , retrieves the full node of the selected Item
* @ return string or null
* /
2021-06-07 17:33:53 +02:00
getSelectedNode ( ) {
2020-02-12 11:29:03 +01:00
if ( this . input == null )
return null ;
// no support for multiple selections
// as there is no get Method to return the full selected node, we use this
return this . options . multiple ? null : this . input . _selected [ 0 ] ;
2021-06-07 17:33:53 +02:00
}
2020-02-12 11:29:03 +01:00
/ * *
* getTreeNodeOpenItems
*
* @ param { string } _nodeID the nodeID where to start from ( initial node )
* @ param { string } mode the mode to run in : "forced" fakes the initial node openState to be open
* @ return { object } structured array of node ids : array ( message - ids )
* /
2021-06-07 17:33:53 +02:00
getTreeNodeOpenItems ( _nodeID , mode ) {
2020-02-12 11:29:03 +01:00
if ( this . input == null )
return null ;
2021-06-07 17:33:53 +02:00
let z = this . input . getSubItems ( _nodeID ) . split ( this . input . dlmtr ) ;
let oS ;
let PoS ;
let rv ;
let returnValue = [ _nodeID ] ;
let modetorun = "none" ;
2020-02-12 11:29:03 +01:00
if ( mode ) {
modetorun = mode ;
}
PoS = this . input . getOpenState ( _nodeID ) ;
if ( modetorun == "forced" )
PoS = 1 ;
if ( PoS == 1 ) {
2021-06-07 17:33:53 +02:00
for ( let i = 0 ; i < z . length ; i ++ ) {
2020-02-12 11:29:03 +01:00
oS = this . input . getOpenState ( z [ i ] ) ;
//alert(z[i]+' OpenState:'+oS);
if ( oS == - 1 ) {
returnValue . push ( z [ i ] ) ;
}
if ( oS == 0 ) {
returnValue . push ( z [ i ] ) ;
}
if ( oS == 1 ) {
//alert("got here")
rv = this . getTreeNodeOpenItems ( z [ i ] ) ;
//returnValue.concat(rv); // not working as expected; the following does
2021-06-07 17:33:53 +02:00
for ( let j = 0 ; j < rv . length ; j ++ ) {
2020-02-12 11:29:03 +01:00
returnValue . push ( rv [ j ] ) ;
}
}
}
}
//alert(returnValue.join('#,#'));
return returnValue ;
2021-06-07 17:33:53 +02:00
}
2020-02-12 11:29:03 +01:00
/ * *
* Fetch user - data stored in specified node under given name
*
* User - data need to be stored in json as follows :
*
* { "id" : "node-id" , "im0" : ... , "userdata" : [ { "name" : "user-name" , "content" : "user-value" } , ... ] }
*
* In above example getUserData ( "node-id" , "user-name" ) will return "user-value"
*
* @ param _nodeId
* @ param _name
* @ returns
* /
2021-06-07 17:33:53 +02:00
getUserData ( _nodeId , _name ) {
2020-02-12 11:29:03 +01:00
if ( this . input == null )
return null ;
return this . input . getUserData ( _nodeId , _name ) ;
2021-06-07 17:33:53 +02:00
}
2020-02-12 11:29:03 +01:00
/ * *
* Stores / updates user - data in specified node and name
*
* @ param _nodeId
* @ param _name
* @ param _value
* @ returns
* /
2021-06-07 17:33:53 +02:00
setUserData ( _nodeId , _name , _value ) {
2020-02-12 11:29:03 +01:00
if ( this . input == null )
return null ;
return this . input . setUserData ( _nodeId , _name , _value ) ;
2021-06-07 17:33:53 +02:00
}
2020-02-12 11:29:03 +01:00
/ * *
* Query nodes open state and optinal change it
*
* @ param _id node - id
* @ param _open specify to change true : open , false : close , everything else toggle
* @ returns true if open , false if closed
* /
2021-06-07 17:33:53 +02:00
openItem ( _id , _open ) {
2020-02-12 11:29:03 +01:00
if ( this . input == null )
return null ;
2021-06-07 17:33:53 +02:00
let is _open = this . input . getOpenState ( _id ) == 1 ;
2020-02-12 11:29:03 +01:00
if ( typeof _open != 'undefined' && is _open !== _open ) {
if ( is _open ) {
this . input . closeItem ( _id ) ;
}
else {
this . input . openItem ( _id ) ;
}
}
return is _open ;
2021-06-07 17:33:53 +02:00
}
2020-02-12 11:29:03 +01:00
/ * *
* reSelectItem , reselects an item by id
* @ param _id ID of the node
* /
2021-06-07 17:33:53 +02:00
reSelectItem ( _id ) {
2020-02-12 11:29:03 +01:00
if ( this . input == null )
return null ;
this . input . selectItem ( _id , false , false ) ;
2021-06-07 17:33:53 +02:00
}
2020-02-12 11:29:03 +01:00
/ * *
* Set images for a specific node or all new nodes ( default )
*
* If images contain an extension eg . "leaf" they are asumed to be in image path ( / p h p g w a p i / t e m p l a t e s / d e f a u l t / i m a g e s / d h t m l x t r e e / ) .
* Otherwise they get searched via egw . image ( ) in current app , phpgwapi or can be specified as "app/image" .
*
* @ param { string } _leaf leaf image , default "leaf"
* @ param { string } _closed closed folder image , default "folderClosed"
* @ param { string } _open opened folder image , default "folderOpen"
* @ param { string } _id if not given , standard images for new nodes are set
* /
2021-06-07 17:33:53 +02:00
setImages ( _leaf , _closed , _open , _id ) {
let images = [ _leaf || 'dhtmlxtree/leaf' , _closed || 'dhtmlxtree/folderClosed' , _open || 'dhtmlxtree/folderOpen' ] ;
let image _extensions = /\.(gif|png|jpe?g|svg)/i ;
for ( let i = 0 ; i < 3 ; ++ i ) {
let image = images [ i ] ;
2020-02-12 11:29:03 +01:00
if ( ! image . match ( image _extensions ) ) {
images [ i ] = this . _rel _url ( this . egw ( ) . image ( image ) || image ) ;
}
}
if ( typeof _id == 'undefined' ) {
this . input . setStdImages . apply ( this . input , images ) ;
}
else {
images . unshift ( _id ) ;
this . input . setItemImage2 . apply ( this . input , images ) ;
}
2021-06-07 17:33:53 +02:00
}
2020-02-12 11:29:03 +01:00
/ * *
* Set state of node incl . it ' s children
*
* @ param { string } _id id of node
* @ param { boolean | string } _state or "toggle" to toggle state
* /
2021-06-07 17:33:53 +02:00
setSubChecked ( _id , _state ) {
2020-02-12 11:29:03 +01:00
if ( _state === "toggle" )
_state = ! this . input . isItemChecked ( _id ) ;
this . input . setSubChecked ( _id , _state ) ;
2021-06-07 17:33:53 +02:00
}
2020-02-12 11:29:03 +01:00
/ * *
* Get URL relative to image _path option
*
* Both URL start with EGroupware webserverUrl and image _path gets allways appended to images by tree .
*
* @ param { string } _url
* @ return { string } relativ url
* /
2021-06-07 17:33:53 +02:00
_rel _url ( _url ) {
let path _parts = this . options . image _path . split ( this . egw ( ) . webserverUrl ) ;
2020-02-12 11:29:03 +01:00
path _parts = path _parts [ 1 ] . split ( '/' ) ;
2021-06-07 17:33:53 +02:00
let url _parts = _url . split ( this . egw ( ) . webserverUrl ) ;
2020-02-12 11:29:03 +01:00
url _parts = url _parts [ 1 ] . split ( '/' ) ;
2021-06-07 17:33:53 +02:00
for ( let i = 0 ; i < path _parts . length ; ++ i ) {
2020-02-12 11:29:03 +01:00
if ( path _parts [ i ] != url _parts [ i ] ) {
while ( ++ i < path _parts . length )
url _parts . unshift ( '..' ) ;
break ;
}
url _parts . shift ( ) ;
}
return url _parts . join ( '/' ) ;
2021-06-07 17:33:53 +02:00
}
}
et2 _tree . _attributes = {
"multiple" : {
"name" : "multiple" ,
"type" : "boolean" ,
"default" : false ,
"description" : "Allow selecting multiple options"
} ,
"select_options" : {
"type" : "any" ,
"name" : "Select options" ,
"default" : { } ,
"description" : "Used to set the tree options."
} ,
"onclick" : {
"description" : "JS code which gets executed when clicks on text of a node"
} ,
"onselect" : {
"name" : "onSelect" ,
"type" : "js" ,
"default" : et2 _no _init ,
"description" : "Javascript executed when user selects a node"
} ,
"oncheck" : {
"name" : "onCheck" ,
"type" : "js" ,
"default" : et2 _no _init ,
"description" : "Javascript executed when user checks a node"
} ,
// onChange event is mapped depending on multiple to onCheck or onSelect
onopenstart : {
"name" : "onOpenStart" ,
"type" : "js" ,
"default" : et2 _no _init ,
"description" : "Javascript function executed when user opens a node: function(_id, _widget, _hasChildren) returning true to allow opening!"
} ,
onopenend : {
"name" : "onOpenEnd" ,
"type" : "js" ,
"default" : et2 _no _init ,
"description" : "Javascript function executed when opening a node is finished: function(_id, _widget, _hasChildren)"
} ,
"image_path" : {
"name" : "Image directory" ,
"type" : "string" ,
"default" : egw ( ) . webserverUrl + "/api/templates/default/images/dhtmlxtree/" ,
"description" : "Directory for tree structure images, set on server-side to 'dhtmlx' subdir of templates image-directory"
} ,
"value" : {
"type" : "any" ,
"default" : { }
} ,
"actions" : {
"name" : "Actions array" ,
"type" : "any" ,
"default" : et2 _no _init ,
"description" : "List of egw actions that can be done on the tree. This includes context menu, drag and drop. TODO: Link to action documentation"
} ,
"autoloading" : {
"name" : "Autoloading" ,
"type" : "string" ,
"default" : "" ,
"description" : "JSON URL or menuaction to be called for nodes marked with child=1, but not having children, GET parameter selected contains node-id"
} ,
"std_images" : {
"name" : "Standard images" ,
"type" : "string" ,
"default" : "" ,
"description" : "comma-separated names of icons for a leaf, closed and opend folder (default: leaf.png,folderClosed.png,folderOpen.png), images with extension get loaded from image_path, just 'image' or 'appname/image' are allowed too"
} ,
"multimarking" : {
"name" : "multimarking" ,
"type" : "any" ,
"default" : false ,
"description" : "Allow marking multiple nodes, default is false which means disabled multiselection, true or 'strict' activates it and 'strict' makes it strick to only same level marking"
} ,
highlighting : {
"name" : "highlighting" ,
"type" : "boolean" ,
"default" : false ,
"description" : "Add highlighting class on hovered over item, highlighting is disabled by default"
}
} ;
et2 _register _widget ( et2 _tree , [ "tree" , "tree-cat" ] ) ;
2020-02-12 11:29:03 +01:00
//# sourceMappingURL=et2_widget_tree.js.map