"use strict"; /** * EGroupware eTemplate2 - JS Tree object * * @link http://community.egroupware.org/egroupware/api/js/dhtmlxtree/docsExplorer/dhtmlxtree/ * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License * @package etemplate * @subpackage api * @link http://www.egroupware.org * @author Nathan Gray * @author Ralf Becker * @copyright Nathan Gray 2011 */ var __extends = (this && this.__extends) || (function () { var extendStatics = function (d, b) { extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; return extendStatics(d, b); }; return function (d, b) { extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.et2_tree = void 0; /*egw:uses et2_core_inputWidget; /api/js/egw_action/egw_dragdrop_dhtmlx_tree.js; /api/js/dhtmlxtree/codebase/dhtmlxcommon.js; // using debugable and fixed source of dhtmltree instead: /api/js/dhtmlxtree/js/dhtmlXTree.js; /api/js/dhtmlxtree/sources/dhtmlxtree.js; /api/js/dhtmlxtree/sources/ext/dhtmlxtree_json.js; // /api/js/dhtmlxtree/sources/ext/dhtmlxtree_start.js; */ var et2_core_widget_1 = require("./et2_core_widget"); var et2_core_inputWidget_1 = require("./et2_core_inputWidget"); var et2_core_inheritance_1 = require("./et2_core_inheritance"); /** * Tree widget * * For syntax of nodes supplied via sel_optons or autoloading refer to Etemplate\Widget\Tree class. * * @augments et2_inputWidget */ var et2_tree = /** @class */ (function (_super) { __extends(et2_tree, _super); /** * Constructor * * @memberOf et2_tree */ function et2_tree(_parent, _attrs, _child) { var _this = // Call the inherited constructor _super.call(this, _parent, _attrs, et2_core_inheritance_1.ClassWithAttributes.extendAttributes(et2_tree._attributes, _child || {})) || this; _this.input = null; /** * Regexp used by _htmlencode */ _this._lt_regexp = /= 0) { _text = _text.replace(this._lt_regexp, '<'); } return _text; }; /** * 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 */ et2_tree.prototype._htmlencode_node = function (_item) { _item.text = this._htmlencode(_item.text); if (_item.item && jQuery.isArray(_item.item)) { for (var i = 0; i < _item.item.length; ++i) { this._htmlencode_node(_item.item[i]); } } return _item; }; et2_tree.prototype.set_value = function (new_value) { 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 var checked = this.input.getAllChecked().split(this.input.dlmtr); for (var i = 0; i < checked.length; i++) { this.input.setCheck(checked[i], false); } // Check selected for (var i = 0; i < this.value.length; i++) { 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); } }; /** * Links actions to tree nodes * * @param {object} actions [ {ID: attributes..}+] as for set_actions */ et2_tree.prototype._link_actions = function (actions) { // Get the top level element for the tree // Only look 1 level deep for application object manager var objectManager = egw_getObjectManager(this.egw().app_name(), true, 1); var treeObj = objectManager.getObjectById(this.id); 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 var action_links = this._get_action_links(actions); if (typeof this.options.select_options != 'undefined') { // Iterate over the options (leaves) and add action to each one var apply_actions_1 = function (treeObj, option) { // Add a new action object to the object manager // @ts-ignore var obj = treeObj.addObject((typeof option.id == 'number' ? String(option.id) : option.id), new dhtmlxtreeItemAOI(this.input, option.id)); obj.updateActionLinks(action_links); if (option.item && option.item.length > 0) { for (var i = 0; i < option.item.length; i++) { apply_actions_1.call(this, treeObj, option.item[i]); } } }; apply_actions_1.call(this, treeObj, this.options.select_options); } }; /** * getValue, retrieves the Id of the selected Item * @return string or object or null */ et2_tree.prototype.getValue = function () { if (this.input == null) return null; if (this.options.multiple) { var allChecked = this.input.getAllChecked().split(this.input.dlmtr); var allUnchecked = this.input.getAllUnchecked().split(this.input.dlmtr); if (this.options.autoloading) { var res = {}; for (var i = 0; i < allChecked.length; i++) { res[allChecked[i]] = { value: true }; } for (var i = 0; i < allUnchecked.length; i++) { res[allUnchecked[i]] = { value: false }; } return res; } else { return allChecked; } } return this.input.getSelectedItemId(); }; /** * getSelectedLabel, retrieves the Label of the selected Item * @return string or null */ et2_tree.prototype.getSelectedLabel = function () { 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(); } }; /** * 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 */ et2_tree.prototype.renameItem = function (_id, _newItemId, _label) { 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); }; /** * deleteItem, deletes an item by id * @param _id ID of the node * @param _selectParent select the parent node true/false * @return void */ et2_tree.prototype.deleteItem = function (_id, _selectParent) { 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 var treeObj = egw_getAppObjectManager().getObjectById(this.id); for (var i = 0; i < treeObj.children.length; i++) { if (treeObj.children[i].id == _id) { treeObj.children.splice(i, 1); } } }; /** * 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 */ et2_tree.prototype.refreshItem = function (_id, data) { if (this.input == null) return null; this.input.deleteChildItems(_id); this.input.setDataMode('JSON'); /* Can't use this, it doesn't allow a callback this.input.refreshItem(_id); */ var self = this; 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); }); } }; /** * focus the item, and scrolls it into view * * @param _id ID of the node * @return void */ et2_tree.prototype.focusItem = function (_id) { if (this.input == null) return null; this.input.focusItem(_id); }; /** * hasChildren * * @param _id ID of the node * @return the number of childelements */ et2_tree.prototype.hasChildren = function (_id) { if (this.input == null) return null; return this.input.hasChildren(_id); }; /** * 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 */ et2_tree.prototype._dhtmlxtree_json_callback = function (new_data, update_option_id) { // not sure if it makes sense to try update_option_id, so far I only seen it to be -1 var parent_id = typeof update_option_id != 'undefined' && update_option_id != -1 ? update_option_id : new_data.id; // find root of loaded data to merge it there var option = this._find_in_item(parent_id, this.options.select_options); // 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 || {}); }; /** * Recursive search item object for given id * * @param {string} _id * @param {object} _item * @returns */ et2_tree.prototype._find_in_item = function (_id, _item) { if (_item && _item.id == _id) { return _item; } if (_item && typeof _item.item != 'undefined') { for (var i = 0; i < _item.item.length; ++i) { var found = this._find_in_item(_id, _item.item[i]); if (found) return found; } } return null; }; /** * 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 */ et2_tree.prototype.getNode = function (_id) { return this._find_in_item(_id, this.options.select_options); }; /** * 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 */ et2_tree.prototype.setLabel = function (_id, _label, _tooltip) { if (this.input == null) return null; var tooltip = _tooltip || (this.getNode(_id) && this.getNode(_id).tooltip ? this.getNode(_id).tooltip : ""); this.input.setItemText(_id, this._htmlencode(_label), tooltip); }; /** * Sets a style for an item by id * * @param {string} _id ID of node * @param {string} _style style to set * @return void */ et2_tree.prototype.setStyle = function (_id, _style) { if (this.input == null) return null; this.input.setItemStyle(_id, _style); }; /** * getLabel, gets the Label of of an item by id * @param _id ID of the node * @return _label */ et2_tree.prototype.getLabel = function (_id) { if (this.input == null) return null; return this.input.getItemText(_id); }; /** * getSelectedNode, retrieves the full node of the selected Item * @return string or null */ et2_tree.prototype.getSelectedNode = function () { 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]; }; /** * 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) */ et2_tree.prototype.getTreeNodeOpenItems = function (_nodeID, mode) { if (this.input == null) return null; var z = this.input.getSubItems(_nodeID).split(this.input.dlmtr); var oS; var PoS; var rv; var returnValue = [_nodeID]; var modetorun = "none"; if (mode) { modetorun = mode; } PoS = this.input.getOpenState(_nodeID); if (modetorun == "forced") PoS = 1; if (PoS == 1) { for (var i = 0; i < z.length; i++) { 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 for (var j = 0; j < rv.length; j++) { returnValue.push(rv[j]); } } } } //alert(returnValue.join('#,#')); return returnValue; }; /** * 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 */ et2_tree.prototype.getUserData = function (_nodeId, _name) { if (this.input == null) return null; return this.input.getUserData(_nodeId, _name); }; /** * Stores / updates user-data in specified node and name * * @param _nodeId * @param _name * @param _value * @returns */ et2_tree.prototype.setUserData = function (_nodeId, _name, _value) { if (this.input == null) return null; return this.input.setUserData(_nodeId, _name, _value); }; /** * 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 */ et2_tree.prototype.openItem = function (_id, _open) { if (this.input == null) return null; var is_open = this.input.getOpenState(_id) == 1; if (typeof _open != 'undefined' && is_open !== _open) { if (is_open) { this.input.closeItem(_id); } else { this.input.openItem(_id); } } return is_open; }; /** * reSelectItem, reselects an item by id * @param _id ID of the node */ et2_tree.prototype.reSelectItem = function (_id) { if (this.input == null) return null; this.input.selectItem(_id, false, false); }; /** * 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 (/phpgwapi/templates/default/images/dhtmlxtree/). * 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 */ et2_tree.prototype.setImages = function (_leaf, _closed, _open, _id) { var images = [_leaf || 'dhtmlxtree/leaf', _closed || 'dhtmlxtree/folderClosed', _open || 'dhtmlxtree/folderOpen']; var image_extensions = /\.(gif|png|jpe?g|svg)/i; for (var i = 0; i < 3; ++i) { var image = images[i]; 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); } }; /** * Set state of node incl. it's children * * @param {string} _id id of node * @param {boolean|string} _state or "toggle" to toggle state */ et2_tree.prototype.setSubChecked = function (_id, _state) { if (_state === "toggle") _state = !this.input.isItemChecked(_id); this.input.setSubChecked(_id, _state); }; /** * 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 */ et2_tree.prototype._rel_url = function (_url) { var path_parts = this.options.image_path.split(this.egw().webserverUrl); path_parts = path_parts[1].split('/'); var url_parts = _url.split(this.egw().webserverUrl); url_parts = url_parts[1].split('/'); for (var i = 0; i < path_parts.length; ++i) { if (path_parts[i] != url_parts[i]) { while (++i < path_parts.length) url_parts.unshift('..'); break; } url_parts.shift(); } return url_parts.join('/'); }; 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" } }; return et2_tree; }(et2_core_inputWidget_1.et2_inputWidget)); exports.et2_tree = et2_tree; et2_core_widget_1.et2_register_widget(et2_tree, ["tree", "tree-cat"]); //# sourceMappingURL=et2_widget_tree.js.map