From 5e3c67a5cf56894d815cb9791aad0245ab0cf1eb Mon Sep 17 00:00:00 2001 From: milan Date: Mon, 10 Jul 2023 16:02:30 +0200 Subject: [PATCH] converted egw_action from javascript to typescript classes are now uppercase and in their own files. lowercase classes are deprecated. Interfaces are now actual interfaces that should be implemented instead of creating and returning an ai Object every time --- admin/js/app.ts | 2 +- api/js/egw_action/Class Diagram.graphml | 0 api/js/egw_action/Class Diagram.png | Bin api/js/egw_action/Class Diagram.svg | 0 api/js/egw_action/EgwAction.ts | 720 +++++ api/js/egw_action/EgwActionImplementation.ts | 47 + api/js/egw_action/EgwActionLink.ts | 52 + api/js/egw_action/EgwActionManager.ts | 27 + api/js/egw_action/EgwActionObject.ts | 1130 ++++++++ api/js/egw_action/EgwActionObjectInterface.ts | 85 + api/js/egw_action/EgwActionObjectManager.ts | 32 + api/js/egw_action/EgwDragAction.ts | 39 + api/js/egw_action/EgwDropAction.ts | 67 + .../egw_action/EgwDropActionImplementation.ts | 302 ++ api/js/egw_action/EgwPopupAction.ts | 120 + .../EgwPopupActionImplementation.ts | 744 +++++ .../egw_action/egwDragActionImplementation.ts | 291 ++ api/js/egw_action/egwGlobal.ts | 41 + api/js/egw_action/egw_action.d.ts | 884 ------ api/js/egw_action/egw_action.js | 2530 ----------------- api/js/egw_action/egw_action.ts | 410 +++ api/js/egw_action/egw_action_common.js | 520 ---- api/js/egw_action/egw_action_common.ts | 487 ++++ ...n_constants.js => egw_action_constants.ts} | 33 +- api/js/egw_action/egw_action_dragdrop.js | 737 ----- api/js/egw_action/egw_action_popup.js | 941 ------ api/js/egw_action/egw_action_popup.ts | 12 + api/js/egw_action/egw_dragdrop_dhtmlx_tree.js | 74 - api/js/egw_action/egw_dragdrop_dhtmlx_tree.ts | 82 + .../{egw_keymanager.js => egw_keymanager.ts} | 167 +- api/js/egw_action/egw_menu.js | 524 ---- api/js/egw_action/egw_menu.ts | 534 ++++ api/js/egw_action/egw_menu_dhtmlx.js | 206 -- api/js/egw_action/egw_menu_dhtmlx.ts | 195 ++ api/js/egw_action/egw_stylesheet.js | 93 - api/js/egw_action/egw_stylesheet.ts | 52 + api/js/egw_action/test/imgs/ajax-loader.gif | Bin api/js/egw_action/test/imgs/arrows.png | Bin api/js/egw_action/test/imgs/arrows.svg | 0 api/js/egw_action/test/imgs/dhxmenu_chrd.gif | Bin .../egw_action/test/imgs/dhxmenu_loader.gif | Bin api/js/egw_action/test/imgs/dhxmenu_subar.gif | Bin .../egw_action/test/imgs/dhxmenu_subselbg.gif | Bin .../egw_action/test/imgs/dhxmenu_subselbg.png | Bin .../egw_action/test/imgs/dhxmenu_subsepbg.gif | Bin api/js/egw_action/test/imgs/dhxmenu_topbg.gif | Bin .../egw_action/test/imgs/dhxmenu_topselbg.gif | Bin .../egw_action/test/imgs/dhxmenu_topsepbg.gif | Bin api/js/egw_action/test/imgs/dhxmenu_upar.gif | Bin .../egw_action/test/imgs/focused_hatching.png | Bin .../egw_action/test/imgs/focused_hatching.svg | 0 .../egw_action/test/imgs/header_overlay.png | Bin .../egw_action/test/imgs/header_overlay.svg | 0 .../egw_action/test/imgs/mime16_directory.png | Bin api/js/egw_action/test/imgs/non_loaded_bg.png | Bin api/js/egw_action/test/imgs/non_loaded_bg.svg | 0 .../egw_action/test/imgs/select_overlay.png | Bin .../egw_action/test/imgs/select_overlay.svg | 0 api/js/egw_action/test/imgs/selectcols.png | Bin api/js/egw_action/test/js/dhtmlxcommon.js | 0 api/js/egw_action/test/js/dhtmlxmenu.js | 0 api/js/egw_action/test/js/dhtmlxmenu_ext.js | 0 api/js/egw_action/test/js/jquery-ui.js | 0 api/js/egw_action/test/js/jquery.js | 0 .../egw_action/test/skins/dhtmlxmenu_egw.css | 0 api/js/egw_action/test/test_action.html | 761 ++--- api/js/egw_action/test/test_menu.html | 6 +- .../test/test_stylesheet_editing.html | 176 +- api/js/etemplate/et2_core_DOMWidget.ts | 6 +- api/js/etemplate/et2_core_baseWidget.ts | 2 +- api/js/etemplate/et2_dataview_controller.ts | 4 +- .../et2_dataview_controller_selection.ts | 6 +- api/js/etemplate/et2_dataview_view_aoi.ts | 8 +- api/js/etemplate/et2_extension_nextmatch.ts | 2 +- .../et2_extension_nextmatch_controller.ts | 6 +- api/js/etemplate/et2_widget_grid.ts | 2 +- api/js/etemplate/et2_widget_portlet.ts | 2 +- api/js/etemplate/et2_widget_toolbar.ts | 4 +- api/js/etemplate/et2_widget_tree.ts | 10 +- api/js/etemplate/et2_widget_vfs.ts | 6 +- api/js/etemplate/etemplate2.ts | 2 +- api/js/etemplate/test/test_dataview.html | 4 +- api/js/framework/fw_browser.js | 2 +- api/js/framework/fw_ui.js | 2 +- api/js/jsapi/egw_app.ts | 4 + api/js/jsapi/egw_global.d.ts | 16 +- api/js/jsapi/egw_preferences.js | 4 +- api/src/Framework/Bundle.php | 15 +- calendar/js/app.ts | 4 +- calendar/js/et2_widget_daycol.ts | 2 +- calendar/js/et2_widget_event.ts | 2 +- calendar/js/et2_widget_planner.ts | 6 +- calendar/js/et2_widget_planner_row.ts | 4 +- calendar/js/et2_widget_timegrid.ts | 4 +- doc/docker/README.md | 3 + doc/docker/development/docker-compose.yml | 46 +- doc/docker/docker-compose.yml | 243 +- filemanager/js/filemanager.ts | 2 +- mail/js/app.js | 8 +- package-lock.json | 10 +- 100 files changed, 6331 insertions(+), 7233 deletions(-) mode change 100644 => 100755 api/js/egw_action/Class Diagram.graphml mode change 100644 => 100755 api/js/egw_action/Class Diagram.png mode change 100644 => 100755 api/js/egw_action/Class Diagram.svg create mode 100644 api/js/egw_action/EgwAction.ts create mode 100644 api/js/egw_action/EgwActionImplementation.ts create mode 100644 api/js/egw_action/EgwActionLink.ts create mode 100644 api/js/egw_action/EgwActionManager.ts create mode 100644 api/js/egw_action/EgwActionObject.ts create mode 100644 api/js/egw_action/EgwActionObjectInterface.ts create mode 100644 api/js/egw_action/EgwActionObjectManager.ts create mode 100644 api/js/egw_action/EgwDragAction.ts create mode 100644 api/js/egw_action/EgwDropAction.ts create mode 100644 api/js/egw_action/EgwDropActionImplementation.ts create mode 100644 api/js/egw_action/EgwPopupAction.ts create mode 100644 api/js/egw_action/EgwPopupActionImplementation.ts create mode 100644 api/js/egw_action/egwDragActionImplementation.ts create mode 100644 api/js/egw_action/egwGlobal.ts delete mode 100644 api/js/egw_action/egw_action.d.ts delete mode 100644 api/js/egw_action/egw_action.js create mode 100755 api/js/egw_action/egw_action.ts delete mode 100644 api/js/egw_action/egw_action_common.js create mode 100755 api/js/egw_action/egw_action_common.ts rename api/js/egw_action/{egw_action_constants.js => egw_action_constants.ts} (74%) mode change 100644 => 100755 delete mode 100644 api/js/egw_action/egw_action_dragdrop.js delete mode 100644 api/js/egw_action/egw_action_popup.js create mode 100644 api/js/egw_action/egw_action_popup.ts delete mode 100644 api/js/egw_action/egw_dragdrop_dhtmlx_tree.js create mode 100755 api/js/egw_action/egw_dragdrop_dhtmlx_tree.ts rename api/js/egw_action/{egw_keymanager.js => egw_keymanager.ts} (52%) mode change 100644 => 100755 delete mode 100644 api/js/egw_action/egw_menu.js create mode 100755 api/js/egw_action/egw_menu.ts delete mode 100644 api/js/egw_action/egw_menu_dhtmlx.js create mode 100644 api/js/egw_action/egw_menu_dhtmlx.ts delete mode 100644 api/js/egw_action/egw_stylesheet.js create mode 100755 api/js/egw_action/egw_stylesheet.ts mode change 100644 => 100755 api/js/egw_action/test/imgs/ajax-loader.gif mode change 100644 => 100755 api/js/egw_action/test/imgs/arrows.png mode change 100644 => 100755 api/js/egw_action/test/imgs/arrows.svg mode change 100644 => 100755 api/js/egw_action/test/imgs/dhxmenu_chrd.gif mode change 100644 => 100755 api/js/egw_action/test/imgs/dhxmenu_loader.gif mode change 100644 => 100755 api/js/egw_action/test/imgs/dhxmenu_subar.gif mode change 100644 => 100755 api/js/egw_action/test/imgs/dhxmenu_subselbg.gif mode change 100644 => 100755 api/js/egw_action/test/imgs/dhxmenu_subselbg.png mode change 100644 => 100755 api/js/egw_action/test/imgs/dhxmenu_subsepbg.gif mode change 100644 => 100755 api/js/egw_action/test/imgs/dhxmenu_topbg.gif mode change 100644 => 100755 api/js/egw_action/test/imgs/dhxmenu_topselbg.gif mode change 100644 => 100755 api/js/egw_action/test/imgs/dhxmenu_topsepbg.gif mode change 100644 => 100755 api/js/egw_action/test/imgs/dhxmenu_upar.gif mode change 100644 => 100755 api/js/egw_action/test/imgs/focused_hatching.png mode change 100644 => 100755 api/js/egw_action/test/imgs/focused_hatching.svg mode change 100644 => 100755 api/js/egw_action/test/imgs/header_overlay.png mode change 100644 => 100755 api/js/egw_action/test/imgs/header_overlay.svg mode change 100644 => 100755 api/js/egw_action/test/imgs/mime16_directory.png mode change 100644 => 100755 api/js/egw_action/test/imgs/non_loaded_bg.png mode change 100644 => 100755 api/js/egw_action/test/imgs/non_loaded_bg.svg mode change 100644 => 100755 api/js/egw_action/test/imgs/select_overlay.png mode change 100644 => 100755 api/js/egw_action/test/imgs/select_overlay.svg mode change 100644 => 100755 api/js/egw_action/test/imgs/selectcols.png mode change 100644 => 100755 api/js/egw_action/test/js/dhtmlxcommon.js mode change 100644 => 100755 api/js/egw_action/test/js/dhtmlxmenu.js mode change 100644 => 100755 api/js/egw_action/test/js/dhtmlxmenu_ext.js mode change 100644 => 100755 api/js/egw_action/test/js/jquery-ui.js mode change 100644 => 100755 api/js/egw_action/test/js/jquery.js mode change 100644 => 100755 api/js/egw_action/test/skins/dhtmlxmenu_egw.css mode change 100644 => 100755 api/js/egw_action/test/test_action.html mode change 100644 => 100755 api/js/egw_action/test/test_menu.html mode change 100644 => 100755 api/js/egw_action/test/test_stylesheet_editing.html mode change 100644 => 100755 api/src/Framework/Bundle.php diff --git a/admin/js/app.ts b/admin/js/app.ts index a4bce4e51d..bbd4ec0931 100644 --- a/admin/js/app.ts +++ b/admin/js/app.ts @@ -16,7 +16,7 @@ import {EgwApp, PushData} from '../../api/js/jsapi/egw_app'; import {etemplate2} from "../../api/js/etemplate/etemplate2"; import {Et2Dialog} from "../../api/js/etemplate/Et2Dialog/Et2Dialog"; import {egw} from "../../api/js/jsapi/egw_global.js"; -import {egwAction, egwActionObject} from '../../api/js/egw_action/egw_action.js'; +import {egwAction, egwActionObject} from '../../api/js/egw_action/egw_action'; import {LitElement} from "@lion/core"; import {et2_nextmatch} from "../../api/js/etemplate/et2_extension_nextmatch"; import {et2_DOMWidget} from "../../api/js/etemplate/et2_core_DOMWidget"; diff --git a/api/js/egw_action/Class Diagram.graphml b/api/js/egw_action/Class Diagram.graphml old mode 100644 new mode 100755 diff --git a/api/js/egw_action/Class Diagram.png b/api/js/egw_action/Class Diagram.png old mode 100644 new mode 100755 diff --git a/api/js/egw_action/Class Diagram.svg b/api/js/egw_action/Class Diagram.svg old mode 100644 new mode 100755 diff --git a/api/js/egw_action/EgwAction.ts b/api/js/egw_action/EgwAction.ts new file mode 100644 index 0000000000..9c1be07dd6 --- /dev/null +++ b/api/js/egw_action/EgwAction.ts @@ -0,0 +1,720 @@ +/** + * EGroupware egw_action framework - egw action framework + * + * @link https://www.egroupware.org + * @author Andreas Stöckel + * @copyright 2011 by Andreas Stöckel + * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License + * @package egw_action + */ + +import {egwActionStoreJSON, EgwFnct} from "./egw_action_common"; +import {IegwAppLocal} from "../jsapi/egw_global"; +import {egw_getObjectManager} from "./egw_action"; + +export class EgwAction { + public readonly id: string; + private caption: string; + group: number; + order: number; + + public set_caption(_value) { + this.caption = _value; + } + + private iconUrl: string; + + public set_iconUrl(_value) { + this.iconUrl = _value; + } + + allowOnMultiple: boolean | string | number; + + /** + * The allowOnMultiple property may be true, false, "only" (> 1) or number of select, e.g. 2 + * + * @param {(boolean|string|number)} _value + */ + public set_allowOnMultiple(_value: boolean | string | number) { + this.allowOnMultiple = _value + } + + public readonly enabled: EgwFnct; + + public set_enabled(_value) { + this.enabled.setValue(_value); + } + + public hideOnDisabled = false; + + public data: any = {}; // Data which can be freely assigned to the action + /** + * @deprecated just set the data parameter with '=' sign to use its setter + * @param _value + */ + public set_data(_value) { + this.data = _value + } + + type = "default"; //All derived classes have to override this! + canHaveChildren: boolean | string[] = false; //Has to be overwritten by inheriting action classes + // this is not bool all the time. Can be ['popup'] e.g. List of egwActionClasses that are allowed to have children? + readonly parent: EgwAction; + children: EgwAction[] = []; //i guess + + private readonly onExecute = new EgwFnct(this, null, []); + + /** + * Set to either a confirmation prompt, or TRUE to indicate that this action + * cares about large selections and to ask the confirmation prompt(s) + * + * --set in egw_action_popup-- + * @param {String|Boolean} _value + */ + public confirm_mass_selection: string | boolean = undefined + + /** + * The set_onExecute function is the setter function for the onExecute event of + * the EgwAction object. There are three possible types the passed "_value" may + * take: + * 1. _value may be a string with the word "javaScript:" prefixed. The function + * which is specified behind the colon and which has to be in the global scope + * will be executed. + * 2. _value may be a boolean, which specifies whether the external onExecute handler + * (passed as "_handler" in the constructor) will be used. + * 3. _value may be a JS function which will then be called. + * In all possible situation, the called function will get the following parameters: + * 1. A reference to this action + * 2. The senders, an array of all objects (JS)/object ids (PHP) which evoked the event + * + * @param {(string|function|boolean)} _value + */ + public set_onExecute(_value) { + this.onExecute.setValue(_value) + } + + public hideOnMobile = false; + public disableIfNoEPL = false; + + /** + * Default icons for given id + */ + public defaultIcons = { + view: 'view', + edit: 'edit', + open: 'edit', // does edit if possible, otherwise view + add: 'new', + new: 'new', + delete: 'delete', + cat: 'attach', // add as category icon to api + document: 'etemplate/merge', + print: 'print', + copy: 'copy', + move: 'move', + cut: 'cut', + paste: 'editpaste', + save: 'save', + apply: 'apply', + cancel: 'cancel', + continue: 'continue', + next: 'continue', + finish: 'finish', + back: 'back', + previous: 'back', + close: 'close' + }; + + + /** + * Constructor for EgwAction object + * + * @param {EgwAction} _parent + * @param {string} _id + * @param {string} _caption + * @param {string} _iconUrl + * @param {(string|function)} _onExecute + * @param {boolean} _allowOnMultiple + * @returns {EgwAction} + **/ + constructor(_parent: EgwAction, _id: string, _caption: string = "", _iconUrl: string = "", _onExecute: string | Function = null, _allowOnMultiple: boolean = true) { + if (_parent && (typeof _id != "string" || !_id) && _parent.type !== "actionManager") { + throw "EgwAction _id must be a non-empty string!"; + } + this.parent = _parent; + this.id = _id; + this.caption = _caption; + this.iconUrl = _iconUrl; + if (_onExecute !== null) { + this.set_onExecute(_onExecute) + } + this.allowOnMultiple = _allowOnMultiple; + this.enabled = new EgwFnct(this, true); + + } + + /** + * Clears the element and removes it from the parent container + */ + public remove() { + // Remove all references to the child elements + this.children = []; + // Remove this element from the parent list + if (this.parent) { + const idx = this.parent.children.indexOf(this); + if (idx >= 0) { + this.parent.children.splice(idx, 1); + } + } + } + + /** + * Searches for a specific action with the given id + * + * @param {(string|number)} _id ID of the action to find + * @param {number} [_search_depth=Infinite] How deep into existing action children + * to search. + * + * @return {(EgwAction|null)} + */ + public getActionById(_id: string, _search_depth: number = Number.MAX_VALUE): EgwAction { + // If the current action object has the given id, return this object + if (this.id == _id) { + return this; + } + // If this element is capable of having children, search those for the given + // action id + if (this.canHaveChildren) { + for (let i = 0; i < this.children.length && _search_depth > 0; i++) { + const elem = this.children[i].getActionById(_id, _search_depth - 1); + if (elem) { + return elem; + } + } + } + + return null; + }; + + /** + * Searches for actions having an attribute with a certain value + * + * Example: actionManager.getActionsByAttr("checkbox", true) returns all checkbox actions + * + * @param {string} _attr attribute name + * @param _val attribute value + * @return array + */ + public getActionsByAttr(_attr: string | number, _val: any = undefined) { + let _actions = []; + + // If the current action object has the given attr AND value, or no value was provided, return it + if (typeof this[_attr] != "undefined" && (this[_attr] === _val || typeof _val === "undefined" && this[_attr] !== null)) { + _actions.push(this); + } + + // If this element is capable of having children, search those too + if (this.canHaveChildren) { + for (let i = 0; i < this.children.length; i++) { + _actions = _actions.concat(this.children[i].getActionsByAttr(_attr, _val)); + } + } + + return _actions; + }; + + /** + * Adds a new action to the child elements. + * + * @param {string} _type + * @param {string} _id + * @param {string} _caption + * @param {string} _iconUrl + * @param {(string|function)} _onExecute + * @param {boolean} _allowOnMultiple + */ + + public addAction(_type: string, _id: string, _caption: string = "", _iconUrl: string = "", _onExecute: string | Function = null, _allowOnMultiple: boolean = true): EgwAction { + //Get the constructor for the given action type + if (!(_type in window._egwActionClasses)) { + //TODO doesn't default instead of popup make more sense here?? + _type = "popup" + } + + // Only allow adding new actions, if this action class allows it. + if (this.canHaveChildren) { + const constructor: any = window._egwActionClasses[_type]?.actionConstructor; + + if (typeof constructor == "function") { + const action: EgwAction = new constructor(this, _id, _caption, _iconUrl, _onExecute, _allowOnMultiple); + this.children.push(action); + + return action; + } else { + throw "Given action type not registered."; + } + } else { + throw "This action does not allow child elements!"; + } + }; + + + /** + * Updates the children of this element + * + * @param {object} _actions { id: action, ...} + * @param {string} _app defaults to egw_getAppname() + */ + public updateActions(_actions: any[] | Object, _app) { + if (this.canHaveChildren) { + if (typeof _app == "undefined") _app = window.egw(window).app_name() + /* + this is an egw Object as defined in egw_core.js + probably not because it changes on runtime + */ + const localEgw: IegwAppLocal = window.egw(_app); + //replaced jQuery calls + if (Array.isArray(_actions)) { + //_actions is now an object for sure + //happens in test website + _actions = {..._actions}; + } + for (const i in _actions) { + let elem = _actions[i]; + + if (typeof elem == "string") { + //changes type of elem to Object {caption:string} + _actions[i] = elem = {caption: elem}; + } + if (typeof elem == "object") // isn't this always true because of step above? Yes if elem was a string before + { + // use attr name as id, if none given + if (typeof elem.id != "string") elem.id = i; + + // if no iconUrl given, check icon and default icons + if (typeof elem.iconUrl == "undefined") { + if (typeof elem.icon == "undefined") elem.icon = this.defaultIcons[elem.id]; // only works if default Icon is available + if (typeof elem.icon != "undefined") { + elem.iconUrl = localEgw.image(elem.icon); + } + //if there is no icon and none can be found remove icon tag from the object + delete elem.icon; + } + + // always add shortcut for delete + if (elem.id == "delete" && typeof elem.shortcut == "undefined") { + elem.shortcut = { + keyCode: 46, shift: false, ctrl: false, alt: false, caption: localEgw.lang('Del') + }; + } + + // translate caption + if (elem.caption && (typeof elem.no_lang == "undefined" || !elem.no_lang)) { + elem.caption = localEgw.lang(elem.caption); + if (typeof elem.hint == "string") elem.hint = localEgw.lang(elem.hint); + } + delete elem.no_lang; + + // translate confirm messages and place '?' at the end iff not there yet + for (const attr in {confirm: '', confirm_multiple: ''}) { + if (typeof elem[attr] == "string") { + elem[attr] = localEgw.lang(elem[attr]) + ((elem[attr].substr(-1) != '?') ? '?' : ''); + } + } + + // set certain enabled functions iff elem.enabled is not set so false + if (typeof elem.enabled == 'undefined' || elem.enabled === true) { + if (typeof elem.enableClass != "undefined") { + elem.enabled = this.enableClass; + } else if (typeof elem.disableClass != "undefined") { + elem.enabled = this.not_disableClass; + } else if (typeof elem.enableId != "undefined") { + elem.enabled = this.enableId; + } + } + + //Check whether the action already exists, and if no, add it to the + //actions list + let action = this.getActionById(elem.id); + if (!action) { + //elem will be popup on default + if (typeof elem.type == "undefined") { + elem.type = "popup"; + } + + let constructor = null; + + // Check whether the given type is inside the "canHaveChildren" + // array // here can have children is used as array where possible types of children are stored + if (this.canHaveChildren !== true && this.canHaveChildren.indexOf(elem.type) == -1) { + throw "This child type '" + elem.type + "' is not allowed!"; + } + + if (typeof window._egwActionClasses[elem.type] != "undefined") { + constructor = window._egwActionClasses[elem.type].actionConstructor; + } else { + throw "Given action type \"" + elem.type + "\" not registered, because type does not exist"; + } + + if (typeof constructor == "function" && constructor) action = new constructor(this, elem.id); else throw "Given action type \"" + elem.type + "\" not registered."; + + this.children.push(action); + } + + action.updateAction(elem); + + // Add sub-actions to the action + if (elem.children) { + action.updateActions(elem.children, _app); + } + } + } + } else { + throw "This action element cannot have children!"; + } + }; + + + /** + * Callback to check if none of _senders rows has disableClass set + * + * @param _action EgwAction object, we use _action.data.disableClass to check + * @param _senders array of egwActionObject objects + * @param _target egwActionObject object, gets called for every object in _senders + * @returns boolean true if none has disableClass, false otherwise + */ + private not_disableClass(_action: EgwAction, _senders: any, _target: any) { + if (_target.iface.getDOMNode()) { + return !(_target.iface.getDOMNode()).classList.contains(_action.data.disableClass); + } else if (_target.id) { + // Checking on a something that doesn't have a DOM node, like a nm row + // that's not currently rendered + const data = window.egw.dataGetUIDdata(_target.id); + if (data && data.data && data.data.class) { + return -1 === data.data.class.split(' ').indexOf(_action.data.disableClass); + } + } + }; + + /** + * Callback to check if all of _senders rows have enableClass set + * + * @param _action EgwAction object, we use _action.data.enableClass to check + * @param _senders array of egwActionObject objects + * @param _target egwActionObject object, gets called for every object in _senders + * @returns boolean true if none has disableClass, false otherwise + */ + //TODO senders is never used in function body?? + private enableClass(_action: EgwAction, _senders: any[], _target: any) { + if (typeof _target == 'undefined') { + return false; + } else if (_target.iface.getDOMNode()) { + return (_target.iface.getDOMNode()).classList.contains(_action.data.enableClass); + } else if (_target.id) { + // Checking on a something that doesn't have a DOM node, like a nm row + // that's not currently rendered. Not as good as an actual DOM node check + // since things can get missed, but better than nothing. + const data = window.egw.dataGetUIDdata(_target.id); + if (data && data.data && data.data.class) { + return -1 !== data.data.class.split(' ').indexOf(_action.data.enableClass); + } + } + }; + + /** + * Enable an _action, if it matches a given regular expression in _action.data.enableId + * + * @param _action EgwAction object, we use _action.data.enableId to check + * @param _senders array of egwActionObject objects + * @param _target egwActionObject object, gets called for every object in _senders + * @returns boolean true if _target.id matches _action.data.enableId + */ + private enableId(_action: EgwAction, _senders: any[], _target: any) { + if (typeof _action.data.enableId == 'string') { + _action.data.enableId = new RegExp(_action.data.enableId); + } + return _target.id.match(_action.data.enableId); + }; + + /** + * Applies the same onExecute handler to all actions which don't have an execute + * handler set. + * + * @param {(string|function)} _value + */ + public setDefaultExecute(_value: string | Function): void { + // Check whether the onExecute handler of this action should be set + if (this.type != "actionManager" && !this.onExecute.hasHandler()) { + this.onExecute.isDefault = true; + this.onExecute.setValue(_value); + } + + // Apply the value to all children + if (this.canHaveChildren) { + for (const elem of this.children) { + elem.setDefaultExecute(_value); + } + } + }; + + /** + * Executes this action by using the method specified in the onExecute setter. + * + * @param {array} _senders array with references to the objects which caused the action + * @param {object} _target is an optional parameter which may represent e.g. a drag drop target + */ + execute(_senders, _target = null): any { + if (!this._check_confirm_mass_selections(_senders, _target)) { + return this._check_confirm(_senders, _target); + } + }; + + /** + * If this action needs to confirm mass selections (attribute confirm_mass_selection = true), + * check for any checkboxes that have a confirmation prompt (confirm_mass_selection is a string) + * and are unchecked. We then show the prompt, and set the checkbox to their answer. + * + * * This is only considered if there are more than 20 entries selected. + * + * * Only the first confirmation prompt / checkbox action will be used, others + * will be ignored. + * + * @param {type} _senders + * @param {type} _target + * @returns {Boolean} + */ + private _check_confirm_mass_selections(_senders, _target) { + const obj_manager: any = egw_getObjectManager(this.getManager().parent.id, false); + if (!obj_manager) { + return false; + } + + // Action needs to care about mass selection - check for parent that cares too + let confirm_mass_needed = false; + let action: EgwAction = this; + while (action && action !== obj_manager.manager && !confirm_mass_needed) { + confirm_mass_needed = !!action.confirm_mass_selection; + action = action.parent; + } + if (!confirm_mass_needed) return false; + + // Check for confirm mass selection checkboxes + const confirm_mass_selections = obj_manager.manager.getActionsByAttr("confirm_mass_selection"); + confirm_mass_needed = _senders.length > 20; + //no longer needed because of '=>' notation + //const self = this; + + // Find & show prompt + for (let i = 0; confirm_mass_needed && i < confirm_mass_selections.length; i++) { + const check = confirm_mass_selections[i]; + if (check.checkbox === false || check.checked === true) { + continue + } + + // Show the mass selection prompt + const msg = window.egw.lang(check.confirm_mass_selection, obj_manager.getAllSelected() ? window.egw.lang('all') : _senders.length); + const callback = (_button) => { + // YES = unchecked, NO = checked + check.set_checked(_button === window.Et2Dialog.NO_BUTTON); + if (_button !== window.Et2Dialog.CANCEL_BUTTON) { + this._check_confirm(_senders, _target); + } + }; + window.Et2Dialog.show_dialog(callback, msg, this.data.hint, {}, window.Et2Dialog.BUTTONS_YES_NO_CANCEL, window.Et2Dialog.QUESTION_MESSAGE); + return true; + } + return false; + }; + + + /** + * Check to see if action needs to be confirmed by user before we do it + */ + private _check_confirm(_senders, _target) { + // check if actions needs to be confirmed first + if (this.data && (this.data.confirm || this.data.confirm_multiple) && + this.onExecute.functionToPerform != window.nm_action && typeof window.Et2Dialog != 'undefined') // let old eTemplate run its own confirmation from nextmatch_action.js + { + let msg = this.data.confirm || ''; + if (_senders.length > 1) { + if (this.data.confirm_multiple) { + msg = this.data.confirm_multiple; + } + // check if we have all rows selected + const obj_manager = egw_getObjectManager(this.getManager().parent.id, false); + if (obj_manager && obj_manager.getAllSelected()) { + msg += "\n\n" + window.egw.lang('Attention: action will be applied to all rows, not only visible ones!'); + } + } + //no longer needed because of '=>' notation + //var self = this; + if (msg.trim().length > 0) { + if (this.data.policy_confirmation && window.egw.app('policy')) { + import(window.egw.link('/policy/js/app.min.js')).then(() => { + if (typeof window.app.policy === 'undefined' || typeof window.app.policy.confirm === 'undefined') { + window.app.policy = new window.app.classes.policy(); + } + window.app.policy.confirm(this, _senders, _target); + } + ); + return; + } + window.Et2Dialog.show_dialog((_button) => { + if (_button == window.Et2Dialog.YES_BUTTON) { + // @ts-ignore + return this.onExecute.exec(this, _senders, _target); + } + }, msg, this.data.hint, {}, window.Et2Dialog.BUTTONS_YES_NO, window.Et2Dialog.QUESTION_MESSAGE); + return; + } + } + // @ts-ignore + return this.onExecute.exec(this, _senders, _target); + }; + + + private updateAction(_data: Object) { + egwActionStoreJSON(_data, this, "data") + } + + /** + * Returns the parent action manager + */ + getManager(): EgwAction { + if (this.type == "actionManager") { + return this; + } else if (this.parent) { + return this.parent.getManager(); + } else { + return null; + } + } + + /** + * The appendToGraph function generates an action tree which automatically contains + * all parent elements. If the appendToGraph function is called for a + * + * @param {not an array} _tree contains the tree structure - pass an object containing {root:Tree}??TODO + * the empty array "root" to this function {"root": []}. The result will be stored in + * this array. + * @param {boolean} _addChildren is used internally to prevent parent elements from + * adding their children automatically to the tree. + */ + public appendToTree(_tree: { root: Tree }, _addChildren: boolean = true) { + + if (typeof _addChildren == "undefined") { + _addChildren = true; + } + + // Preset some variables + const root: Tree = _tree.root; + let parentNode: TreeElem = null; + let node: TreeElem = { + "action": this, "children": [] + }; + + + if (this.parent && this.type != "actionManager") { + // Check whether the parent container has already been added to the tree + parentNode = _egwActionTreeFind(root, this.parent); + + if (!parentNode) { + parentNode = this.parent.appendToTree(_tree, false); + } + + // Check whether this element has already been added to the parent container + let added = false; + for (const child of parentNode.children) { + if (child.action == this) { + node = child; + added = true; + break; + } + } + + if (!added) { + parentNode.children.push(node); + } + } else { + let added = false; + for (const treeElem of root) { + if (treeElem.action == this) { + node = treeElem; + added = true; + break; + } + } + + if (!added) { + // Add this element to the root if it has no parent + root.push(node); + } + } + + if (_addChildren) { + for (const child of this.children) { + child.appendToTree(_tree, true); + } + } + + return node; + }; + + /** + * @deprecated directly set value instead + * @param _value + */ + set_hideOnDisabled(_value) { + this.hideOnDisabled = _value; + + }; + + /** + * @deprecated directly set value instead + * @param _value + */ + set_hideOnMobile(_value) { + this.hideOnMobile = _value; + + }; + + /** + * @deprecated directly set value instead + * @param _value + */ + set_disableIfNoEPL(_value) { + this.disableIfNoEPL = _value; + + }; + + + set_hint(hint: string) { + + } +} + + +type TreeElem = { action: EgwAction, children: Tree } +type Tree = TreeElem[] + +/** + * finds an egwAction in the given tree + * @param {Tree}_tree where to search + * @param {EgwAction}_elem elem to search + * @returns {TreeElem} the treeElement for corresponding _elem if found, null else + */ +function _egwActionTreeFind(_tree: Tree, _elem: EgwAction): TreeElem { + for (const current of _tree) { + if (current.action == _elem) { + return current; + } + + if (typeof current.children != "undefined") { + const elem = _egwActionTreeFind(current.children, _elem); + if (elem) { + return elem; + } + } + } + + return null; +} \ No newline at end of file diff --git a/api/js/egw_action/EgwActionImplementation.ts b/api/js/egw_action/EgwActionImplementation.ts new file mode 100644 index 0000000000..1d40717bcd --- /dev/null +++ b/api/js/egw_action/EgwActionImplementation.ts @@ -0,0 +1,47 @@ +/** + * EGroupware egw_action framework - egw action framework + * + * @link https://www.egroupware.org + * @author Andreas Stöckel + * @copyright 2011 by Andreas Stöckel + * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License + * @package egw_action + */ +import type {EgwActionObjectInterface} from "./EgwActionObjectInterface"; + +/** + * Abstract interface for the EgwActionImplementation object. The EgwActionImplementation + * object is responsible for inserting the actual action representation (context menu, + * drag-drop code) into the DOM Tree by using the egwActionObjectInterface object + * supplied by the object. + * To write a "class" which derives from this object, simply write an own constructor, + * which replaces "this" with a "new EgwActionImplementation" and implement your + * code in "doRegisterAction" und "doUnregisterAction". + * Register your own implementation within the _egwActionClasses object. + * + */ +export interface EgwActionImplementation { + /** + * @param {object} _actionObjectInterface is the AOI in which the implementation + * should be registered. + * @param {function} _triggerCallback is the callback function which will be triggered + * when the user triggeres this action implementatino (e.g. starts a drag-drop or + * right-clicks on an object.) + * @param {object} _context in which the triggerCallback should get executed. + * @returns {boolean} true if the Action had been successfully registered, false if it + * had not. + */ + registerAction: (_actionObjectInterface: EgwActionObjectInterface, _triggerCallback: Function, _context: object) => boolean; + /** + * Unregister action will be called before an actionObjectInterface is destroyed, + * which gives the EgwActionImplementation the opportunity to remove the previously + * injected code. + * + * @param {egwActionObjectInterface} _actionObjectInterface + * @returns true if the Action had been successfully unregistered, false if it + * had not. + */ + unregisterAction: (_actionObjectInterface: EgwActionObjectInterface) => boolean; + executeImplementation: (_context: any, _selected: any, _links: any, _target?:any) => any; + type: string; +} diff --git a/api/js/egw_action/EgwActionLink.ts b/api/js/egw_action/EgwActionLink.ts new file mode 100644 index 0000000000..7429643e39 --- /dev/null +++ b/api/js/egw_action/EgwActionLink.ts @@ -0,0 +1,52 @@ +/** + * EGroupware egw_action framework - egw action framework + * + * @link https://www.egroupware.org + * @author Andreas Stöckel + * @copyright 2011 by Andreas Stöckel + * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License + * @package egw_action + */ + + +import {EgwActionManager} from "./EgwActionManager"; +import {egwActionStoreJSON} from "./egw_action_common"; + +/** + * The egwActionLink is used to interconnect egwActionObjects and egwActions. + * This gives each action object the possibility to decide, whether the action + * should be active in this context or not. + * + * @param _manager is a reference to the egwActionManager which contains the action + * the object wants to link to. + */ +export class EgwActionLink { + enabled = true; + visible = true; + actionId = ""; + actionObj = null; + manager:EgwActionManager; + + constructor(_manager) { + this.manager = _manager; + } + updateLink(_data) + { + egwActionStoreJSON(_data, this, true); + } + set_enabled(_value) { + this.enabled = _value; + }; + + set_visible(_value) { + this.visible = _value; + }; + set_actionId(_value) + { + this.actionId = _value; + this.actionObj = this.manager.getActionById(_value); + + if (!this.actionObj) + throw "Action object with id '"+_value+"' does not exist!"; + }; +} \ No newline at end of file diff --git a/api/js/egw_action/EgwActionManager.ts b/api/js/egw_action/EgwActionManager.ts new file mode 100644 index 0000000000..3d810a0049 --- /dev/null +++ b/api/js/egw_action/EgwActionManager.ts @@ -0,0 +1,27 @@ +/** + * EGroupware egw_action framework - egw action framework + * + * @link https://www.egroupware.org + * @author Andreas Stöckel + * @copyright 2011 by Andreas Stöckel + * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License + * @package egw_action + */ + +import {EgwAction} from "./EgwAction"; + +/** + * egwActionManager manages a list of actions - it overwrites the egwAction class + * and allows child actions to be added to it. + * + * @param {EgwAction} _parent + * @param {string} _id + * @return {EgwActionManager} + */ +export class EgwActionManager extends EgwAction { + constructor(_parent = null, _id = "") { + super(_parent, _id); + this.type = "actionManager"; + this.canHaveChildren = true; + } +} \ No newline at end of file diff --git a/api/js/egw_action/EgwActionObject.ts b/api/js/egw_action/EgwActionObject.ts new file mode 100644 index 0000000000..61de2d4a8c --- /dev/null +++ b/api/js/egw_action/EgwActionObject.ts @@ -0,0 +1,1130 @@ +/** + * EGroupware egw_action framework - egw action framework + * + * @link https://www.egroupware.org + * @author Andreas Stöckel + * @copyright 2011 by Andreas Stöckel + * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License + * @package egw_action + */ + +import {EgwActionLink} from "./EgwActionLink"; +import {EgwActionManager} from "./EgwActionManager"; +import {egwBitIsSet, egwObjectLength, egwQueueCallback, egwSetBit} from "./egw_action_common"; +import { + EGW_AO_EXEC_SELECTED, EGW_AO_EXEC_THIS, + EGW_AO_FLAG_IS_CONTAINER, + EGW_AO_SHIFT_STATE_BLOCK, + EGW_AO_SHIFT_STATE_MULTI, + EGW_AO_SHIFT_STATE_NONE, + EGW_AO_STATE_FOCUSED, + EGW_AO_STATE_NORMAL, + EGW_AO_STATE_SELECTED, + EGW_AO_STATE_VISIBLE, + EGW_KEY_A, + EGW_KEY_ARROW_DOWN, + EGW_KEY_ARROW_UP, + EGW_KEY_PAGE_DOWN, + EGW_KEY_PAGE_UP, + EGW_KEY_SPACE +} from "./egw_action_constants"; +import type {EgwActionObjectInterface} from "./EgwActionObjectInterface"; +import {egwActionObjectInterface} from "./egw_action"; + +/** + * The egwActionObject represents an abstract object to which actions may be + * applied. Communication with the DOM tree is established by using the + * egwActionObjectInterface (AOI), which is passed in the constructor. + * egwActionObjects are organized in a tree structure. + * + * @param {string} _id is the identifier of the object which + * @param {EgwActionObject} _parent is the parent object in the hierarchy. This may be set to NULL + * @param {egwActionObjectInterface} _iface is the egwActionObjectInterface which connects the object + * to the outer world. + * @param {EgwActionManager} _manager is the action manager this object is connected to + * this object to the DOM tree. If the _manager isn't supplied, the parent manager + * is taken. + * @param {number} _flags a set of additional flags being applied to the object, + * defaults to 0 + */ +export class EgwActionObject { + readonly id: string + readonly parent: EgwActionObject + public readonly children: EgwActionObject[] = [] + private actionLinks: EgwActionLink[] = [] + iface: EgwActionObjectInterface + readonly manager: EgwActionManager + readonly flags: number + data: any = null + private readonly setSelectedCallback: any = null; + private registeredImpls: any[] = []; + // Two variables which help fast travelling through the object tree, when + // searching for the selected/focused object. + private selectedChildren = []; + private focusedChild:EgwActionObject = null; + private readonly onBeforeTrigger: Function = undefined + _context: any = undefined + + + constructor(_id: string, _parent, _interface:EgwActionObjectInterface, _manager?, _flags: number=0) { + if (typeof _manager == "undefined" && typeof _parent == "object" && _parent) _manager = _parent.manager; + if (typeof _flags == "undefined") _flags = 0; + + + this.id = _id + this.parent = _parent + this.iface = _interface + this.manager = _manager + this.flags = _flags + + this.setAOI(_interface) + } + + /** + * Sets the action object interface - if "NULL" is given, the iface is set + * to a dummy interface which is used to store the temporary data. + * + * @param {egwActionObjectInterface} _aoi + */ + setAOI(_aoi) { + if (_aoi == null) { + //TODo replace DummyInterface + _aoi = new egwActionObjectInterface(); + } + + // Copy the state from the old interface + if (this.iface) { + _aoi.setState(this.iface.getState()); + } + + // Replace the interface object + this.iface = _aoi; + this.iface.setStateChangeCallback(this._ifaceCallback, this); + this.iface.setReconnectActionsCallback(this._reconnectCallback, this); + }; + + /** + // * Returns the object from the tree with the given ID + // * + // * @param {string} _id + // * @param {number} _search_depth + // * @return {egwActionObject} description + // * @todo Add search function to egw_action_commons.js + // */ + getObjectById(_id, _search_depth) { + if (this.id == _id) { + return this; + } + if (typeof _search_depth == "undefined") { + _search_depth = Number.MAX_VALUE; + } + + for (let i = 0; i < this.children.length && _search_depth > 0; i++) { + const obj = this.children[i].getObjectById(_id, _search_depth - 1); + if (obj) { + return obj; + } + } + + return null; + }; + + + /** + * Adds an object as child to the actionObject and returns it - if the supplied + * parameter is an object, the object will be added directly, otherwise an object + * with the given id will be created. + * + * @param {(string|object)} _id Id of the object which will be created or the object + * that will be added. + * @param {object} _interface if _id was a string, _interface defines the interface which + * will be connected to the newly generated object. + * @param {number} _flags are the flags will which be supplied to the newly generated + * object. May be omitted. + * @returns object the generated object + */ + addObject(_id: any, _interface: EgwActionObjectInterface=null, _flags: number=0) { + return this.insertObject(false, _id, _interface, _flags); + }; + + /** + * Inserts an object as child to the actionObject and returns it - if the supplied + * parameter is an object, the object will be added directly, otherwise an object + * with the given id will be created. + * + * @param {number} _index Position where the object will be inserted, "false" will add it + * to the end of the list. + * @param {string|object} _id Id of the object which will be created or the object + * that will be added. + * @param {object} _iface if _id was a string, _iface defines the interface which + * will be connected to the newly generated object. + * @param {number} _flags are the flags will which be supplied to the newly generated + * object. May be omitted. + * @returns object the generated object + */ + + insertObject(_index: number | boolean, _id: string | EgwActionObject, _iface: EgwActionObjectInterface, _flags: number) { + if (_index === false) _index = this.children.length; + + let obj = null; + + if (typeof _id == "object") { + obj = _id; + + // Set the parent to null and reset the focus of the object + obj.parent = null; + obj.setFocused(false); + + // Set the parent to this object + obj.parent = this; + } else if (typeof _id == "string") { + obj = new EgwActionObject(_id, this, _iface, this.manager, _flags); + } + + if (obj) { + // Add the element to the children + this.children.splice(_index as number, 0, obj); + } else { + throw "Error while adding new element to the ActionObjects!"; + } + + return obj; + }; + + /** + * Deletes all children of the egwActionObject + */ + + clear() { + // Remove all children + while (this.children.length > 0) { + this.children[0].remove(); + } + + // Delete all other references + this.selectedChildren = []; + this.focusedChild = null; + + // Remove links + this.actionLinks = []; + }; + + /** + * Deletes this object from the parent container + */ + remove() { + // Remove focus and selection from this element + this.setFocused(false); + this.setSelected(false); + this.setAllSelected(false); + + // Unregister all registered action implementations + this.unregisterActions(); + + // Clear the child-list + this.clear(); + + // Remove this element from the parent list + if (this.parent != null) { + const idx = this.parent.children.indexOf(this); + + if (idx >= 0) { + this.parent.children.splice(idx, 1); + } + } + }; + + /** + * Searches for the root object in the action object tree and returns it. + */ + getRootObject() { + if (this.parent === null) { + return this; + } else { + return this.parent.getRootObject(); + } + }; + + /** + * Returns a list with all parents of this object. + */ + getParentList() { + if (this.parent === null) { + return []; + } else { + const list = this.parent.getParentList(); + list.unshift(this.parent); + return list; + } + }; + + /** + * Returns the first parent which has the container flag + */ + getContainerRoot(): EgwActionObject { + if (egwBitIsSet(this.flags, EGW_AO_FLAG_IS_CONTAINER) || this.parent === null) { + return this; + } else { + return this.parent.getContainerRoot(); + } + }; + + /** + * Returns all selected objects which are in the current subtree. + * + * @param {function} _test is a function, which gets an object and checks whether + * it will be added to the list. + * @param {array} _list is internally used to fetch all selected elements, please + * omit this parameter when calling the function. + */ + getSelectedObjects(_test?, _list?) { + if (typeof _test == "undefined") _test = null; + + if (typeof _list == "undefined") { + _list = {"elements": []}; + } + + if ((!_test || _test(this)) && this.getSelected()) _list.elements.push(this); + + if (this.selectedChildren) { + for (let i = 0; i < this.selectedChildren.length; i++) { + this.selectedChildren[i].getSelectedObjects(_test, _list); + } + } + + return _list.elements; + }; + + /** + * Returns whether all objects in this tree are selected + */ + getAllSelected() { + if (this.children.length == this.selectedChildren.length) { + for (let i = 0; i < this.children.length; i++) { + if (!this.children[i].getAllSelected()) return false; + } + // If this element is a container *and* does not have any children, we + // should return false. If this element is not a container we have to + // return true has this is the recursion base case + return (!egwBitIsSet(this.flags, EGW_AO_FLAG_IS_CONTAINER)) || (this.children.length > 0); + } + + return false; + }; + + /** + * Toggles the selection of all objects. + * + * @param _select boolean specifies whether the objects should get selected or not. + * If this parameter is not supplied, the selection will be toggled. + */ + toggleAllSelected(_select?) { + if (typeof _select == "undefined") { + _select = !this.getAllSelected(); + } + + // Check for a select_all action + if (_select && this.manager && this.manager.getActionById('select_all')) { + return this.manager.getActionById('select_all').execute(this); + } + this.setAllSelected(_select); + }; + + + /** + * Creates a list which contains all items of the element tree. + * + * @param {boolean} _visibleOnly + * @param {object} _obj is used internally to pass references to the array inside + * the object. + * @return {array} + */ + flatList(_visibleOnly?: boolean, _obj?: { elements: EgwActionObject[] }) { + if (typeof (_obj) == "undefined") { + _obj = { + "elements": [] + }; + } + + if (typeof (_visibleOnly) == "undefined") { + _visibleOnly = false; + } + + if (!_visibleOnly || this.getVisible()) { + _obj.elements.push(this); + } + + for (const child of this.children) { + child.flatList(_visibleOnly, _obj); + } + + return _obj.elements; + }; + + /** + * Returns a traversal list with all objects which are in between the given object + * and this one. The operation returns an empty list, if a container object is + * found on the way. + * + * @param {object} _to + * @return {array} + * @todo Remove flatList here! + */ + traversePath(_to) { + const contRoot: EgwActionObject = this.getContainerRoot(); + + if (contRoot) { + // Get a flat list of all the hncp elements and search for this object + // and the object supplied in the _to parameter. + const flatList = contRoot.flatList(); + const thisId = flatList.indexOf(this); + const toId = flatList.indexOf(_to); + + // Check whether both elements have been found in this part of the tree, + // return the slice of that list. + if (thisId !== -1 && toId !== -1) { + const from = Math.min(thisId, toId); + const to = Math.max(thisId, toId); + + return flatList.slice(from, to + 1); + } + } + + return []; + }; + + /** + * Returns the index of this object in the children list of the parent object. + */ + getIndex() { + if (this.parent === null) { + //TODO check: should be -1 for invalid + return 0; + } else { + return this.parent.children.indexOf(this); + } + }; + + /** + * Returns the deepest object which is currently focused. Objects with the + * "container"-flag will not be returned. + */ + getFocusedObject() { + return this.focusedChild || null; + }; + + /** + * Internal function which is connected to the ActionObjectInterface associated + * with this object in the constructor. It gets called, whenever the object + * gets (de)selected. + * + * @param {number} _newState is the new state of the object + * @param {number} _changedBit + * @param {number} _shiftState is the status of extra keys being pressed during the + * selection process. + *///TODO check + _ifaceCallback(_newState: number, _changedBit: number, _shiftState?: number) { + if (typeof _shiftState == "undefined") _shiftState = EGW_AO_SHIFT_STATE_NONE; + + let selected: boolean = egwBitIsSet(_newState, EGW_AO_STATE_SELECTED); + const visible: boolean = egwBitIsSet(_newState, EGW_AO_STATE_VISIBLE); + + // Check whether the visibility of the object changed + if (_changedBit == EGW_AO_STATE_VISIBLE && visible != this.getVisible()) { + // Deselect the object + if (!visible) { + this.setSelected(false); + this.setFocused(false); + return EGW_AO_STATE_NORMAL; + } else { + // Auto-register the actions attached to this object + this.registerActions(); + } + } + + // Remove the focus from all children on the same level + if (this.parent && visible && _changedBit == EGW_AO_STATE_SELECTED) { + selected = egwBitIsSet(_newState, EGW_AO_STATE_SELECTED); + let objs = []; + + if (selected) { + // Deselect all other objects inside this container, if the "MULTI" shift-state is not set + if (!egwBitIsSet(_shiftState, EGW_AO_SHIFT_STATE_MULTI)) { + this.getContainerRoot().setAllSelected(false); + } + + // If the LIST state is active, get all objects in between this one and the focused one + // and set their select state. + if (egwBitIsSet(_shiftState, EGW_AO_SHIFT_STATE_BLOCK)) { + const focused = this.getFocusedObject(); + if (focused) { + objs = this.traversePath(focused); + for (let i = 0; i < objs.length; i++) { + objs[i].setSelected(true); + } + } + } + } + + // If the focused element didn't belong to this container, or the "list" + // shift-state isn't active, set the focus to this element. + if (objs.length == 0 || !egwBitIsSet(_shiftState, EGW_AO_SHIFT_STATE_BLOCK)) { + this.setFocused(true); + _newState = egwSetBit(EGW_AO_STATE_FOCUSED, _newState, true); + } + + this.setSelected(selected); + } + + return _newState; + }; + + /** + * Handler for key presses + * + * @param {number} _keyCode + * @param {boolean} _shift + * @param {boolean} _ctrl + * @param {boolean} _alt + * @returns {boolean} + */ + handleKeyPress(_keyCode, _shift, _ctrl, _alt) { + switch (_keyCode) { + case EGW_KEY_ARROW_UP: + case EGW_KEY_ARROW_DOWN: + case EGW_KEY_PAGE_UP: + case EGW_KEY_PAGE_DOWN: + + if (!_alt) { + const intval = (_keyCode == EGW_KEY_ARROW_UP || _keyCode == EGW_KEY_ARROW_DOWN) ? 1 : 10; + + if (this.children.length > 0) { + // Get the focused object + const focused = this.getFocusedObject(); + + // Determine the object which should get selected + let selObj = null; + if (!focused) { + selObj = this.children[0]; + } else { + selObj = (_keyCode == EGW_KEY_ARROW_UP || _keyCode == EGW_KEY_PAGE_UP) ? + focused.getPrevious(intval) : focused.getNext(intval); + } + + if (selObj != null) { + if (!_shift && !(this.parent && this.parent.data && this.parent.data.keyboard_select)) { + this.setAllSelected(false); + } else if (!(this.parent && this.parent.data && this.parent.data.keyboard_select)) { + const objs = focused.traversePath(selObj); + for (let i = 0; i < objs.length; i++) { + objs[i].setSelected(true); + } + } + + if (!(this.parent.data && this.parent.data.keyboard_select)) { + selObj.setSelected(true); + } + selObj.setFocused(true); + + // Tell the aoi of the object to make it visible + selObj.makeVisible(); + } + + return true; + } + } + + break; + + // Space bar toggles selected for current row + case EGW_KEY_SPACE: + if (this.children.length <= 0) { + break; + } + // Mark that we're selecting by keyboard, or arrows will reset selection + if (!this.parent.data) { + this.parent.data = {}; + } + this.parent.data.keyboard_select = true; + + // Get the focused object + const focused = this.getFocusedObject(); + + focused.setSelected(!focused.getSelected()); + + // Tell the aoi of the object to make it visible + focused.makeVisible(); + return true; + + // Handle CTRL-A to select all elements in the current container + case EGW_KEY_A: + if (_ctrl && !_shift && !_alt) { + this.toggleAllSelected(); + return true; + } + + break; + } + + return false; + }; + + getPrevious(_intval) { + if (this.parent != null) { + if (this.getFocused() && !this.getSelected()) { + return this; + } + + const flatTree = this.getContainerRoot().flatList(); + + let idx = flatTree.indexOf(this); + if (idx > 0) { + idx = Math.max(1, idx - _intval); + return flatTree[idx]; + } + } + + return this; + }; + + + getNext(_intval) { + if (this.parent != null) { + if (this.getFocused() && !this.getSelected()) { + return this; + } + + const flatTree = this.getContainerRoot().flatList(true); + + let idx = flatTree.indexOf(this); + if (idx < flatTree.length - 1) { + idx = Math.min(flatTree.length - 1, idx + _intval); + return flatTree[idx]; + } + } + + return this; + }; + + /** + * Returns whether the object is currently selected. + */ + + getSelected() { + return egwBitIsSet(this.getState(), EGW_AO_STATE_SELECTED); + }; + + /** + * Returns whether the object is currently focused. + */ + + getFocused() { + return egwBitIsSet(this.getState(), EGW_AO_STATE_FOCUSED); + }; + + /** + * Returns whether the object currently is visible - visible means, that the + * AOI has a dom node and is visible. + */ + + getVisible() { + return egwBitIsSet(this.getState(), EGW_AO_STATE_VISIBLE); + }; + + /** + * Returns the complete state of the object. + */ + + getState() { + return this.iface.getState(); + }; + + + /** + * Sets the focus of the element. The formerly focused element in the tree will + * be de-focused. + * + * @param {boolean} _focused - whether to remove or set the focus. Defaults to true + */ + + setFocused(_focused) { + if (typeof _focused == "undefined") _focused = true; + + const state = this.iface.getState(); + + if (egwBitIsSet(state, EGW_AO_STATE_FOCUSED) != _focused) { + // Un-focus the currently focused object + const currentlyFocused = this.getFocusedObject(); + if (currentlyFocused && currentlyFocused != this) { + currentlyFocused.setFocused(false); + } + + this.iface.setState(egwSetBit(state, EGW_AO_STATE_FOCUSED, _focused)); + if (this.parent) { + this.parent.updateFocusedChild(this, _focused); + } + } + + if (this.focusedChild != null && _focused == false) { + this.focusedChild.setFocused(false); + } + }; + + /** + * Sets the selected state of the element. + * + * @param {boolean} _selected + * @TODO Callback + */ + + setSelected(_selected) { + const state = this.iface.getState(); + + if ((egwBitIsSet(state, EGW_AO_STATE_SELECTED) != _selected) && egwBitIsSet(state, EGW_AO_STATE_VISIBLE)) { + this.iface.setState(egwSetBit(state, EGW_AO_STATE_SELECTED, _selected)); + if (this.parent) { + this.parent.updateSelectedChildren(this, _selected || this.selectedChildren.length > 0); + } + } + }; + + /** + * Sets the selected state of all elements, including children + * + * @param {boolean} _selected + * @param {boolean} _informParent + */ + + setAllSelected(_selected, _informParent = true) { + + const state = this.iface.getState(); + + // Update this element + if (egwBitIsSet(state, EGW_AO_STATE_SELECTED) != _selected) { + this.iface.setState(egwSetBit(state, EGW_AO_STATE_SELECTED, _selected)); + if (_informParent && this.parent) { + this.parent.updateSelectedChildren(this, _selected); + } + if (this.parent?.data && this.parent?.data?.keyboard_select) { + this.parent.data.keyboard_select = false; + } + } + + // Update the children if they should be selected or if they should be + // deselected and there are selected children. + if (_selected || this.selectedChildren.length > 0) { + for (let i = 0; i < this.children.length; i++) { + this.children[i].setAllSelected(_selected, false); + } + } + + // Copy the selected children list + this.selectedChildren = []; + if (_selected) { + for (let i = 0; i < this.children.length; i++) { + this.selectedChildren.push(this.children[i]); + } + } + + // Call the setSelectedCallback + egwQueueCallback(this.setSelectedCallback, [], this, "setSelectedCallback"); + }; + + + /** + * Updates the selectedChildren array each actionObject has in order to determine + * all selected children in a very fast manner. + * + * @param {(string|egwActionObject} _child + * @param {boolean} _selected + * @todo Has also to be updated, if an child is added/removed! + */ + + updateSelectedChildren(_child, _selected) { + const id: number = this.selectedChildren.indexOf(_child); // TODO Replace by binary search, insert children sorted by index! + const wasEmpty: boolean = this.selectedChildren.length == 0; + + // Add or remove the given child from the selectedChildren list + if (_selected && id == -1) { + this.selectedChildren.push(_child); + } else if (!_selected && id != -1) { + this.selectedChildren.splice(id, 1); + } + + // If the emptiness of the selectedChildren array has changed, update the + // parent selected children array. + if (wasEmpty != (this.selectedChildren.length == 0) && this.parent) { + this.parent.updateSelectedChildren(this, wasEmpty); + } + + // Call the setSelectedCallback + egwQueueCallback(this.setSelectedCallback, this.getContainerRoot().getSelectedObjects(), this, "setSelectedCallback"); + }; + + /** + * Updates the focusedChild up to the container boundary. + * + * @param {(string|egwActionObject} _child + * @param {boolean} _focused + */ + + updateFocusedChild(_child: EgwActionObject, _focused: boolean) { + if (_focused) { + this.focusedChild = _child; + } else { + if (this.focusedChild == _child) { + this.focusedChild = null; + } + } + + if (this.parent /*&& !egwBitIsSet(this.flags, EGW_AO_FLAG_IS_CONTAINER)*/) { + this.parent.updateFocusedChild(_child, _focused); + } + }; + + /** + * Updates the actionLinks of the given ActionObject. + * + * @param {array} _actionLinks contains the information about the actionLinks which + * should be updated as an array of objects. Example + * [ + * { + * "actionId": "file_delete", + * "enabled": true + * } + * ] + * string[] or {actionID:string,enabled:boolean}[] + * If an supplied link doesn't exist yet, it will be created (if _doCreate is true) + * and added to the list. Otherwise, the information will just be updated. + * @param {boolean} _recursive If true, the settings will be applied to all child + * object (default false) + * @param {boolean} _doCreate If true, not yet existing links will be created (default true) + */ + + updateActionLinks(_actionLinks: string[] | { + actionId: string, + enabled: boolean + }[], _recursive: boolean = false, _doCreate: boolean = true) { + for (let elem of _actionLinks) { + + // Allow single strings for simple action links. + if (typeof elem == "string") { + elem = { + actionId: elem, + enabled: true + }; + } + + if (typeof elem.actionId != "undefined" && elem.actionId) { + //Get the action link object, if it doesn't exist yet, create it + let actionLink = this.getActionLink(elem.actionId); + if (!actionLink && _doCreate) { + actionLink = new EgwActionLink(this.manager); + this.actionLinks.push(actionLink); + } + + //Set the supplied data + if (actionLink) { + actionLink.updateLink(elem); + } + } + } + + if (_recursive) { + for (let i = 0; i < this.children.length; i++) { + this.children[i].updateActionLinks(_actionLinks, true, _doCreate); + } + } + + if (this.getVisible() && this.iface != null) { + this.registerActions(); + } + }; + + /** + * Reconnects the actions. + */ + + _reconnectCallback() { + this.registeredImpls = []; + this.registerActions(); + }; + + /** + * Registers the action implementations inside the DOM-Tree. + */ + registerActions() { + const groups = this.getActionImplementationGroups(); + + for (const group in groups) { + // Get the action implementation for each group + if (typeof window._egwActionClasses[group] != "undefined" && window._egwActionClasses[group].implementation && this.iface) { + const impl = window._egwActionClasses[group].implementation(); + + if (this.registeredImpls.indexOf(impl) == -1) { + // Register a handler for that action with the iface of that object, + // the callback and this object as context for the callback + if (impl.registerAction(this.iface, this.executeActionImplementation, this)) { + this.registeredImpls.push(impl); + } + } + } + } + }; + + /** + * Unregisters all action implementations registered to this element + */ + unregisterActions() { + while (this.registeredImpls.length > 0) { + const impl = this.registeredImpls.pop(); + if (this.iface) { + impl.unregisterAction(this.iface); + } + } + }; + + protected triggerCallback(): boolean { + if (this.onBeforeTrigger) { + return this.onBeforeTrigger() + } + return true; + } + + makeVisible() { + this.iface.makeVisible(); + }; + + + /** + * Executes the action implementation which is associated to the given action type. + * + * @param {object} _implContext is data which should be delivered to the action implementation. + * E.g. in case of the popup action implementation, the x and y coordinates where the + * menu should open, and contextmenu event are transmitted. + * @param {string} _implType is the action type for which the implementation should be + * executed. + * @param {number} _execType specifies in which context the execution should take place. + * defaults to EGW_AO_EXEC_SELECTED + */ + + executeActionImplementation(_implContext, _implType, _execType) { + if (typeof _execType == "undefined") { + _execType = EGW_AO_EXEC_SELECTED; + } + + if (typeof _implType == "string") { + _implType = window._egwActionClasses[_implType].implementation(); + } + + if (typeof _implType == "object" && _implType) { + let selectedActions; + if (_execType == EGW_AO_EXEC_SELECTED) { + if (!(egwBitIsSet(EGW_AO_FLAG_IS_CONTAINER, this.flags))) { + this.forceSelection(); + } + selectedActions = this.getSelectedLinks(_implType.type); + } else if (_execType == EGW_AO_EXEC_THIS) { + selectedActions = this._getLinks([this], _implType.type); + } + + if (selectedActions.selected.length > 0 && egwObjectLength(selectedActions.links) > 0) { + return _implType.executeImplementation(_implContext, selectedActions.selected, selectedActions.links); + } + } + + return false; + }; + + /** + * Forces the object to be inside the currently selected objects. If this is + * not the case, the object will select itself and deselect all other objects. + */ + + forceSelection() { + const selected = this.getContainerRoot().getSelectedObjects(); + + // Check whether this object is in the list + const thisInList: boolean = selected.indexOf(this) != -1; + + // If not, select it + if (!thisInList) { + this.getContainerRoot().setAllSelected(false); + this.setSelected(true); + } + + this.setFocused(true); + }; + + /** + * Returns all selected objects, and all action links of those objects, which are + * of the given implementation type, actionLink properties such as + * "enabled" and "visible" are accumulated. + * + * Objects have the chance to change their action links or to deselect themselves + * in the onBeforeTrigger event, which is evaluated by the triggerCallback function. + * + * @param _actionType is the action type for which the actionLinks should be collected. + * @returns object An object which contains a "links" and a "selected" section with + * an array of links/selected objects- + */ + + getSelectedLinks(_actionType) { + // Get all objects in this container which are currently selected + const selected = this.getContainerRoot().getSelectedObjects(); + + return this._getLinks(selected, _actionType); + }; + + /** + * + * @param {array} _objs + * @param {string} _actionType + * @return {object} with attributes "selected" and "links" + */ + + _getLinks(_objs, _actionType) { + const actionLinks:any = {}; + const testedSelected = []; + + const test = function (olink,obj) { + // Test whether the action type is of the given implementation type + if (olink.actionObj.type == _actionType) { + if (typeof actionLinks[olink.actionId] == "undefined") { + actionLinks[olink.actionId] = { + "actionObj": olink.actionObj, + "enabled": (testedSelected.length == 1), + "visible": false, + "cnt": 0 + }; + } + + // Accumulate the action link properties + const llink = actionLinks[olink.actionId]; + llink.enabled = llink.enabled && olink.actionObj.enabled.exec(olink.actionObj, _objs, obj) && olink.enabled && olink.visible; + llink.visible = (llink.visible || olink.visible); + llink.cnt++; + + // Add in children, so they can get checked for visible / enabled + if (olink.actionObj && olink.actionObj.children.length > 0) { + for (let j = 0; j < olink.actionObj.children.length; j++) { + const child = olink.actionObj.children[j]; + test({ + actionObj: child, actionId: child.id, enabled: olink.enabled, visible: olink.visible + },obj); + } + } + } + }; + + for (const obj of _objs) { + if (!egwBitIsSet(obj.flags, EGW_AO_FLAG_IS_CONTAINER) && obj.triggerCallback()) { + testedSelected.push(obj); + + obj.actionLinks.forEach(item => { + test(item,obj); //object link + }); + } + } + + // Check whether all objects supported the action + for (let k in actionLinks) { + actionLinks[k].enabled = actionLinks[k].enabled && (actionLinks[k].cnt >= testedSelected.length) && ((actionLinks[k].actionObj.allowOnMultiple === true) || (actionLinks[k].actionObj.allowOnMultiple == "only" && _objs.length > 1) || (actionLinks[k].actionObj.allowOnMultiple == false && _objs.length === 1) || (typeof actionLinks[k].actionObj.allowOnMultiple === 'number' && _objs.length == actionLinks[k].actionObj.allowOnMultiple)); + if (!window.egwIsMobile()) actionLinks[k].actionObj.hideOnMobile = false; + actionLinks[k].visible = actionLinks[k].visible && !actionLinks[k].actionObj.hideOnMobile && (actionLinks[k].enabled || !actionLinks[k].actionObj.hideOnDisabled); + } + + // Return an object which contains the accumulated actionLinks and all selected + // objects. + return { + "selected": testedSelected, "links": actionLinks + }; + }; + + /** + * Returns the action link, which contains the association to the action with + * the given actionId. + * + * @param {string} _actionId name of the action associated to the link + */ + + getActionLink(_actionId: string) { + for (let i = 0; i < this.actionLinks.length; i++) { + if (this.actionLinks[i].actionObj?.id == _actionId) { + return this.actionLinks[i]; + } + } + + return null; + }; + + /** + * Returns all actions associated to the object tree, grouped by type. + * + * @param {function} _test gets an egwActionObject and should return, whether the + * actions of this object are added to the result. Defaults to an "always true" + * function. + * @param {object} _groups is an internally used parameter, may be omitted. + */ + + getActionImplementationGroups(_test?, _groups?) { + // If the _groups parameter hasn't been given preset it to an empty object + // (associative array). + if (typeof _groups == "undefined") _groups = {}; + if (typeof _test == "undefined") _test = function (_obj) { + return true; + }; + + this.actionLinks.forEach(item => { + const action = item.actionObj; + if (typeof action != "undefined" && _test(this)) { + if (typeof _groups[action.type] == "undefined") { + _groups[action.type] = []; + } + + _groups[action.type].push({ + "object": this, "link": item + }); + } + }); + + // Recursively add the actions of the children to the result (as _groups is + // an object, only the reference is passed). + this.children.forEach(item => { + item.getActionImplementationGroups(_test, _groups); + }); + + return _groups; + }; + + /** + * Check if user tries to get dragOut action + * + * keys for dragOut: + * -Mac: Command + Shift + * -Others: Alt + Shift + * + * @param {event} _event + * @return {boolean} return true if Alt+Shift keys and left mouse click are pressed, otherwise false + */ + + isDragOut(_event) { + return (_event.altKey || _event.metaKey) && _event.shiftKey && _event.which == 1; + }; + + /** + * Check if user tries to get selection action + * + * Keys for selection: + * -Mac: Command key + * -Others: Ctrl key + * + * @param {type} _event + * @returns {Boolean} return true if left mouse click and Ctrl/Alt key are pressed, otherwise false + */ + + isSelection(_event) { + return !(_event.shiftKey) && _event.which == 1 && (_event.metaKey || _event.ctrlKey || _event.altKey); + }; + +} diff --git a/api/js/egw_action/EgwActionObjectInterface.ts b/api/js/egw_action/EgwActionObjectInterface.ts new file mode 100644 index 0000000000..fe8447cde1 --- /dev/null +++ b/api/js/egw_action/EgwActionObjectInterface.ts @@ -0,0 +1,85 @@ +/** + * EGroupware egw_action framework - egw action framework + * + * @link https://www.egroupware.org + * @author Andreas Stöckel + * @copyright 2011 by Andreas Stöckel + * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License + * @package egw_action + */ + +/** + * The egwActionObjectInterface has to be implemented for each actual object in + * the browser. E.g. for the object "DataGridRow", there has to be an + * egwActionObjectInterface which is responsible for returning the outer DOMNode + * of the object to which JS-Events may be attached by the EgwActionImplementation + * object, and to do object specific stuff like highlighting the object in the + * correct way and to route state changes (like: "object has been selected") + * to the egwActionObject object the interface is associated to. + * + * @return {egwActionObjectInterface} + */ +export interface EgwActionObjectInterface { + //TODO abstract class might be better + //properties + _state: number; + stateChangeCallback: Function; + stateChangeContext: any; + reconnectActionsCallback: Function; + reconnectActionsContext: any; + + //functions + /** + * Sets the callback function which will be called when a user interaction changes + * state of the object. + * + * @param {function} _callback + * @param {object} _context + */ + setStateChangeCallback(_callback: Function, _context: any): void; + + /** + * Sets the reconnectActions callback, which will be called by the AOI if its + * DOM-Node has been replaced and the actions have to be re-registered. + * + * @param {function} _callback + * @param {object} _context + */ + setReconnectActionsCallback(_callback: Function, _context: any): void; + + /** + * Will be called by the aoi if the actions have to be re-registered due to a + * DOM-Node exchange. + */ + reconnectActions(): void; + + /** + * Internal function which should be used whenever the select status of the object + * has been changed by the user. This will automatically calculate the new state of + * the object and call the stateChangeCallback (if it has been set) + * + * @param {number} _stateBit is the bit in the state bit which should be changed + * @param {boolean} _set specifies whether the state bit should be set or not + * @param {boolean} _shiftState + */ + updateState(_stateBit: number, _set: boolean, _shiftState: boolean): void; + + + /** + * Returns the DOM-Node the ActionObject is actually a representation of. + * Calls the internal "doGetDOMNode" function, which has to be overwritten + * by implementations of this class. + */ + getDOMNode(): Element; + + setState(_state: any): void; + + getState(): number; + + triggerEvent(_event: any, _data: any): boolean; + + /** + * Scrolls the element into a visible area if it is currently hidden + */ + makeVisible(): void; +} diff --git a/api/js/egw_action/EgwActionObjectManager.ts b/api/js/egw_action/EgwActionObjectManager.ts new file mode 100644 index 0000000000..40226805ec --- /dev/null +++ b/api/js/egw_action/EgwActionObjectManager.ts @@ -0,0 +1,32 @@ +/** + * EGroupware egw_action framework - egw action framework + * + * @link https://www.egroupware.org + * @author Andreas Stöckel + * @copyright 2011 by Andreas Stöckel + * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License + * @package egw_action + */ + +import {EGW_AO_FLAG_IS_CONTAINER} from "./egw_action_constants"; +import {EgwActionObject} from "./EgwActionObject"; +import {egwActionObjectInterface} from "./egw_action"; + +/** + * The egwActionObjectManager is a dummy class which only contains a dummy + * AOI. It may be used as root object or as object containers. + * + * @param {string} _id + * @param {string} _manager + * @return {EgwActionObjectManager} + */ +export class EgwActionObjectManager extends EgwActionObject { + constructor(_id: string, _manager: any) { + const aoi = new egwActionObjectInterface(); + //const ao = new egwActionObject(_id, null, aoi, _manager, EGW_AO_FLAG_IS_CONTAINER) + super(_id, null, aoi, _manager, EGW_AO_FLAG_IS_CONTAINER); + this.triggerCallback = function () { + return false; + } + } +} \ No newline at end of file diff --git a/api/js/egw_action/EgwDragAction.ts b/api/js/egw_action/EgwDragAction.ts new file mode 100644 index 0000000000..63a338d283 --- /dev/null +++ b/api/js/egw_action/EgwDragAction.ts @@ -0,0 +1,39 @@ +/** + * EGroupware egw_action framework - egw action framework + * + * @link https://www.egroupware.org + * @author Andreas Stöckel + * @copyright 2011 by Andreas Stöckel + * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License + * @package egw_action + */ + +import {EgwAction} from "./EgwAction"; + +/** + * The egwDragAction class overwrites the egwAction class and adds the new + * "dragType" property. The "onExecute" event of the drag action will be called + * whenever dragging starts. The onExecute JS handler should return the + * drag-drop helper object - otherwise a default helper will be generated. + */ +export class EgwDragAction extends EgwAction { + private dragType = "default" + + public set_dragType(_value) { + this.dragType = _value + } + + /** + * @param {EgwAction} parent + * @param {string} _id + * @param {string} _caption + * @param {string} _iconUrl + * @param {(string|function)} _onExecute + * @param {bool} _allowOnMultiple + */ + constructor(parent: EgwAction, _id, _caption, _iconUrl, _onExecute, _allowOnMultiple) { + super(parent, _id, _caption, _iconUrl, _onExecute, _allowOnMultiple); + this.type = "drag"; + this.hideOnDisabled = true; + } +} \ No newline at end of file diff --git a/api/js/egw_action/EgwDropAction.ts b/api/js/egw_action/EgwDropAction.ts new file mode 100644 index 0000000000..7b2881d45a --- /dev/null +++ b/api/js/egw_action/EgwDropAction.ts @@ -0,0 +1,67 @@ +/** + * EGroupware egw_action framework - egw action framework + * + * @link https://www.egroupware.org + * @author Andreas Stöckel + * @copyright 2011 by Andreas Stöckel + * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License + * @package egw_action + */ + +import {EgwAction} from "./EgwAction"; + +/** + * The egwDropAction class overwrites the egwAction class and adds the "acceptedTypes" + * property. This array should contain all "dragTypes" the drop action is allowed to + * + * @param {EgwAction} _id + * @param {string} _handler + * @param {string} _caption + * @param {string} _icon + * @param {(string|function)} _onExecute + * @param {bool} _allowOnMultiple + * @returns {egwDropAction} + */ +export class EgwDropAction extends EgwAction{ + acceptedTypes: string[]; + order: number; + group: number; + + constructor(_id, _handler, _caption, _icon, _onExecute, _allowOnMultiple) { + + super(_id, _handler, _caption, _icon, _onExecute, _allowOnMultiple); + + this.type = "drop"; + this.acceptedTypes = ["default"]; + this.canHaveChildren = ["drag", "popup"]; + this["default"] = false; + this.order = 0; + this.group = 0; + } + + set_default(_value) { + this["default"] = _value; + }; + + set_order(_value) { + this.order = _value; + }; + + set_group(_value) { + this.group = _value; + }; + + /** + * The acceptType property allows strings as well as arrays - strings are + * automatically included in an array. + * + * @param {(string|array)} _value + */ + set_acceptedTypes(_value) { + if (_value instanceof Array) { + this.acceptedTypes = _value; + } else { + this.acceptedTypes = [_value]; + } + }; +} \ No newline at end of file diff --git a/api/js/egw_action/EgwDropActionImplementation.ts b/api/js/egw_action/EgwDropActionImplementation.ts new file mode 100644 index 0000000000..9cb18aa74f --- /dev/null +++ b/api/js/egw_action/EgwDropActionImplementation.ts @@ -0,0 +1,302 @@ +/** + * EGroupware egw_action framework - egw action framework + * + * @link https://www.egroupware.org + * @author Andreas Stöckel + * @copyright 2011 by Andreas Stöckel + * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License + * @package egw_action + */ + +import {EgwActionImplementation} from "./EgwActionImplementation"; +import {EGW_AI_DRAG_ENTER, EGW_AI_DRAG_OUT, EGW_AI_DRAG_OVER, EGW_AO_EXEC_THIS} from "./egw_action_constants"; +import {egw_getObjectManager} from "./egw_action"; +import {getPopupImplementation} from "./EgwPopupActionImplementation"; + +export class EgwDropActionImplementation implements EgwActionImplementation { + type: string = "drop"; + //keeps track of current drop element where dragged item's entered. + // it's necessary for dragenter/dragleave issue correction. + private currentDropEl = null + + + registerAction: (_actionObjectInterface: any, _triggerCallback: Function, _context: object) => boolean = (_aoi, _callback, _context)=> { + const node = _aoi.getDOMNode() && _aoi.getDOMNode()[0] ? _aoi.getDOMNode()[0] : _aoi.getDOMNode(); + const self:EgwDropActionImplementation = this; + if (node) { + node.classList.add('et2dropzone'); + const dragover = (event)=> { + if (event.preventDefault) { + event.preventDefault(); + } + if (!this.getTheDraggedDOM()) return; + + const data = { + event: event, + ui: this.getTheDraggedData() + }; + _aoi.triggerEvent(EGW_AI_DRAG_OVER, data); + + return true; + + }; + + const dragenter = function (event) { + event.stopImmediatePropagation(); + // don't trigger dragenter if we are entering the drag element + // don't go further if the dragged element is no there (happens when a none et2 dragged element is being dragged) + if (!self.getTheDraggedDOM() || self.isTheDraggedDOM(this) || this == self.currentDropEl) return; + + self.currentDropEl = event.currentTarget; + event.dataTransfer.dropEffect = 'link'; + + const data = { + event: event, + ui: self.getTheDraggedData() + }; + + _aoi.triggerEvent(EGW_AI_DRAG_ENTER, data); + + // cleanup drop hover class from all other DOMs if there's still anything left + Array.from(document.getElementsByClassName('et2dropzone drop-hover')).forEach(_i => { + _i.classList.remove('drop-hover') + }) + + this.classList.add('drop-hover'); + + // stop the event from being fired for its children + event.preventDefault(); + return false; + }; + + const drop = function (event) { + event.preventDefault(); + // don't go further if the dragged element is no there (happens when a none et2 dragged element is being dragged) + if (!self.getTheDraggedDOM()) return; + + // remove the hover class + this.classList.remove('drop-hover'); + + const helper = self.getHelperDOM(); + let ui = self.getTheDraggedData(); + ui.position = {top: event.clientY, left: event.clientX}; + ui.offset = {top: event.offsetY, left: event.offsetX}; + + + let data = JSON.parse(event.dataTransfer.getData('application/json')); + + if (!self.isAccepted(data, _context, _callback,undefined) || self.isTheDraggedDOM(this)) { + // clean up the helper dom + if (helper) helper.remove(); + return; + } + + let selected = data.selected.map((item) => { + return egw_getObjectManager(item.id, false) + }); + + //links is an Object of DropActions bound to their names + const links = _callback.call(_context, "links", self, EGW_AO_EXEC_THIS); + + // Disable all links which only accept types which are not + // inside ddTypes + for (const k in links) { + const accepted = links[k].actionObj.acceptedTypes; + + let enabled = false; + for (let i = 0; i < data.ddTypes.length; i++) { + if (accepted.indexOf(data.ddTypes[i]) != -1) { + enabled = true; + break; + } + } + // Check for allowing multiple selected + if (!links[k].actionObj.allowOnMultiple && selected.length > 1) { + enabled = false; + } + if (!enabled) { + links[k].enabled = false; + links[k].visible = !links[k].actionObj.hideOnDisabled; + } + } + + // Check whether there is only one link + let cnt = 0; + let lnk = null; + for (const k in links) { + if (links[k].enabled && links[k].visible) { + lnk = links[k]; + cnt += 1 + links[k].actionObj.children.length; + + // Add ui, so you know what happened where + lnk.actionObj.ui = ui; + + } + } + + if (cnt == 1) { + window.setTimeout(function () { + lnk.actionObj.execute(selected, _context); + }, 0); + } + + if (cnt > 1) { + // More than one drop action link is associated + // to the drop event - show those as a popup menu + // and let the user decide which one to use. + // This is possible as the popup and the popup action + // object and the drop action object share same + // set of properties. + const popup = getPopupImplementation(); + const pos = popup._getPageXY(event); + + // Don't add paste actions, this is a drop + popup.auto_paste = false; + + window.setTimeout(function () { + popup.executeImplementation(pos, selected, links, + _context); + // Reset, popup is reused + popup.auto_paste = true; + }, 0); // Timeout is needed to have it working in IE + } + // Set cursor back to auto. Seems FF can't handle cursor reversion + jQuery('body').css({cursor: 'auto'}); + + _aoi.triggerEvent(EGW_AI_DRAG_OUT, {event: event, ui: self.getTheDraggedData()}); + + // clean up the helper dom + if (helper) helper.remove(); + self.getTheDraggedDOM().classList.remove('drag--moving'); + }; + + const dragleave = function (event) { + event.stopImmediatePropagation(); + + // don't trigger dragleave if we are leaving the drag element + // don't go further if the dragged element is no there (happens when a none et2 dragged element is being dragged) + if (!self.getTheDraggedDOM() || self.isTheDraggedDOM(this) || this == self.currentDropEl) return; + + const data = { + event: event, + ui: self.getTheDraggedData() + }; + + _aoi.triggerEvent(EGW_AI_DRAG_OUT, data); + + this.classList.remove('drop-hover'); + + event.preventDefault(); + return false; + }; + + // DND Event listeners + node.addEventListener('dragover', dragover, false); + + node.addEventListener('dragenter', dragenter, false); + + node.addEventListener('drop', drop, false); + + node.addEventListener('dragleave', dragleave, false); + + return true; + } + return false; + }; + + unregisterAction: (_actionObjectInterface: any) => boolean = function (_aoi) { + const node = _aoi.getDOMNode(); + + if (node) { + node.classList.remove('et2dropzone'); + } + return true; + }; + + /** + * Builds the context menu and shows it at the given position/DOM-Node. + * + * @param {string} _context + * @param {array} _selected + * @param {object} _links + */ + executeImplementation: (_context: any, _selected: any, _links: any) => any = function (_context, _selected, _links) { + if (_context == "links") { + return _links; + } + }; + + + isTheDraggedDOM = function (_dom) { + return _dom.classList.contains('drag--moving'); + } + + getTheDraggedDOM = function () { + return document.querySelector('.drag--moving'); + } + + getHelperDOM = function () { + return document.querySelector('.et2_egw_action_ddHelper'); + } + + getTheDraggedData = ()=> { + // @ts-ignore // in our case dataset will be present + let data = this.getTheDraggedDOM().dataset.egwactionobjid; + let selected = []; + if (data) { + data = JSON.parse(data); + selected = data.map((item) => { + return egw_getObjectManager(item.id, false) + }); + } + return { + draggable: this.getTheDraggedDOM(), + helper: this.getHelperDOM(), + selected: selected, + position: undefined, + offset: undefined + + } + } + + // check if given draggable is accepted for drop + isAccepted = (_data, _context, _callback, _node)=> { + if (_node && !_node.classList.contains('et2dropzone')) return false; + if (typeof _data.ddTypes != "undefined") { + const accepted = this._fetchAccepted( + _callback.call(_context, "links", this, EGW_AO_EXEC_THIS)); + + // Check whether all drag types of the selected objects + // are accepted + const ddTypes = _data.ddTypes; + + for (let i = 0; i < ddTypes.length; i++) { + if (accepted.indexOf(ddTypes[i]) != -1) { + return true; + } + } + } + return false; + }; + + + private _fetchAccepted = (_links) =>{ + // Accumulate the accepted types + const accepted = []; + for (let k in _links) { + for (let i = 0; i < _links[k].actionObj.acceptedTypes.length; i++) { + const type = _links[k].actionObj.acceptedTypes[i]; + + if (accepted.indexOf(type) == -1) { + accepted.push(type); + } + } + } + + return accepted; + }; + +} + +export class egwDropActionImplementation extends EgwDropActionImplementation { +} \ No newline at end of file diff --git a/api/js/egw_action/EgwPopupAction.ts b/api/js/egw_action/EgwPopupAction.ts new file mode 100644 index 0000000000..d72c4640d5 --- /dev/null +++ b/api/js/egw_action/EgwPopupAction.ts @@ -0,0 +1,120 @@ +/** + * EGroupware egw_action framework - egw action framework + * + * @link https://www.egroupware.org + * @author Andreas Stöckel + * @copyright 2011 by Andreas Stöckel + * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License + * @package egw_action + */ + +import {EgwAction} from "./EgwAction"; +import {EgwFnct} from "./egw_action_common"; + +export class EgwPopupAction extends EgwAction { + default = false; + order = 0; + group = 0; + hint = false; + checkbox = false; + radioGroup = 0; + checked = false; + confirm_mass_selection = null; + shortcut = null; + singleClick = false; + private isChecked: EgwFnct; + + constructor(_id, _handler, _caption, _icon, _onExecute, _allowOnMultiple) { + super(_id, _handler, _caption, _icon, _onExecute, _allowOnMultiple) + //var action = new EgwAction(_id, _handler, _caption, _icon, _onExecute, _allowOnMultiple); + this.type = "popup"; + this.canHaveChildren = ["popup"]; + + } + + set_singleClick (_value) { + this.singleClick = _value; + }; + + set_default (_value) { + this.default = _value; + }; + + set_order (_value) { + this.order = _value; + }; + + set_group (_value) { + this.group = _value; + }; + + set_hint (_value) { + this.hint = _value; + }; + + // If true, the action will be rendered as checkbox + set_checkbox (_value) { + this.checkbox = _value; + }; + + set_checked (_value) { + this.checked = _value; + }; + + /** + * Set either a confirmation prompt, or TRUE to indicate that this action + * cares about large selections and to ask the confirmation prompt(s) + * + * @param {String|Boolean} _value + */ + set_confirm_mass_selection (_value) { + this.confirm_mass_selection = _value; + }; + + // Allow checkbox to be set from context using the given function + set_isChecked (_value) { + this.isChecked = new EgwFnct(this, null, []); + if (_value !== null) { + this.isChecked.setValue(_value); + } + }; + + // If radioGroup is >0 and the element is a checkbox, radioGroup specifies + // the group of radio buttons this one belongs to + set_radioGroup (_value) { + this.radioGroup = _value; + }; + + set_shortcut (_value) { + if (_value) { + const sc = { + "keyCode": -1, + "shift": false, + "ctrl": false, + "alt": false, + "caption":"" + }; + + if (typeof _value == "object" && typeof _value.keyCode != "undefined" && + typeof _value.caption != "undefined") { + sc.keyCode = _value.keyCode; + sc.caption = _value.caption; + sc.shift = (typeof _value.shift == "undefined") ? false : _value.shift; + sc.ctrl = (typeof _value.ctrl == "undefined") ? false : _value.ctrl; + sc.alt = (typeof _value.alt == "undefined") ? false : _value.alt; + } + + this.shortcut = sc; + } else { + this.shortcut = false; + } + }; + +} + +/** + * @deprecated + * use uppercase class + */ +export class egwPopupAction extends EgwPopupAction { +} \ No newline at end of file diff --git a/api/js/egw_action/EgwPopupActionImplementation.ts b/api/js/egw_action/EgwPopupActionImplementation.ts new file mode 100644 index 0000000000..e1fc77f263 --- /dev/null +++ b/api/js/egw_action/EgwPopupActionImplementation.ts @@ -0,0 +1,744 @@ +/** + * EGroupware egw_action framework - egw action framework + * + * @link https://www.egroupware.org + * @author Andreas Stöckel + * @copyright 2011 by Andreas Stöckel + * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License + * @package egw_action + */ + +import {_egw_active_menu, egwMenu, egwMenuItem} from "./egw_menu"; +import {EGW_KEY_ENTER, EGW_KEY_MENU} from "./egw_action_constants"; +import {tapAndSwipe} from "../tapandswipe"; +import {EgwAction} from "./EgwAction"; +import {EgwFnct} from "./egw_action_common"; +import "./egwGlobal" +import {EgwActionImplementation} from "./EgwActionImplementation"; +import {EgwActionObject} from "./EgwActionObject"; +import {EgwPopupAction} from "./EgwPopupAction"; + +export class EgwPopupActionImplementation implements EgwActionImplementation { + type = "popup"; + auto_paste = true; + + registerAction = (_aoi, _callback, _context) => { + const node = _aoi.getDOMNode(); + + if (node) { + this._registerDefault(node, _callback, _context); + this._registerContext(node, _callback, _context); + return true; + } + return false; + }; + + unregisterAction = function (_aoi) { + const node = _aoi.getDOMNode(); + //TODO jQuery replacement + jQuery(node).off(); + return true + }; + + /** + * Builds the context menu and shows it at the given position/DOM-Node. + * + * @param {object} _context + * @param {type} _selected + * @param {type} _links + * @param {type} _target + * @returns {Boolean} + */ + executeImplementation = (_context, _selected, _links, _target)=> { + if (typeof _target == "undefined") { + _target = null; + } + + this._context = _context; + if (typeof _context == "object" && typeof _context.keyEvent == "object") { + return this._handleKeyPress(_context.keyEvent, _selected, _links, _target); + } else if (_context != "default") { + //Check whether the context has the posx and posy parameters + if ((typeof _context.posx != "number" || typeof _context.posy != "number") && + typeof _context.id != "undefined") { + // Calculate context menu position from the given DOM-Node + let node = _context; + + const x = jQuery(node).offset().left; + const y = jQuery(node).offset().top; + + _context = {"posx": x, "posy": y}; + } + + const menu = this._buildMenu(_links, _selected, _target); + menu.showAt(_context.posx, _context.posy); + + return true; + } else { + const defaultAction = this._getDefaultLink(_links); + if (defaultAction) { + defaultAction.execute(_selected); + } + } + + return false; + }; + + /** + * Registers the handler for the default action + * + * @param {any} _node + * @param {function} _callback + * @param {object} _context + * @returns {boolean} + */ + private _registerDefault = (_node, _callback, _context)=> { + const defaultHandler = (e)=> { + // Prevent bubbling bound event on tag, on touch devices + // a tag should be handled by default event + if (window.egwIsMobile() && e.target.tagName == "A") return true; + + if (typeof document["selection"] != "undefined" && typeof document["selection"].empty != "undefined") { + document["selection"].empty(); + } else if (typeof window.getSelection != "undefined") { + const sel = window.getSelection(); + sel.removeAllRanges(); + } + + if (!(_context.manager.getActionsByAttr('singleClick', true).length > 0 && + e.originalEvent.target.classList.contains('et2_clickable'))) { + _callback.call(_context, "default", this); + } + + // Stop action from bubbling up to parents + e.stopPropagation(); + e.cancelBubble = true; + + // remove context menu if we are in mobile theme + // and intended to open the entry + if (_egw_active_menu && e.which == 1) _egw_active_menu.hide(); + return false; + }; + + if (window.egwIsMobile() || _context.manager.getActionsByAttr('singleClick', true).length > 0) { + _node.addEventListener('click',defaultHandler)//jQuery(_node).on('click', defaultHandler); + } else { + _node.ondblclick = defaultHandler; + } + }; + + private _getDefaultLink = function (_links) { + let defaultAction = null; + for (const k in _links) { + if (_links[k].actionObj["default"] && _links[k].enabled) { + defaultAction = _links[k].actionObj; + break; + } + } + + return defaultAction; + }; + + private _searchShortcut = (_key, _objs, _links) => { + for (const item of _objs) { + const shortcut = item.shortcut; + if (shortcut && shortcut.keyCode == _key.keyCode && shortcut.shift == _key.shift && + shortcut.ctrl == _key.ctrl && shortcut.alt == _key.alt && + item.type == "popup" && (typeof _links[item.id] == "undefined" || + _links[item.id].enabled)) { + return item; + } + + const obj = this._searchShortcut(_key, item.children, _links); + if (obj) { + return obj; + } + } + }; + + private _searchShortcutInLinks = (_key, _links)=> { + const objs = []; + for (const k in _links) { + if (_links[k].enabled) { + objs.push(_links[k].actionObj); + } + } + + return this._searchShortcut(_key, objs, _links); + }; + + /** + * Handles a key press + * + * @param {object} _key + * @param {type} _selected + * @param {type} _links + * @param {type} _target + * @returns {Boolean} + */ + private _handleKeyPress = (_key, _selected, _links, _target) => { + // Handle the default + if (_key.keyCode == EGW_KEY_ENTER && !_key.ctrl && !_key.shift && !_key.alt) { + const defaultAction = this._getDefaultLink(_links); + if (defaultAction) { + defaultAction.execute(_selected); + return true; + } + } + + // Menu button + if (_key.keyCode == EGW_KEY_MENU && !_key.ctrl) { + return this.executeImplementation({posx: 0, posy: 0}, _selected, _links, _target); + } + + + // Check whether the given shortcut exists + const obj = this._searchShortcutInLinks(_key, _links); + if (obj) { + obj.execute(_selected); + return true; + } + + return false; + }; + + private _handleTapHold = function (_node, _callback) { + //TODO (todo-jquery): ATM we need to convert the possible given jquery dom node object into DOM Element, this + // should be no longer necessary after removing jQuery nodes. + if (_node instanceof jQuery) { + _node = _node[0]; + } + + let tap = new tapAndSwipe(_node, { + // this threshold must be the same as the one set in et2_dataview_view_aoi + tapHoldThreshold: 1000, + allowScrolling: "both", + tapAndHold: function (event, fingercount) { + if (fingercount >= 2) return; + // don't trigger contextmenu if sorting is happening + if (document.querySelector('.sortable-drag')) return; + + _callback(event); + } + }); + // bind a custom event tapandhold to be able to call it from nm action button + _node.addEventListener('tapandhold', _event => { + _callback(_event) + }); + } + + /** + * Registers the handler for the context menu + * + * @param {any} _node + * @param {function} _callback + * @param {object} _context + * @returns {boolean} + */ + private _registerContext = (_node, _callback, _context) => { + const contextHandler = (e) => { + + //Obtain the event object, this should not happen at any point + if (!e) { + e = window.event; + } + + if (_egw_active_menu) { + _egw_active_menu.hide(); + } else if (!e.ctrlKey && e.which == 3 || e.which === 0 || e.type === 'tapandhold') // tap event indicates by 0 + { + const _xy = this._getPageXY(e); + const _implContext = {event: e, posx: _xy.posx, posy: _xy.posy}; + _callback.call(_context, _implContext, this); + } + + e.cancelBubble = !e.ctrlKey || e.which == 1; + if (e.stopPropagation && e.cancelBubble) { + e.stopPropagation(); + } + return !e.cancelBubble; + }; + // Safari still needs the taphold to trigger contextmenu + // Chrome has default event on touch and hold which acts like right click + this._handleTapHold(_node, contextHandler); + if (!window.egwIsMobile()) jQuery(_node).on('contextmenu', contextHandler); + }; + + /** + * Groups and sorts the given action tree layer + * + * @param {type} _layer + * @param {type} _links + * @param {type} _parentGroup + */ + private _groupLayers = (_layer, _links, _parentGroup) => { + // Separate the multiple groups out of the layer + const link_groups = {}; + + for (let i = 0; i < _layer.children.length; i++) { + const popupAction:EgwPopupAction = _layer.children[i].action; + + // Check whether the link group of the current element already exists, + // if not, create the group + const grp = popupAction.group; + if (typeof link_groups[grp] == "undefined") { + link_groups[grp] = []; + } + + // Search the link data for this action object if none is found, + // visible and enabled = true is assumed + let visible = true; + let enabled = true; + + if (typeof _links[popupAction.id] != "undefined") { + visible = _links[popupAction.id].visible; + enabled = _links[popupAction.id].enabled; + } + + // Insert the element in order + let inserted = false; + const groupObj = { + "actionObj": popupAction, + "visible": visible, + "enabled": enabled, + "groups": [] + }; + + for (let j = 0; j < link_groups[grp].length; j++) { + const elem:EgwPopupAction = link_groups[grp][j].actionObj; + if (elem.order > popupAction.order) { + inserted = true; + link_groups[grp].splice(j, 0, groupObj); + break; + } + } + + // If the object hasn't been inserted, add it to the end of the list + if (!inserted) { + link_groups[grp].push(groupObj); + } + + // If this child itself has children, group those elements too + if (_layer.children[i].children.length > 0) { + this._groupLayers(_layer.children[i], _links, groupObj); + } + } + + // Transform the link_groups object into a sorted array + const groups = []; + + for (const k in link_groups) { + groups.push({"grp": k, "links": link_groups[k]}); + } + + groups.sort(function (a, b) { + const ia = parseInt(a.grp); + const ib = parseInt(b.grp); + return (ia > ib) ? 1 : ((ia < ib) ? -1 : 0); + }); + + // Append the groups to the groups2 array + const groups2 = []; + for (const item of groups) { + groups2.push(item.links); + } + + _parentGroup.groups = groups2; + }; + + /** + * Build the menu layers + * + * @param {type} _menu + * @param {type} _groups + * @param {type} _selected + * @param {type} _enabled + * @param {type} _target + */ + private _buildMenuLayer = (_menu, _groups, _selected, _enabled, _target) => { + let firstGroup = true; + + for (const item1 of _groups) { + let firstElem = true; + + // Go through the elements of each group + for (const link of item1) { + if (link.visible) { + // Add a separator after each group + if (!firstGroup && firstElem) { + _menu.addItem("", "-"); + } + firstElem = false; + + const item:egwMenuItem = _menu.addItem(link.actionObj.id, link.actionObj.caption, + link.actionObj.iconUrl); + item.default= link.actionObj["default"]; + + // As this code is also used when a drag-drop popup menu is built, + // we have to perform this check + if (link.actionObj.type == "popup") { + item.set_hint(link.actionObj.hint); + item.set_checkbox(link.actionObj.checkbox); + item.set_checked(link.actionObj.checked); + if (link.actionObj.checkbox && link.actionObj.isChecked) { + item.set_checked(link.actionObj.isChecked.exec(link.actionObj, _selected)); + } + item.set_groupIndex(link.actionObj.radioGroup); + + if (link.actionObj.shortcut && !window.egwIsMobile()) { + const shortcut = link.actionObj.shortcut; + item.set_shortcutCaption(shortcut.caption); + } + } + + item.set_data(link.actionObj); + if (link.enabled && _enabled) { + item.set_onClick( (elem) => { + // Pass the context + elem.data.menu_context = this._context; + + // Copy the "checked" state + if (typeof elem.data.checked != "undefined") { + elem.data.checked = elem.checked; + } + + elem.data.execute(_selected, _target); + + if (typeof elem.data.checkbox != "undefined" && elem.data.checkbox) { + return elem.data.checked; + } + }); + } else { + item.set_enabled(false); + } + + // Append the parent groups + if (link.groups) { + this._buildMenuLayer(item, link.groups, _selected, link.enabled, _target); + } + } + } + + firstGroup = firstGroup && firstElem; + } + }; + + /** + * Builds the context menu from the given action links + * + * @param {type} _links + * @param {type} _selected + * @param {type} _target + * @returns {egwMenu|EgwActionImplementation._buildMenu.menu} + */ + private _buildMenu = (_links, _selected, _target) => { + // Build a tree containing all actions + const tree = {"root": []}; + + // Automatically add in Drag & Drop actions + if (this.auto_paste && !window.egwIsMobile()&& !this._context.event.type.match(/touch/)) { + this._addCopyPaste(_links, _selected); + } + + for (const k in _links) { + _links[k].actionObj.appendToTree(tree); + } + + // We need the dummy object container in order to pass the array by + // reference + const groups = { + "groups": [] + }; + + if (tree.root.length > 0) { + // Sort every action object layer by the given sort position and grouping + this._groupLayers(tree.root[0], _links, groups); + } + + const menu = new egwMenu(); + + // Build the menu layers + this._buildMenuLayer(menu, groups.groups, _selected, true, _target); + + return menu; + }; + + _getPageXY = function getPageXY(event) { + // document.body.scrollTop does not work in IE + const scrollTop = document.body.scrollTop ? document.body.scrollTop : + document.documentElement.scrollTop; + const scrollLeft = document.body.scrollLeft ? document.body.scrollLeft : + document.documentElement.scrollLeft; + + return {'posx': (event.clientX + scrollLeft), 'posy': (event.clientY + scrollTop)}; + }; + + /** + * Automagically add in context menu items for copy and paste from + * drag and drop actions, based on current clipboard and the accepted types + * + * @param {object[]} _links Actions for inclusion in the menu + * @param {EgwActionObject[]} _selected Currently selected entries + */ + private _addCopyPaste = (_links, _selected:EgwActionObject[])=> { + // Get a list of drag & drop actions + const drag = _selected[0].getSelectedLinks('drag').links; + const drop = _selected[0].getSelectedLinks('drop').links; + + // No drags & no drops means early exit (only by default added egw_cancel_drop does NOT count!) + if ((!drag || jQuery.isEmptyObject(drag)) && + (!drop || jQuery.isEmptyObject(drop) || + Object.keys(drop).length === 1 && typeof drop.egw_cancel_drop !== 'undefined')) { + return; + } + + // Find existing actions so we don't get copies + const mgr = _selected[0].manager; + let copy_action = mgr.getActionById('egw_copy'); + let add_action = mgr.getActionById('egw_copy_add'); + let clipboard_action = mgr.getActionById('egw_os_clipboard'); + let paste_action = mgr.getActionById('egw_paste'); + + // Fake UI so we can simulate the position of the drop + const ui = { + position: {top: 0, left: 0}, + offset: {top: 0, left: 0} + }; + if (this._context.event) { + const event = this._context.event.originalEvent; + ui.position = {top: event.pageY, left: event.pageX}; + ui.offset = {top: event.offsetY, left: event.offsetX}; + } + // Create default copy menu action + if (drag && !jQuery.isEmptyObject(drag)) { + // Don't re-add if it's there + if (copy_action == null) { + // Create a drag action that allows linking + copy_action = mgr.addAction('popup', 'egw_copy', window.egw.lang('Copy to clipboard'), window.egw.image('copy'), function (action, selected) { + // Copied, now add to clipboard + const clipboard = { + type: [], + selected: [] + }; + + // When pasting we need to know the type of drag + for (const k in drag) { + if (drag[k].enabled && drag[k].actionObj.dragType.length > 0) { + clipboard.type = clipboard.type.concat(drag[k].actionObj.dragType); + } + } + clipboard.type = jQuery.uniqueSort(clipboard.type); + // egwAction is a circular structure and can't be stringified so just take what we want + // Hopefully that's enough for the action handlers + for (const k in selected) { + if (selected[k].id) clipboard.selected.push({id: selected[k].id, data: selected[k].data}); + } + + // Save it in session + window.egw.setSessionItem('phpgwapi', 'egw_clipboard', JSON.stringify(clipboard)); + }, true); + copy_action.group = 2.5; + } + if (add_action == null) { + // Create an action to add selected to clipboard + add_action = mgr.addAction('popup', 'egw_copy_add', window.egw.lang('Add to clipboard'), window.egw.image('copy'), function (action, selected) { + // Copied, now add to clipboard + const clipboard = JSON.parse(window.egw.getSessionItem('phpgwapi', 'egw_clipboard')) || { + type: [], + selected: [] + }; + + // When pasting we need to know the type of drag + for (const k in drag) { + if (drag[k].enabled && drag[k].actionObj.dragType.length > 0) { + clipboard.type = clipboard.type.concat(drag[k].actionObj.dragType); + } + } + clipboard.type = [...new Set(clipboard.type)].sort(); + // egwAction is a circular structure and can't be stringified so just take what we want + // Hopefully that's enough for the action handlers + for (const k in selected) { + if (selected[k].id) clipboard.selected.push({id: selected[k].id, data: selected[k].data}); + } + + // Save it in session + window.egw.setSessionItem('phpgwapi', 'egw_clipboard', JSON.stringify(clipboard)); + }, true); + add_action.group = 2.5; + + } + if (clipboard_action == null) { + // Create an action to add selected to clipboard + clipboard_action = mgr.addAction('popup', 'egw_os_clipboard', window.egw.lang('Copy to OS clipboard'), window.egw.image('copy'), function (action) { + + if (document.queryCommandSupported('copy')) { + jQuery(action.data.target).trigger('copy'); + } + }, true); + clipboard_action.group = 2.5; + } + let os_clipboard_caption = ""; + if (this._context.event) { + os_clipboard_caption = this._context.event.originalEvent.target.innerText.trim(); + clipboard_action.set_caption(window.egw.lang('Copy "%1"', os_clipboard_caption.length > 20 ? os_clipboard_caption.substring(0, 20) + '...' : os_clipboard_caption)); + clipboard_action.data.target = this._context.event.originalEvent.target; + } + jQuery(clipboard_action.data.target).off('copy').on('copy', function (event) { + try { + window.egw.copyTextToClipboard(os_clipboard_caption, clipboard_action.data.target, event).then((successful) => { + // Fallback + if (typeof successful == "undefined") { + // Clear message + window.egw.message(window.egw.lang("'%1' copied to clipboard", os_clipboard_caption.length > 20 ? os_clipboard_caption.substring(0, 20) + '...' : os_clipboard_caption)); + window.getSelection().removeAllRanges(); + return false; + } else { + // Show fail message + window.egw.message(window.egw.lang('Use Ctrl-C/Cmd-C to copy')); + } + }); + + } catch (err) { + } + }); + if (typeof _links[copy_action.id] == 'undefined') { + _links[copy_action.id] = { + "actionObj": copy_action, + "enabled": true, + "visible": true, + "cnt": 0 + }; + } + if (typeof _links[add_action.id] == 'undefined') { + _links[add_action.id] = { + "actionObj": add_action, + "enabled": true, + "visible": true, + "cnt": 0 + }; + } + if (typeof _links[clipboard_action.id] == 'undefined') { + _links[clipboard_action.id] = { + "actionObj": clipboard_action, + "enabled": os_clipboard_caption.length > 0, + "visible": os_clipboard_caption.length > 0, + "cnt": 0 + }; + } + } + + // Create default paste menu item + if (drop && !jQuery.isEmptyObject(drop)) { + // Create paste action + // This injects the clipboard data and calls the original handler + let paste_exec = function (action, selected) { + // Add in clipboard as a sender + let clipboard = JSON.parse(window.egw.getSessionItem('phpgwapi', 'egw_clipboard')); + // Fake drop position + drop[action.id].actionObj.ui = ui; + // Set a flag so apps can tell the difference, if they need to + drop[action.id].actionObj.paste = true; + + drop[action.id].actionObj.execute(clipboard.selected, selected[0]); + + drop[action.id].actionObj.paste = false; + }; + + let clipboard = JSON.parse(window.egw.getSessionItem('phpgwapi', 'egw_clipboard')) || { + type: [], + selected: [] + }; + + // Don't re-add if action already exists + if (paste_action == null) { + paste_action = mgr.addAction('popup', 'egw_paste', window.egw.lang('Paste'), window.egw.image('editpaste'), paste_exec, true); + paste_action.group = 2.5; + paste_action.order = 9; + if (typeof paste_action.canHaveChildren !== "boolean") { + paste_action.canHaveChildren.push('drop'); + } + } + + // Set hint to something resembling current clipboard + let hint = window.egw.lang('Clipboard') + ":\n"; + paste_action.set_hint(hint); + // Add titles of entries + for (let i = 0; i < clipboard.selected.length; i++) { + let id = clipboard.selected[i].id.split('::'); + window.egw.link_title(id[0], id[1], function (title) { + if (title) this.hint += title + "\n"; + }, paste_action); + } + + // Add into links, so it's included in menu + // @ts-ignore exec uses arguments:IArguments and therefor can consume them even if ts does not know it + if (paste_action && paste_action.enabled.exec(paste_action, clipboard.selected, _selected[0])) { + if (typeof _links[paste_action.id] == 'undefined') { + _links[paste_action.id] = { + "actionObj": paste_action, + "enabled": false, + "visible": clipboard != null, + "cnt": 0 + }; + } + while (paste_action.children.length > 0) { + paste_action.children[0].remove(); + } + + // If nothing [valid] in the clipboard, don't bother with children + if (clipboard == null || typeof clipboard.type != 'object') { + return; + } + + // Add in actual actions as children + for (let k in drop) { + // Add some choices - need to be a copy, or they interfere with + // the original + //replace jQuery with spread operator + // set the Prototype of the copy set_onExecute is not available otherwise + //TODO is this a valid/elegant way to do this??? give egwAction a methode clone -- make abstract parent class + let drop_clone = {...drop[k].actionObj}; + //warning This method is really slow + Object.setPrototypeOf(drop_clone, EgwAction.prototype) + let parent = paste_action.parent === drop_clone.parent ? paste_action : (paste_action.getActionById(drop_clone.parent.id) || paste_action); + drop_clone.parent = parent; + drop_clone.onExecute = new EgwFnct(this, null, []); + drop_clone.children = []; + drop_clone.set_onExecute(paste_exec); + parent.children.push(drop_clone); + parent.allowOnMultiple = paste_action.allowOnMultiple && drop_clone.allowOnMultiple; + _links[k] = jQuery.extend({}, drop[k]); + _links[k].actionObj = drop_clone; + + // Drop is allowed if clipboard types intersect drop types + _links[k].enabled = false; + _links[k].visible = false; + for (let i = 0; i < drop_clone.acceptedTypes.length; i++) { + if (clipboard.type.indexOf(drop_clone.acceptedTypes[i]) != -1) { + _links[paste_action.id].enabled = true; + _links[k].enabled = true; + _links[k].visible = true; + break; + } + } + } + } + } + }; + private _context: any; + +} + + +/** + * @deprecated use uppercase class + */ +export class egwPopupActionImplementation extends EgwPopupActionImplementation{} +let _popupActionImpl = null; + +export function getPopupImplementation(): EgwPopupActionImplementation { + if (!_popupActionImpl) { + _popupActionImpl = new EgwPopupActionImplementation(); + } + return _popupActionImpl; +} diff --git a/api/js/egw_action/egwDragActionImplementation.ts b/api/js/egw_action/egwDragActionImplementation.ts new file mode 100644 index 0000000000..025abfdf00 --- /dev/null +++ b/api/js/egw_action/egwDragActionImplementation.ts @@ -0,0 +1,291 @@ +/** + * EGroupware egw_action framework - egw action framework + * + * @link https://www.egroupware.org + * @author Andreas Stöckel + * @copyright 2011 by Andreas Stöckel + * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License + * @package egw_action + */ + +import {EgwActionImplementation} from "./EgwActionImplementation"; +import {egw} from "../jsapi/egw_global"; +import {EgwActionObjectInterface} from "./EgwActionObjectInterface"; + +export class EgwDragActionImplementation implements EgwActionImplementation { + type = "drag"; + helper: HTMLDivElement = null; + ddTypes: any[] = []; + selected: any[] = []; + defaultDDHelper: (_selected) => HTMLDivElement = (_selected) => { + // Table containing clone of rows + const table: HTMLTableElement = (document.createElement("table")); + table.classList.add('egwGridView_grid', 'et2_egw_action_ddHelper_row'); + // tr element to use as last row to show 'more ...' label + const moreRow: HTMLTableRowElement = (document.createElement('tr')) + moreRow.classList.add('et2_egw_action_ddHelper_moreRow'); + // Main div helper container + const div: HTMLDivElement = (document.createElement("div")); + div.append(table); + + let rows = []; + // Maximum number of rows to show + let maxRows = 3; + // item label + const itemLabel = egw.lang( + ( + egw.link_get_registry(egw.app_name(), _selected.length > 1 ? 'entries' : 'entry') || egw.app_name() + ) as string + ); + let index = 0; + + // Take select all into account when counting number of rows, because they may not be + // in _selected object + const pseudoNumRows = (_selected[0]?._context?._selectionMgr?._selectAll) ? + _selected[0]._context?._selectionMgr?._total : _selected.length; + + for (const egwActionObject of _selected) { + const row: Node = (egwActionObject.iface.getDOMNode()).cloneNode(true); + if (row) { + rows.push(row); + table.append(row); + } + index++; + if (index == maxRows) { + // Label to show number of items + const spanCnt = (document.createElement('span')) + spanCnt.classList.add('et2_egw_action_ddHelper_itemsCnt') + div.append(spanCnt); + + spanCnt.textContent = (pseudoNumRows + ' ' + itemLabel); + // Number of not shown rows + const restRows = pseudoNumRows - maxRows; + if (restRows > 0) { + moreRow.textContent = egw.lang(`${pseudoNumRows - maxRows} more ${itemLabel} selected ...`); + } + table.append(moreRow); + break; + } + } + + const text = (document.createElement('div')) + text.classList.add('et2_egw_action_ddHelper_tip'); + div.append(text); + + // Add notice of Ctrl key, if supported + if ('draggable' in document.createElement('span') && + navigator && navigator.userAgent.indexOf('Chrome') >= 0 && egw.app_name() == 'filemanager') // currently only filemanager supports drag out + { + if (rows.length == 1) + { + text.textContent=(egw.lang('You may drag file out to your desktop', itemLabel)); + } + else + { + text.textContent=(egw.lang('Note: If you drag out these selected rows to desktop only the first selected row will be downloaded.', itemLabel)); + } + } +// Final html DOM return as helper structure + return div; + }; + + registerAction: (_actionObjectInterface: EgwActionObjectInterface, _triggerCallback: Function, _context: any) => boolean = (_aoi, _callback, _context) => { + const node = _aoi.getDOMNode() && _aoi.getDOMNode()[0] ? _aoi.getDOMNode()[0] : _aoi.getDOMNode(); + + if (node) { + // Prevent selection + node.onselectstart = function () { + return false; + }; + if (!(window.FileReader && 'draggable' in document.createElement('span'))) { + // No DnD support + return; + } + + // It shouldn't be so hard to get the action... + let action = null; + const groups = _context.getActionImplementationGroups(); + if (!groups.drag) { + return; + } + + // Bind mouse handlers + //TODO can i just remove jquery.off?? + //jQuery(node).off("mousedown") + node.addEventListener("mousedown", (event) => { + if (_context.isSelection(event)) { + node.setAttribute("draggable", false); + } else if (event.which != 3) { + document.getSelection().removeAllRanges(); + } + }) + node.addEventListener("mouseup", (event) => { + if (_context.isSelection(event) && document.getSelection().type === 'Range') { + //let the draggable be reactivated by another click up as the range selection is + // not working as expected in shadow-dom as expected in all browsers + } else { + node.setAttribute("draggable", true); + } + + // Set cursor back to auto. Seems FF can't handle cursor reversion + document.body.style.cursor = 'auto' + }) + + + node.setAttribute('draggable', true); + const ai = this + const dragstart = function (event) { + + // The helper function is called before the start function + // is evoked. Call the given callback function. The callback + // function will gather the selected elements and action links + // and call the doExecuteImplementation function. This + // will call the onExecute function of the first action + // in order to obtain the helper object (stored in ai.helper) + // and the multiple dragDropTypes (ai.ddTypes) + _callback.call(_context, false, ai); + + if (action && egw.app_name() == 'filemanager') { + if (_context.isSelection(event)) return; + + // Get all selected + const selected = ai.selected; + + // Set file data + for (let i = 0; i < 1; i++) { + let d = selected[i].data || egw.dataGetUIDdata(selected[i].id).data || {}; + if (d && d.mime && d.download_url) { + let url = d.download_url; + + // NEED an absolute URL + if (url[0] == '/') url = egw.link(url); + // egw.link adds the webserver, but that might not be an absolute URL - try again + if (url[0] == '/') url = window.location.origin + url; + event.dataTransfer.setData("DownloadURL", d.mime + ':' + d.name + ':' + url); + } + } + event.dataTransfer.effectAllowed = 'copy'; + + if (event.dataTransfer.types.length == 0) { + // No file data? Abort: drag does nothing + event.preventDefault(); + return; + } + } else { + event.dataTransfer.effectAllowed = 'linkMove'; + } + + + const data = { + ddTypes: ai.ddTypes, + selected: ai.selected.map((item) => { + return {id: item.id} + }) + }; + + if (!ai.helper) { + ai.helper = ai.defaultDDHelper(ai.selected); + } + // Add a basic class to the helper in order to standardize the background layout + ai.helper.classList.add('et2_egw_action_ddHelper', 'ui-draggable-dragging'); + document.body.append(ai.helper); + this.classList.add('drag--moving'); + + event.dataTransfer.setData('application/json', JSON.stringify(data)) + + event.dataTransfer.setDragImage(ai.helper, 12, 12); + + this.setAttribute('data-egwActionObjID', JSON.stringify(data.selected)); + }; + + const dragend = (_) => { + const helper = document.querySelector('.et2_egw_action_ddHelper'); + if (helper) helper.remove(); + const draggable = document.querySelector('.drag--moving'); + if (draggable) draggable.classList.remove('drag--moving'); + // cleanup drop hover class from all other DOMs if there's still anything left + Array.from(document.getElementsByClassName('et2dropzone drop-hover')).forEach(_i=>{_i.classList.remove('drop-hover')}) + }; + + // Drag Event listeners + node.addEventListener('dragstart', dragstart, false); + node.addEventListener('dragend', dragend, false); + + + return true; + } + return false; + }; + + unregisterAction: (_actionObjectInterface: EgwActionObjectInterface) => boolean =(_aoi) => { + const node = _aoi.getDOMNode(); + + if (node) { + node.setAttribute('draggable', "false"); + } + return true; + }; + + /** + * Builds the context menu and shows it at the given position/DOM-Node. + * + * @param {string} _context + * @param {array} _selected + * @param {object} _links + */ + executeImplementation: (_context: any, _selected: any, _links: any) => any = (_context, _selected, _links) => { + // Reset the helper object of the action implementation + this.helper = null; + let hasLink = false; + + // Store the drag-drop types + this.ddTypes = []; + this.selected = _selected; + + // Call the onExecute event of the first actionObject + for (const k in _links) { + if (_links[k].visible) { + hasLink = true; + + // Only execute the following code if a JS function is registered + // for the action and this is the first action link + if (!this.helper && _links[k].actionObj.onExecute.hasHandler()) { + this.helper = _links[k].actionObj.execute(_selected); + } + + // Push the dragType of the associated action object onto the + // drag type list - this allows an element to support multiple + // drag/drop types. + const type: string[] = Array.isArray(_links[k].actionObj.dragType) + ? _links[k].actionObj.dragType + : [_links[k].actionObj.dragType]; + for (const i of type) { + if (this.ddTypes.indexOf(i) === -1) { + this.ddTypes.push(i); + } + } + } + } + // If no helper has been defined, create a default one + if (!this.helper && hasLink) { + this.helper = this.defaultDDHelper(_selected); + } + + return true; + }; +} + +/** + * @deprecated use upper case class + */ +export class egwDragActionImplementation extends EgwDragActionImplementation { +} + +let _dragActionImpl = null + +export function getDragImplementation():EgwDragActionImplementation { + if (!_dragActionImpl) { + _dragActionImpl = new EgwDragActionImplementation(); + } + return _dragActionImpl +} diff --git a/api/js/egw_action/egwGlobal.ts b/api/js/egw_action/egwGlobal.ts new file mode 100644 index 0000000000..72c13c221f --- /dev/null +++ b/api/js/egw_action/egwGlobal.ts @@ -0,0 +1,41 @@ +/** + * EGroupware egw_action framework - egw action framework + * + * @link https://www.egroupware.org + * @author Andreas Stöckel + * @copyright 2011 by Andreas Stöckel + * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License + * @package egw_action + */ + +import type {Iegw} from "../jsapi/egw_global"; +import type {nm_action} from "../etemplate/et2_extension_nextmatch_actions"; + +/** + * holds the constructor and implementation of an EgwActionClass + */ +export type EgwActionClassData = { + //type EgwAction["constructor"], + actionConstructor: Function, + implementation: any +} + +/** + * holds all possible Types of a egwActionClass + */ +type EgwActionClasses = { + default: EgwActionClassData,// + actionManager: EgwActionClassData, drag: EgwActionClassData, drop: EgwActionClassData, popup: EgwActionClassData +} +//TODO egw global.js +declare global { + interface Window { + _egwActionClasses: EgwActionClasses; + egw: Iegw //egw returns instance of client side api -- set in egw_core.js + egwIsMobile: () => boolean // set in egw_action_commons.ts + nm_action: typeof nm_action + egw_getAppName: () => string + Et2Dialog: any + app: any + } +} \ No newline at end of file diff --git a/api/js/egw_action/egw_action.d.ts b/api/js/egw_action/egw_action.d.ts deleted file mode 100644 index c7af7f2457..0000000000 --- a/api/js/egw_action/egw_action.d.ts +++ /dev/null @@ -1,884 +0,0 @@ -/** - * EGroupware egw_action framework - TS declarations - * - * Generated with: - * mkdir /tmp/egw_action - * cd api/js/egw_action - * tsc --declaration --allowJS --outDir /tmp/egw_action *.js - * cat /tmp/egw_action/*.d.ts > egw_action.d.ts - * - * @link http://www.egroupware.org - * @author Andreas Stöckel - * @copyright 2011 by Andreas Stöckel - * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License - * @package egw_action - */ -/** - * Returns the action manager for the given application - each application has its - * own sub-ActionManager in the global action manager object to prevent collisions - * from happening - * - * @param _id is the name of the sub-actionManager which should be returned. - * If the action manager does not exist right now, it is created. If the - * parameter is ommited or null, the global action manager is returned. - * @param {boolean} [_create=true] If an objectManager with the given id is not - * found, it will be created at the top level. - * @param {number} [_search_depth=Infinite] How deep into existing action children - * to search. - */ -declare function egw_getActionManager(_id: any, _create?: boolean, _search_depth?: number): any; -/** - * Returns the object manager for the given application - each application may - * have its own object manager where it can place action objects or containers. - * - * @param _id is the name of the sub-object manager should be returned. If the - * object manager does not exists right now, it is created. If the parameter - * is ommited or null, the global object manager is returned. - * @param {boolean} [_create=true] If an objectManager with the given id is not - * found, it will be created at the top level. - * @param {number} [_search_depth=Infinite] How deep into existing action children - * to search. - */ -declare function egw_getObjectManager(_id: any, _create?: boolean, _search_depth?: number): any; -/** - * Returns the object manager for the current application - * - * @param {boolean} _create - * @param {string} _appName - * @return {egwActionObjectManager} - */ -declare function egw_getAppObjectManager(_create?: boolean, _appName?: string): typeof egwActionObjectManager; -/** - * Returns the action manager for the current application - * - * @param {boolean} _create - * @return {egwActionManager} - */ -declare function egw_getAppActionManager(_create: boolean): typeof egwActionManager; -/** egwActionHandler Interface **/ -/** - * Constructor for the egwActionHandler interface which (at least) should have the - * execute function implemented. - * - * @param {function} _executeEvent - * @return {egwActionHandler} - */ -declare function egwActionHandler(_executeEvent: Function): egwActionHandler; -declare class egwActionHandler { - /** egwActionHandler Interface **/ - /** - * Constructor for the egwActionHandler interface which (at least) should have the - * execute function implemented. - * - * @param {function} _executeEvent - * @return {egwActionHandler} - */ - constructor(_executeEvent: Function); - execute: Function; -} -/** - * Constructor for egwAction object - * - * @param {egwAction} _parent - * @param {string} _id - * @param {string} _caption - * @param {string} _iconUrl - * @param {(string|function)} _onExecute - * @param {boolean} _allowOnMultiple - * @returns {egwAction} - */ -declare function egwAction(_parent: egwAction, _id: string, _caption: string, _iconUrl: string, _onExecute: TimerHandler, _allowOnMultiple: boolean): egwAction; -declare class egwAction { - /** - * Constructor for egwAction object - * - * @param {egwAction} _parent - * @param {string} _id - * @param {string} _caption - * @param {string} _iconUrl - * @param {(string|function)} _onExecute - * @param {boolean} _allowOnMultiple - * @returns {egwAction} - */ - constructor(_parent: egwAction, _id: string, _caption: string, _iconUrl: string, _onExecute: TimerHandler, _allowOnMultiple: boolean); - id: string; - caption: string; - iconUrl: string; - allowOnMultiple: boolean; - enabled: any; - hideOnDisabled: boolean; - data : { [key : string] : any }; - type: string; - canHaveChildren: boolean; - parent: egwAction; - children: any[]; - onExecute: egwFnct; - hideOnMobile: boolean; - disableIfNoEPL: boolean; - remove(): void; - getActionById(_id: string | number, _search_depth?: number): egwAction; - getActionsByAttr(_attr: string, _val: any): egwAction[]; - addAction(_type: string, _id: string, _caption: string, _iconUrl: any, _onExecute: TimerHandler, _allowOnMultiple: boolean): any; - /** - * Default icons for given id - */ - defaultIcons: { - view: string; - edit: string; - open: string; - add: string; - "new": string; - "delete": string; - cat: string; - document: string; - print: string; - copy: string; - move: string; - cut: string; - paste: string; - save: string; - apply: string; - cancel: string; - 'continue': string; - next: string; - finish: string; - back: string; - previous: string; - close: string; - }; - updateActions(_actions: any, _app?: string): void; - not_disableClass(_action: any, _senders: any, _target: any): boolean; - enableClass(_action: any, _senders: any, _target: any): boolean; - enableId(_action: any, _senders: any, _target: any): any; - setDefaultExecute(_value: TimerHandler): void; - execute(_senders: any[], _target: any): void; - _check_confirm_mass_selections(_senders: any, _target: any): boolean; - _check_confirm(_senders: any, _target: any): any; - set_onExecute(_value: string | boolean | Function): void; - set_caption(_value: any): void; - set_iconUrl(_value: any): void; - set_enabled(_value: any): void; - set_allowOnMultiple(_value: string | number | boolean): void; - set_hideOnDisabled(_value: any): void; - set_hideOnMobile(_value: any): void; - set_disableIfNoEPL(_value: any): void; - set_data(_value: any): void; - updateAction(_data: any): void; - appendToTree(_tree: any[], _addChildren: boolean): { - "action": egwAction; - "children": any[]; - }; - getManager(): any; -} -declare function _egwActionTreeContains(_tree: any, _elem: any): any; -/** egwActionManager Object **/ -/** - * egwActionManager manages a list of actions - it overwrites the egwAction class - * and allows child actions to be added to it. - * - * @param {egwAction} _parent - * @param {string} _id - * @return {egwActionManager} - */ -declare function egwActionManager(_parent: egwAction, _id: string): typeof egwActionManager; -/** egwActionImplementation Interface **/ -/** - * Abstract interface for the egwActionImplementation object. The egwActionImplementation - * object is responsible for inserting the actual action representation (context menu, - * drag-drop code) into the DOM Tree by using the egwActionObjectInterface object - * supplied by the object. - * To write a "class" which derives from this object, simply write a own constructor, - * which replaces "this" with a "new egwActionImplementation" and implement your - * code in "doRegisterAction" und "doUnregisterAction". - * Register your own implementation within the _egwActionClasses object. - * - * @return {egwActionImplementation} - */ -declare function egwActionImplementation(): egwActionImplementation; -declare class egwActionImplementation { - doRegisterAction: () => never; - doUnregisterAction: () => never; - doExecuteImplementation: () => never; - type: string; - registerAction(_actionObjectInterface: any, _triggerCallback: Function, _context: any): any; - unregisterAction(_actionObjectInterface: egwActionObjectInterface): any; - executeImplementation(_context: any, _selected: any, _links: any): any; -} -/** egwActionLink Object **/ -/** - * The egwActionLink is used to interconnect egwActionObjects and egwActions. - * This gives each action object the possibility to decide, whether the action - * should be active in this context or not. - * - * @param _manager is a reference to the egwActionManager whic contains the action - * the object wants to link to. - */ -declare function egwActionLink(_manager: any): void; -declare class egwActionLink { - /** egwActionLink Object **/ - /** - * The egwActionLink is used to interconnect egwActionObjects and egwActions. - * This gives each action object the possibility to decide, whether the action - * should be active in this context or not. - * - * @param _manager is a reference to the egwActionManager whic contains the action - * the object wants to link to. - */ - constructor(_manager: any); - enabled: boolean; - visible: boolean; - actionId: string; - actionObj: any; - manager: any; - updateLink(_data: any): void; - set_enabled(_value: any): void; - set_visible(_value: any): void; - set_actionId(_value: any): void; -} -/** - * The egwActionObject represents an abstract object to which actions may be - * applied. Communication with the DOM tree is established by using the - * egwActionObjectInterface (AOI), which is passed in the constructor. - * egwActionObjects are organized in a tree structure. - * - * @param {string} _id is the identifier of the object which - * @param {egwActionObject} _parent is the parent object in the hirachy. This may be set to NULL - * @param {egwActionObjectInterface} _iface is the egwActionObjectInterface which connects the object - * to the outer world. - * @param {egwActionManager} _manager is the action manager this object is connected to - * this object to the DOM tree. If the _manager isn't supplied, the parent manager - * is taken. - * @param {number} _flags a set of additional flags being applied to the object, - * defaults to 0 - */ -declare function egwActionObject(_id: string, _parent: egwActionObject, _iface?: egwActionObjectInterface, _manager?: typeof egwActionManager, _flags?: number): void; -declare class egwActionObject { - /** - * The egwActionObject represents an abstract object to which actions may be - * applied. Communication with the DOM tree is established by using the - * egwActionObjectInterface (AOI), which is passed in the constructor. - * egwActionObjects are organized in a tree structure. - * - * @param {string} _id is the identifier of the object which - * @param {egwActionObject} _parent is the parent object in the hirachy. This may be set to NULL - * @param {egwActionObjectInterface} _iface is the egwActionObjectInterface which connects the object - * to the outer world. - * @param {egwActionManager} _manager is the action manager this object is connected to - * this object to the DOM tree. If the _manager isn't supplied, the parent manager - * is taken. - * @param {number} _flags a set of additional flags being applied to the object, - * defaults to 0 - */ - constructor(_id: string, _parent: egwActionObject, _iface?: egwActionObjectInterface, _manager?: typeof egwActionManager, _flags?: number); - id: string; - parent: egwActionObject; - children: any[]; - actionLinks: any[]; - manager: typeof egwActionManager; - flags: number; - data: any; - setSelectedCallback: any; - registeredImpls: any[]; - selectedChildren: any[]; - focusedChild: string | egwActionObject; - setAOI(_aoi: egwActionObjectInterface): void; - iface: egwActionObjectInterface; - getObjectById(_id: string, _search_depth?: number): egwActionObject; - addObject(_id: any, _interface: any, _flags?: number): any; - insertObject(_index: number | boolean, _id: any, _iface?: any, _flags?: number): any; - clear(): void; - remove(): void; - getRootObject(): any; - getParentList(): any; - getContainerRoot(): any; - getSelectedObjects(_test: Function, _list: any[]): any; - getAllSelected(): boolean; - toggleAllSelected(_select: any): any; - flatList(_visibleOnly: boolean, _obj: any): any[]; - traversePath(_to: any): any[]; - getIndex(): number; - getFocusedObject(): string | egwActionObject; - _ifaceCallback(_newState: number, _changedBit: number, _shiftState: number): number; - handleKeyPress(_keyCode: number, _shift: boolean, _ctrl: boolean, _alt: boolean): boolean; - getPrevious(_intval: any): any; - getNext(_intval: any): any; - getSelected(): boolean; - getFocused(): boolean; - getVisible(): boolean; - getState(): number; - setFocused(_focused: boolean): void; - setSelected(_selected: boolean): void; - setAllSelected(_selected: boolean, _informParent: boolean): void; - updateSelectedChildren(_child: string | egwActionObject, _selected: boolean): void; - updateFocusedChild(_child: string | egwActionObject, _focused: boolean): void; - updateActionLinks(_actionLinks: any[], _recursive?: boolean, _doCreate?: boolean): void; - _reconnectCallback(): void; - registerActions(): void; - unregisterActions(): void; - triggerCallback(): any; - makeVisible(): void; - executeActionImplementation(_implContext: any, _implType: string, _execType: number): any; - forceSelection(): void; - getSelectedLinks(_actionType: any): any; - _getLinks(_objs: any[], _actionType: string): any; - getActionLink(_actionId: string): any; - getActionImplementationGroups(_test: Function, _groups: any): any; - isDragOut(_event: Event): boolean; - isSelection(_event: any): boolean; -} -/** egwActionObjectInterface Interface **/ -/** - * The egwActionObjectInterface has to be implemented for each actual object in - * the browser. E.g. for the object "DataGridRow", there has to be an - * egwActionObjectInterface which is responsible for returning the outer DOMNode - * of the object to which JS-Events may be attached by the egwActionImplementation - * object, and to do object specific stuff like highlighting the object in the - * correct way and to route state changes (like: "object has been selected") - * to the egwActionObject object the interface is associated to. - * - * @return {egwActionObjectInterface} - */ -declare function egwActionObjectInterface(): egwActionObjectInterface; -declare class egwActionObjectInterface { - doGetDOMNode: () => any; - doSetState: (_state: any, _outerCall: any) => void; - doTriggerEvent: (_event: any, _data: any) => boolean; - doMakeVisible: () => void; - _state: number; - stateChangeCallback: Function; - stateChangeContext: any; - reconnectActionsCallback: Function; - reconnectActionsContext: any; - setStateChangeCallback(_callback: Function, _context: any): void; - setReconnectActionsCallback(_callback: Function, _context: any): void; - reconnectActions(): void; - updateState(_stateBit: number, _set: boolean, _shiftState: boolean): void; - getDOMNode(): any; - setState(_state: any): void; - getState(): number; - triggerEvent(_event: any, _data: any): boolean; - makeVisible(): void; -} -/** egwActionObjectManager Object **/ -/** - * The egwActionObjectManager is a dummy class which only contains a dummy - * AOI. It may be used as root object or as object containers. - * - * @param {string} _id - * @param {string} _manager - * @return {egwActionObjectManager} - */ -declare function egwActionObjectManager(_id: egwAction, _manager: string): typeof egwActionObjectManager; -/** - * eGroupWare egw_action framework - egw action framework - * - * @link http://www.egroupware.org - * @author Andreas Stöckel - * @copyright 2011 by Andreas Stöckel - * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License - * @package egw_action - * @version $Id$ - */ -/** - * Getter functions for the global egwActionManager and egwObjectManager objects - */ -declare var egw_globalActionManager: any; -declare var egw_globalObjectManager: any; -/** egwActionObject Object **/ -declare var EGW_AO_STATE_NORMAL: number; -declare var EGW_AO_STATE_SELECTED: number; -declare var EGW_AO_STATE_FOCUSED: number; -declare var EGW_AO_STATE_VISIBLE: number; -declare var EGW_AO_EVENT_DRAG_OVER_ENTER: number; -declare var EGW_AO_EVENT_DRAG_OVER_LEAVE: number; -declare var EGW_AO_SHIFT_STATE_NONE: number; -declare var EGW_AO_SHIFT_STATE_MULTI: number; -declare var EGW_AO_SHIFT_STATE_BLOCK: number; -declare var EGW_AO_FLAG_IS_CONTAINER: number; -declare var EGW_AO_FLAG_DEFAULT_FOCUS: number; -declare var EGW_AO_EXEC_SELECTED: number; -declare var EGW_AO_EXEC_THIS: number; -/** -- egwActionObjectDummyInterface Class -- **/ -declare var egwActionObjectDummyInterface: typeof egwActionObjectInterface; -/** - * eGroupWare egw_action framework - egw action framework - * - * @link http://www.egroupware.org - * @author Andreas Stöckel - * @copyright 2011 by Andreas Stöckel - * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License - * @package egw_action - * @version $Id$ - */ -/** - * Sets properties given in _data in _obj. Checks whether the property keys - * exists and if corresponding setter functions are available. Properties starting - * with "_" are ignored. - * - * @param object _data may be an object with data that will be stored inside the - * given object. - * @param object _obj is the object where the data will be stored. - * @param mixed _setterOnly false: store everything, true: only store when setter exists, "data" store rest in data property - */ -declare function egwActionStoreJSON(_data: any, _obj: any, _setterOnly: any): void; -/** - * Switches the given bit in the set on or off. - * - * @param int _set is the current set - * @param int _bit is the position of the bit which should be switched on/off - * @param boolean _state is whether the bit should be switched on or off - * @returns the new set - */ -declare function egwSetBit(_set: any, _bit: any, _state: any): number; -/** - * Returns whether the given bit is set in the set. - */ -declare function egwBitIsSet(_set: any, _bit: any): boolean; -declare function egwObjectLength(_obj: any): number; -/** - * Isolates the shift state from an event object - */ -declare function egwGetShiftState(e: any): number; -declare function egwPreventSelect(e: any): boolean; -declare class egwPreventSelect { - constructor(e: any); - onselectstart: () => boolean; -} -declare function egwResetPreventSelect(elem: any): void; -declare function egwUnfocus(): void; -declare function egwCallAbstract(_obj: any, _fn: any, _args: any): any; -declare function egwArraysEqual(_ar1: any, _ar2: any): boolean; -declare function egwQueueCallback(_proc: any, _args: any, _context: any, _id: any): void; -/** - * The eventQueue object is used to have control over certain events such as - * ajax responses or timeouts. Sometimes it may happen, that a function attached - * to such an event should no longer be called - with egwEventQueue one has - * a simple possibility to control that. - */ -/** - * Constructor for the egwEventQueue class. Initializes the queue object and the - * internal data structures such as the internal key. - */ -declare function egwEventQueue(): void; -declare class egwEventQueue { - events: {}; - key_id: number; - flush(): void; - queue(_proc: any, _context: any, _args: any, _id: any): string; - run(_key: any): void; - queueTimeout(_proc: any, _context: any, _args: any, _id: any, _timeout: any): void; -} -/** - * Class which is used to be able to handle references to JavaScript functions - * from strings. - * - * @param object _context is the context in which the function will be executed. - * @param mixed _default is the default value which should be returned when no - * function (string) has been set. If it is a function this function will be - * called. - * @param array _acceptedTypes is an array of types which contains the "typeof" - * strings of accepted non-functions in setValue - */ -declare function egwFnct(_context: any, _default: any, _acceptedTypes?: any): void; -declare class egwFnct { - /** - * Class which is used to be able to handle references to JavaScript functions - * from strings. - * - * @param object _context is the context in which the function will be executed. - * @param mixed _default is the default value which should be returned when no - * function (string) has been set. If it is a function this function will be - * called. - * @param array _acceptedTypes is an array of types which contains the "typeof" - * strings of accepted non-functions in setValue - */ - constructor(_context: any, _default: any, _acceptedTypes?: any); - context: any; - acceptedTypes: any; - fnct: any; - value: any; - isDefault: boolean; - hasHandler(): boolean; - setValue(_value: any): void; - exec(...args: any[]): any; -} -declare function egwIsMobile(): any; -/** -sprintf() for JavaScript 0.6 - -Copyright (c) Alexandru Marasteanu -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of sprintf() for JavaScript nor the - names of its contributors may be used to endorse or promote products - derived from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL Alexandru Marasteanu BE LIABLE FOR ANY -DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - -Changelog: -2007.04.03 - 0.1: - - initial release -2007.09.11 - 0.2: - - feature: added argument swapping -2007.09.17 - 0.3: - - bug fix: no longer throws exception on empty paramenters (Hans Pufal) -2007.10.21 - 0.4: - - unit test and patch (David Baird) -2010.05.09 - 0.5: - - bug fix: 0 is now preceeded with a + sign - - bug fix: the sign was not at the right position on padded results (Kamal Abdali) - - switched from GPL to BSD license -2010.05.22 - 0.6: - - reverted to 0.4 and fixed the bug regarding the sign of the number 0 - Note: - Thanks to Raphael Pigulla (http://www.n3rd.org/) - who warned me about a bug in 0.5, I discovered that the last update was - a regress. I appologize for that. -**/ -declare function str_repeat(i: any, m: any): string; -declare function sprintf(...args: any[]): string; -declare var _egwQueuedCallbacks: {}; -/** - * Checks whether this is currently run on a mobile browser - */ -declare var _egw_mobileBrowser: any; -/** - * The egwDragAction class overwrites the egwAction class and adds the new - * "dragType" propery. The "onExecute" event of the drag action will be called - * whenever dragging starts. The onExecute JS handler should return the - * drag-drop helper object - otherwise an default helper will be generated. - * - * @param {egwAction} _id - * @param {string} _handler - * @param {string} _caption - * @param {string} _icon - * @param {(string|function)} _onExecute - * @param {bool} _allowOnMultiple - * @returns {egwDragAction} - */ -declare function egwDragAction(_id: egwAction, _handler: string, _caption: string, _icon: string, _onExecute: TimerHandler, _allowOnMultiple: any): typeof egwDragAction; -declare function getDragImplementation(): any; -declare function egwDragActionImplementation(): egwActionImplementation; -/** - * The egwDropAction class overwrites the egwAction class and adds the "acceptedTypes" - * property. This array should contain all "dragTypes" the drop action is allowed to - * - * @param {egwAction} _id - * @param {string} _handler - * @param {string} _caption - * @param {string} _icon - * @param {(string|function)} _onExecute - * @param {bool} _allowOnMultiple - * @returns {egwDropAction} - */ -declare function egwDropAction(_id: egwAction, _handler: string, _caption: string, _icon: string, _onExecute: TimerHandler, _allowOnMultiple: any): typeof egwDropAction; -declare function getDropImplementation(): any; -declare function egwDropActionImplementation(): egwActionImplementation; -declare var _dragActionImpl: any; -declare var _dropActionImpl: any; -declare var EGW_AI_DRAG: number; -declare var EGW_AI_DRAG_OUT: number; -declare var EGW_AI_DRAG_OVER: number; -declare function egwPopupAction(_id: any, _handler: any, _caption: any, _icon: any, _onExecute: any, _allowOnMultiple: any): egwAction; -declare function getPopupImplementation(): any; -declare function egwPopupActionImplementation(): egwActionImplementation; -declare var _popupActionImpl: any; -/** - * eGroupWare egw_dragdrop_dhtmlxmenu - egw action framework - * - * @link http://www.egroupware.org - * @author Andreas Stöckel - * @copyright 2011 by Andreas Stöckel - * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License - * @package egw_action - * @version $Id$ - */ -/** -* This file contains an egw_actionObjectInterface which allows a dhtmlx tree -* row to be a drag target and contains a function which transforms a complete -* dhtmlx tree into egw_actionObjects -*/ -declare function dhtmlxTree_getNode(_tree: any, _itemId: any): JQuery; -declare function dhtmlxtreeItemAOI(_tree: any, _itemId: any): egwActionObjectInterface; -/** - * Checks whether the given keycode is in the list of valid key codes. If not, - * returns -1. - */ -declare function egw_keycode_makeValid(_keyCode: any): any; -declare function _egw_nodeIsInInput(_node: any): any; -/** - * Creates an unique key for the given shortcut - */ -declare function egw_shortcutIdx(_keyCode: any, _shift: any, _ctrl: any, _alt: any): string; -/** - * Registers a global shortcut. If the shortcut already exists, it is overwritten. - * @param int _keyCode is one of the keycode constants - * @param bool _shift whether shift has to be set - * @param bool _ctrl whether ctrl has to be set - * @param bool _alt whether alt has to be set - * @param function _handler the function which will be called when the shortcut - * is evoked. An object containing the shortcut data will be passed as first - * parameter. - * @param object _context is the context in which the function will be executed - */ -declare function egw_registerGlobalShortcut(_keyCode: any, _shift: any, _ctrl: any, _alt: any, _handler: any, _context: any): void; -/** - * Unregisters the given shortcut. - */ -declare function egw_unregisterGlobalShortcut(_keyCode: any, _shift: any, _ctrl: any, _alt: any): void; -/** - * the egw_keyHandler function handles various key presses. The boolean - * _shift, _ctrl, _alt values have been translated into platform independent - * values (for apple devices). - */ -declare function egw_keyHandler(_keyCode: any, _shift: any, _ctrl: any, _alt: any): any; -/** - * eGroupWare egw_action framework - Shortcut/Keyboard input manager - * - * @link http://www.egroupware.org - * @author Andreas Stöckel - * @copyright 2011 by Andreas Stöckel - * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License - * @package egw_action - * @version $Id$ - */ -/** - * Define the key constants (IE doesn't support "const" keyword) - */ -declare var EGW_KEY_BACKSPACE: number; -declare var EGW_KEY_TAB: number; -declare var EGW_KEY_ENTER: number; -declare var EGW_KEY_ESCAPE: number; -declare var EGW_KEY_DELETE: number; -declare var EGW_KEY_SPACE: number; -declare var EGW_KEY_PAGE_UP: number; -declare var EGW_KEY_PAGE_DOWN: number; -declare var EGW_KEY_ARROW_LEFT: number; -declare var EGW_KEY_ARROW_UP: number; -declare var EGW_KEY_ARROW_RIGHT: number; -declare var EGW_KEY_ARROW_DOWN: number; -declare var EGW_KEY_0: number; -declare var EGW_KEY_1: number; -declare var EGW_KEY_2: number; -declare var EGW_KEY_3: number; -declare var EGW_KEY_4: number; -declare var EGW_KEY_5: number; -declare var EGW_KEY_6: number; -declare var EGW_KEY_7: number; -declare var EGW_KEY_8: number; -declare var EGW_KEY_9: number; -declare var EGW_KEY_A: number; -declare var EGW_KEY_B: number; -declare var EGW_KEY_C: number; -declare var EGW_KEY_D: number; -declare var EGW_KEY_E: number; -declare var EGW_KEY_F: number; -declare var EGW_KEY_G: number; -declare var EGW_KEY_H: number; -declare var EGW_KEY_I: number; -declare var EGW_KEY_J: number; -declare var EGW_KEY_K: number; -declare var EGW_KEY_L: number; -declare var EGW_KEY_M: number; -declare var EGW_KEY_N: number; -declare var EGW_KEY_O: number; -declare var EGW_KEY_P: number; -declare var EGW_KEY_Q: number; -declare var EGW_KEY_R: number; -declare var EGW_KEY_S: number; -declare var EGW_KEY_T: number; -declare var EGW_KEY_U: number; -declare var EGW_KEY_V: number; -declare var EGW_KEY_W: number; -declare var EGW_KEY_X: number; -declare var EGW_KEY_Y: number; -declare var EGW_KEY_Z: number; -declare var EGW_KEY_MENU: number; -declare var EGW_KEY_F1: number; -declare var EGW_KEY_F2: number; -declare var EGW_KEY_F3: number; -declare var EGW_KEY_F4: number; -declare var EGW_KEY_F5: number; -declare var EGW_KEY_F6: number; -declare var EGW_KEY_F7: number; -declare var EGW_KEY_F8: number; -declare var EGW_KEY_F9: number; -declare var EGW_KEY_F10: number; -declare var EGW_KEY_F11: number; -declare var EGW_KEY_F12: number; -declare var EGW_VALID_KEYS: number[]; -declare function egw_keycode_translation_function(_nativeKeyCode: any): any; -declare var egw_registeredShortcuts: {}; -/** - * Internal function which generates a menu item with the given parameters as used - * in e.g. the egwMenu.addItem function. - */ -declare function _egwGenMenuItem(_parent: any, _id: any, _caption: any, _iconUrl: any, _onClick: any): egwMenuItem; -/** - * Internal function which parses the given menu tree in _elements and adds the - * elements to the given parent. - */ -declare function _egwGenMenuStructure(_elements: any, _parent: any): egwMenuItem[]; -/** - * Internal function which searches for the given ID inside an element tree. - */ -declare function _egwSearchMenuItem(_elements: any, _id: any): any; -/** - * Internal function which alows to set the onClick handler of multiple menu items - */ -declare function _egwSetMenuOnClick(_elements: any, _onClick: any): void; -/** - * Constructor for the egwMenu object. The egwMenu object is a abstract representation - * of a context/popup menu. The actual generation of the menu can by done by so - * called menu implementations. Those are activated by simply including the JS file - * of such an implementation. - * - * The currently available implementation is the "egwDhtmlxMenu.js" which is based - * upon the dhtmlxmenu component. - */ -declare function egwMenu(): void; -declare class egwMenu { - children: any[]; - instance: egwMenuImpl; - _checkImpl(): boolean; - showAt(_x: any, _y: any, _force: any): boolean; - hide(): void; - addItem(_id: any, _caption: any, _iconUrl: any, _onClick: any): egwMenuItem; - clear(): void; - loadStructure(_elements: any): void; - getItem(_id: any): any; - setGlobalOnClick(_onClick: any): void; -} -/** - * Constructor for the egwMenuItem. Each entry in a menu (including seperators) - * is represented by a menu item. - */ -declare function egwMenuItem(_parent: any, _id: any): void; -declare class egwMenuItem { - /** - * Constructor for the egwMenuItem. Each entry in a menu (including seperators) - * is represented by a menu item. - */ - constructor(_parent: any, _id: any); - id: any; - caption: string; - checkbox: boolean; - checked: boolean; - groupIndex: number; - enabled: boolean; - iconUrl: string; - onClick: any; - default: boolean; - data: any; - shortcutCaption: any; - children: any[]; - parent: any; - getItem(_id: any): any; - setGlobalOnClick(_onClick: any): void; - addItem(_id: any, _caption: any, _iconUrl: any, _onClick: any): egwMenuItem; - set_id(_value: any): void; - set_caption(_value: any): void; - set_checkbox(_value: any): void; - set_checked(_value: any): void; - set_groupIndex(_value: any): void; - set_enabled(_value: any): void; - set_onClick(_value: any): void; - set_iconUrl(_value: any): void; - set_default(_value: any): void; - set_data(_value: any): void; - set_hint(_value: any): void; - hint: any; - set_shortcutCaption(_value: any): void; -} -/** - * eGroupWare egw_action framework - JS Menu abstraction - * - * @link http://www.egroupware.org - * @author Andreas Stöckel - * @copyright 2011 by Andreas Stöckel - * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License - * @package egw_action - * @version $Id$ - */ -declare var _egw_active_menu: any; -/** - * eGroupWare egw_action framework - JS Menu abstraction - * - * @link http://www.egroupware.org - * @author Andreas Stöckel - * @copyright 2011 by Andreas Stöckel - * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License - * @package egw_action - * @version $Id$ - */ -/** - * - * @param {type} _structure - */ -declare function egwMenuImpl(_structure: any): void; -declare class egwMenuImpl { - /** - * eGroupWare egw_action framework - JS Menu abstraction - * - * @link http://www.egroupware.org - * @author Andreas Stöckel - * @copyright 2011 by Andreas Stöckel - * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License - * @package egw_action - * @version $Id$ - */ - /** - * - * @param {type} _structure - */ - constructor(_structure: any); - dhtmlxmenu: any; - _translateStructure(_structure: any, _parentId: any, _idCnt: any): number; - showAt(_x: any, _y: any, _onHide: any): void; - hide(): void; -} -/** - * Main egwDynStyleSheet class - all egwDynStyleSheets share the same stylesheet - * which is dynamically inserted into the head section of the DOM-Tree. - * This stylesheet is created with the first egwDynStyleSheet class. - */ -declare function egwDynStyleSheet(): any; -declare class egwDynStyleSheet { - styleSheet: any; - selectors: {}; - selectorCount: number; - updateRule(_selector: any, _rule: any): void; -} -/** - * eGroupWare egw_action framework - egw action framework - * - * @link http://www.egroupware.org - * @author Andreas Stöckel - * @copyright 2011 by Andreas Stöckel - * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License - * @package egw_action - * @version $Id$ - */ -/** - * Contains the egwDynStyleSheet class which allows dynamic generation of stylesheet - * rules - updating a single stylesheet rule is way more efficient than updating - * the element style of many objects. - */ -declare var EGW_DYNAMIC_STYLESHEET: any; diff --git a/api/js/egw_action/egw_action.js b/api/js/egw_action/egw_action.js deleted file mode 100644 index 132dee96e2..0000000000 --- a/api/js/egw_action/egw_action.js +++ /dev/null @@ -1,2530 +0,0 @@ -/** - * EGroupware egw_action framework - egw action framework - * - * @link https://www.egroupware.org - * @author Andreas Stöckel - * @copyright 2011 by Andreas Stöckel - * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License - * @package egw_action - */ - -/*egw:uses - egw_action_common; -*/ -import { - EGW_AO_STATE_NORMAL, - EGW_AO_STATE_VISIBLE, - EGW_AO_STATE_SELECTED, - EGW_AO_STATE_FOCUSED, - EGW_AO_SHIFT_STATE_MULTI, - EGW_AO_SHIFT_STATE_NONE, - EGW_AO_FLAG_IS_CONTAINER, - EGW_AO_SHIFT_STATE_BLOCK, - EGW_KEY_ARROW_UP, - EGW_KEY_ARROW_DOWN, - EGW_KEY_PAGE_UP, - EGW_KEY_PAGE_DOWN, - EGW_AO_EXEC_THIS, - EGW_AO_EXEC_SELECTED, - EGW_KEY_A, EGW_KEY_SPACE -} from './egw_action_constants.js'; -import {egwFnct, egwActionStoreJSON, egwBitIsSet, egwQueueCallback, egwSetBit, egwObjectLength} from './egw_action_common.js'; -import './egw_action_popup.js'; -import "./egw_action_dragdrop.js"; -import "./egw_menu_dhtmlx.js"; - -/** - * Getter functions for the global egwActionManager and egwObjectManager objects - */ - -var egw_globalActionManager = null; -export var egw_globalObjectManager = null; - -/** - * Returns the action manager for the given application - each application has its - * own sub-ActionManager in the global action manager object to prevent collisions - * from happening - * - * @param _id is the name of the sub-actionManager which should be returned. - * If the action manager does not exist right now, it is created. If the - * parameter is ommited or null, the global action manager is returned. - * @param {boolean} [_create=true] If an objectManager with the given id is not - * found, it will be created at the top level. - * @param {number} [_search_depth=Infinite] How deep into existing action children - * to search. - */ -export function egw_getActionManager(_id, _create,_search_depth) { - if (typeof _create == 'undefined') { - _create = true; - } - if (typeof _search_depth == "undefined") { - _search_depth = Number.MAX_VALUE; - } - - // Check whether the global action manager had been created, if not do so - var res = egw_globalActionManager; - if (egw_globalActionManager == null) { - res = egw_globalActionManager = new egwActionManager(); - } - - // Check whether the sub-action manager exists, if not, create it - if (typeof _id != 'undefined' && _id != null) { - res = egw_globalActionManager.getActionById(_id,_search_depth); - if (res == null && _create) { - res = egw_globalActionManager.addAction("actionManager", _id); - } - } - - return res; -} - -/** - * Returns the object manager for the given application - each application may - * have its own object manager where it can place action objects or containers. - * - * @param _id is the name of the sub-object manager should be returned. If the - * object manager does not exists right now, it is created. If the parameter - * is ommited or null, the global object manager is returned. - * @param {boolean} [_create=true] If an objectManager with the given id is not - * found, it will be created at the top level. - * @param {number} [_search_depth=Infinite] How deep into existing action children - * to search. - */ -export function egw_getObjectManager(_id, _create, _search_depth) { - if (typeof _create == "undefined") { - _create = true; - } - if (typeof _search_depth == "undefined") { - _search_depth = Number.MAX_VALUE; - } - - // Check whether the global object manager exists - var res = egw_globalObjectManager; - if (res == null) { - res = egw_globalObjectManager = - new egwActionObjectManager("_egwGlobalObjectManager", - egw_getActionManager()); - } - - // Check whether the sub-object manager exists, if not, create it - if (typeof _id != 'undefined' && _id != null) { - res = egw_globalObjectManager.getObjectById(_id, _search_depth); - if (res == null && _create) { - res = new egwActionObjectManager(_id, - egw_getActionManager(_id,true,_search_depth)); - egw_globalObjectManager.addObject(res); - } - } - - return res; -} - -/** - * Returns the object manager for the current application - * - * @param {boolean} _create - * @param {string} _appName //appname might not always be the current app, e.g. running app content under admin tab - * @return {egwActionObjectManager} - */ -export function egw_getAppObjectManager(_create, _appName) { - return egw_getObjectManager(_appName ? _appName : egw_getAppName(), _create,1); -} - -/** - * Returns the action manager for the current application - * - * @param {boolean} _create - * @return {egwActionManager} - */ -export function egw_getAppActionManager(_create) { - return egw_getActionManager(egw_getAppName(), _create,1); -} - - - -/** egwActionHandler Interface **/ - -/** - * Constructor for the egwActionHandler interface which (at least) should have the - * execute function implemented. - * - * @param {function} _executeEvent - * @return {egwActionHandler} - */ -export function egwActionHandler(_executeEvent) -{ - //Copy the executeEvent parameter - this.execute = _executeEvent; -} - - -/** egwAction Object **/ - -/** - * Associative array where action classes may register themselves - */ -if (typeof window._egwActionClasses == "undefined") - window._egwActionClasses = {}; - -_egwActionClasses["default"] = { - "actionConstructor": egwAction, - "implementation": null -}; -_egwActionClasses["actionManager"] = { - "actionConstructor": egwActionManager, - "implementation": null -}; - -/** - * Constructor for egwAction object - * - * @param {egwAction} _parent - * @param {string} _id - * @param {string} _caption - * @param {string} _iconUrl - * @param {(string|function)} _onExecute - * @param {boolean} _allowOnMultiple - * @returns {egwAction} - */ -export function egwAction(_parent, _id, _caption, _iconUrl, _onExecute, _allowOnMultiple) -{ - //Default and check the values - if (_parent && (typeof _id != "string" || !_id) && _parent.type != "actionManager") - throw "egwAction _id must be a non-empty string!"; - if (typeof _caption == "undefined") - _caption = ""; - if (typeof _iconUrl == "undefined") - _iconUrl = ""; - if (typeof _onExecute == "undefined") - _onExecute = null; - if (typeof _allowOnMultiple == "undefined") - _allowOnMultiple = true; - - this.id = _id; - this.caption = _caption; - this.iconUrl = _iconUrl; - this.allowOnMultiple = _allowOnMultiple; - this.enabled = new egwFnct(this, true); - this.hideOnDisabled = false; - this.data = {}; // Data which can be freely assigned to the action - - this.type = "default"; //All derived classes have to override this! - this.canHaveChildren = false; //Has to be overwritten by inherited action classes - this.parent = _parent; - this.children = []; - - this.onExecute = new egwFnct(this, null, []); - if(_onExecute !== null) - { - this.set_onExecute(_onExecute); - } - this.hideOnMobile = false; - this.disableIfNoEPL = false; -} - -/** - * Clears the element and removes it from the parent container - */ -egwAction.prototype.remove = function () { - // Remove all references to the child elements - this.children = []; - - // Remove this element from the parent list - if (this.parent) - { - var idx = this.parent.children.indexOf(this); - if (idx >= 0) - { - this.parent.children.splice(idx, 1); - } - } -}; - -/** - * Searches for a specific action with the given id - * - * @param {(string|number)} _id ID of the action to find - * @param {number} [_search_depth=Infinite] How deep into existing action children - * to search. - * - * @return {(egwAction|null)} - */ -egwAction.prototype.getActionById = function(_id,_search_depth) -{ - // If the current action object has the given id, return this object - if (this.id == _id) - { - return this; - } - if (typeof _search_depth == "undefined") { - _search_depth = Number.MAX_VALUE; - } - - // If this element is capable of having children, search those for the given - // action id - if (this.canHaveChildren) - { - for (var i = 0; i < this.children.length && _search_depth > 0; i++) - { - var elem = this.children[i].getActionById(_id,_search_depth-1); - if (elem) - { - return elem; - } - } - } - - return null; -}; - -/** - * Searches for actions having an attribute with a certain value - * - * Example: actionManager.getActionsByAttr("checkbox", true) returns all checkbox actions - * - * @param {string} _attr attribute name - * @param _val attribute value - * @return array - */ -egwAction.prototype.getActionsByAttr = function(_attr, _val) -{ - var _actions = []; - - // If the current action object has the given attr AND value, or no value was provided, return it - if (typeof this[_attr] != "undefined" && (this[_attr] === _val || typeof _val === "undefined" && this[_attr] !== null)) - { - _actions.push(this); - } - - // If this element is capable of having children, search those too - if (this.canHaveChildren) - { - for (var i = 0; i < this.children.length; i++) - { - _actions = _actions.concat(this.children[i].getActionsByAttr(_attr, _val)); - } - } - - return _actions; -}; - -/** - * Adds a new action to the child elements. - * - * @param {string} _type - * @param {string} _id - * @param {string} _caption - * @param {string] _iconUrl - * @param {(string|function)} _onExecute - * @param {boolean} _allowOnMultiple - */ -egwAction.prototype.addAction = function(_type, _id, _caption, _iconUrl, - _onExecute, _allowOnMultiple) -{ - //Get the constructor for the given action type - if (!_type) - { - _type = "popup"; - } - - // Only allow adding new actions, if this action class allows it. - if (this.canHaveChildren) - { - var constructor = _egwActionClasses[_type].actionConstructor; - - if (typeof constructor == "function") - { - var action = new constructor(this, _id, _caption, _iconUrl, _onExecute, - _allowOnMultiple); - this.children.push(action); - - return action; - } - else - { - throw "Given action type not registered."; - } - } - else - { - throw "This action does not allow child elements!"; - } -}; - -/** - * Default icons for given id - */ -egwAction.prototype.defaultIcons = { - view: 'view', - edit: 'edit', - open: 'edit', // does edit if possible, otherwise view - add: 'new', - "new": 'new', - "delete": 'delete', - cat: 'attach', // add as category icon to api - document: 'etemplate/merge', - print: 'print', - copy: 'copy', - move: 'move', - cut: 'cut', - paste: 'editpaste', - save: 'save', - apply: 'apply', - cancel: 'cancel', - 'continue': 'continue', - next: 'continue', - finish: 'finish', - back: 'back', - previous: 'back', - close: 'close' -}; - -/** - * Updates the children of this element - * - * @param {object} _actions { id: action, ...} - * @param {string} _app defaults to egw_getAppname() - */ -egwAction.prototype.updateActions = function(_actions, _app) -{ - if (this.canHaveChildren) - { - if (typeof _app == "undefined") _app = egw_getAppName(); // this can probably be queried from actionObjectManager ... - var egw = window.egw(_app); - - if (jQuery.isArray(_actions)) - { - _actions = jQuery.extend({}, _actions); - } - for (var i in _actions) - { - var elem = _actions[i]; - - if (typeof elem == "string") - { - _actions[i] = elem = { caption: elem }; - } - if (typeof elem == "object") - { - // use attr name as id, if none given - if (typeof elem.id != "string") elem.id = i; - - // if no iconUrl given, check icon and default icons - if (typeof elem.iconUrl == "undefined") - { - if (typeof elem.icon == "undefined") elem.icon = this.defaultIcons[elem.id]; - if (typeof elem.icon != "undefined") - { - elem.iconUrl = egw.image(elem.icon, _app); - } - delete elem.icon; - } - - // allways add shortcut for delete - if (elem.id == "delete" && typeof elem.shortcut == "undefined") - { - elem.shortcut = { keyCode: 46, shift: false, ctrl: false, alt: false, caption: egw.lang('Del') }; - } - - // translate caption - if (elem.caption && (typeof elem.no_lang == "undefined" || !elem.no_lang)) - { - elem.caption = egw.lang(elem.caption); - if (typeof elem.hint == "string") elem.hint = egw.lang(elem.hint); - } - delete elem.no_lang; - - // translate confirm messages - for(var attr in {confirm: '', confirm_multiple: ''}) - { - if (typeof elem[attr] == "string") - { - elem[attr] = egw.lang(elem[attr])+(elem[attr].substr(-1) != '?' ? '?' : ''); - } - } - - // set certain enabled functions (if enabled is on it's default of true) - if (typeof elem.enabled == 'undefined' || elem.enabled === true) - { - if (typeof elem.enableClass != "undefined") - { - elem.enabled = this.enableClass; - } - else if (typeof elem.disableClass != "undefined") - { - elem.enabled = this.not_disableClass; - } - else if (typeof elem.enableId != "undefined") - { - elem.enabled = this.enableId; - } - } - - //Check whether the action already exists, and if no, add it to the - //actions list - var action = this.getActionById(elem.id); - if (!action) - { - if (typeof elem.type == "undefined") - elem.type = "popup"; - - var constructor = null; - - // Check whether the given type is inside the "canHaveChildren" - // array - if (this.canHaveChildren !== true && this.canHaveChildren.indexOf(elem.type) == -1) - { - throw "This child type '" + elem.type + "' is not allowed!"; - } - - if (typeof _egwActionClasses[elem.type] != "undefined") - constructor = _egwActionClasses[elem.type].actionConstructor; - - if (typeof constructor == "function" && constructor) - action = new constructor(this, elem.id); - else - throw "Given action type \"" + elem.type + "\" not registered."; - - this.children.push(action); - } - - action.updateAction(elem); - - // Add sub-actions to the action - if (elem.children) - { - action.updateActions(elem.children, _app); - } - } - } - } - else - { - throw "This action element cannot have children!"; - } -}; - -/** - * Callback to check if none of _senders rows has disableClass set - * - * @param _action egwAction object, we use _action.data.disableClass to check - * @param _senders array of egwActionObject objects - * @param _target egwActionObject object, get's called for every object in _senders - * @returns boolean true if none has disableClass, false otherwise - */ -egwAction.prototype.not_disableClass = function(_action, _senders, _target) -{ - if(_target.iface.getDOMNode()) - { - return !jQuery(_target.iface.getDOMNode()).hasClass(_action.data.disableClass); - } - else if (_target.id) - { - // Checking on a something that doesn't have a DOM node, like a nm row - // that's not currently rendered - var data = egw.dataGetUIDdata(_target.id); - if(data && data.data && data.data.class) - { - return -1 === data.data.class.split(' ').indexOf(_action.data.disableClass); - } - } -}; - -/** - * Callback to check if all of _senders rows have enableClass set - * - * @param _action egwAction object, we use _action.data.enableClass to check - * @param _senders array of egwActionObject objects - * @param _target egwActionObject object, get's called for every object in _senders - * @returns boolean true if none has disableClass, false otherwise - */ -egwAction.prototype.enableClass = function(_action, _senders, _target) -{ - if (typeof _target == 'undefined') - { - return false; - } else - if(_target.iface.getDOMNode()) - { - return jQuery(_target.iface.getDOMNode()).hasClass(_action.data.enableClass); - } - else if (_target.id) - { - // Checking on a something that doesn't have a DOM node, like a nm row - // that's not currently rendered. Not as good as an actual DOM node check - // since things can get missed, but better than nothing. - var data = egw.dataGetUIDdata(_target.id); - if(data && data.data && data.data.class) - { - return -1 !== data.data.class.split(' ').indexOf(_action.data.enableClass); - } - } -}; - -/** - * Enable an _action, if it matches a given regular expresstion in _action.data.enableId - * - * @param _action egwAction object, we use _action.data.enableId to check - * @param _senders array of egwActionObject objects - * @param _target egwActionObject object, get's called for every object in _senders - * @returns boolean true if _target.id matches _action.data.enableId - */ -egwAction.prototype.enableId = function(_action, _senders, _target) -{ - if (typeof _action.data.enableId == 'string') - _action.data.enableId = new RegExp(_action.data.enableId); - - return _target.id.match(_action.data.enableId); -}; - -/** - * Applys the same onExecute handler to all actions which don't have an execute - * handler set. - * - * @param {(string|function)} _value - */ -egwAction.prototype.setDefaultExecute = function(_value) -{ - // Check whether the onExecute handler of this action should be set - if (this.type != "actionManager" && !this.onExecute.hasHandler()) - { - this.onExecute.isDefault = true; - this.onExecute.setValue(_value); - } - - // Apply the value to all children - if (this.canHaveChildren) - { - for (var i = 0; i < this.children.length; i++) - { - this.children[i].setDefaultExecute(_value); - } - } -}; - -/** - * Executes this action by using the method specified in the onExecute setter. - * - * @param {array} _senders array with references to the objects which caused the action - * @param {object} _target is an optional parameter which may represent e.g. an drag drop target - */ -egwAction.prototype.execute = function(_senders, _target) -{ - if (typeof _target === "undefined") - { - _target = null; - } - - if(!this._check_confirm_mass_selections(_senders, _target)) - { - return this._check_confirm(_senders, _target); - } -}; - -/** - * If this action needs to confirm mass selections (attribute confirm_mass_selection = true), - * check for any checkboxes that have a confirmation prompt (confirm_mass_selection is a string) - * and are unchecked. We then show the prompt, and set the checkbox to their answer. - * - * * This is only considered if there are more than 20 entries selected. - * - * * Only the first confirmation prompt / checkbox action will be used, others - * will be ignored. - * - * @param {type} _senders - * @param {type} _target - * @returns {Boolean} - */ -egwAction.prototype._check_confirm_mass_selections = function(_senders, _target) -{ - var obj_manager = egw_getObjectManager(this.getManager().parent.id, false); - if (!obj_manager) - { - return false; - } - - // Action needs to care about mass selection - check for parent that cares too - var confirm_mass_needed = false; - var action = this; - while(action && action !== obj_manager.manager && !confirm_mass_needed) - { - confirm_mass_needed = action.confirm_mass_selection; - action = action.parent; - } - if(!confirm_mass_needed) return false; - - // Check for confirm mass selection checkboxes - var confirm_mass_selections = obj_manager.manager.getActionsByAttr("confirm_mass_selection"); - confirm_mass_needed = _senders.length > 20; - var self = this; - - // Find & show prompt - for (var i = 0; confirm_mass_needed && i < confirm_mass_selections.length; i++) - { - var check = confirm_mass_selections[i]; - if(check.checkbox === false || check.checked === true) continue; - - // Show the mass selection prompt - var msg = egw.lang(check.confirm_mass_selection, obj_manager.getAllSelected() ? egw.lang('all') : _senders.length); - var callback = function(_button) - { - // YES = unchecked, NO = checked - check.set_checked(_button === Et2Dialog.NO_BUTTON); - if (_button !== Et2Dialog.CANCEL_BUTTON) - { - self._check_confirm(_senders, _target); - } - }; - Et2Dialog.show_dialog(callback, msg, self.data.hint, {}, Et2Dialog.BUTTONS_YES_NO_CANCEL, Et2Dialog.QUESTION_MESSAGE); - return true; - } - return false; -}; - -/** - * Check to see if action needs to be confirmed by user before we do it - */ -egwAction.prototype._check_confirm = function(_senders, _target) -{ - // check if actions needs to be confirmed first - if (this.data && (this.data.confirm || this.data.confirm_multiple) && this.onExecute.fnct != window.nm_action && - typeof Et2Dialog != 'undefined') // let old eTemplate run it's own confirmation from nextmatch_action.js - { - var msg = this.data.confirm || ''; - if (_senders.length > 1) - { - if (this.data.confirm_multiple) - { - msg = this.data.confirm_multiple; - } - // check if we have all rows selected - var obj_manager = egw_getObjectManager(this.getManager().parent.id, false); - if (obj_manager && obj_manager.getAllSelected()) - { - msg += "\n\n" + egw().lang('Attention: action will be applied to all rows, not only visible ones!'); - } - } - var self = this; - if(msg.trim().length > 0) - { - if(this.data.policy_confirmation && egw.app('policy')) - { - egw.includeJS(egw.link('/policy/js/app.min.js'), function () - { - if (typeof app.policy === 'undefined' || typeof app.policy.confirm === 'undefined') - { - app.policy = new app.classes.policy(); - } - app.policy.confirm(self, _senders, _target); - }); - return; - } - Et2Dialog.show_dialog(function (_button) - { - if (_button == Et2Dialog.YES_BUTTON) - { - return self.onExecute.exec(self, _senders, _target); - } - }, msg, self.data.hint, {}, Et2Dialog.BUTTONS_YES_NO, Et2Dialog.QUESTION_MESSAGE); - return; - } - } - return this.onExecute.exec(this, _senders, _target); -}; - -/** - * The set_onExecute function is the setter function for the onExecute event of - * the egwAction object. There are three possible types the passed "_value" may - * take: - * 1. _value may be a string with the word "javaScript:" prefixed. The function - * which is specified behind the colon and which has to be in the global scope - * will be executed. - * 2. _value may be a boolean, which specifies whether the external onExecute handler - * (passed as "_handler" in the constructor) will be used. - * 3. _value may be a JS function which will then be called. - * In all possible situation, the called function will get the following parameters: - * 1. A reference to this action - * 2. The senders, an array of all objects (JS)/object ids (PHP) which evoked the event - * - * @param {(string|function|boolean)} _value - */ -egwAction.prototype.set_onExecute = function(_value) -{ - this.onExecute.setValue(_value); -}; - -egwAction.prototype.set_caption = function(_value) -{ - this.caption = _value; -}; - -egwAction.prototype.set_iconUrl = function(_value) -{ - this.iconUrl = _value; -}; - -egwAction.prototype.set_enabled = function(_value) -{ - this.enabled.setValue(_value); -}; - -/** - * The allowOnMultiple property may be true, false, "only" (> 1) or number of select, eg. 2 - * - * @param {(boolean|string|number)} _value - */ -egwAction.prototype.set_allowOnMultiple = function(_value) -{ - this.allowOnMultiple = _value; -}; - -egwAction.prototype.set_hideOnDisabled = function(_value) -{ - this.hideOnDisabled = _value; -}; - -egwAction.prototype.set_hideOnMobile = function(_value) -{ - this.hideOnMobile = _value; -}; - -egwAction.prototype.set_disableIfNoEPL = function(_value) -{ - this.disableIfNoEPL = _value; -}; - -egwAction.prototype.set_data = function(_value) -{ - this.data = _value; -}; - -egwAction.prototype.updateAction = function(_data) -{ - egwActionStoreJSON(_data, this, "data"); -}; - -function _egwActionTreeContains(_tree, _elem) -{ - for (var i = 0; i < _tree.length; i++) - { - if (_tree[i].action == _elem) - { - return _tree[i]; - } - - if (typeof _tree[i].children != "undefined") - { - var elem = _egwActionTreeContains(_tree[i].children, _elem); - if (elem) - { - return elem; - } - } - } - - return null; -} - -/** - * The appendToGraph function generates an action tree which automatically contains - * all parent elements. If the appendToGraph function is called for a - * - * @param {array} _tree contains the tree structure - pass an object containing - * the empty array "root" to this function {"root": []}. The result will be stored in - * this array. - * @param {boolean} _addChildren is used internally to prevent parent elements from - * adding their children automatically to the tree. - */ -egwAction.prototype.appendToTree = function(_tree, _addChildren) -{ - let _addParent = false; - if (typeof _addChildren == "undefined") - { - _addChildren = true; - } - - if (typeof _addParent == "undefined") - { - _addParent = true; - } - - // Preset some variables - var root = _tree.root; - var parent_cntr = null; - var cntr = { - "action": this, - "children": [] - }; - - - if (this.parent && this.type != "actionManager") - { - // Check whether the parent container has already been added to the tree - parent_cntr = _egwActionTreeContains(root, this.parent); - - if (!parent_cntr) - { - parent_cntr = this.parent.appendToTree(_tree, false); - } - - // Check whether this element has already been added to the parent container - var added = false; - for (var i = 0; i < parent_cntr.children.length; i++) - { - if (parent_cntr.children[i].action == this) - { - cntr = parent_cntr.children[i]; - added = true; - break; - } - } - - if (!added) - { - parent_cntr.children.push(cntr); - } - } - else - { - var added = false; - for (var i = 0; i < root.length; i++) - { - if (root[i].action == this) - { - cntr = root[i]; - added = true; - break; - } - } - - if (!added) - { - // Add this element to the root if it has no parent - root.push(cntr); - } - } - - if (_addChildren) - { - for (var i = 0; i < this.children.length; i++) - { - this.children[i].appendToTree(_tree, true); - } - } - - return cntr; -}; - -/** - * Returns the parent action manager - */ -egwAction.prototype.getManager = function() { - if (this.type == "actionManager") { - return this; - } else if (this.parent) { - return this.parent.getManager(); - } else { - return null; - } -}; - - -/** egwActionManager Object **/ - -/** - * egwActionManager manages a list of actions - it overwrites the egwAction class - * and allows child actions to be added to it. - * - * @param {egwAction} _parent - * @param {string} _id - * @return {egwActionManager} - */ -export function egwActionManager(_parent, _id) -{ - if (typeof _parent == 'undefined') { - _parent = null; - } - if (typeof _id == 'undefined') { - _id = false; - } - - var action = new egwAction(_parent, _id); - - action.type = "actionManager"; - action.canHaveChildren = true; - - return action; -} - - -/** egwActionImplementation Interface **/ - -/** - * Abstract interface for the egwActionImplementation object. The egwActionImplementation - * object is responsible for inserting the actual action representation (context menu, - * drag-drop code) into the DOM Tree by using the egwActionObjectInterface object - * supplied by the object. - * To write a "class" which derives from this object, simply write a own constructor, - * which replaces "this" with a "new egwActionImplementation" and implement your - * code in "doRegisterAction" und "doUnregisterAction". - * Register your own implementation within the _egwActionClasses object. - * - * @return {egwActionImplementation} - */ -export function egwActionImplementation() -{ - this.doRegisterAction = function() {throw "Abstract function call: registerAction";}; - this.doUnregisterAction = function() {throw "Abstract function call: unregisterAction";}; - this.doExecuteImplementation = function() {throw "Abstract function call: executeImplementation";}; - this.type = ""; -} - -/** - * Injects the implementation code into the DOM tree by using the supplied - * actionObjectInterface. - * - * @param {object} _actionObjectInterface is the AOI in which the implementation - * should be registered. - * @param {function} _triggerCallback is the callback function which will be triggered - * when the user triggeres this action implementatino (e.g. starts a drag-drop or - * right-clicks on an object.) - * @param {object} _context in which the triggerCallback should get executed. - * @returns true if the Action had been successfully registered, false if it - * had not. - */ -egwActionImplementation.prototype.registerAction = function(_actionObjectInterface, _triggerCallback, _context) -{ - if (typeof _context == "undefined") - _context = null; - - return this.doRegisterAction(_actionObjectInterface, _triggerCallback, _context); -}; - -/** - * Unregister action will be called before an actionObjectInterface is destroyed, - * which gives the egwActionImplementation the opportunity to remove the previously - * injected code. - * - * @param {egwActionObjectInterface} _actionObjectInterface - * @returns true if the Action had been successfully unregistered, false if it - * had not. - */ -egwActionImplementation.prototype.unregisterAction = function(_actionObjectInterface) -{ - return this.doUnregisterAction(_actionObjectInterface); -}; - -egwActionImplementation.prototype.executeImplementation = function(_context, _selected, _links) -{ - return this.doExecuteImplementation(_context, _selected, _links); -}; - - -/** egwActionLink Object **/ - -/** - * The egwActionLink is used to interconnect egwActionObjects and egwActions. - * This gives each action object the possibility to decide, whether the action - * should be active in this context or not. - * - * @param _manager is a reference to the egwActionManager whic contains the action - * the object wants to link to. - */ -export function egwActionLink(_manager) -{ - this.enabled = true; - this.visible = true; - this.actionId = ""; - this.actionObj = null; - this.manager = _manager; -} - -egwActionLink.prototype.updateLink = function (_data) -{ - egwActionStoreJSON(_data, this, true); -}; - -egwActionLink.prototype.set_enabled = function(_value) -{ - this.enabled = _value; -}; - -egwActionLink.prototype.set_visible = function(_value) -{ - this.visible = _value; -}; - -egwActionLink.prototype.set_actionId = function(_value) -{ - this.actionId = _value; - this.actionObj = this.manager.getActionById(_value); - - if (!this.actionObj) - throw "Action object with id '"+_value+"' does not exist!"; -}; - -/** - * The egwActionObject represents an abstract object to which actions may be - * applied. Communication with the DOM tree is established by using the - * egwActionObjectInterface (AOI), which is passed in the constructor. - * egwActionObjects are organized in a tree structure. - * - * @param {string} _id is the identifier of the object which - * @param {egwActionObject} _parent is the parent object in the hirachy. This may be set to NULL - * @param {egwActionObjectInterface} _iface is the egwActionObjectInterface which connects the object - * to the outer world. - * @param {egwActionManager} _manager is the action manager this object is connected to - * this object to the DOM tree. If the _manager isn't supplied, the parent manager - * is taken. - * @param {number} _flags a set of additional flags being applied to the object, - * defaults to 0 - */ -export function egwActionObject(_id, _parent, _iface, _manager, _flags) -{ - //Preset some parameters - if (typeof _manager == "undefined" && typeof _parent == "object" && _parent) - _manager = _parent.manager; - if (typeof _flags == "undefined") - _flags = 0; - - this.id = _id; - this.parent = _parent; - this.children = []; - this.actionLinks = []; - this.manager = _manager; - this.flags = _flags; - this.data = null; - this.setSelectedCallback = null; - - this.registeredImpls = []; - - // Two variables which help fast travelling through the object tree, when - // searching for the selected/focused object. - this.selectedChildren = []; - this.focusedChild = null; - - this.setAOI(_iface); -} - -/** - * Sets the action object interface - if "NULL" is given, the iface is set - * to a dummy interface which is used to store the temporary data. - * - * @param {egwActionObjectInterface} _aoi - */ -egwActionObject.prototype.setAOI = function(_aoi) -{ - if (_aoi == null) - { - _aoi = new egwActionObjectDummyInterface(); - } - - // Copy the state from the old interface - if (this.iface) - { - _aoi.setState(this.iface.getState()); - } - - // Replace the interface object - this.iface = _aoi; - this.iface.setStateChangeCallback(this._ifaceCallback, this); - this.iface.setReconnectActionsCallback(this._reconnectCallback, this); -}; - -/** - * Returns the object from the tree with the given ID - * - * @param {string} _id - * @param {number} _search_depth - * @return {egwActionObject} description - * @todo Add search function to egw_action_commons.js - */ -egwActionObject.prototype.getObjectById = function(_id, _search_depth) -{ - if (this.id == _id) - { - return this; - } - if (typeof _search_depth == "undefined") { - _search_depth = Number.MAX_VALUE; - } - - for (var i = 0; i < this.children.length && _search_depth > 0; i++) - { - var obj = this.children[i].getObjectById(_id, _search_depth - 1); - if (obj) - { - return obj; - } - } - - return null; -}; - -/** - * Adds an object as child to the actionObject and returns it - if the supplied - * parameter is a object, the object will be added directly, otherwise an object - * with the given id will be created. - * - * @param {(string|object)} _id Id of the object which will be created or the object - * that will be added. - * @param {object} _interface if _id was an string, _interface defines the interface which - * will be connected to the newly generated object. - * @param {number} _flags are the flags will which be supplied to the newly generated - * object. May be omitted. - * @returns object the generated object - */ -egwActionObject.prototype.addObject = function(_id, _interface, _flags) -{ - return this.insertObject(false, _id, _interface, _flags); -}; - -/** - * Inserts an object as child to the actionObject and returns it - if the supplied - * parameter is a object, the object will be added directly, otherwise an object - * with the given id will be created. - * - * @param {number} _index Position where the object will be inserted, "false" will add it - * to the end of the list. - * @param {string|object} _id Id of the object which will be created or the object - * that will be added. - * @param {object} _iface if _id was an string, _iface defines the interface which - * will be connected to the newly generated object. - * @param {number} _flags are the flags will which be supplied to the newly generated - * object. May be omitted. - * @returns object the generated object - */ -egwActionObject.prototype.insertObject = function(_index, _id, _iface, _flags) -{ - if (_index === false) - _index = this.children.length; - - var obj = null; - - if (typeof _id == "object") - { - obj = _id; - - // Set the parent to null and reset the focus of the object - obj.parent = null; - obj.setFocused(false); - - // Set the parent to this object - obj.parent = this; - } - else if (typeof _id == "string") - { - obj = new egwActionObject(_id, this, _iface, this.manager, _flags); - } - - if (obj) - { - // Add the element to the children - this.children.splice(_index, 0, obj); - } - else - { - throw "Error while adding new element to the ActionObjects!"; - } - - return obj; -}; - -/** - * Deletes all children of the egwActionObject - */ -egwActionObject.prototype.clear = function() { - // Remove all children - while (this.children.length > 0) { - this.children[0].remove(); - } - - // Delete all other references - this.selectedChildren = []; - this.focusedChild = null; - - // Remove links - this.actionLinks = []; -}; - -/** - * Deletes this object from the parent container - */ -egwActionObject.prototype.remove = function() { - // Remove focus and selection from this element - this.setFocused(false); - this.setSelected(false); - this.setAllSelected(false); - - // Unregister all registered action implementations - this.unregisterActions(); - - // Clear the child-list - this.clear(); - - // Remove this element from the parent list - if (this.parent != null) - { - var idx = this.parent.children.indexOf(this); - - if (idx >= 0) - { - this.parent.children.splice(idx, 1); - } - } -}; - -/** - * Searches for the root object in the action object tree and returns it. - */ -egwActionObject.prototype.getRootObject = function() -{ - if (this.parent === null) - { - return this; - } - else - { - return this.parent.getRootObject(); - } -}; - -/** - * Returns a list with all parents of this object. - */ -egwActionObject.prototype.getParentList = function() -{ - if (this.parent === null) - { - return []; - } - else - { - var list = this.parent.getParentList(); - list.unshift(this.parent); - return list; - } -}; - -/** - * Returns the first parent which has the container flag - */ -egwActionObject.prototype.getContainerRoot = function() -{ - if (egwBitIsSet(this.flags, EGW_AO_FLAG_IS_CONTAINER) || this.parent === null) - { - return this; - } - else - { - return this.parent.getContainerRoot(); - } -}; - -/** - * Returns all selected objects which are in the current subtree. - * - * @param {function} _test is a function, which gets an object and checks whether - * it will be added to the list. - * @param {array} _list is internally used to fetch all selected elements, please - * omit this parameter when calling the function. - */ -egwActionObject.prototype.getSelectedObjects = function(_test, _list) -{ - if (typeof _test == "undefined") - _test = null; - - if (typeof _list == "undefined") - { - _list = {"elements": []}; - } - - if ((!_test || _test(this)) && this.getSelected()) - _list.elements.push(this); - - if (this.selectedChildren) - { - for (var i = 0; i < this.selectedChildren.length; i++) - { - this.selectedChildren[i].getSelectedObjects(_test, _list); - } - } - - return _list.elements; -}; - -/** - * Returns whether all objects in this tree are selected - */ -egwActionObject.prototype.getAllSelected = function() -{ - if (this.children.length == this.selectedChildren.length) - { - for (var i = 0; i < this.children.length; i++) - { - if (!this.children[i].getAllSelected()) - return false; - } - // If this element is an container *and* does not have any children, we - // should return false. If this element is not an container we have to - // return true has this is the recursion base case - return (!egwBitIsSet(this.flags, EGW_AO_FLAG_IS_CONTAINER)) || - (this.children.length > 0); - } - - return false; -}; - -/** - * Toggles the selection of all objects. - * - * @param _select boolean specifies whether the objects should get selected or not. - * If this parameter is not supplied, the selection will be toggled. - */ -egwActionObject.prototype.toggleAllSelected = function(_select) -{ - if (typeof _select == "undefined") - { - _select = !this.getAllSelected(); - } - - // Check for a select_all action - if(_select && this.manager && this.manager.getActionById('select_all')) - { - return this.manager.getActionById('select_all').execute(this); - } - this.setAllSelected(_select); -}; - -/** - * Creates a list which contains all items of the element tree. - * - * @param {boolean} _visibleOnly - * @param {object} _obj is used internally to pass references to the array inside - * the object. - * @return {array} - */ -egwActionObject.prototype.flatList = function(_visibleOnly, _obj) -{ - if (typeof(_obj) == "undefined") - { - _obj = { - "elements": [] - }; - } - - if (typeof(_visibleOnly) == "undefined") - { - _visibleOnly = false; - } - - if (!_visibleOnly || this.getVisible()) - { - _obj.elements.push(this); - } - - for (var i = 0; i < this.children.length; i++) - { - this.children[i].flatList(_visibleOnly, _obj); - } - - return _obj.elements; -}; - -/** - * Returns a traversal list with all objects which are in between the given object - * and this one. The operation returns an empty list, if a container object is - * found on the way. - * - * @param {object} _to - * @return {array} - * @todo Remove flatList here! - */ -egwActionObject.prototype.traversePath = function(_to) -{ - var contRoot = this.getContainerRoot(); - - if (contRoot) - { - // Get a flat list of all the hncp elements and search for this object - // and the object supplied in the _to parameter. - var flatList = contRoot.flatList(); - var thisId = flatList.indexOf(this); - var toId = flatList.indexOf(_to); - - // Check whether both elements have been found in this part of the tree, - // return the slice of that list. - if (thisId !== -1 && toId !== -1) - { - var from = Math.min(thisId, toId); - var to = Math.max(thisId, toId); - - return flatList.slice(from, to + 1); - } - } - - return []; -}; - -/** - * Returns the index of this object in the children list of the parent object. - */ -egwActionObject.prototype.getIndex = function() -{ - if (this.parent === null) - { - return 0; - } - else - { - return this.parent.children.indexOf(this); - } -}; - -/** - * Returns the deepest object which is currently focused. Objects with the - * "container"-flag will not be returned. - */ -egwActionObject.prototype.getFocusedObject = function() -{ - return this.focusedChild || null; -}; - -/** - * Internal function which is connected to the ActionObjectInterface associated - * with this object in the constructor. It gets called, whenever the object - * gets (de)selected. - * - * @param {number} _newState is the new state of the object - * @param {number} _changedBit - * @param {number} _shiftState is the status of extra keys being pressed during the - * selection process. - * @param {number} - */ -egwActionObject.prototype._ifaceCallback = function(_newState, _changedBit, _shiftState) -{ - if (typeof _shiftState == "undefined") - _shiftState = EGW_AO_SHIFT_STATE_NONE; - - var selected = egwBitIsSet(_newState, EGW_AO_STATE_SELECTED); - var visible = egwBitIsSet(_newState, EGW_AO_STATE_VISIBLE); - - // Check whether the visibility of the object changed - if (_changedBit == EGW_AO_STATE_VISIBLE && visible != this.getVisible()) - { - // Deselect the object - if (!visible) - { - this.setSelected(false); - this.setFocused(false); - return EGW_AO_STATE_NORMAL; - } - else - { - // Auto-register the actions attached to this object - this.registerActions(); - } - } - - // Remove the focus from all children on the same level - if (this.parent && visible && _changedBit == EGW_AO_STATE_SELECTED) - { - var selected = egwBitIsSet(_newState, EGW_AO_STATE_SELECTED); - var objs = []; - - if (selected) - { - // Search the index of this object - var id = this.parent.children.indexOf(this); - - // Deselect all other objects inside this container, if the "MULTI" shift- - // state is not set - if (!egwBitIsSet(_shiftState, EGW_AO_SHIFT_STATE_MULTI)) - { - var lst = this.getContainerRoot().setAllSelected(false); - } - - // If the LIST state is active, get all objects inbetween this one and the focused one - // and set their select state. - if (egwBitIsSet(_shiftState, EGW_AO_SHIFT_STATE_BLOCK)) - { - var focused = this.getFocusedObject(); - if (focused) - { - objs = this.traversePath(focused); - for (var i = 0; i < objs.length; i++) - { - objs[i].setSelected(true); - } - } - } - } - - // If the focused element didn't belong to this container, or the "list" - // shift-state isn't active, set the focus to this element. - if (objs.length == 0 || !egwBitIsSet(_shiftState, EGW_AO_SHIFT_STATE_BLOCK)) - { - this.setFocused(true); - _newState = egwSetBit(EGW_AO_STATE_FOCUSED, _newState, true); - } - - this.setSelected(selected); - } - - return _newState; -}; - -/** - * Handler for key presses - * - * @param {number} _keyCode - * @param {boolean} _shift - * @param {boolean} _ctrl - * @param {boolean} _alt - * @returns {boolean} - */ -egwActionObject.prototype.handleKeyPress = function(_keyCode, _shift, _ctrl, _alt) { - switch (_keyCode) { - case EGW_KEY_ARROW_UP: - case EGW_KEY_ARROW_DOWN: - case EGW_KEY_PAGE_UP: - case EGW_KEY_PAGE_DOWN: - - if (!_alt) - { - var intval = - (_keyCode == EGW_KEY_ARROW_UP || _keyCode == EGW_KEY_ARROW_DOWN) ? - 1 : 10; - - if (this.children.length > 0) - { - // Get the focused object - var focused = this.getFocusedObject(); - - // Determine the object which should get selected - var selObj = null; - if (!focused) - { - selObj = this.children[0]; - } - else - { - selObj = (_keyCode == EGW_KEY_ARROW_UP || _keyCode == EGW_KEY_PAGE_UP) ? - focused.getPrevious(intval) : focused.getNext(intval); - } - - if (selObj != null) - { - if (!_shift && !(this.parent && this.parent.data && this.parent.data.keyboard_select)) - { - this.setAllSelected(false); - } - else if (!(this.parent && this.parent.data && this.parent.data.keyboard_select)) - { - var objs = focused.traversePath(selObj); - for (var i = 0; i < objs.length; i++) - { - objs[i].setSelected(true); - } - } - - if(!(this.parent.data && this.parent.data.keyboard_select)) - { - selObj.setSelected(true); - } - selObj.setFocused(true); - - // Tell the aoi of the object to make it visible - selObj.makeVisible(); - } - - return true; - } - } - - break; - - // Space bar toggles selected for current row - case EGW_KEY_SPACE: - if (this.children.length <= 0) - { - break; - } - // Mark that we're selecting by keyboard, or arrows will reset selection - if(!this.parent.data) - { - this.parent.data = {}; - } - this.parent.data.keyboard_select = true; - - // Get the focused object - var focused = this.getFocusedObject(); - - focused.setSelected(!focused.getSelected()); - - // Tell the aoi of the object to make it visible - focused.makeVisible(); - return true; - - break; - // Handle CTRL-A to select all elements in the current container - case EGW_KEY_A: - if (_ctrl && !_shift && !_alt) - { - this.toggleAllSelected(); - return true; - } - - break; - } - - return false; -}; - -egwActionObject.prototype.getPrevious = function(_intval) -{ - if (this.parent != null) - { - if (this.getFocused() && !this.getSelected()) { - return this; - } - - var flatTree = this.getContainerRoot().flatList(); - - var idx = flatTree.indexOf(this); - if (idx > 0) - { - idx = Math.max(1, idx - _intval); - return flatTree[idx]; - } - } - - return this; -}; - -egwActionObject.prototype.getNext = function(_intval) -{ - if (this.parent != null) - { - if (this.getFocused() && !this.getSelected()) { - return this; - } - - var flatTree = this.getContainerRoot().flatList(true); - - var idx = flatTree.indexOf(this); - if (idx < flatTree.length - 1) - { - idx = Math.min(flatTree.length - 1, idx + _intval); - return flatTree[idx]; - } - } - - return this; -}; - -/** - * Returns whether the object is currently selected. - */ -egwActionObject.prototype.getSelected = function() -{ - return egwBitIsSet(this.getState(), EGW_AO_STATE_SELECTED); -}; - -/** - * Returns whether the object is currently focused. - */ -egwActionObject.prototype.getFocused = function() -{ - return egwBitIsSet(this.getState(), EGW_AO_STATE_FOCUSED); -}; - -/** - * Returns whether the object currently is visible - visible means, that the - * AOI has a dom node and is visible. - */ -egwActionObject.prototype.getVisible = function() -{ - return egwBitIsSet(this.getState(), EGW_AO_STATE_VISIBLE); -}; - -/** - * Returns the complete state of the object. - */ -egwActionObject.prototype.getState = function() -{ - return this.iface.getState(); -}; - - -/** - * Sets the focus of the element. The formerly focused element in the tree will - * be de-focused. - * - * @param {boolean} _focused - whether to remove or set the focus. Defaults to true - */ -egwActionObject.prototype.setFocused = function(_focused) -{ - if (typeof _focused == "undefined") - _focused = true; - - var state = this.iface.getState(); - - if (egwBitIsSet(state, EGW_AO_STATE_FOCUSED) != _focused) - { - // Un-focus the currently focused object - var currentlyFocused = this.getFocusedObject(); - if (currentlyFocused && currentlyFocused != this) - { - currentlyFocused.setFocused(false); - } - - this.iface.setState(egwSetBit(state, EGW_AO_STATE_FOCUSED, _focused)); - if (this.parent) - { - this.parent.updateFocusedChild(this, _focused); - } - } - - if (this.focusedChild != null && _focused == false) - { - this.focusedChild.setFocused(false); - } -}; - -/** - * Sets the selected state of the element. - * - * @param {boolean} _selected - * @TODO Callback - */ -egwActionObject.prototype.setSelected = function(_selected) -{ - var state = this.iface.getState(); - - if ((egwBitIsSet(state, EGW_AO_STATE_SELECTED) != _selected) && - egwBitIsSet(state, EGW_AO_STATE_VISIBLE)) - { - this.iface.setState(egwSetBit(state, EGW_AO_STATE_SELECTED, _selected)); - if (this.parent) - { - this.parent.updateSelectedChildren(this, _selected || this.selectedChildren.length > 0); - } - } -}; - -/** - * Sets the selected state of all elements, including children - * - * @param {boolean} _selected - * @param {boolean} _informParent - */ -egwActionObject.prototype.setAllSelected = function(_selected, _informParent) -{ - if (typeof _informParent == "undefined") - _informParent = true; - - var state = this.iface.getState(); - - // Update this element - if (egwBitIsSet(state, EGW_AO_STATE_SELECTED) != _selected) - { - this.iface.setState(egwSetBit(state, EGW_AO_STATE_SELECTED, _selected)); - if (_informParent && this.parent) - { - this.parent.updateSelectedChildren(this, _selected); - } - if(this.parent.data && this.parent.data.keyboard_select) - { - this.parent.data.keyboard_select = false; - } - } - - // Update the children if the should be selected or if they should be - // deselected and there are selected children. - if (_selected || this.selectedChildren.length > 0) - { - for (var i = 0; i < this.children.length; i++) - { - this.children[i].setAllSelected(_selected, false); - } - } - - // Copy the selected children list - this.selectedChildren = []; - if (_selected) - { - for (var i = 0; i < this.children.length; i++) - { - this.selectedChildren.push(this.children[i]); - } - } - - // Call the setSelectedCallback - egwQueueCallback(this.setSelectedCallback, [], this, "setSelectedCallback"); -}; - - -/** - * Updates the selectedChildren array each actionObject has in order to determine - * all selected children in a very fast manner. - * - * @param {(string|egwActionObject} _child - * @param {boolean} _selected - * @todo Has also to be updated, if an child is added/removed! - */ -egwActionObject.prototype.updateSelectedChildren = function(_child, _selected) -{ - var id = this.selectedChildren.indexOf(_child); // TODO Replace by binary search, insert children sorted by index! - var wasEmpty = this.selectedChildren.length == 0; - - // Add or remove the given child from the selectedChildren list - if (_selected && id == -1) - { - this.selectedChildren.push(_child); - } - else if (!_selected && id != -1) - { - this.selectedChildren.splice(id, 1); - } - - // If the emptieness of the selectedChildren array has changed, update the - // parent selected children array. - if (wasEmpty != this.selectedChildren.length == 0 && this.parent) - { - this.parent.updateSelectedChildren(this, wasEmpty); - } - - // Call the setSelectedCallback - egwQueueCallback(this.setSelectedCallback, this.getContainerRoot().getSelectedObjects(), this, "setSelectedCallback"); -}; - -/** - * Updates the focusedChild up to the container boundary. - * - * @param {(string|egwActionObject} _child - * @param {boolean} _focused - */ -egwActionObject.prototype.updateFocusedChild = function(_child, _focused) -{ - if (_focused) - { - this.focusedChild = _child; - } - else - { - if (this.focusedChild == _child) - { - this.focusedChild = null; - } - } - - if (this.parent /*&& !egwBitIsSet(this.flags, EGW_AO_FLAG_IS_CONTAINER)*/) - { - this.parent.updateFocusedChild(_child, _focused); - } -}; - -/** - * Updates the actionLinks of the given ActionObject. - * - * @param {array} _actionLinks contains the information about the actionLinks which - * should be updated as an array of objects. Example - * [ - * { - * "actionId": "file_delete", - * "enabled": true - * } - * ] - * If an supplied link doesn't exist yet, it will be created (if _doCreate is true) - * and added to the list. Otherwise the information will just be updated. - * @param {boolean} _recursive If true, the settings will be applied to all child - * object (default false) - * @param {boolean} _doCreate If true, not yet existing links will be created (default true) - */ -egwActionObject.prototype.updateActionLinks = function(_actionLinks, _recursive, _doCreate) -{ - if (typeof _recursive == "undefined") - _recursive = false; - if (typeof _doCreate == "undefined") - _doCreate = true; - - for (var i = 0; i < _actionLinks.length; i++) - { - var elem = _actionLinks[i]; - - // Allow single strings for simple action links. - if (typeof elem == "string") - { - elem = {"actionId": elem}; - } - - if (typeof elem.actionId != "undefined" && elem.actionId) - { - //Get the action link object, if it doesn't exist yet, create it - var actionLink = this.getActionLink(elem.actionId); - if (!actionLink && _doCreate) - { - actionLink = new egwActionLink(this.manager); - this.actionLinks.push(actionLink); - } - - //Set the supplied data - if (actionLink) - { - actionLink.updateLink(elem); - } - } - } - - if (_recursive) - { - for (var i = 0; i < this.children.length; i++) - { - this.children[i].updateActionLinks(_actionLinks, true, _doCreate); - } - } - - if (this.getVisible() && this.iface != null) - { - this.registerActions(); - } -}; - -/** - * Reconnects the actions. - */ -egwActionObject.prototype._reconnectCallback = function() -{ - this.registeredImpls = []; - this.registerActions(); -}; - -/** - * Registers the action implementations inside the DOM-Tree. - */ -egwActionObject.prototype.registerActions = function() -{ - var groups = this.getActionImplementationGroups(); - - for (var group in groups) - { - // Get the action implementation for each group - if (typeof _egwActionClasses[group] != "undefined" && - _egwActionClasses[group].implementation && - this.iface) - { - var impl = _egwActionClasses[group].implementation(); - - if (this.registeredImpls.indexOf(impl) == -1) - { - // Register a handler for that action with the interface of that object, - // the callback and this object as context for the callback - if (impl.registerAction(this.iface, this.executeActionImplementation, this)) - { - this.registeredImpls.push(impl); - } - } - } - } -}; - -/** - * Unregisters all action implementations registerd to this element - */ -egwActionObject.prototype.unregisterActions = function() -{ - while (this.registeredImpls.length > 0) { - var impl = this.registeredImpls.pop(); - if (this.iface) { - impl.unregisterAction(this.iface); - } - } -}; - - -/** - * Calls the onBeforeTrigger function - if it is set - or returns false. - */ -egwActionObject.prototype.triggerCallback = function() -{ - if (this.onBeforeTrigger) - { - return this.onBeforeTrigger(); - } - return true; -}; - -/** - * Calls the corresponding function of the AOI which tries to make the object - * visible. - */ -egwActionObject.prototype.makeVisible = function() -{ - this.iface.makeVisible(); -}; - - -/** - * Executes the action implementation which is associated to the given action type. - * - * @param {object} _implContext is data which should be delivered to the action implementation. - * E.g. in case of the popup action implementation, the x and y coordinates where the - * menu should open, and contextmenu event are transmitted. - * @param {string} _implType is the action type for which the implementation should be - * executed. - * @param {number} _execType specifies in which context the execution should take place. - * defaults to EGW_AO_EXEC_SELECTED - */ -egwActionObject.prototype.executeActionImplementation = function(_implContext, _implType, _execType) -{ - if (typeof _execType == "undefined") - { - _execType = EGW_AO_EXEC_SELECTED; - } - - if (typeof _implType == "string") - { - _implType = _egwActionClasses[_implType].implementation(); - } - - if (typeof _implType == "object" && _implType) - { - if (_execType == EGW_AO_EXEC_SELECTED) - { - if (!(egwBitIsSet(EGW_AO_FLAG_IS_CONTAINER, this.flags))) - { - this.forceSelection(); - } - var selectedActions = this.getSelectedLinks(_implType.type); - } - else if (_execType == EGW_AO_EXEC_THIS) - { - selectedActions = this._getLinks([this], _implType.type); - } - - if (selectedActions.selected.length > 0 && egwObjectLength(selectedActions.links) > 0) - { - return _implType.executeImplementation(_implContext, - selectedActions.selected, selectedActions.links); - } - } - - return false; -}; - -/** - * Forces the object to be inside the currently selected objects. If this is - * not the case, the object will select itself and deselect all other objects. - */ -egwActionObject.prototype.forceSelection = function() -{ - var selected = this.getContainerRoot().getSelectedObjects(); - - // Check whether this object is in the list - var thisInList = selected.indexOf(this) != -1; - - // If not, select it - if (!thisInList) - { - this.getContainerRoot().setAllSelected(false); - this.setSelected(true); - } - - this.setFocused(true); -}; - -/** - * Returns all selected objects, and all action links of those objects, which are - * of the given implementation type, wheras actionLink properties such as - * "enabled" and "visible" are accumulated. - * - * Objects have the chance to change their action links or to deselect themselves - * in the onBeforeTrigger event, which is evaluated by the triggerCallback function. - * - * @param _actionType is the action type for which the actionLinks should be collected. - * @returns object An object which contains a "links" and a "selected" section with - * an array of links/selected objects- - */ -egwActionObject.prototype.getSelectedLinks = function(_actionType) -{ - // Get all objects in this container which are currently selected - var selected = this.getContainerRoot().getSelectedObjects(); - - return this._getLinks(selected, _actionType); -}; - -/** - * - * @param {array} _objs - * @param {string} _actionType - * @return {object} with attributes "selected" and "links" - */ -egwActionObject.prototype._getLinks = function(_objs, _actionType) -{ - var actionLinks = {}; - var testedSelected = []; - - var test = function(olink) - { - // Test whether the action type is of the given implementation type - if (olink.actionObj.type == _actionType) - { - if (typeof actionLinks[olink.actionId] == "undefined") - { - actionLinks[olink.actionId] = { - "actionObj": olink.actionObj, - "enabled": (testedSelected.length == 1), - "visible": false, - "cnt": 0 - }; - } - - // Accumulate the action link properties - var llink = actionLinks[olink.actionId]; - llink.enabled = llink.enabled && olink.actionObj.enabled.exec(olink.actionObj, _objs, _objs[i]) && - olink.enabled && olink.visible; - llink.visible = (llink.visible || olink.visible); - llink.cnt++; - - // Add in children, so they can get checked for visible / enabled - if(olink.actionObj && olink.actionObj.children.length > 0) - { - for(var j = 0; j < olink.actionObj.children.length; j++) - { - var child = olink.actionObj.children[j]; - test({ - actionObj: child, - actionId: child.id, - enabled: olink.enabled, - visible: olink.visible - }); - } - } - } - }; - - for (var i = 0; i < _objs.length; i++) - { - var obj = _objs[i]; - if (!egwBitIsSet(obj.flags, EGW_AO_FLAG_IS_CONTAINER) && obj.triggerCallback()) - { - testedSelected.push(obj); - - for (var j = 0; j < obj.actionLinks.length; j++) - { - test(obj.actionLinks[j]); //object link - } - } - } - - // Check whether all objects supported the action - for (var k in actionLinks) - { - actionLinks[k].enabled = actionLinks[k].enabled && - (actionLinks[k].cnt >= testedSelected.length) && - ( - (actionLinks[k].actionObj.allowOnMultiple === true) || - (actionLinks[k].actionObj.allowOnMultiple == "only" && _objs.length > 1) || - (actionLinks[k].actionObj.allowOnMultiple == false && _objs.length === 1) || - (typeof actionLinks[k].actionObj.allowOnMultiple === 'number' && _objs.length == actionLinks[k].actionObj.allowOnMultiple) - ); - if (!egwIsMobile()) actionLinks[k].actionObj.hideOnMobile = false; - actionLinks[k].visible = actionLinks[k].visible && !actionLinks[k].actionObj.hideOnMobile && - (actionLinks[k].enabled || !actionLinks[k].actionObj.hideOnDisabled); - } - - // Return an object which contains the accumulated actionLinks and all selected - // objects. - return { - "selected": testedSelected, - "links": actionLinks - }; -}; - -/** - * Returns the action link, which contains the association to the action with - * the given actionId. - * - * @param {string} _actionId name of the action associated to the link - */ -egwActionObject.prototype.getActionLink = function(_actionId) -{ - for (var i = 0; i < this.actionLinks.length; i++) - { - if (this.actionLinks[i].actionObj.id == _actionId) - { - return this.actionLinks[i]; - } - } - - return null; -}; - -/** - * Returns all actions associated to the object tree, grouped by type. - * - * @param {function} _test gets an egwActionObject and should return, whether the - * actions of this object are added to the result. Defaults to a "always true" - * function. - * @param {object} _groups is an internally used parameter, may be omitted. - */ -egwActionObject.prototype.getActionImplementationGroups = function(_test, _groups) -{ - // If the _groups parameter hasn't been given preset it to an empty object - // (associative array). - if (typeof _groups == "undefined") - _groups = {}; - if (typeof _test == "undefined") - _test = function(_obj) {return true;}; - - for (var i = 0; i < this.actionLinks.length; i++) - { - var action = this.actionLinks[i].actionObj; - if (typeof action != "undefined" && _test(this)) - { - if (typeof _groups[action.type] == "undefined") - { - _groups[action.type] = []; - } - - _groups[action.type].push( - { - "object": this, - "link": this.actionLinks[i] - } - ); - } - } - - // Recursively add the actions of the children to the result (as _groups is - // an object, only the reference is passed). - for (var i = 0; i < this.children.length; i++) - { - this.children[i].getActionImplementationGroups(_test, _groups); - } - - return _groups; -}; - -/** - * Check if user tries to get dragOut action - * - * keys for dragOut: - * -Mac: Command + Shift - * -Others: Alt + Shift - * - * @param {event} _event - * @return {boolean} return true if Alt+Shift keys and left mouse click arre pressed, otherwise false - */ -egwActionObject.prototype.isDragOut = function (_event) -{ - return (_event.altKey || _event.metaKey) && _event.shiftKey && _event.which == 1; -}; - -/** - * Check if user tries to get selection action - * - * Keys for selection: - * -Mac: Command key - * -Others: Ctrl key - * - * @param {type} _event - * @returns {Boolean} return true if left mouse click and Ctrl/Alt key are pressed, otherwise false - */ -egwActionObject.prototype.isSelection = function (_event) -{ - return !(_event.shiftKey) && _event.which == 1 && (_event.metaKey || _event.ctrlKey || _event.altKey); -}; - -/** egwActionObjectInterface Interface **/ - -/** - * The egwActionObjectInterface has to be implemented for each actual object in - * the browser. E.g. for the object "DataGridRow", there has to be an - * egwActionObjectInterface which is responsible for returning the outer DOMNode - * of the object to which JS-Events may be attached by the egwActionImplementation - * object, and to do object specific stuff like highlighting the object in the - * correct way and to route state changes (like: "object has been selected") - * to the egwActionObject object the interface is associated to. - * - * @return {egwActionObjectInterface} - */ -export function egwActionObjectInterface() -{ - //Preset the interface functions - - this.doGetDOMNode = function() {return null;}; - - // _outerCall may be used to determine, whether the state change has been - // evoked from the outside and the stateChangeCallback has to be called - // or not. - this.doSetState = function(_state, _outerCall) {}; - - // The doTiggerEvent function may be overritten by the aoi if it wants to - // support certain action implementation specific events like EGW_AI_DRAG_OVER - // or EGW_AI_DRAG_OUT - this.doTriggerEvent = function(_event, _data) {return false;}; - - this.doMakeVisible = function() {}; - - this._state = EGW_AO_STATE_NORMAL || EGW_AO_STATE_VISIBLE; - - this.stateChangeCallback = null; - this.stateChangeContext = null; - this.reconnectActionsCallback = null; - this.reconnectActionsContext = null; -} - -/** - * Sets the callback function which will be called when a user interaction changes - * state of the object. - * - * @param {function} _callback - * @param {object} _context - */ -egwActionObjectInterface.prototype.setStateChangeCallback = function(_callback, _context) -{ - this.stateChangeCallback = _callback; - this.stateChangeContext = _context; -}; - -/** - * Sets the reconnectActions callback, which will be called by the AOI if its - * DOM-Node has been replaced and the actions have to be re-registered. - * - * @param {function} _callback - * @param {object} _context - */ -egwActionObjectInterface.prototype.setReconnectActionsCallback = function(_callback, _context) -{ - this.reconnectActionsCallback = _callback; - this.reconnectActionsContext = _context; -}; - -/** - * Will be called by the aoi if the actions have to be re-registered due to a - * DOM-Node exchange. - */ -egwActionObjectInterface.prototype.reconnectActions = function() -{ - if (this.reconnectActionsCallback) - { - this.reconnectActionsCallback.call(this.reconnectActionsContext); - } -}; - -/** - * Internal function which should be used whenever the select status of the object - * has been changed by the user. This will automatically calculate the new state of - * the object and call the stateChangeCallback (if it has been set) - * - * @param {number} _stateBit is the bit in the state bit which should be changed - * @param {boolean} _set specifies whether the state bit should be set or not - * @param {boolean} _shiftState - */ -egwActionObjectInterface.prototype.updateState = function(_stateBit, _set, _shiftState) -{ - // Calculate the new state - var newState = egwSetBit(this._state, _stateBit, _set); - - // Call the stateChangeCallback if the state really changed - if (this.stateChangeCallback) - { - this._state = this.stateChangeCallback.call(this.stateChangeContext, newState, - _stateBit, _shiftState); - } - else - { - this._state = newState; - } -}; - -/** - * Returns the DOM-Node the ActionObject is actually a representation of. - * Calls the internal "doGetDOMNode" function, which has to be overwritten - * by implementations of this class. - */ -egwActionObjectInterface.prototype.getDOMNode = function() -{ - return this.doGetDOMNode(); -}; - -/** - * Sets the state of the object. - * Calls the internal "doSetState" function, which has to be overwritten - * by implementations of this class. The state-change callback must not be evoked! - * - * @param _state is the state of the object. - */ -egwActionObjectInterface.prototype.setState = function(_state) -{ - //Call the doSetState function with the new state (if it has changed at all) - if (_state != this._state) - { - this._state = _state; - this.doSetState(_state); - } -}; - -/** - * Returns the current state of the object. The state is maintained by the - * egwActionObjectInterface and implementations do not have to overwrite this - * function as long as they call the _selectChange function. - */ -egwActionObjectInterface.prototype.getState = function() -{ - return this._state; -}; - -/** - * The trigger event function can be called by the action implementation in order - * to tell the AOI to perform some action. - * In the drag/drop handler this function is e.g. used for telling the droppable - * element that there was a drag over/out event. - * - * @param {object} _event - * @param _data - */ -egwActionObjectInterface.prototype.triggerEvent = function(_event, _data) -{ - if (typeof _data == "undefined") - { - _data = null; - } - - return this.doTriggerEvent(_event, _data); -}; - -/** - * Scrolls the element into a visble area if it is currently hidden - */ -egwActionObjectInterface.prototype.makeVisible = function() -{ - return this.doMakeVisible(); -}; - -/** -- egwActionObjectDummyInterface Class -- **/ - -var egwActionObjectDummyInterface = egwActionObjectInterface; - -/** egwActionObjectManager Object **/ - -/** - * The egwActionObjectManager is a dummy class which only contains a dummy - * AOI. It may be used as root object or as object containers. - * - * @param {string} _id - * @param {string} _manager - * @return {egwActionObjectManager} - */ -export function egwActionObjectManager(_id, _manager) -{ - var ao = new egwActionObject(_id, null, new egwActionObjectInterface(), - _manager, EGW_AO_FLAG_IS_CONTAINER); - - // The object manager doesn't allow selections and cannot perform actions - ao.triggerCallback = function() {return false;}; - - return ao; -} diff --git a/api/js/egw_action/egw_action.ts b/api/js/egw_action/egw_action.ts new file mode 100755 index 0000000000..5354a75491 --- /dev/null +++ b/api/js/egw_action/egw_action.ts @@ -0,0 +1,410 @@ +/** + * EGroupware egw_action framework - egw action framework + * + * @link https://www.egroupware.org + * @author Andreas Stöckel + * @copyright 2011 by Andreas Stöckel + * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License + * @package egw_action + */ + +import {EGW_AO_STATE_NORMAL, EGW_AO_STATE_VISIBLE, EGW_AO_STATES} from './egw_action_constants'; +import {egwSetBit} from './egw_action_common'; +import '././egw_action_popup'; +import "./egw_menu_dhtmlx"; +import {EgwAction} from "./EgwAction"; +import {EgwActionManager} from "./EgwActionManager"; +import {EgwActionImplementation} from "./EgwActionImplementation"; +import {EgwActionLink} from "./EgwActionLink"; +import {EgwActionObject} from "./EgwActionObject"; +import {EgwActionObjectInterface} from "./EgwActionObjectInterface"; +import {EgwActionObjectManager} from "./EgwActionObjectManager"; +import {EgwDragAction} from "./EgwDragAction"; +import {EgwDragActionImplementation} from "./egwDragActionImplementation"; +import {EgwDropAction} from "./EgwDropAction"; +import {egwDropActionImplementation} from "./EgwDropActionImplementation"; +import "./egwGlobal" +import {EgwPopupAction} from "./EgwPopupAction"; +import {getPopupImplementation} from "./EgwPopupActionImplementation"; + + +/** + * Getter functions for the global egwActionManager and egwObjectManager objects + */ + +let egw_globalActionManager: EgwActionManager = null; +export var egw_globalObjectManager: EgwActionObjectManager = null; + +/** + * Returns the action manager for the given application - each application has its + * own sub-ActionManager in the global action manager object to prevent collisions + * from happening + * + * @param _id is the name of the sub-actionManager which should be returned. + * If the action manager does not exist right now, it is created. If the + * parameter is omitted or null, the global action manager is returned. + * @param {boolean} [_create=true] If an objectManager with the given id is not + * found, it will be created at the top level. + * @param {number} [_search_depth=Infinite] How deep into existing action children + * to search. + */ +export function egw_getActionManager(_id?: string, _create: boolean = true, _search_depth: number = Number.MAX_VALUE) { + + // Check whether the global action manager had been created, if not do so + let res = egw_globalActionManager; + if (egw_globalActionManager == null) { + res = egw_globalActionManager = new EgwActionManager(); + } + + // Check whether the sub-action manager exists, if not, create it + if (typeof _id != 'undefined' && _id != null) { + res = egw_globalActionManager.getActionById(_id, _search_depth); + if (res == null && _create) { + res = egw_globalActionManager.addAction("actionManager", _id); + } + } + + return res; +} + +/** + * Returns the object manager for the given application - each application may + * have its own object manager where it can place action objects or containers. + * + * @param _id is the name of the sub-object manager should be returned. If the + * object manager does not exist right now, it is created. If the parameter + * is ommited or null, the global object manager is returned. + * @param {boolean} [_create=true] If an objectManager with the given id is not + * found, it will be created at the top level. + * @param {number} [_search_depth=Infinite] How deep into existing action children + * to search. + */ +export function egw_getObjectManager(_id, _create = true, _search_depth = Number.MAX_VALUE): EgwActionObjectManager { + + // Check whether the global object manager exists + let res = egw_globalObjectManager; + if (res == null) { + res = egw_globalObjectManager = new EgwActionObjectManager("_egwGlobalObjectManager", egw_getActionManager()); + } + + // Check whether the sub-object manager exists, if not, create it + if (typeof _id != 'undefined' && _id != null) { + res = egw_globalObjectManager.getObjectById(_id, _search_depth); + if (res == null && _create) { + res = new EgwActionObjectManager(_id, egw_getActionManager(_id, true, _search_depth)); + egw_globalObjectManager.addObject(res); + } + } + + return res; +} + +/** + * Returns the object manager for the current application + * + * @param {boolean} _create + * @param {string} _appName //appName might not always be the current app, e.g. running app content under admin tab + * @return {EgwActionObjectManager} + */ +export function egw_getAppObjectManager(_create, _appName = "") { + return egw_getObjectManager(_appName ? _appName : window.egw(window).app_name(), _create, 1); +} + +/** + * Returns the action manager for the current application + * + * @param {boolean} _create + * @return {EgwActionManager} + */ +// this function is never used +export function egw_getAppActionManager(_create) { + return egw_getActionManager(window.egw_getAppName(), _create, 1); +} + + +/** egwActionHandler Interface **/ + +/** + * Constructor for the egwActionHandler interface which (at least) should have the + * execute function implemented. + * + * @param {function} _executeEvent + * @return {egwActionHandler} + * TODO no usage? + */ +export function egwActionHandler(_executeEvent) { + //Copy the executeEvent parameter + this.execute = _executeEvent; +} + + +/** egwAction Object + * @deprecated use EgwAction + * **/ +export class egwAction extends EgwAction { + +} + + +/** egwActionManager Object **/ + +/** + * @deprecated + */ +export class egwActionManager extends EgwActionManager { +} + +/** + * Associative array where action classes may register themselves + * + */ +if (typeof window._egwActionClasses == "undefined") { + window._egwActionClasses = { + actionManager: undefined, + default: undefined, + drag: undefined, + drop: undefined, + popup: undefined + }; +} +if (typeof window._egwActionClasses.actionManager == "undefined") { + window._egwActionClasses.actionManager = {actionConstructor: EgwActionManager, implementation: null} +} +if (typeof window._egwActionClasses.default == "undefined") { + window._egwActionClasses.default = {actionConstructor: EgwAction, implementation: null} +} +if (typeof window._egwActionClasses.drag == "undefined") { + window._egwActionClasses.drag = {actionConstructor: EgwDragAction, implementation: getDragImplementation()} +} +if (typeof window._egwActionClasses.drop == "undefined") { + window._egwActionClasses.drop = {actionConstructor: EgwDropAction, implementation: getDropImplementation()} +} + + + +if (typeof window._egwActionClasses.popup == "undefined") { + window._egwActionClasses.popup = { + "actionConstructor": EgwPopupAction, + "implementation": getPopupImplementation + }; +} + + +/** EgwActionImplementation Interface **/ + +/** + * @deprecated implement upperCase interface EgwActionImplementation instead + */ +export class egwActionImplementation implements EgwActionImplementation { + + type: string; + + doRegisterAction = function (...args) { + throw "Abstract function call: registerAction"; + }; + + doUnregisterAction = function (...args) { + throw "Abstract function call: unregisterAction"; + }; + + doExecuteImplementation = function (...args) { + throw "Abstract function call: executeImplementation"; + }; + + executeImplementation(_context: any, _selected: any, _links: any): any { + return this.doExecuteImplementation(_context, _selected, _links); + } + + registerAction(_actionObjectInterface: EgwActionObjectInterface, _triggerCallback: Function, _context: object = null): boolean { + return this.doRegisterAction(_actionObjectInterface, _triggerCallback, _context); + } + + unregisterAction(_actionObjectInterface: EgwActionObjectInterface): boolean { + return this.doUnregisterAction(_actionObjectInterface); + } + +} + +/** egwActionLink Object **/ + +/** + * @deprecated implement upperCase class instead + */ +export class egwActionLink extends EgwActionLink { +} + +/** + * @deprecated implement upperCase interface EgwActionImplementation instead + */ +export class egwActionObject extends EgwActionObject { +} + +/** egwActionObjectInterface Interface **/ + +/** + * @deprecated This is just a default wrapper class for the EgwActionObjectInterface interface. + * Please directly implement it instead! + * ... implements EgwActionObjectInterface{ + * getDomNode(){...} + * } + * instead of className{ + * var aoi = new egwActionObjectInterface() + * aoi.doGetDomNode = function ... + * } + * + * @return {egwActionObjectInterface} + */ +export class egwActionObjectInterface implements EgwActionObjectInterface { + //Preset the iface functions + + _state = EGW_AO_STATE_NORMAL || EGW_AO_STATE_VISIBLE; + + // _outerCall may be used to determine, whether the state change has been + // evoked from the outside and the stateChangeCallback has to be called + stateChangeCallback = null; + + // The doTriggerEvent function may be overwritten by the aoi if it wants to + // support certain action implementation specific events like EGW_AI_DRAG_OVER + stateChangeContext = null; + reconnectActionsCallback = null; + reconnectActionsContext = null; + + doGetDOMNode() { + return null; + }; + + // or not. + doSetState(_state) { + }; + + // or EGW_AI_DRAG_OUT + doTriggerEvent(_event, _data) { + return false; + }; + + doMakeVisible() { + }; + + getDOMNode(): Element { + return this.doGetDOMNode(); + } + + getState(): number { + return this._state; + } + + makeVisible(): void { + return this.doMakeVisible(); + } + + reconnectActions(): void { + if (this.reconnectActionsCallback) { + this.reconnectActionsCallback.call(this.reconnectActionsContext); + } + } + + setReconnectActionsCallback(_callback: Function, _context: any): void { + this.reconnectActionsCallback = _callback; + this.reconnectActionsContext = _context; + } + + setState(_state: any): void { + //Call the doSetState function with the new state (if it has changed at all) + if (_state != this._state) { + this._state = _state; + this.doSetState(_state); + } + } + + setStateChangeCallback(_callback: Function, _context: any): void { + this.stateChangeCallback = _callback; + this.stateChangeContext = _context; + } + + triggerEvent(_event: any, _data: any = null): boolean { + + return this.doTriggerEvent(_event, _data); + } + + updateState(_stateBit: number, _set: boolean, _shiftState: boolean): void { + // Calculate the new state + //this does not guarantee a valid state at runtime + const newState: EGW_AO_STATES = egwSetBit(this._state, _stateBit, _set); + + // Call the stateChangeCallback if the state really changed + if (this.stateChangeCallback) { + this._state = this.stateChangeCallback.call(this.stateChangeContext, newState, _stateBit, _shiftState); + } else { + this._state = newState; + } + } +} + +/** egwActionObjectManager Object **/ + + +/** + * @deprecated implement upperCase class instead + */ +export class egwActionObjectManager extends EgwActionObjectManager { +} + + +/** + * dragdrop + */ + +/** + * Register the drag and drop handlers + */ +if (typeof window._egwActionClasses == "undefined") + window._egwActionClasses = { + actionManager: undefined, + default: undefined, + drag: undefined, + drop: undefined, + popup: undefined + }; + +/** + * @deprecated + */ +export class egwDropAction extends EgwDropAction { +} + +window._egwActionClasses["drop"] = { + "actionConstructor": EgwDropAction, + "implementation": getDropImplementation +}; + +/** + * @deprecated + */ +export class egwDragAction extends EgwDragAction { +} + +(() => { + window._egwActionClasses.drag = { + "actionConstructor": EgwDragAction, "implementation": getDragImplementation + }; +})() + +let _dragActionImpl = null; + +export function getDragImplementation() { + if (!_dragActionImpl) { + _dragActionImpl = new EgwDragActionImplementation(); + } + return _dragActionImpl; +} + + +let _dropActionImpl = null; + +export function getDropImplementation() { + if (!_dropActionImpl) { + _dropActionImpl = new egwDropActionImplementation(); + } + return _dropActionImpl; +} + diff --git a/api/js/egw_action/egw_action_common.js b/api/js/egw_action/egw_action_common.js deleted file mode 100644 index ea45b11b13..0000000000 --- a/api/js/egw_action/egw_action_common.js +++ /dev/null @@ -1,520 +0,0 @@ -/** - * eGroupWare egw_action framework - egw action framework - * - * @link http://www.egroupware.org - * @author Andreas Stöckel - * @copyright 2011 by Andreas Stöckel - * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License - * @package egw_action - */ - -import {EGW_AO_SHIFT_STATE_BLOCK, EGW_AO_SHIFT_STATE_MULTI, EGW_AO_SHIFT_STATE_NONE} from "./egw_action_constants.js"; - -/** - * Sets properties given in _data in _obj. Checks whether the property keys - * exists and if corresponding setter functions are available. Properties starting - * with "_" are ignored. - * - * @param object _data may be an object with data that will be stored inside the - * given object. - * @param object _obj is the object where the data will be stored. - * @param mixed _setterOnly false: store everything, true: only store when setter exists, "data" store rest in data property - */ -export function egwActionStoreJSON(_data, _obj, _setterOnly) -{ - for (var key in _data) - { - if (key.charAt(0) != '_') - { - //Check whether there is a setter function available - if (typeof _obj['set_' + key] == "function") - { - _obj['set_' + key](_data[key]); - } - else if (typeof _obj[key] != "undefined" && !_setterOnly) - { - _obj[key] = _data[key]; - } - else if (_setterOnly === 'data') - { - if (typeof _data.data == 'undefined') _data.data = {}; - _data.data[key] = _data[key]; - _obj.set_data(_data.data); - } - } - } -} - -/** - * Switches the given bit in the set on or off. - * - * @param int _set is the current set - * @param int _bit is the position of the bit which should be switched on/off - * @param boolean _state is whether the bit should be switched on or off - * @returns the new set - */ -export function egwSetBit(_set, _bit, _state) -{ - if (_state) - return _set |= _bit; - else - return _set &= ~_bit; -} - -/** - * Returns whether the given bit is set in the set. - */ -export function egwBitIsSet(_set, _bit) -{ - return (_set & _bit) > 0; -} - -export function egwObjectLength(_obj) -{ - var len = 0; - for (var k in _obj) len++; - return len; -} - -/** - * IE Fix for array.indexOf - */ -if (typeof Array.prototype.indexOf == "undefined") -{ - Array.prototype.indexOf = function(_elem) { - for (var i = 0; i < this.length; i++) - { - if (this[i] === _elem) - return i; - } - return -1; - }; -} - -/** - * Isolates the shift state from an event object - */ -export function egwGetShiftState(e) -{ - var state = EGW_AO_SHIFT_STATE_NONE; - state = egwSetBit(state, EGW_AO_SHIFT_STATE_MULTI, e.ctrlKey || e.metaKey); - state = egwSetBit(state, EGW_AO_SHIFT_STATE_BLOCK, e.shiftKey); - - return state; -} - -export function egwPreventSelect(e) -{ - if (egwGetShiftState(e) > EGW_AO_SHIFT_STATE_NONE) - { - this.onselectstart = function() { - return false; - } - - return false; - } - - return true; -} - -export function egwResetPreventSelect(elem) -{ -} - -export function egwUnfocus() -{ - if (document.activeElement) - { - document.activeElement.blur(); - } -} - - -export function egwCallAbstract(_obj, _fn, _args) -{ - if (_fn) - { - return _fn.apply(_obj, _args); - } - else - { - throw "egw_action Exception: Abstract function call in JS code."; - } - - return false; -} - -export function egwArraysEqual(_ar1, _ar2) -{ - var result = _ar1.length == _ar2.length; - - for (var i = 0; i < _ar1.length; i++) - { - result = result && (_ar1[i] == _ar2[i]) - } - - return result; -} - -var _egwQueuedCallbacks = {}; -export function egwQueueCallback(_proc, _args, _context, _id) -{ - if (_proc) - { - var cur_id = 0; - if (typeof _egwQueuedCallbacks[_id] == "undefined") - { - cur_id = _egwQueuedCallbacks[_id] = 1; - } - else - { - cur_id = ++_egwQueuedCallbacks[_id]; - } - - window.setTimeout(function() { - if (_egwQueuedCallbacks[_id] == cur_id) - { - _proc.apply(_context, _args); - delete _egwQueuedCallbacks[_id]; - } - }, 0); - } -} - -/** - * The eventQueue object is used to have control over certain events such as - * ajax responses or timeouts. Sometimes it may happen, that a function attached - * to such an event should no longer be called - with egwEventQueue one has - * a simple possibility to control that. - */ - -/** - * Constructor for the egwEventQueue class. Initializes the queue object and the - * internal data structures such as the internal key. - */ -export function egwEventQueue() -{ - this.events = {}; - this.key_id = 0; -} - -/** - * Flushes all queued events - all events which had been queued in this queue objects - * can no longer be ran by calling egwEventQueue.run - */ -egwEventQueue.prototype.flush = function() -{ - this.events = {}; -} - -/** - * Queues the given function. A key is returned which can be passed to the "run" - * function which will then run the passed function. - * - * @param function _proc is the funciton which should be called - * @param object _context is the context in which the function should be called - * @param array _args is an optional array of parameters which should be passed - * to the function. Defaults to an emtpy array. - * @param string _id is an optional id which can be used to identify the event (may - * also be a key returned by this funciton). If the queue function is called multiple - * times for a given _id, the so called "call counter" of the event will be incremented. - * Each time "run" is called for a given event, the "call counter" will be decremented. - * Only if it reaches 0, the function connected to the event will be executed. - * - * @returns string the key which has to be passed to the "run" function. - */ -egwEventQueue.prototype.queue = function(_proc, _context, _args, _id) -{ - // Default _args to an empty array - if (typeof _args == "undefined" || !(_args instanceof Array)) - { - _args = []; - } - - // Increment the queue key which is used to store the event objectes with - // a unique key - this.key_id++; - - var key = ""; - - // _id must be a string and evaluate to true - if this is not - // generate an unique key. - if (typeof _id != "string" || !_id) - { - key = "ev_" + this.key_id; - } - else - { - key = _id; - } - - // Check whether an queued event with the given id already exists - var cnt = 1; - if (typeof this.events[key] != "undefined") - { - // The specified event already existed - increment the call counter. - cnt = this.events[key].cnt + 1; - } - - // Generate the event object - this.events[key] = { - "proc": _proc, - "context": _context, - "args": _args, - "cnt": cnt - } - - // Return the key which has to be passed to the "run" function in order to - // run the specified function - return key; -} - -/** - * Runs the event specified by the given key. The key is the string which had been - * returned by the queue function. - */ -egwEventQueue.prototype.run = function(_key) -{ - // Check whether the given key exists - if (typeof this.events[_key] != "undefined") - { - // Fetch the event object - var eventObj = this.events[_key]; - - // Decrement the call counter - eventObj.cnt--; - - // Only run the event if the call counter has reached zero - if (eventObj.cnt == 0) - { - // Run the event - eventObj.proc.apply(eventObj.context, eventObj.args); - - // Delete the given key from the event set - delete this.events[_key]; - } - } -} - -/** - * Does the same as "queue" but runs the event after the given timeout. - */ -egwEventQueue.prototype.queueTimeout = function(_proc, _context, _args, _id, _timeout) -{ - // Create the queue entry - var key = this.queue(_proc, _context, _args, _id); - - // Run the event in the setTimeout event. - var self = this; - window.setTimeout(function() { - self.run(key); - }, _timeout) -} - - -/** - * Class which is used to be able to handle references to JavaScript functions - * from strings. - * - * @param object _context is the context in which the function will be executed. - * @param mixed _default is the default value which should be returned when no - * function (string) has been set. If it is a function this function will be - * called. - * @param array _acceptedTypes is an array of types which contains the "typeof" - * strings of accepted non-functions in setValue - */ -export function egwFnct(_context, _default, _acceptedTypes) -{ - if (typeof _context == "undefined") - { - _context = null; - } - - if (typeof _default == "undefined") - { - _default = false; - } - - if (typeof _acceptedTypes == "undefined") - { - _acceptedTypes = ["boolean"]; - } - - this.context = _context; - this.acceptedTypes = _acceptedTypes; - this.fnct = null; - this.value = null; - // Flag for if this action is using a default handler - this.isDefault = false; - this.setValue(_default); -} - -egwFnct.prototype.hasHandler = function() -{ - return this.fnct !== null && !this.isDefault; -} - -/** - * Sets the function/return value for the exec function - */ -egwFnct.prototype.setValue = function(_value) -{ - this.value = null; - this.fnct = null; - - // Actual function - if (typeof _value == "function") - { - this.fnct = _value; - } - else if (typeof _value == "string" && _value.substring(0, 11) === 'javaScript:') - { - this.fnct = function() - { - const manager = this.context && this.context.getManager ? this.context.getManager() : null; - return egw.applyFunc(_value.substring(11), arguments, (manager ? manager.data.context : null) || window); - } - } - else if (this.acceptedTypes.indexOf(typeof _value) >= 0) - { - this.value = _value; - } - // Something, but could not figure it out - else if (_value) - { - egw.debug("warn", "Unable to parse Exec function %o for action '%s'",_value, this.context.id); - } -} - -/** - * Executes the function - */ -egwFnct.prototype.exec = function() -{ - if (this.fnct) - { - return this.fnct.apply(this.context, arguments); - } - else - { - return this.value; - } -} - -/** - * Checks whether this is currently run on a mobile browser - */ -var _egw_mobileBrowser = null; - -export function egwIsMobile() { - - if (_egw_mobileBrowser == null) - { - var ua = navigator.userAgent; - - _egw_mobileBrowser = - ua.match(/iPhone/i) || ua.match(/iPad/i) || ua.match(/iPod/) || - ua.match(/Android/i) || ua.match(/SymbianOS/i); - } - - return _egw_mobileBrowser; -} -window.egwIsMobile = egwIsMobile; - - -/** -sprintf() for JavaScript 0.6 - -Copyright (c) Alexandru Marasteanu -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of sprintf() for JavaScript nor the - names of its contributors may be used to endorse or promote products - derived from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL Alexandru Marasteanu BE LIABLE FOR ANY -DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - -Changelog: -2007.04.03 - 0.1: - - initial release -2007.09.11 - 0.2: - - feature: added argument swapping -2007.09.17 - 0.3: - - bug fix: no longer throws exception on empty paramenters (Hans Pufal) -2007.10.21 - 0.4: - - unit test and patch (David Baird) -2010.05.09 - 0.5: - - bug fix: 0 is now preceeded with a + sign - - bug fix: the sign was not at the right position on padded results (Kamal Abdali) - - switched from GPL to BSD license -2010.05.22 - 0.6: - - reverted to 0.4 and fixed the bug regarding the sign of the number 0 - Note: - Thanks to Raphael Pigulla (http://www.n3rd.org/) - who warned me about a bug in 0.5, I discovered that the last update was - a regress. I appologize for that. -**/ - -export function str_repeat(i, m) { - for (var o = []; m > 0; o[--m] = i); - return o.join(''); -} - -export function sprintf() { - var i = 0, a, f = arguments[i++], o = [], m, p, c, x, s = ''; - while (f) { - if (m = /^[^\x25]+/.exec(f)) { - o.push(m[0]); - } - else if (m = /^\x25{2}/.exec(f)) { - o.push('%'); - } - else if (m = /^\x25(?:(\d+)\$)?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-fosuxX])/.exec(f)) { - if (((a = arguments[m[1] || i++]) == null) || (a == undefined)) { - throw('Too few arguments.'); - } - if (/[^s]/.test(m[7]) && (typeof(a) != 'number')) { - throw('Expecting number but found ' + typeof(a)); - } - switch (m[7]) { - case 'b': a = a.toString(2); break; - case 'c': a = String.fromCharCode(a); break; - case 'd': a = parseInt(a); break; - case 'e': a = m[6] ? a.toExponential(m[6]) : a.toExponential(); break; - case 'f': a = m[6] ? parseFloat(a).toFixed(m[6]) : parseFloat(a); break; - case 'o': a = typeof(a) == 'number' ? a.toString(8):JSON.stringify(a); break; - case 's': a = ((a = String(a)) && m[6] ? a.substring(0, m[6]) : a); break; - case 'u': a = Math.abs(a); break; - case 'x': a = a.toString(16); break; - case 'X': a = a.toString(16).toUpperCase(); break; - } - a = (/[def]/.test(m[7]) && m[2] && a >= 0 ? '+'+ a : a); - c = m[3] ? m[3] == '0' ? '0' : m[3].charAt(1) : ' '; - x = m[5] - String(a).length - s.length; - p = m[5] ? str_repeat(c, x) : ''; - o.push(s + (m[4] ? a + p : p + a)); - } - else { - throw('Invalid sprintf format "' + arguments[0]+'"'); - } - f = f.substring(m[0].length); - } - return o.join(''); -} \ No newline at end of file diff --git a/api/js/egw_action/egw_action_common.ts b/api/js/egw_action/egw_action_common.ts new file mode 100755 index 0000000000..5eefd2eef3 --- /dev/null +++ b/api/js/egw_action/egw_action_common.ts @@ -0,0 +1,487 @@ +/** + * eGroupWare egw_action framework - egw action framework + * + * @link http://www.egroupware.org + * @author Andreas Stöckel + * @copyright 2011 by Andreas Stöckel + * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License + * @package egw_action + */ + +import {EGW_AO_SHIFT_STATE_BLOCK, EGW_AO_SHIFT_STATE_MULTI, EGW_AO_SHIFT_STATE_NONE} from "./egw_action_constants"; +// this import breaks shoelace with ("Illegal constructor (custom element class must be registered with global customElements registry to be newable)"); in SlAvatar +//et2 also has this import without problems +//import {egw} from "../jsapi/egw_global.js"; +/** + * Sets properties given in _data in _obj. Checks whether the property keys + * exists and if corresponding setter functions are available. Properties starting + * with "_" are ignored. + * + * @param _data may be an object with data that will be stored inside the + * given object. + * @param _obj is the object where the data will be stored. + * @param _setterOnly false: store everything, true: only store when setter function exists, "data" store rest in data property + */ +export function egwActionStoreJSON(_data: any, _obj: any, _setterOnly: boolean | string): void { + for (let key in _data) { + if (key.charAt(0) != '_') { + //Check whether there is a setter function available + if (typeof _obj['set_' + key] == "function") { + _obj['set_' + key](_data[key]); + } else if (typeof _obj[key] != "undefined" && !_setterOnly) { + _obj[key] = _data[key]; + } else if (_setterOnly === 'data') { + if (typeof _data.data == 'undefined') _data.data = {}; + _data.data[key] = _data[key]; + _obj.set_data(_data.data); + } + } + } +} + +/** + * Switches the given bit in the set on or off. + * only iff _bit is exponent of 2; 2^^i means i_th bit from the right will be set + * might switch all bits of _set, depending on _bit + * + * @param _set is the current set + * @param _bit is the position of the bit which should be switched on/off + * @param _state is whether the bit should be switched on or off + * @returns the new set + */ +export function egwSetBit(_set: number, _bit: number, _state: boolean) { + if (_state) + return _set | _bit; + else + return _set & ~_bit; +} + +/** + * Returns whether the given bit is set in the set. + */ +export function egwBitIsSet(_set: number, _bit: number) { + return (_set & _bit) > 0; +} + +export function egwObjectLength(_obj) { + let len = 0; + for (let k in _obj) len++; + return len; +} + + +/** + * Isolates the shift state from an event object + */ +export function egwGetShiftState(e) { + let state = EGW_AO_SHIFT_STATE_NONE; + state = egwSetBit(state, EGW_AO_SHIFT_STATE_MULTI, e.ctrlKey || e.metaKey); + state = egwSetBit(state, EGW_AO_SHIFT_STATE_BLOCK, e.shiftKey); + + return state; +} + +export function egwPreventSelect(e) { + if (egwGetShiftState(e) > EGW_AO_SHIFT_STATE_NONE) { + this.onselectstart = function () { + return false; + } + + return false; + } + + return true; +} + +export function egwUnfocus() { + //TODO + if (document.activeElement) { + try { + (document.activeElement as HTMLElement).blur(); + } catch (e) { + //console.log("Can't be unfocused because active element isn't an HTMLElement") + } + + } +} + +// function never used +// export function egwCallAbstract(_obj: any, _fn: { apply: (arg0: any, arg1: any) => any; }, _args: any) { +// if (_fn) { +// return _fn.apply(_obj, _args); +// } else { +// throw "egw_action Exception: Abstract function call in JS code."; +// } +// } + +//never used +// export function egwArraysEqual(_ar1, _ar2) { +// var result = _ar1.length == _ar2.length; +// +// for (var i = 0; i < _ar1.length; i++) { +// result = result && (_ar1[i] == _ar2[i]) +// } +// +// return result; +// } + +let _egwQueuedCallbacks = {}; + +export function egwQueueCallback(_proc: { apply: (arg0: any, arg1: any) => void; }, _args: any, _context: any, _id: string | number) { + if (_proc) { + let cur_id = 0; + if (typeof _egwQueuedCallbacks[_id] == "undefined") { + cur_id = _egwQueuedCallbacks[_id] = 1; + } else { + cur_id = ++_egwQueuedCallbacks[_id]; + } + + window.setTimeout(function () { + if (_egwQueuedCallbacks[_id] == cur_id) { + _proc.apply(_context, _args); + delete _egwQueuedCallbacks[_id]; + } + }, 0); + } +} + +/** + * The eventQueue object is used to have control over certain events such as + * ajax responses or timeouts. Sometimes it may happen, that a function attached + * to such an event should no longer be called - with egwEventQueue one has + * a simple possibility to control that. + */ + +//egwEventQueue is never used +// +// /** +// * Constructor for the egwEventQueue class. Initializes the queue object and the +// * internal data structures such as the internal key. +// */ +// export function egwEventQueue() { +// this.events = {}; +// this.key_id = 0; +// } +// +// /** +// * Flushes all queued events - all events which had been queued in this queue objects +// * can no longer be ran by calling egwEventQueue.run +// */ +// egwEventQueue.prototype.flush = function () { +// this.events = {}; +// } +// +// /** +// * Queues the given function. A key is returned which can be passed to the "run" +// * function which will then run the passed function. +// * +// * @param function _proc is the funciton which should be called +// * @param object _context is the context in which the function should be called +// * @param array _args is an optional array of parameters which should be passed +// * to the function. Defaults to an emtpy array. +// * @param string _id is an optional id which can be used to identify the event (may +// * also be a key returned by this funciton). If the queue function is called multiple +// * times for a given _id, the so called "call counter" of the event will be incremented. +// * Each time "run" is called for a given event, the "call counter" will be decremented. +// * Only if it reaches 0, the function connected to the event will be executed. +// * +// * @returns string the key which has to be passed to the "run" function. +// */ +// egwEventQueue.prototype.queue = function (_proc, _context, _args, _id) { +// // Default _args to an empty array +// if (typeof _args == "undefined" || !(_args instanceof Array)) { +// _args = []; +// } +// +// // Increment the queue key which is used to store the event objectes with +// // a unique key +// this.key_id++; +// +// var key = ""; +// +// // _id must be a string and evaluate to true - if this is not +// // generate an unique key. +// if (typeof _id != "string" || !_id) { +// key = "ev_" + this.key_id; +// } else { +// key = _id; +// } +// +// // Check whether an queued event with the given id already exists +// var cnt = 1; +// if (typeof this.events[key] != "undefined") { +// // The specified event already existed - increment the call counter. +// cnt = this.events[key].cnt + 1; +// } +// +// // Generate the event object +// this.events[key] = { +// "proc": _proc, +// "context": _context, +// "args": _args, +// "cnt": cnt +// } +// +// // Return the key which has to be passed to the "run" function in order to +// // run the specified function +// return key; +// } +// +// /** +// * Runs the event specified by the given key. The key is the string which had been +// * returned by the queue function. +// */ +// egwEventQueue.prototype.run = function (_key) { +// // Check whether the given key exists +// if (typeof this.events[_key] != "undefined") { +// // Fetch the event object +// var eventObj = this.events[_key]; +// +// // Decrement the call counter +// eventObj.cnt--; +// +// // Only run the event if the call counter has reached zero +// if (eventObj.cnt == 0) { +// // Run the event +// eventObj.proc.apply(eventObj.context, eventObj.args); +// +// // Delete the given key from the event set +// delete this.events[_key]; +// } +// } +// } +// +// /** +// * Does the same as "queue" but runs the event after the given timeout. +// */ +// egwEventQueue.prototype.queueTimeout = function (_proc, _context, _args, _id, _timeout) { +// // Create the queue entry +// var key = this.queue(_proc, _context, _args, _id); +// +// // Run the event in the setTimeout event. +// var self = this; +// window.setTimeout(function () { +// self.run(key); +// }, _timeout) +// } + +/** + * @deprecated use class EgwFnct instead + */ +/** + * Class which is used to be able to handle references to JavaScript functions + * from strings. + * + * @param object _context is the context in which the function will be executed. + * @param mixed _default is the default value which should be returned when no + * function (string) has been set. If it is a function this function will be + * called. + * @param array _acceptedTypes is an array of types which contains the "typeof" + * strings of accepted non-functions in setValue + */ +export class EgwFnct +{ + private readonly context: any; + private readonly acceptedTypes: string[]; + functionToPerform; + private value; + // Flag for if this action is using a default handler + // I don't think this is ever used @unused + public isDefault; + + constructor(_context = null, _default: any = false, _acceptedTypes = ["boolean"]) { + this.context = _context; + this.acceptedTypes = _acceptedTypes; + this.functionToPerform = null; + this.value = null; + this.isDefault = false + this.setValue(_default) + } + + /** + * @returns true iff there is a function to perform and is not default + */ + public hasHandler = (): boolean => this.functionToPerform !== null && !this.isDefault; + + /** + * Sets the function/return value for the exec function + * egw_action hints that value has to be on of 3: + * 1. _value may be a string with the word "javaScript:" prefixed. The function + * which is specified behind the colon and which has to be in the global scope + * will be executed. + * 2. _value may be a boolean, which specifies whether the external onExecute handler + * (passed as "_handler" in the constructor) will be used. + * 3. _value may be a JS function which will then be called. + * + * TODO check if there should be other options + */ + public setValue(_value: any) { + this.value = null; + this.functionToPerform = null; + + // Actual function + if (typeof _value == "function") { + this.functionToPerform = _value; + } else if (typeof _value == "string" && _value.substring(0, 11) === 'javaScript:') { + this.functionToPerform = function (): any { + const manager = this.context && this.context.getManager ? this.context.getManager() : null; + return window.egw.applyFunc(_value.substring(11), arguments, (manager ? manager.data.context : null) || window); + } + } else if (this.acceptedTypes.includes(typeof _value)) { + this.value = _value; + }// Something, but could not figure it out + else if (_value) { + //egw.debug("warn", "Unable to parse Exec function %o for action '%s'", _value, this.context.id); + } + } + + /** + * Executes the function + */ + public exec() { + if (this.functionToPerform) { + return this.functionToPerform.apply(this.context,arguments) + } else { + return this.value + } + } + +} + +/** + * @deprecated use upperCase class instead + */ +export class egwFnct extends EgwFnct {} + +/** + * Checks whether this is currently run on a mobile browser + */ +let _egw_mobileBrowser: boolean = null; + +export function egwIsMobile(): boolean +{ + if (_egw_mobileBrowser == null) { + let ua = navigator.userAgent; + _egw_mobileBrowser = + !!(ua.match(/iPhone/i) || ua.match(/iPad/i) || ua.match(/iPod/) || + ua.match(/Android/i) || ua.match(/SymbianOS/i)); + } + + return _egw_mobileBrowser; +} + +window.egwIsMobile = egwIsMobile; + + +/** + sprintf() for JavaScript 0.6 + + Copyright (c) Alexandru Marasteanu + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of sprintf() for JavaScript nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL Alexandru Marasteanu BE LIABLE FOR ANY + DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + + Changelog: + 2007.04.03 - 0.1: + - initial release + 2007.09.11 - 0.2: + - feature: added argument swapping + 2007.09.17 - 0.3: + - bug fix: no longer throws exception on empty paramenters (Hans Pufal) + 2007.10.21 - 0.4: + - unit test and patch (David Baird) + 2010.05.09 - 0.5: + - bug fix: 0 is now preceeded with a + sign + - bug fix: the sign was not at the right position on padded results (Kamal Abdali) + - switched from GPL to BSD license + 2010.05.22 - 0.6: + - reverted to 0.4 and fixed the bug regarding the sign of the number 0 + Note: + Thanks to Raphael Pigulla (http://www.n3rd.org/) + who warned me about a bug in 0.5, I discovered that the last update was + a regress. I appologize for that. + **/ +export function str_repeat(i, m) { + for (var o = []; m > 0; o[--m] = i) ; + return o.join(''); +} + +export function sprintf() { + var i = 0, a, f = arguments[i++], o = [], m, p, c, x, s = ''; + while (f) { + if (m = /^[^\x25]+/.exec(f)) { + o.push(m[0]); + } else if (m = /^\x25{2}/.exec(f)) { + o.push('%'); + } else if (m = /^\x25(?:(\d+)\$)?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-fosuxX])/.exec(f)) { + if (((a = arguments[m[1] || i++]) == null) || (a == undefined)) { + throw('Too few arguments.'); + } + if (/[^s]/.test(m[7]) && (typeof (a) != 'number')) { + throw('Expecting number but found ' + typeof (a)); + } + switch (m[7]) { + case 'b': + a = a.toString(2); + break; + case 'c': + a = String.fromCharCode(a); + break; + case 'd': + a = parseInt(a); + break; + case 'e': + a = m[6] ? a.toExponential(m[6]) : a.toExponential(); + break; + case 'f': + a = m[6] ? parseFloat(a).toFixed(m[6]) : parseFloat(a); + break; + case 'o': + a = typeof (a) == 'number' ? a.toString(8) : JSON.stringify(a); + break; + case 's': + a = ((a = String(a)) && m[6] ? a.substring(0, m[6]) : a); + break; + case 'u': + a = Math.abs(a); + break; + case 'x': + a = a.toString(16); + break; + case 'X': + a = a.toString(16).toUpperCase(); + break; + } + a = (/[def]/.test(m[7]) && m[2] && a >= 0 ? '+' + a : a); + c = m[3] ? m[3] == '0' ? '0' : m[3].charAt(1) : ' '; + x = m[5] - String(a).length - s.length; + p = m[5] ? str_repeat(c, x) : ''; + o.push(s + (m[4] ? a + p : p + a)); + } else { + throw('Invalid sprintf format "' + arguments[0] + '"'); + } + f = f.substring(m[0].length); + } + return o.join(''); +} \ No newline at end of file diff --git a/api/js/egw_action/egw_action_constants.js b/api/js/egw_action/egw_action_constants.ts old mode 100644 new mode 100755 similarity index 74% rename from api/js/egw_action/egw_action_constants.js rename to api/js/egw_action/egw_action_constants.ts index ac854124ed..fb30165f10 --- a/api/js/egw_action/egw_action_constants.js +++ b/api/js/egw_action/egw_action_constants.ts @@ -1,24 +1,43 @@ +// noinspection JSUnusedGlobalSymbols + +/** + * eGroupWare egw_action framework - egw action framework + * + * @link http://www.egroupware.org + * @author Andreas Stöckel + * @copyright 2011 by Andreas Stöckel + * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License + * @package egw_action + */ + + //State bitmask (only use powers of two for new states!) + export const EGW_AO_STATE_NORMAL = 0x00; export const EGW_AO_STATE_SELECTED = 0x01; export const EGW_AO_STATE_FOCUSED = 0x02; export const EGW_AO_STATE_VISIBLE = 0x04; //< Can only be set by the AOI, means that the object is attached to the DOM-Tree and visible +export type EGW_AO_STATES = + typeof EGW_AO_STATE_NORMAL + | typeof EGW_AO_STATE_SELECTED + | typeof EGW_AO_STATE_FOCUSED + | typeof EGW_AO_STATE_VISIBLE export const EGW_AO_EVENT_DRAG_OVER_ENTER = 0x00; export const EGW_AO_EVENT_DRAG_OVER_LEAVE = 0x01; // No shift key is pressed export const EGW_AO_SHIFT_STATE_NONE = 0x00; -// A shift key, which allows multiselection is pressed (usually CTRL on a PC keyboard) +// A shift key, which allows multi-selection is pressed (usually CTRL on a PC keyboard) export const EGW_AO_SHIFT_STATE_MULTI = 0x01; -// A shift key is pressed, which forces blockwise selection (SHIFT on a PC keyboard) +// A shift key is pressed, which forces block-wise selection (SHIFT on a PC keyboard) export const EGW_AO_SHIFT_STATE_BLOCK = 0x02; // If this flag is set, this object will not be returned as "focused". If this // flag is not applied to container objects, it may lead to some strange behaviour. export const EGW_AO_FLAG_IS_CONTAINER = 0x01; -// If this flag is set, the object will gets its focus when no other object is +// If this flag is set, the object will get its focus when no other object is // selected and e.g. a key is pressed. export const EGW_AO_FLAG_DEFAULT_FOCUS = 0x02; export const EGW_AI_DRAG = 0x0100; // Use the first byte as mask for event types - 01 is for events used with drag stuff @@ -102,10 +121,6 @@ export const EGW_KEY_F10 = 121; export const EGW_KEY_F11 = 122; export const EGW_KEY_F12 = 123; -export const EGW_VALID_KEYS = [ - 8, 9, 13, 27, 46, 32, 33, 34, 37, 38, 39, 40, 48, 49, 50, 51, 52, 53, 54, - 55, 56, 57, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, - 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 93, 112, 113, 114, 115, 116, 117, 118, - 119, 120, 121, 122, 123 -] +export const EGW_VALID_KEYS = [8, 9, 13, 27, 46, 32, 33, 34, 37, 38, 39, 40, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 93, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123] as const; +export type EGW_KEY_TYPES = typeof EGW_VALID_KEYS[number] diff --git a/api/js/egw_action/egw_action_dragdrop.js b/api/js/egw_action/egw_action_dragdrop.js deleted file mode 100644 index 3b3bf71989..0000000000 --- a/api/js/egw_action/egw_action_dragdrop.js +++ /dev/null @@ -1,737 +0,0 @@ -/** - * eGroupWare egw_action framework - egw action framework - * - * @link http://www.egroupware.org - * @author Andreas Stöckel - * @copyright 2011 by Andreas Stöckel - * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License - * @package egw_action - */ - -/*egw:uses - egw_action; - egw_action_common; - egw_action_popup; - vendor.bower-asset.jquery.dist.jquery; -*/ - -import {egwAction,egwActionImplementation, egw_getObjectManager} from "./egw_action.js"; -import {getPopupImplementation} from "./egw_action_popup.js"; -import {EGW_AI_DRAG_OUT, EGW_AI_DRAG_OVER, EGW_AO_EXEC_THIS, EGW_AI_DRAG_ENTER} from "./egw_action_constants.js"; - -/** - * Register the drag and drop handlers - */ -if (typeof window._egwActionClasses == "undefined") - window._egwActionClasses = {}; -_egwActionClasses["drag"] = { - "actionConstructor": egwDragAction, - "implementation": getDragImplementation -}; -_egwActionClasses["drop"] = { - "actionConstructor": egwDropAction, - "implementation": getDropImplementation -}; - -/** - * The egwDragAction class overwrites the egwAction class and adds the new - * "dragType" propery. The "onExecute" event of the drag action will be called - * whenever dragging starts. The onExecute JS handler should return the - * drag-drop helper object - otherwise an default helper will be generated. - * - * @param {egwAction} _id - * @param {string} _handler - * @param {string} _caption - * @param {string} _icon - * @param {(string|function)} _onExecute - * @param {bool} _allowOnMultiple - * @returns {egwDragAction} - */ -export function egwDragAction(_id, _handler, _caption, _icon, _onExecute, _allowOnMultiple) -{ - var action = new egwAction(_id, _handler, _caption, _icon, _onExecute, _allowOnMultiple); - - action.type = "drag"; - action.dragType = "default"; - action.hideOnDisabled = true; - - action.set_dragType = function(_value) { - action.dragType = _value; - }; - - return action; -} - - -var - _dragActionImpl = null; - -export function getDragImplementation() -{ - if (!_dragActionImpl) - { - _dragActionImpl = new egwDragActionImplementation(); - } - return _dragActionImpl; -} - -export function egwDragActionImplementation() -{ - var ai = new egwActionImplementation(); - - ai.type = "drag"; - - ai.helper = null; - ai.ddTypes = []; - ai.selected = []; - - // Define default helper DOM - // default helper also can be called later in application code in order to customization - ai.defaultDDHelper = function (_selected) - { - // Table containing clone of rows - var table = jQuery(document.createElement("table")).addClass('egwGridView_grid et2_egw_action_ddHelper_row'); - // tr element to use as last row to show lable more ... - var moreRow = jQuery(document.createElement('tr')).addClass('et2_egw_action_ddHelper_moreRow'); - // Main div helper container - var div = jQuery(document.createElement("div")).append(table); - - var rows = []; - // Maximum number of rows to show - var maxRows = 3; - // item label - var itemLabel = egw.lang(egw.link_get_registry(egw.app_name(),_selected.length > 1?'entries':'entry')||egw.app_name()); - - var index = 0; - - // Take select all into account when counting number of rows, because they may not be - // in _selected object - var pseudoNumRows = (_selected[0] && _selected[0]._context && _selected[0]._context._selectionMgr && - _selected[0]._context._selectionMgr._selectAll) ? - _selected[0]._context._selectionMgr._total : _selected.length; - - for (var i = 0; i < _selected.length;i++) - { - var row = jQuery(_selected[i].iface.getDOMNode()).clone(); - if (row) - { - rows.push(row); - table.append(row); - } - index++; - if (index == maxRows) - { - // Lable to show number of items - var spanCnt = jQuery(document.createElement('span')) - .addClass('et2_egw_action_ddHelper_itemsCnt') - .appendTo(div); - - spanCnt.text(pseudoNumRows +' '+ itemLabel); - // Number of not shown rows - var restRows = pseudoNumRows - maxRows; - if (restRows) - { - moreRow.text(egw.lang("%1 more %2 selected ...", (pseudoNumRows - maxRows), itemLabel)); - } - table.append(moreRow); - break; - } - } - - var text = jQuery(document.createElement('div')).addClass('et2_egw_action_ddHelper_tip'); - div.append(text); - - // Add notice of Ctrl key, if supported - if('draggable' in document.createElement('span') && - navigator && navigator.userAgent.indexOf('Chrome') >= 0 && egw.app_name() == 'filemanager') // currently only filemanager supports drag out - { - if (rows.length == 1) - { - text.text(egw.lang('You may darg file out to your desktop', itemLabel)); - } - else - { - text.text(egw.lang('Note: If you drag out these selected rows to desktop only the first selected row will be downloaded.', itemLabel)); - } - } - // Final html DOM return as helper structor - return div; - }; - - ai.doRegisterAction = function(_aoi, _callback, _context) - { - var node = _aoi.getDOMNode() && _aoi.getDOMNode()[0] ? _aoi.getDOMNode()[0] : _aoi.getDOMNode(); - - if (node) - { - // Prevent selection - node.onselectstart = function () { - return false; - }; - if (!(window.FileReader && 'draggable' in document.createElement('span'))) - { - // No DnD support - return; - } - - // It shouldn't be so hard to get the action... - var action = null; - var groups = _context.getActionImplementationGroups(); - if (!groups.drag) - { - return; - } - for (var i = 0; i < groups.drag.length; i++) - { - // dragType 'file' says it can be dragged as a file - if(groups.drag[i].link.actionObj.dragType == 'file' || groups.drag[i].link.actionObj.dragType.indexOf('file') > -1) - { - action = groups.drag[i].link.actionObj; - break; - } - } - - // Bind mouse handlers - jQuery(node).off("mousedown") - .on({ - mousedown: function(event){ - if (_context.isSelection(event)){ - node.setAttribute("draggable", false); - } - else if(event.which != 3) - { - document.getSelection().removeAllRanges(); - } - }, - mouseup: function (event){ - if (_context.isSelection(event) && document.getSelection().type === 'Range'){ - //let the draggable be reactivated by another click up as the range selection is - // not working as expected in shadow-dom as expected in all browsers - } - else - { - node.setAttribute("draggable", true); - } - - // Set cursor back to auto. Seems FF can't handle cursor reversion - jQuery('body').css({cursor:'auto'}); - } - }); - - - node.setAttribute('draggable', true); - const dragstart = function(event) { - - // The helper function is called before the start function - // is evoked. Call the given callback function. The callback - // function will gather the selected elements and action links - // and call the doExecuteImplementation function. This - // will call the onExecute function of the first action - // in order to obtain the helper object (stored in ai.helper) - // and the multiple dragDropTypes (ai.ddTypes) - _callback.call(_context, false, ai); - - if (action && egw.app_name() == 'filemanager') { - if (_context.isSelection(event)) return; - - // Get all selected - var selected = ai.selected; - - // Set file data - for (let i = 0; i < 1; i++) { - let d = selected[i].data || egw.dataGetUIDdata(selected[i].id).data || {}; - if (d && d.mime && d.download_url) { - var url = d.download_url; - - // NEED an absolute URL - if (url[0] == '/') url = egw.link(url); - // egw.link adds the webserver, but that might not be an absolute URL - try again - if (url[0] == '/') url = window.location.origin + url; - event.dataTransfer.setData("DownloadURL", d.mime + ':' + d.name + ':' + url); - } - } - event.dataTransfer.effectAllowed = 'copy'; - - if (event.dataTransfer.types.length == 0) { - // No file data? Abort: drag does nothing - event.preventDefault(); - return; - } - } else { - event.dataTransfer.effectAllowed = 'linkMove'; - } - - - const data = { - ddTypes: ai.ddTypes, - selected: ai.selected.map((item) => { - return {id: item.id} - }) - }; - - if (!ai.helper) { - ai.helper = ai.defaultDDHelper(ai.selected); - } - // Add a basic class to the helper in order to standardize the background layout - ai.helper[0].classList.add('et2_egw_action_ddHelper', 'ui-draggable-dragging'); - document.body.append(ai.helper[0]); - this.classList.add('drag--moving'); - - event.dataTransfer.setData('application/json', JSON.stringify(data)) - - event.dataTransfer.setDragImage(ai.helper[0], 12, 12); - - this.setAttribute('data-egwActionObjID', JSON.stringify(data.selected)); - }; - - const dragend = function(event){ - const helper = document.querySelector('.et2_egw_action_ddHelper'); - if (helper) helper.remove(); - const draggable = document.querySelector('.drag--moving'); - if (draggable) draggable.classList.remove('drag--moving'); - // cleanup drop hover class from all other DOMs if there's still anything left - Array.from(document.getElementsByClassName('et2dropzone drop-hover')).forEach(_i=>{_i.classList.remove('drop-hover')}) - }; - - // Drag Event listeners - node.addEventListener('dragstart', dragstart , false); - node.addEventListener('dragend', dragend, false); - - - return true; - } - return false; - }; - - ai.doUnregisterAction = function(_aoi) - { - var node = _aoi.getDOMNode(); - - if (node){ - node.setAttribute('draggable', false); - } - }; - - /** - * Builds the context menu and shows it at the given position/DOM-Node. - * - * @param {string} _context - * @param {array} _selected - * @param {object} _links - */ - ai.doExecuteImplementation = function(_context, _selected, _links) - { - // Reset the helper object of the action implementation - this.helper = null; - var hasLink = false; - - // Store the drag-drop types - this.ddTypes = []; - this.selected = _selected; - - // Call the onExecute event of the first actionObject - for (var k in _links) - { - if (_links[k].visible) - { - hasLink = true; - - // Only execute the following code if a JS function is registered - // for the action and this is the first action link - if (!this.helper && _links[k].actionObj.onExecute.hasHandler()) - { - this.helper = _links[k].actionObj.execute(_selected); - } - - // Push the dragType of the associated action object onto the - // drag type list - this allows an element to support multiple - // drag/drop types. - var type = jQuery.isArray(_links[k].actionObj.dragType) ? _links[k].actionObj.dragType : [_links[k].actionObj.dragType]; - for(var i = 0; i < type.length; i++) - { - if (this.ddTypes.indexOf(type[i]) == -1) - { - this.ddTypes.push(type[i]); - } - } - } - } - - // If no helper has been defined, create an default one - if (!this.helper && hasLink) - { - this.helper = ai.defaultDDHelper(_selected); - } - - return true; - }; - - return ai; -} - - - -/** - * The egwDropAction class overwrites the egwAction class and adds the "acceptedTypes" - * property. This array should contain all "dragTypes" the drop action is allowed to - * - * @param {egwAction} _id - * @param {string} _handler - * @param {string} _caption - * @param {string} _icon - * @param {(string|function)} _onExecute - * @param {bool} _allowOnMultiple - * @returns {egwDropAction} - */ -export function egwDropAction(_id, _handler, _caption, _icon, _onExecute, _allowOnMultiple) -{ - var action = new egwAction(_id, _handler, _caption, _icon, _onExecute, _allowOnMultiple); - - action.type = "drop"; - action.acceptedTypes = ["default"]; - action.canHaveChildren = ["drag","popup"]; - action["default"] = false; - action.order = 0; - action.group = 0; - - action.set_default = function(_value) { - action["default"] = _value; - }; - - action.set_order = function(_value) { - action.order = _value; - }; - - action.set_group = function(_value) { - action.group = _value; - }; - - /** - * The acceptType property allows strings as well as arrays - strings are - * automatically included in an array. - * - * @param {(string|array)} _value - */ - action.set_acceptedTypes = function(_value) { - if (_value instanceof Array) - { - action.acceptedTypes = _value; - } - else - { - action.acceptedTypes = [_value]; - } - }; - - return action; -} - -var - _dropActionImpl = null; - -export function getDropImplementation() -{ - if (!_dropActionImpl) - { - _dropActionImpl = new egwDropActionImplementation(); - } - return _dropActionImpl; -} - -export function egwDropActionImplementation() -{ - var ai = new egwActionImplementation(); - - //keeps track of current drop element where dragged item's entered. - // it's necessary for dragenter/dragleave issue correction. - var currentDropEl = null; - - ai.type = "drop"; - - ai.doRegisterAction = function(_aoi, _callback, _context) - { - var node = _aoi.getDOMNode() && _aoi.getDOMNode()[0] ? _aoi.getDOMNode()[0] : _aoi.getDOMNode(); - var self = this; - if (node) - { - node.classList.add('et2dropzone'); - const dragover = function (event) { - if (event.preventDefault) { - event.preventDefault(); - } - if (!self.getTheDraggedDOM()) return ; - - const data = { - event: event, - ui: self.getTheDraggedData() - }; - _aoi.triggerEvent(EGW_AI_DRAG_OVER, data); - - return true; - - }; - - const dragenter = function (event) { - event.stopImmediatePropagation(); - // don't trigger dragenter if we are entering the drag element - // don't go further if the dragged element is no there (happens when a none et2 dragged element is being dragged) - if (!self.getTheDraggedDOM() || self.isTheDraggedDOM(this) || this == currentDropEl) return; - - currentDropEl = event.currentTarget; - event.dataTransfer.dropEffect = 'link'; - - const data = { - event: event, - ui: self.getTheDraggedData() - }; - - _aoi.triggerEvent(EGW_AI_DRAG_ENTER, data); - - // cleanup drop hover class from all other DOMs if there's still anything left - Array.from(document.getElementsByClassName('et2dropzone drop-hover')).forEach(_i=>{_i.classList.remove('drop-hover')}) - - this.classList.add('drop-hover'); - - // stop the event from being fired for its children - event.preventDefault(); - return false; - }; - - const drop = function (event) { - event.preventDefault(); - // don't go further if the dragged element is no there (happens when a none et2 dragged element is being dragged) - if (!self.getTheDraggedDOM()) return ; - - // remove the hover class - this.classList.remove('drop-hover'); - - const helper = self.getHelperDOM(); - let ui = self.getTheDraggedData(); - ui.position = {top: event.clientY, left: event.clientX}; - ui.offset = {top: event.offsetY, left: event.offsetX}; - - - let data = JSON.parse(event.dataTransfer.getData('application/json')); - - if (!self.isAccepted(data, _context, _callback) || self.isTheDraggedDOM(this)) - { - // clean up the helper dom - if (helper) helper.remove(); - return; - } - - let selected = data.selected.map((item) => { - return egw_getObjectManager(item.id, false) - }); - - - var links = _callback.call(_context, "links", self, EGW_AO_EXEC_THIS); - - // Disable all links which only accept types which are not - // inside ddTypes - for (var k in links) { - var accepted = links[k].actionObj.acceptedTypes; - - var enabled = false; - for (var i = 0; i < data.ddTypes.length; i++) { - if (accepted.indexOf(data.ddTypes[i]) != -1) { - enabled = true; - break; - } - } - // Check for allowing multiple selected - if (!links[k].actionObj.allowOnMultiple && selected.length > 1) { - enabled = false; - } - if (!enabled) { - links[k].enabled = false; - links[k].visible = !links[k].actionObj.hideOnDisabled; - } - } - - // Check whether there is only one link - var cnt = 0; - var lnk = null; - for (var k in links) { - if (links[k].enabled && links[k].visible) { - lnk = links[k]; - cnt += 1 + links[k].actionObj.children.length; - - // Add ui, so you know what happened where - lnk.actionObj.ui = ui; - - } - } - - if (cnt == 1) { - window.setTimeout(function () { - lnk.actionObj.execute(selected, _context); - }, 0); - } - - if (cnt > 1) { - // More than one drop action link is associated - // to the drop event - show those as a popup menu - // and let the user decide which one to use. - // This is possible as the popup and the popup action - // object and the drop action object share same - // set of properties. - var popup = getPopupImplementation(); - var pos = popup._getPageXY(event); - - // Don't add paste actions, this is a drop - popup.auto_paste = false; - - window.setTimeout(function () { - popup.doExecuteImplementation(pos, selected, links, - _context); - // Reset, popup is reused - popup.auto_paste = true; - }, 0); // Timeout is needed to have it working in IE - } - // Set cursor back to auto. Seems FF can't handle cursor reversion - jQuery('body').css({cursor: 'auto'}); - - _aoi.triggerEvent(EGW_AI_DRAG_OUT, {event: event, ui: self.getTheDraggedData()}); - - // clean up the helper dom - if (helper) helper.remove(); - self.getTheDraggedDOM().classList.remove('drag--moving'); - }; - - const dragleave = function (event) { - event.stopImmediatePropagation(); - - // don't trigger dragleave if we are leaving the drag element - // don't go further if the dragged element is no there (happens when a none et2 dragged element is being dragged) - if (!self.getTheDraggedDOM() || self.isTheDraggedDOM(this) || this == currentDropEl) return; - - const data = { - event: event, - ui: self.getTheDraggedData() - }; - - _aoi.triggerEvent(EGW_AI_DRAG_OUT, data); - - this.classList.remove('drop-hover'); - - event.preventDefault(); - return false; - }; - - // DND Event listeners - node.addEventListener('dragover', dragover, false); - - node.addEventListener('dragenter', dragenter, false); - - node.addEventListener('drop', drop, false); - - node.addEventListener('dragleave', dragleave, false); - - return true; - } - return false; - }; - - ai.isTheDraggedDOM = function (_dom) - { - return _dom.classList.contains('drag--moving'); - } - - ai.getTheDraggedDOM = function () - { - return document.querySelector('.drag--moving'); - } - - ai.getHelperDOM = function () - { - return document.querySelector('.et2_egw_action_ddHelper'); - } - - ai.getTheDraggedData = function() - { - let data = this.getTheDraggedDOM().dataset.egwactionobjid; - let selected = []; - if (data) - { - data = JSON.parse(data); - selected = data.map((item)=>{return egw_getObjectManager(item.id, false)}); - } - return { - draggable: this.getTheDraggedDOM(), - helper: this.getHelperDOM(), - selected: selected - - } - } - - // check if given draggable is accepted for drop - ai.isAccepted = function(_data, _context, _callback, _node) - { - if (_node && !_node.classList.contains('et2dropzone')) return false; - if (typeof _data.ddTypes != "undefined") - { - const accepted = this._fetchAccepted( - _callback.call(_context, "links", this, EGW_AO_EXEC_THIS)); - - // Check whether all drag types of the selected objects - // are accepted - var ddTypes = _data.ddTypes; - - for (let i = 0; i < ddTypes.length; i++) - { - if (accepted.indexOf(ddTypes[i]) != -1) - { - return true; - } - } - } - return false; - }; - - ai.doUnregisterAction = function(_aoi) - { - var node = _aoi.getDOMNode(); - - if (node) { - node.classList.remove('et2dropzone'); - } - }; - - ai._fetchAccepted = function(_links) - { - // Accumulate the accepted types - var accepted = []; - for (var k in _links) - { - for (var i = 0; i < _links[k].actionObj.acceptedTypes.length; i++) - { - var type = _links[k].actionObj.acceptedTypes[i]; - - if (accepted.indexOf(type) == -1) - { - accepted.push(type); - } - } - } - - return accepted; - }; - - /** - * Builds the context menu and shows it at the given position/DOM-Node. - * - * @param {string} _context - * @param {array} _selected - * @param {object} _links - */ - ai.doExecuteImplementation = function(_context, _selected, _links) - { - if (_context == "links") - { - return _links; - } - }; - - return ai; -} diff --git a/api/js/egw_action/egw_action_popup.js b/api/js/egw_action/egw_action_popup.js deleted file mode 100644 index 84cc6dd002..0000000000 --- a/api/js/egw_action/egw_action_popup.js +++ /dev/null @@ -1,941 +0,0 @@ -/** - * eGroupWare egw_action framework - egw action framework - * - * @link http://www.egroupware.org - * @author Andreas Stöckel - * @copyright 2011 by Andreas Stöckel - * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License - * @package egw_action - * @version $Id$ - */ - -/*egw:uses - vendor.bower-asset.jquery.dist.jquery; - egw_menu; -*/ - -import {egwAction, egwActionImplementation, egwActionObject} from './egw_action.js'; -import {egwFnct} from './egw_action_common.js'; -import {egwMenu, _egw_active_menu} from "./egw_menu.js"; -import {EGW_KEY_ENTER, EGW_KEY_MENU} from "./egw_action_constants.js"; -import {tapAndSwipe} from "../tapandswipe"; - -if (typeof window._egwActionClasses == "undefined") - window._egwActionClasses = {}; -_egwActionClasses["popup"] = { - "actionConstructor": egwPopupAction, - "implementation": getPopupImplementation -}; - -export function egwPopupAction(_id, _handler, _caption, _icon, _onExecute, _allowOnMultiple) -{ - var action = new egwAction(_id, _handler, _caption, _icon, _onExecute, _allowOnMultiple); - action.type = "popup"; - action.canHaveChildren = ["popup"]; - action["default"] = false; - action.order = 0; - action.group = 0; - action.hint = false; - action.checkbox = false; - action.radioGroup = 0; - action.checked = false; - action.confirm_mass_selection = null; - action.shortcut = null; - action.singleClick = false; - - action.set_singleClick = function(_value) { - action["singleClick"] = _value; - }; - - action.set_default = function(_value) { - action["default"] = _value; - }; - - action.set_order = function(_value) { - action.order = _value; - }; - - action.set_group = function(_value) { - action.group = _value; - }; - - action.set_hint = function(_value) { - action.hint = _value; - }; - - // If true, the action will be rendered as checkbox - action.set_checkbox = function(_value) { - action.checkbox = _value; - }; - - action.set_checked = function(_value) { - action.checked = _value; - }; - - /** - * Set either a confirmation prompt, or TRUE to indicate that this action - * cares about large selections and to ask the confirmation prompt(s) - * - * @param {String|Boolean} _value - */ - action.set_confirm_mass_selection = function(_value) { - action.confirm_mass_selection = _value; - }; - - // Allow checkbox to be set from context using the given function - action.set_isChecked = function(_value) { - action.isChecked = new egwFnct(this, null, []); - if(_value !== null) - { - action.isChecked.setValue(_value); - } - }; - - // If radioGroup is >0 and the element is a checkbox, radioGroup specifies - // the group of radio buttons this one belongs to - action.set_radioGroup = function(_value) { - action.radioGroup = _value; - }; - - action.set_shortcut = function(_value) { - if (_value) - { - var sc = { - "keyCode": -1, - "shift": false, - "ctrl": false, - "alt": false - }; - - if (typeof _value == "object" && typeof _value.keyCode != "undefined" && - typeof _value.caption != "undefined") - { - sc.keyCode = _value.keyCode; - sc.caption = _value.caption; - sc.shift = (typeof _value.shift == "undefined") ? false : _value.shift; - sc.ctrl = (typeof _value.ctrl == "undefined") ? false : _value.ctrl; - sc.alt = (typeof _value.alt == "undefined") ? false : _value.alt; - } - - this.shortcut = sc; - } - else - { - this.shortcut = false; - } - }; - - return action; -} - -var - _popupActionImpl = null; - -export function getPopupImplementation() -{ - if (!_popupActionImpl) - { - _popupActionImpl = new egwPopupActionImplementation(); - } - return _popupActionImpl; -} - -export function egwPopupActionImplementation() -{ - var ai = new egwActionImplementation(); - - ai.type = "popup"; - - ai.auto_paste = true; - - /** - * Registers the handler for the default action - * - * @param {DOMNode} _node - * @param {function} _callback - * @param {object} _context - * @returns {boolean} - */ - ai._registerDefault = function(_node, _callback, _context) - { - var defaultHandler = function(e) { - // Prevent bubbling bound event on tag, on touch devices - // a tag should be handled by default event - if (egwIsMobile() && e.target.tagName == "A") return true; - - if (typeof document.selection != "undefined" && typeof document.selection.empty != "undefined") - { - document.selection.empty(); - } - else if( typeof window.getSelection != "undefined") - { - var sel = window.getSelection(); - sel.removeAllRanges(); - } - - if (!(_context.manager.getActionsByAttr('singleClick', true).length > 0 && - e.originalEvent.target.classList.contains('et2_clickable'))) - { - _callback.call(_context, "default", ai); - } - - // Stop action from bubbling up to parents - e.stopPropagation(); - e.cancelBubble = true; - - // remove context menu if we are in mobile theme - // and intended to open the entry - if (_egw_active_menu && e.which == 1) _egw_active_menu.hide(); - return false; - }; - - if (egwIsMobile() || _context.manager.getActionsByAttr('singleClick', true).length > 0) { - jQuery(_node).bind('click', defaultHandler); - } else { - _node.ondblclick = defaultHandler; - } - }; - - ai._getDefaultLink = function(_links) { - var defaultAction = null; - for (var k in _links) - { - if (_links[k].actionObj["default"] && _links[k].enabled) - { - defaultAction = _links[k].actionObj; - break; - } - } - - return defaultAction; - }; - - ai._searchShortcut = function (_key, _objs, _links) { - for (var i = 0; i < _objs.length; i++) - { - var sc = _objs[i].shortcut; - if (sc && sc.keyCode == _key.keyCode && sc.shift == _key.shift && - sc.ctrl == _key.ctrl && sc.alt == _key.alt && - _objs[i].type == "popup" && (typeof _links[_objs[i].id] == "undefined" || - _links[_objs[i].id].enabled)) - { - return _objs[i]; - } - - var obj = this._searchShortcut(_key, _objs[i].children, _links); - if (obj) { - return obj; - } - } - }; - - ai._searchShortcutInLinks = function(_key, _links) { - var objs = []; - for (var k in _links) - { - if (_links[k].enabled) - { - objs.push(_links[k].actionObj); - } - } - - return ai._searchShortcut(_key, objs, _links); - }; - - /** - * Handles a key press - * - * @param {object} _key - * @param {type} _selected - * @param {type} _links - * @param {type} _target - * @returns {Boolean} - */ - ai._handleKeyPress = function(_key, _selected, _links, _target) { - // Handle the default - if (_key.keyCode == EGW_KEY_ENTER && !_key.ctrl && !_key.shift && !_key.alt) { - var defaultAction = this._getDefaultLink(_links); - if (defaultAction) - { - defaultAction.execute(_selected); - return true; - } - } - - // Menu button - if (_key.keyCode == EGW_KEY_MENU && !_key.ctrl) - { - return this.doExecuteImplementation({posx:0,posy:0}, _selected, _links, _target); - } - - - // Check whether the given shortcut exists - var obj = this._searchShortcutInLinks(_key, _links); - if (obj) - { - obj.execute(_selected); - return true; - } - - return false; - }; - ai._handleTapHold = function (_node, _callback) - { - //TODO (todo-jquery): ATM we need to convert the possible given jquery dom node object into DOM Element, this - // should be no longer neccessary after removing jQuery nodes. - if (_node instanceof jQuery) - { - _node = _node[0]; - } - - let tap = new tapAndSwipe(_node, { - // this threshold must be the same as the one set in et2_dataview_view_aoi - tapHoldThreshold: 1000, - allowScrolling: "both", - tapAndHold: function(event, fingercount) - { - if (fingercount >= 2) return; - // don't trigger contextmenu if sorting is happening - if (document.querySelector('.sortable-drag')) return; - - _callback(event); - } - }); - // bind a custom event tapandhold to be able to call it from nm action button - _node.addEventListener('tapandhold', _event=>{_callback(_event)}); - } - /** - * Registers the handler for the context menu - * - * @param {DOMNode} _node - * @param {function} _callback - * @param {object} _context - * @returns {boolean} - */ - ai._registerContext = function(_node, _callback, _context) - { - var contextHandler = function(e) { - - //Obtain the event object - if (!e) - { - e = window.event; - } - - if (_egw_active_menu) - { - _egw_active_menu.hide(); - } - else if (!e.ctrlKey && e.which == 3 || e.which === 0 || e.type === 'tapandhold') // tap event indicates by 0 - { - var _xy = ai._getPageXY(e); - var _implContext = {event:e, posx:_xy.posx, posy: _xy.posy}; - _callback.call(_context, _implContext, ai); - } - - e.cancelBubble = !e.ctrlKey || e.which == 1; - if (e.stopPropagation && e.cancelBubble) - { - e.stopPropagation(); - } - return !e.cancelBubble; - }; - // Safari still needs the taphold to trigger contextmenu - // Chrome has default event on touch and hold which acts like right click - this._handleTapHold(_node, contextHandler); - if (!egwIsMobile()) jQuery(_node).on('contextmenu', contextHandler); - }; - - ai.doRegisterAction = function(_aoi, _callback, _context) - { - var node = _aoi.getDOMNode(); - - if (node) - { - this._registerDefault(node, _callback, _context); - this._registerContext(node, _callback, _context); - return true; - } - return false; - }; - - ai.doUnregisterAction = function(_aoi) - { - var node = _aoi.getDOMNode(); - jQuery(node).off(); - }; - - /** - * Builds the context menu and shows it at the given position/DOM-Node. - * - * @param {object} _context - * @param {type} _selected - * @param {type} _links - * @param {type} _target - * @returns {Boolean} - */ - ai.doExecuteImplementation = function(_context, _selected, _links, _target) - { - if (typeof _target == "undefined") - { - _target = null; - } - - ai._context = _context; - if (typeof _context == "object" && typeof _context.keyEvent == "object") - { - return ai._handleKeyPress(_context.keyEvent, _selected, _links, _target); - } - else if (_context != "default") - { - //Check whether the context has the posx and posy parameters - if ((typeof _context.posx != "number" || typeof _context.posy != "number") && - typeof _context.id != "undefined") - { - // Calculate context menu position from the given DOM-Node - var node = _context; - - x = jQuery(node).offset().left; - y = jQuery(node).offset().top; - - _context = {"posx": x, "posy": y}; - } - - var menu = ai._buildMenu(_links, _selected, _target); - menu.showAt(_context.posx, _context.posy); - - return true; - } - else - { - var defaultAction = ai._getDefaultLink(_links); - if (defaultAction) - { - defaultAction.execute(_selected); - } - } - - return false; - }; - - /** - * Groups and sorts the given action tree layer - * - * @param {type} _layer - * @param {type} _links - * @param {type} _parentGroup - */ - ai._groupLayers = function(_layer, _links, _parentGroup) - { - // Seperate the multiple groups out of the layer - var link_groups = {}; - - for (var i = 0; i < _layer.children.length; i++) - { - var actionObj = _layer.children[i].action; - - // Check whether the link group of the current element already exists, - // if not, create the group - var grp = actionObj.group; - if (typeof link_groups[grp] == "undefined") - { - link_groups[grp] = []; - } - - // Search the link data for this action object if none is found, - // visible and enabled = true is assumed - var visible = true; - var enabled = true; - - if (typeof _links[actionObj.id] != "undefined") - { - visible = _links[actionObj.id].visible; - enabled = _links[actionObj.id].enabled; - } - - // Insert the element in order - var inserted = false; - var groupObj = { - "actionObj": actionObj, - "visible": visible, - "enabled": enabled, - "groups": [] - }; - - for (var j = 0; j < link_groups[grp].length; j++) - { - var elem = link_groups[grp][j].actionObj; - if (elem.order > actionObj.order) - { - inserted = true; - link_groups[grp].splice(j, 0, groupObj); - break; - } - } - - // If the object hasn't been inserted, add it to the end of the list - if (!inserted) - { - link_groups[grp].push(groupObj); - } - - // If this child itself has children, group those elements too - if (_layer.children[i].children.length > 0) - { - this._groupLayers(_layer.children[i], _links, groupObj); - } - } - - // Transform the link_groups object into an sorted array - var groups = []; - - for (var k in link_groups) - { - groups.push({"grp": k, "links": link_groups[k]}); - } - - groups.sort(function(a, b) { - var ia = parseInt(a.grp); - var ib = parseInt(b.grp); - return (ia > ib) ? 1 : ((ia < ib) ? -1 : 0); - }); - - // Append the groups to the groups2 array - var groups2 = []; - for (var i = 0; i < groups.length; i++) - { - groups2.push(groups[i].links); - } - - _parentGroup.groups = groups2; - }; - - /** - * Build the menu layers - * - * @param {type} _menu - * @param {type} _groups - * @param {type} _selected - * @param {type} _enabled - * @param {type} _target - */ - ai._buildMenuLayer = function(_menu, _groups, _selected, _enabled, _target) - { - var firstGroup = true; - - for (var i = 0; i < _groups.length; i++) - { - var firstElem = true; - - // Go through the elements of each group - for (var j = 0; j < _groups[i].length; j++) - { - var link = _groups[i][j]; - - if (link.visible) - { - // Add an seperator after each group - if (!firstGroup && firstElem) - { - _menu.addItem("", "-"); - } - firstElem = false; - - var item = _menu.addItem(link.actionObj.id, link.actionObj.caption, - link.actionObj.iconUrl); - item["default"] = link.actionObj["default"]; - - // As this code is also used when a drag-drop popup menu is built, - // we have to perform this check - if (link.actionObj.type == "popup") - { - item.set_hint(link.actionObj.hint); - item.set_checkbox(link.actionObj.checkbox); - item.set_checked(link.actionObj.checked); - if(link.actionObj.checkbox && link.actionObj.isChecked) - { - item.set_checked(link.actionObj.isChecked.exec(link.actionObj, _selected)); - } - item.set_groupIndex(link.actionObj.radioGroup); - - if (link.actionObj.shortcut && !egwIsMobile()) - { - var sc = link.actionObj.shortcut; - item.set_shortcutCaption(sc.caption); - } - } - - item.set_data(link.actionObj); - if (link.enabled && _enabled) - { - item.set_onClick(function(elem) { - // Pass the context - elem.data.menu_context = ai._context; - - // Copy the "checked" state - if (typeof elem.data.checked != "undefined") - { - elem.data.checked = elem.checked; - } - - elem.data.execute(_selected, _target); - - if (typeof elem.data.checkbox != "undefined" && elem.data.checkbox) - { - return elem.data.checked; - } - }); - } - else - { - item.set_enabled(false); - } - - // Append the parent groups - if (link.groups) - { - this._buildMenuLayer(item, link.groups, _selected, link.enabled, _target); - } - } - } - - firstGroup = firstGroup && firstElem; - } - }; - - /** - * Builds the context menu from the given action links - * - * @param {type} _links - * @param {type} _selected - * @param {type} _target - * @returns {egwMenu|egwActionImplementation._buildMenu.menu} - */ - ai._buildMenu = function(_links, _selected, _target) - { - // Build a tree containing all actions - var tree = {"root": []}; - - // Automatically add in Drag & Drop actions - if(this.auto_paste && !egwIsMobile() && !ai._context.event.type.match(/touch/)) - { - this._addCopyPaste(_links,_selected); - } - - for (var k in _links) - { - _links[k].actionObj.appendToTree(tree); - } - - // We need the dummy object container in order to pass the array by - // reference - var groups = { - "groups": [] - }; - - if (tree.root.length > 0) - { - // Sort every action object layer by the given sort position and grouping - this._groupLayers(tree.root[0], _links, groups); - } - - var menu = new egwMenu(); - - // Build the menu layers - this._buildMenuLayer(menu, groups.groups, _selected, true, _target); - - return menu; - }; - - ai._getPageXY = function getPageXY(event) - { - // document.body.scrollTop does not work in IE - var scrollTop = document.body.scrollTop ? document.body.scrollTop : - document.documentElement.scrollTop; - var scrollLeft = document.body.scrollLeft ? document.body.scrollLeft : - document.documentElement.scrollLeft; - - return {'posx': (event.clientX + scrollLeft), 'posy': (event.clientY + scrollTop)}; - }; - - /** - * Automagically add in context menu items for copy and paste from - * drag and drop actions, based on current clipboard and the accepted types - * - * @param {object[]} _links Actions for inclusion in the menu - * @param {egwActionObject[]} _selected Currently selected entries - */ - ai._addCopyPaste = function (_links, _selected) - { - // Get a list of drag & drop actions - var drag = _selected[0].getSelectedLinks('drag').links; - var drop = _selected[0].getSelectedLinks('drop').links; - - // No drags & no drops means early exit (only by default added egw_cancel_drop does NOT count!) - if ((!drag || jQuery.isEmptyObject(drag)) && - (!drop || jQuery.isEmptyObject(drop) || - Object.keys(drop).length === 1 && typeof drop.egw_cancel_drop !== 'undefined')) - { - return; - } - - // Find existing actions so we don't get copies - var mgr = _selected[0].manager; - var copy_action = mgr.getActionById('egw_copy'); - var add_action = mgr.getActionById('egw_copy_add'); - var clipboard_action = mgr.getActionById('egw_os_clipboard'); - var paste_action = mgr.getActionById('egw_paste'); - - // Fake UI so we can simulate the position of the drop - var ui = { - position: {top: 0, left: 0}, - offset: {top: 0, left: 0} - }; - if(this._context.event) - { - var event = this._context.event.originalEvent; - ui.position = {top: event.pageY, left: event.pageX}; - ui.offset = {top: event.offsetY, left: event.offsetX}; - } - // Create default copy menu action - if(drag && !jQuery.isEmptyObject(drag)) - { - // Don't re-add if it's there - if(copy_action == null) - { - // Create a drag action that allows linking - copy_action = mgr.addAction('popup', 'egw_copy', egw.lang('Copy to clipboard'), egw.image('copy'), function(action, selected) { - // Copied, now add to clipboard - var clipboard = { - type:[], - selected:[] - }; - - // When pasting we need to know the type of drag - for(var k in drag) - { - if(drag[k].enabled && drag[k].actionObj.dragType.length > 0) - { - clipboard.type = clipboard.type.concat(drag[k].actionObj.dragType); - } - } - clipboard.type = jQuery.unique(clipboard.type); - // egwAction is a circular structure and can't be stringified so just take what we want - // Hopefully that's enough for the action handlers - for(var k in selected) - { - if(selected[k].id) clipboard.selected.push({id:selected[k].id, data:selected[k].data}); - } - - // Save it in session - egw.setSessionItem('phpgwapi', 'egw_clipboard', JSON.stringify(clipboard)); - },true); - copy_action.group = 2.5; - } - if(add_action == null) - { - // Create an action to add selected to clipboard - add_action = mgr.addAction('popup', 'egw_copy_add', egw.lang('Add to clipboard'), egw.image('copy'), function(action, selected) { - // Copied, now add to clipboard - var clipboard = JSON.parse(egw.getSessionItem('phpgwapi', 'egw_clipboard')) || { - type:[], - selected:[] - }; - - // When pasting we need to know the type of drag - for(var k in drag) - { - if(drag[k].enabled && drag[k].actionObj.dragType.length > 0) - { - clipboard.type = clipboard.type.concat(drag[k].actionObj.dragType); - } - } - clipboard.type = jQuery.unique(clipboard.type); - // egwAction is a circular structure and can't be stringified so just take what we want - // Hopefully that's enough for the action handlers - for(var k in selected) - { - if(selected[k].id) clipboard.selected.push({id:selected[k].id, data:selected[k].data}); - } - - // Save it in session - egw.setSessionItem('phpgwapi', 'egw_clipboard', JSON.stringify(clipboard)); - },true); - add_action.group = 2.5; - - } - if(clipboard_action == null) - { - // Create an action to add selected to clipboard - clipboard_action = mgr.addAction('popup', 'egw_os_clipboard', egw.lang('Copy to OS clipboard'), egw.image('copy'), function (action) - { - - if (document.queryCommandSupported('copy')) - { - jQuery(action.data.target).trigger('copy'); - } - }, true); - clipboard_action.group = 2.5; - } - let os_clipboard_caption = ""; - if (this._context.event) - { - os_clipboard_caption = this._context.event.originalEvent.target.innerText.trim(); - clipboard_action.set_caption(egw.lang('Copy "%1"', os_clipboard_caption.length > 20 ? os_clipboard_caption.substring(0, 20) + '...' : os_clipboard_caption)); - clipboard_action.data.target = this._context.event.originalEvent.target; - } - jQuery(clipboard_action.data.target).off('copy').on('copy', function (event) - { - try - { - egw.copyTextToClipboard(os_clipboard_caption, clipboard_action.data.target, event).then((successful) => - { - // Fallback - if (typeof successful == "undefined") - { - // Clear message - egw.message(egw.lang("'%1' copied to clipboard", os_clipboard_caption.length > 20 ? os_clipboard_caption.substring(0, 20) + '...' : os_clipboard_caption)); - window.getSelection().removeAllRanges(); - return false; - } - else - { - // Show fail message - egw.message(egw.lang('Use Ctrl-C/Cmd-C to copy')); - } - }); - - } - catch (err) - { - } - }); - if(typeof _links[copy_action.id] == 'undefined') - { - _links[copy_action.id] = { - "actionObj": copy_action, - "enabled": true, - "visible": true, - "cnt": 0 - }; - } - if(typeof _links[add_action.id] == 'undefined') - { - _links[add_action.id] = { - "actionObj": add_action, - "enabled": true, - "visible": true, - "cnt": 0 - }; - } - if(typeof _links[clipboard_action.id] == 'undefined') - { - _links[clipboard_action.id] = { - "actionObj": clipboard_action, - "enabled": os_clipboard_caption.length > 0, - "visible": os_clipboard_caption.length > 0, - "cnt": 0 - }; - } - } - - // Create default paste menu item - if(drop && !jQuery.isEmptyObject(drop)) - { - // Create paste action - // This injects the clipboard data and calls the original handler - var paste_exec = function(action, selected) { - // Add in clipboard as a sender - var clipboard = JSON.parse(egw.getSessionItem('phpgwapi', 'egw_clipboard')); - // Fake drop position - drop[action.id].actionObj.ui = ui; - // Set a flag so apps can tell the difference, if they need to - drop[action.id].actionObj.paste = true; - - drop[action.id].actionObj.execute(clipboard.selected,selected[0]); - - drop[action.id].actionObj.paste = false; - }; - - var clipboard = JSON.parse(egw.getSessionItem('phpgwapi', 'egw_clipboard')) || { - type:[], - selected:[] - }; - - // Don't re-add if action already exists - if(paste_action == null) - { - paste_action = mgr.addAction('popup', 'egw_paste', egw.lang('Paste'), egw.image('editpaste'), paste_exec,true); - paste_action.group = 2.5; - paste_action.order = 9; - paste_action.canHaveChildren.push('drop'); - } - - // Set hint to something resembling current clipboard - var hint = egw.lang('Clipboard') + ":\n"; - paste_action.set_hint(hint); - // Add titles of entries - for(var i = 0; i < clipboard.selected.length; i++) - { - var id = clipboard.selected[i].id.split('::'); - egw.link_title(id[0],id[1],function(title) {if(title)this.hint += title+"\n";},paste_action); - } - - // Add into links so it's included in menu - if(paste_action && paste_action.enabled.exec(paste_action, clipboard.selected, _selected[0])) - { - if(typeof _links[paste_action.id] == 'undefined') - { - _links[paste_action.id] = { - "actionObj": paste_action, - "enabled": false, - "visible": clipboard != null, - "cnt": 0 - }; - } - while(paste_action.children.length > 0) - { - paste_action.children[0].remove(); - } - - // If nothing [valid] in the clipboard, don't bother with children - if(clipboard == null || typeof clipboard.type != 'object') - { - return; - } - - // Add in actual actions as children - for(var k in drop) - { - // Add some choices - need to be a copy, or they interfere with - // the original - var drop_clone = jQuery.extend({},drop[k].actionObj); - var parent = paste_action.parent === drop_clone.parent ? paste_action : (paste_action.getActionById(drop_clone.parent.id) || paste_action); - drop_clone.parent = parent; - drop_clone.onExecute = new egwFnct(this, null, []); - drop_clone.children = []; - drop_clone.set_onExecute(paste_exec); - parent.children.push(drop_clone); - parent.allowOnMultiple = paste_action.allowOnMultiple && drop_clone.allowOnMultiple; - _links[k] = jQuery.extend({},drop[k]); - _links[k].actionObj = drop_clone; - - // Drop is allowed if clipboard types intersect drop types - _links[k].enabled = false; - _links[k].visible = false; - for (var i = 0; i < drop_clone.acceptedTypes.length; i++) - { - if (clipboard.type.indexOf(drop_clone.acceptedTypes[i]) != -1) - { - _links[paste_action.id].enabled = true; - _links[k].enabled = true; - _links[k].visible = true; - break; - } - } - } - } - } - }; - return ai; -} \ No newline at end of file diff --git a/api/js/egw_action/egw_action_popup.ts b/api/js/egw_action/egw_action_popup.ts new file mode 100644 index 0000000000..880a467b7f --- /dev/null +++ b/api/js/egw_action/egw_action_popup.ts @@ -0,0 +1,12 @@ +/** + * eGroupWare egw_action framework - egw action framework + * + * @link http://www.egroupware.org + * @author Andreas Stöckel + * @copyright 2011 by Andreas Stöckel + * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License + * @package egw_action + * @version $Id$ + */ + +//TODO this wants to be removed diff --git a/api/js/egw_action/egw_dragdrop_dhtmlx_tree.js b/api/js/egw_action/egw_dragdrop_dhtmlx_tree.js deleted file mode 100644 index 90b18cd6a9..0000000000 --- a/api/js/egw_action/egw_dragdrop_dhtmlx_tree.js +++ /dev/null @@ -1,74 +0,0 @@ -/** - * EGroupware egw_dragdrop_dhtmlxmenu - egw action framework - * - * @link https://www.egroupware.org - * @author Andreas Stöckel - * @copyright 2011 by Andreas Stöckel - * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License - * @package egw_action - */ - -/*egw:uses - egw_action; -*/ -import {egwActionObjectInterface} from "./egw_action.js"; -import {egwBitIsSet} from "./egw_action_common.js"; -import {EGW_AI_DRAG_OUT, EGW_AI_DRAG_OVER, EGW_AO_STATE_FOCUSED, EGW_AO_STATE_SELECTED} from "./egw_action_constants.js"; - -/** -* This file contains an egw_actionObjectInterface which allows a dhtmlx tree -* row to be a drag target and contains a function which transforms a complete -* dhtmlx tree into egw_actionObjects -*/ - -export function dhtmlxTree_getNode(_tree, _itemId) { - var node = _tree._globalIdStorageFind(_itemId); - if (node != null) - { - // Get the outer html table node of the tree node - return the first - // "tr" child of the element - return jQuery("tr:first", node.htmlNode).get(0); - } -} - -// An action object interface for an dhtmlxTree entry - it only contains the -// code needed for drag/drop handling -export function dhtmlxtreeItemAOI(_tree, _itemId) -{ - var aoi = new egwActionObjectInterface(); - - // Retrieve the actual node from the tree - aoi.node = dhtmlxTree_getNode(_tree, _itemId); - aoi.id = _itemId; - aoi.doGetDOMNode = function() { - return aoi.node; - } - - aoi.doTriggerEvent = function(_event) { - if (_event == EGW_AI_DRAG_OVER) - { - jQuery(this.node).addClass("draggedOver"); - } - if (_event == EGW_AI_DRAG_OUT) - { - jQuery(this.node).removeClass("draggedOver"); - } - } - - aoi.doSetState = function(_state) { - if(!_tree || !_tree.focusItem) return; - - // Update the "focused" flag - if(egwBitIsSet(_state, EGW_AO_STATE_FOCUSED)) - { - _tree.focusItem(this.id); - } - if(egwBitIsSet(_state, EGW_AO_STATE_SELECTED)) - { - _tree.selectItem(this.id, false); // false = do not trigger onSelect - } - } - - return aoi; -} - diff --git a/api/js/egw_action/egw_dragdrop_dhtmlx_tree.ts b/api/js/egw_action/egw_dragdrop_dhtmlx_tree.ts new file mode 100755 index 0000000000..b4a1d5ed5d --- /dev/null +++ b/api/js/egw_action/egw_dragdrop_dhtmlx_tree.ts @@ -0,0 +1,82 @@ +/** + * EGroupware egw_dragdrop_dhtmlxmenu - egw action framework + * + * @link https://www.egroupware.org + * @author Andreas Stöckel + * @copyright 2011 by Andreas Stöckel + * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License + * @package egw_action + */ + + +import {egwBitIsSet} from "./egw_action_common"; +import {EGW_AI_DRAG_OUT, EGW_AI_DRAG_OVER, EGW_AO_STATE_FOCUSED, EGW_AO_STATE_SELECTED} from "./egw_action_constants"; +import {egwActionObjectInterface} from "./egw_action"; + +/** + * This file contains an egw_actionObjectInterface which allows a dhtmlx tree + * row to be a drag target and contains a function which transforms a complete + * dhtmlx tree into egw_actionObjects + */ +declare class dhtmlXTreeObject +{ + + _globalIdStorageFind(_itemId: string): any +} + +export function dhtmlxTree_getNode(_tree: dhtmlXTreeObject, _itemId: string) +{ + const node = _tree._globalIdStorageFind(_itemId); + if (node != null) + { + // Get the outer html table node of the tree node - return the first + // "tr" child of the element + return node.htmlNode.querySelector("tr:first-child") + } +} + +// An action object interface for an dhtmlxTree entry - it only contains the +// code needed for drag/drop handling +export class dhtmlxtreeItemAOI +{ + constructor(_tree, _itemId) + { + + const aoi = new egwActionObjectInterface(); + + // Retrieve the actual node from the tree + aoi.node = dhtmlxTree_getNode(_tree, _itemId); + aoi.id = _itemId; + aoi.doGetDOMNode = function () { + return aoi.node; + } + + aoi.doTriggerEvent = function (_event) { + if (_event == EGW_AI_DRAG_OVER) + { + jQuery(this.node).addClass("draggedOver"); + } + if (_event == EGW_AI_DRAG_OUT) + { + jQuery(this.node).removeClass("draggedOver"); + } + } + + aoi.doSetState = function (_state) { + if (!_tree || !_tree.focusItem) return; + + // Update the "focused" flag + if (egwBitIsSet(_state, EGW_AO_STATE_FOCUSED)) + { + _tree.focusItem(this.id); + } + if (egwBitIsSet(_state, EGW_AO_STATE_SELECTED)) + { + _tree.selectItem(this.id, false); // false = do not trigger onSelect + } + } + + return aoi; + } +} + diff --git a/api/js/egw_action/egw_keymanager.js b/api/js/egw_action/egw_keymanager.ts old mode 100644 new mode 100755 similarity index 52% rename from api/js/egw_action/egw_keymanager.js rename to api/js/egw_action/egw_keymanager.ts index 1213d7e5af..3eba2fc68d --- a/api/js/egw_action/egw_keymanager.js +++ b/api/js/egw_action/egw_keymanager.ts @@ -13,23 +13,25 @@ egw_action; */ -import {egw_getAppObjectManager, egw_globalObjectManager} from "./egw_action.js"; -import {_egw_active_menu} from "./egw_menu.js"; +import {egw_getAppObjectManager, egw_globalObjectManager} from "./egw_action"; +import {_egw_active_menu} from "./egw_menu"; import { EGW_AO_FLAG_DEFAULT_FOCUS, EGW_AO_EXEC_SELECTED, EGW_VALID_KEYS, EGW_KEY_MENU, EGW_KEY_F1, EGW_KEY_F12 -} from "./egw_action_constants.js"; -import {egwBitIsSet} from "./egw_action_common.js"; +} from "./egw_action_constants"; +import {egwBitIsSet} from "./egw_action_common"; +import type {EgwActionObject} from "./EgwActionObject"; +import type {EgwActionObjectManager} from "./EgwActionObjectManager"; /** - * The tranlation function converts the given native key code into one of the + * The translation function converts the given native key code into one of the * egw key constants as listed above. This key codes were chosen to match the * key codes of IE and FF. */ -export var egw_keycode_translation_function = function(_nativeKeyCode) { +export var egw_keycode_translation_function = function (_nativeKeyCode) { // Map the numpad to the 0..9 keys if (_nativeKeyCode >= 96 && _nativeKeyCode <= 105) { @@ -43,9 +45,11 @@ export var egw_keycode_translation_function = function(_nativeKeyCode) { * Checks whether the given keycode is in the list of valid key codes. If not, * returns -1. */ -export function egw_keycode_makeValid(_keyCode) { - var idx = EGW_VALID_KEYS.indexOf(_keyCode); - if (idx >= 0) { +export function egw_keycode_makeValid(_keyCode) +{ + const idx = EGW_VALID_KEYS.indexOf(_keyCode); + if (idx >= 0) + { return _keyCode; } @@ -56,34 +60,49 @@ function _egw_nodeIsInInput(_node) { if ((_node != null) && (_node != document)) { - var tagName = _node.tagName.toLowerCase(); + const tagName = _node.tagName.toLowerCase(); if (tagName == "input" || tagName == "select" || tagName == 'textarea' || tagName == 'button' || ['et2-textbox', 'et2-number', 'et2-searchbox', 'et2-select', 'et2-textarea', 'et2-button'].indexOf(tagName) != -1) { return true; - } - else + } else { return _egw_nodeIsInInput(_node.parentNode); } - } - else + } else { return false; } } +/** + * execute + * @param fn after DOM is ready + * replacement for jQuery.ready() + */ +function ready(fn) +{ + if (document.readyState !== 'loading') + { + fn(); + } else + { + document.addEventListener('DOMContentLoaded', fn); + } +} + /** * Register the onkeypress handler on the document */ -jQuery(function() { +ready(() => {//waits for DOM ready // Fetch the key down event and translate it into browser-independent and // easy to use key codes and shift states - jQuery(document).keydown( function(e) { + document.addEventListener("keydown", (keyboardEvent: KeyboardEvent) => { // Translate the given key code and make it valid - var keyCode = e.which; + // TODO there are actual string key codes now so it would be nice to use those standardized ones instead of using our own + let keyCode = keyboardEvent.keyCode; keyCode = egw_keycode_translation_function(keyCode); keyCode = egw_keycode_makeValid(keyCode); @@ -91,76 +110,90 @@ jQuery(function() { if (keyCode != -1) { // Check whether the event came from the sidebox - if yes, ignore - if(jQuery(e.target).parents("#egw_fw_sidemenu").length > 0) return; + //if(jQuery(keyboardEvent.target).parents("#egw_fw_sidemenu").length > 0) return; + //TODO check replacement with ralf or Nathan + let target: any = keyboardEvent.target // this is some kind of element + while ((target = target.parentNode) && target !== document) + { + if (!"#egw_fw_sidemenu" || target.matches("#egw_fw_sidemenu")) return; + } // If a context menu is open, give the keyboard to it if (typeof _egw_active_menu !== undefined && _egw_active_menu && - _egw_active_menu.keyHandler(keyCode, e.shiftKey, e.ctrlKey || e.metaKey, e.altKey)) + _egw_active_menu.keyHandler(keyCode, keyboardEvent.shiftKey, keyboardEvent.ctrlKey || keyboardEvent.metaKey, keyboardEvent.altKey)) { - e.preventDefault(); + keyboardEvent.preventDefault(); return; } // Check whether the event came from an input field - if yes, only // allow function keys (like F1) to be captured by our code - var inInput = _egw_nodeIsInInput(e.target); + const inInput = _egw_nodeIsInInput(keyboardEvent.target); if (!inInput || (keyCode >= EGW_KEY_F1 && keyCode <= EGW_KEY_F12)) { - if (egw_keyHandler(keyCode, e.shiftKey, e.ctrlKey || e.metaKey, e.altKey)) + if (egw_keyHandler(keyCode, keyboardEvent.shiftKey, keyboardEvent.ctrlKey || keyboardEvent.metaKey, keyboardEvent.altKey)) { // If the key handler successfully passed the key event to some - // sub component, prevent the default action - e.preventDefault(); + // subcomponent, prevent the default action + keyboardEvent.preventDefault(); } } } }); }); - /** * Required to catch the context menu */ jQuery(window).on("contextmenu",document, function(event) { // Check for actual key press - if(!(event.originalEvent.x == 1 && event.originalEvent.y == 1)) return true; - if(!event.ctrlKey && egw_keyHandler(EGW_KEY_MENU, event.shiftKey, event.ctrlKey || event.metaKey, event.altKey)) + if (!(event.originalEvent.x == 1 && event.originalEvent.y == 1)) return true; + if (!event.ctrlKey && egw_keyHandler(EGW_KEY_MENU, event.shiftKey, event.ctrlKey || event.metaKey, event.altKey)) { // If the key handler successfully passed the key event to some - // sub component, prevent the default action + // subcomponent, prevent the default action event.preventDefault(); return false; } return true; -}); +}) /** - * Creates an unique key for the given shortcut + * Creates a unique key for the given shortcut + * TODO those ids exist already */ -export function egw_shortcutIdx(_keyCode, _shift, _ctrl, _alt) +export function egw_shortcutIdx(_keyCode: number, _shift: boolean, _ctrl: boolean, _alt: boolean):string { return "_" + _keyCode + "_" + (_shift ? "S" : "") + (_ctrl ? "C" : "") + (_alt ? "A" : ""); } +type Shortcut= {"handler":()=> boolean, + "context": any, + "shortcut":{ + "keyCode": number, + "shift": boolean, + "ctrl": boolean, + "alt":boolean + }} -var egw_registeredShortcuts = {} +export var egw_registeredShortcuts = {} /** * Registers a global shortcut. If the shortcut already exists, it is overwritten. - * @param int _keyCode is one of the keycode constants - * @param bool _shift whether shift has to be set - * @param bool _ctrl whether ctrl has to be set - * @param bool _alt whether alt has to be set - * @param function _handler the function which will be called when the shortcut + * @param {int} _keyCode is one of the keycode constants + * @param {bool} _shift whether shift has to be set + * @param {bool} _ctrl whether ctrl has to be set + * @param {bool} _alt whether alt has to be set + * @param {function} _handler the function which will be called when the shortcut * is evoked. An object containing the shortcut data will be passed as first * parameter. - * @param object _context is the context in which the function will be executed + * @param {any} _context is the context in which the function will be executed */ -export function egw_registerGlobalShortcut(_keyCode, _shift, _ctrl, _alt, _handler, _context) +export function egw_registerGlobalShortcut(_keyCode: number, _shift: boolean, _ctrl: boolean, _alt: boolean, _handler: () => boolean, _context: any) { // Generate the hash map index for the shortcut - var idx = egw_shortcutIdx(_keyCode, _shift, _ctrl, _alt); + const idx = egw_shortcutIdx(_keyCode, _shift, _ctrl, _alt); // Register the shortcut egw_registeredShortcuts[idx] = { @@ -181,7 +214,7 @@ export function egw_registerGlobalShortcut(_keyCode, _shift, _ctrl, _alt, _handl export function egw_unregisterGlobalShortcut(_keyCode, _shift, _ctrl, _alt) { // Generate the hash map index for the shortcut - var idx = egw_shortcutIdx(_keyCode, _shift, _ctrl, _alt); + const idx = egw_shortcutIdx(_keyCode, _shift, _ctrl, _alt); // Delete the entry from the hash map delete egw_registeredShortcuts[idx]; @@ -192,25 +225,26 @@ export function egw_unregisterGlobalShortcut(_keyCode, _shift, _ctrl, _alt) * _shift, _ctrl, _alt values have been translated into platform independent * values (for apple devices). */ -export function egw_keyHandler(_keyCode, _shift, _ctrl, _alt) { +export function egw_keyHandler(_keyCode, _shift, _ctrl, _alt) +{ // Check whether there is a global shortcut waiting for the keypress event - var idx = egw_shortcutIdx(_keyCode, _shift, _ctrl, _alt); + const idx = egw_shortcutIdx(_keyCode, _shift, _ctrl, _alt); if (typeof egw_registeredShortcuts[idx] != "undefined") { - var shortcut = egw_registeredShortcuts[idx]; + const shortcut:Shortcut = egw_registeredShortcuts[idx]; // Call the registered shortcut function and return its result, if it handled it - var result = shortcut.handler.call(shortcut.context, shortcut.shortcut); - if(result) return result; + const result = shortcut.handler.call(shortcut.context, shortcut.shortcut); + if (result) return result; } // Pass the keypress to the currently focused action object // Get the object manager and fetch the container of the currently // focused object - var focusedObject = egw_globalObjectManager ? egw_globalObjectManager.getFocusedObject() : null; - var appMgr = egw_getAppObjectManager(false); + let focusedObject: EgwActionObject = egw_globalObjectManager ? egw_globalObjectManager.getFocusedObject() : null; + const appMgr: EgwActionObjectManager = egw_getAppObjectManager(false); if (appMgr && !focusedObject) { focusedObject = appMgr.getFocusedObject(); @@ -220,49 +254,48 @@ export function egw_keyHandler(_keyCode, _shift, _ctrl, _alt) { // If the current application doesn't have a focused object, // check whether the application object manager contains an object // with the EGW_AO_FLAG_DEFAULT_FOCUS flag set. - var cntr = null; - for (var i = 0; i < appMgr.children.length; i++) + let egwActionObject:EgwActionObject = null; + for (const child of appMgr.children) { - var child = appMgr.children[i]; if (egwBitIsSet(EGW_AO_FLAG_DEFAULT_FOCUS, child.flags)) { - cntr = child; + egwActionObject = child; break; } } // Get the first child of the found container and focus the first // object - if (cntr && cntr.children.length > 0) + if (egwActionObject && egwActionObject.children.length > 0) { - cntr.children[0].setFocused(true); - focusedObject = cntr.children[0]; + egwActionObject.children[0].setFocused(true); + focusedObject = egwActionObject.children[0]; } } } if (focusedObject) { // Handle the default keys (arrow_up, down etc.) - var cntr = focusedObject.getContainerRoot(); - var handled = false; + let egwActionObject = focusedObject.getContainerRoot(); + let handled = false; - if (cntr) + if (egwActionObject) { - handled = cntr.handleKeyPress(_keyCode, _shift, _ctrl, _alt); + handled = egwActionObject.handleKeyPress(_keyCode, _shift, _ctrl, _alt); } // Execute the egw_popup key handler of the focused object if (!handled) { return focusedObject.executeActionImplementation( - { - "keyEvent": { - "keyCode": _keyCode, - "shift": _shift, - "ctrl": _ctrl, - "alt": _alt - } - }, "popup", EGW_AO_EXEC_SELECTED); + { + "keyEvent": { + "keyCode": _keyCode, + "shift": _shift, + "ctrl": _ctrl, + "alt": _alt + } + }, "popup", EGW_AO_EXEC_SELECTED); } return handled; diff --git a/api/js/egw_action/egw_menu.js b/api/js/egw_action/egw_menu.js deleted file mode 100644 index 9dc792907c..0000000000 --- a/api/js/egw_action/egw_menu.js +++ /dev/null @@ -1,524 +0,0 @@ -/** - * eGroupWare egw_action framework - JS Menu abstraction - * - * @link http://www.egroupware.org - * @author Andreas Stöckel - * @copyright 2011 by Andreas Stöckel - * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License - * @package egw_action - * - * @Todo: @new-js-loader port to TypeScript - */ -import {egwMenuImpl} from './egw_menu_dhtmlx.js'; -import {egw_shortcutIdx} from './egw_keymanager.js'; -import { - EGW_KEY_ARROW_DOWN, - EGW_KEY_ARROW_LEFT, - EGW_KEY_ARROW_RIGHT, - EGW_KEY_ARROW_UP, EGW_KEY_ENTER, - EGW_KEY_ESCAPE -} from "./egw_action_constants.js"; -//Global variable which is used to store the currently active menu so that it -//may be closed when another menu openes -export var _egw_active_menu = null; - -/** - * Internal function which generates a menu item with the given parameters as used - * in e.g. the egwMenu.addItem function. - */ -//TODO Icons: write PHP GD script which is cabable of generating the menu icons in various states (disabled, highlighted) -function _egwGenMenuItem(_parent, _id, _caption, _iconUrl, _onClick) -{ - //Preset the parameters - if (typeof _parent == "undefined") - _parent = null; - if (typeof _id == "undefined") - _id = ""; - if (typeof _caption == "undefined") - _caption = ""; - if (typeof _iconUrl == "undefined") - _iconUrl = ""; - if (typeof _onClick == "undefined") - _onClick = null; - - //Create a menu item with no parent (null) and set the given parameters - var item = new egwMenuItem(_parent, _id); - item.set_caption(_caption); - item.set_iconUrl(_iconUrl); - item.set_onClick(_onClick); - - return item; -} - -/** - * Internal function which parses the given menu tree in _elements and adds the - * elements to the given parent. - */ -function _egwGenMenuStructure(_elements, _parent) -{ - var items = []; - - //Go through each object in the elements array - for (var i = 0; i < _elements.length; i++) - { - //Go through each key of the current object - var obj = _elements[i]; - var item = new egwMenuItem(_parent, null); - for (var key in obj) - { - if (key == "children" && obj[key].constructor === Array) - { - //Recursively load the children. - item.children = _egwGenMenuStructure(obj[key], item); - } - else - { - //Directly set the other keys - //TODO Sanity neccessary checks here? - //TODO Implement menu item getters? - if (key == "id" || key == "caption" || key == "iconUrl" || - key == "checkbox" || key == "checked" || key == "groupIndex" || - key == "enabled" || key == "default" || key == "onClick" || - key == "hint" || key == "shortcutCaption") - { - item['set_' + key](obj[key]); - } - } - } - - items.push(item); - } - - return items; -} - -/** - * Internal function which searches for the given ID inside an element tree. - */ -function _egwSearchMenuItem(_elements, _id) -{ - for (var i = 0; i < _elements.length; i++) - { - if (_elements[i].id === _id) - return _elements[i]; - - var item = _egwSearchMenuItem(_elements[i].children, _id); - if (item) - return item; - } - - return null; -} - -/** - * Internal function which alows to set the onClick handler of multiple menu items - */ -function _egwSetMenuOnClick(_elements, _onClick) -{ - for (var i = 0; i < _elements.length; i++) - { - if (_elements[i].onClick === null) - { - _elements[i].onClick = _onClick; - } - _egwSetMenuOnClick(_elements[i].children, _onClick); - } -} - -/** - * Constructor for the egwMenu object. The egwMenu object is a abstract representation - * of a context/popup menu. The actual generation of the menu can by done by so - * called menu implementations. Those are activated by simply including the JS file - * of such an implementation. - * - * The currently available implementation is the "egwDhtmlxMenu.js" which is based - * upon the dhtmlxmenu component. - */ -export function egwMenu() -{ - //The "items" variable contains all menu items of the menu - this.children = []; - - //The "instance" variable contains the currently opened instance. There may - //only be one instance opened at a time. - this.instance = null; -} - -/** - * The private _checkImpl function checks whether a menu implementation is available. - * - * @returns bool whether a menu implemenation is available. - */ -egwMenu.prototype._checkImpl = function() -{ - return typeof egwMenuImpl == 'function'; -} - -/** - * The showAtElement function shows the menu at the given screen position in an - * (hopefully) optimal orientation. There can only be one instance of the menu opened at - * one time and the menu implementation should care that there is only one menu - * opened globaly at all. - * - * @param int _x is the x position at which the menu will be opened - * @param int _y is the y position at which the menu will be opened - * @param bool _force if true, the menu will be reopened at the given position, - * even if it already had been opened. Defaults to false. - * @returns bool whether the menu had been opened - */ -egwMenu.prototype.showAt = function(_x, _y, _force) -{ - if (typeof _force == "undefined") - _force = false; - - //Hide any other currently active menu - if (_egw_active_menu != null) - { - if (_egw_active_menu == this && !_force) - { - this.hide(); - return false; - } - else - { - _egw_active_menu.hide(); - } - } - - if (this.instance == null && this._checkImpl) - { - //Obtain a new egwMenuImpl object and pass this instance to it - this.instance = new egwMenuImpl(this.children); - - _egw_active_menu = this; - - var self = this; - this.instance.showAt(_x, _y, function() { - self.instance = null; - _egw_active_menu = null; - }); - return true; - } - - return false; -} - -/** - * Keyhandler to allow keyboard navigation of menu - * - * @return boolean true if we dealt with the keypress - */ -egwMenu.prototype.keyHandler = function(_keyCode, _shift, _ctrl, _alt) -{ - // Let main keyhandler deal with shortcuts - var idx = egw_shortcutIdx(_keyCode, _shift, _ctrl, _alt); - if (typeof egw_registeredShortcuts[idx] !== "undefined") - { - return false; - } - - let current = this.instance.dhtmlxmenu.menuSelected; - if(current !== -1) - { - let find_func = function(child) { - if( child.id === current.replace(this.instance.dhtmlxmenu.idPrefix, "")) - { - return child; - } - else if (child.children) - { - for(let i = 0; i < child.children.length; i++) - { - const result = find_func(child.children[i]); - if(result) return result; - } - } - return null; - }.bind(this); - current = find_func(this); - } - else - { - current = this.children[0]; - jQuery("#"+this.instance.dhtmlxmenu.idPrefix + current.id).trigger("mouseover"); - return true; - } - - switch (_keyCode) { - case EGW_KEY_ENTER: - jQuery("#"+this.instance.dhtmlxmenu.idPrefix + current.id).trigger("click"); - return true; - case EGW_KEY_ESCAPE: - this.hide(); - return true; - case EGW_KEY_ARROW_RIGHT: - if(current.children) - { - current = current.children[0]; - } - break; - case EGW_KEY_ARROW_LEFT: - if(current.parent && current.parent !== this) - { - current = current.parent; - } - break; - case EGW_KEY_ARROW_UP: - case EGW_KEY_ARROW_DOWN: - const direction = _keyCode === EGW_KEY_ARROW_DOWN ? 1 : -1; - let parent = current.parent; - let index = parent.children.indexOf(current); - let cont = false; - - // Don't run off ends, skip disabled - do - { - index += direction; - cont = !parent.children[index] || !parent.children[index].enabled || !parent.children[index].id; - } while (cont && index + direction < parent.children.length && index + direction >= 0); - if(index > parent.children.length - 1) - { - index = parent.children.length-1; - } - if(index < 0) - { - index = 0; - } - current = parent.children[index]; - break; - default: - return false; - } - - if(current) - { - jQuery("#" + this.instance.dhtmlxmenu.idPrefix + current.id).trigger("mouseover"); - this.instance.dhtmlxmenu._redistribSubLevelSelection(this.instance.dhtmlxmenu.idPrefix + current.id,this.instance.dhtmlxmenu.idPrefix + ( current.parent ? current.parent.id : this.instance.dhtmlxmenu.topId)); - } - return true; -}; - -/** - * Hides the menu if it is currently opened. Otherwise nothing happenes. - */ -egwMenu.prototype.hide = function() -{ - //Reset the currently active menu variable - if (_egw_active_menu == this) - _egw_active_menu = null; - - //Check whether an currently opened instance exists. If it does, close it. - if (this.instance != null) - { - this.instance.hide(); - this.instance = null; - } -} - -/** - * Adds a new menu item to the list and returns a reference to that object. - * - * @param string _id is a unique identifier of the menu item. You can use the - * the getItem function to search a specific menu item inside the menu tree. The - * id may also be false, null or "", which makes sense for items like seperators, - * which you don't want to access anymore after adding them to the menu tree. - * @param string _caption is the caption of the newly generated menu item. Set the caption - * to "-" in order to create a sperator. - * @param string _iconUrl is the URL of the icon which should be prepended to the - * menu item. It may be false, null or "" if you don't want a icon to be displayed. - * @param function _onClick is the JS function which is being executed when the - * menu item is clicked. - * @returns egwMenuItem the newly generated menu item, which had been appended to the - * menu item list. - */ -egwMenu.prototype.addItem = function(_id, _caption, _iconUrl, _onClick) -{ - //Append the item to the list - var item = _egwGenMenuItem(this, _id, _caption, _iconUrl, _onClick); - this.children.push(item); - - return item; -} - -/** - * Removes all elements fromt the menu structure. - */ -egwMenu.prototype.clear = function() -{ - this.children = []; -} - -/** - * Loads the menu structure from the given object tree. The object tree is an array - * of objects which may contain a subset of the menu item properties. The "children" - * property of such an object is interpreted as a new sub-menu tree and appended - * to that child. - * - * @param array _elements is a array of elements which should be added to the menu - */ -egwMenu.prototype.loadStructure = function(_elements) -{ - this.children = _egwGenMenuStructure(_elements, this); -} - -/** - * Searches for the given item id within the element tree. - */ -egwMenu.prototype.getItem = function(_id) -{ - return _egwSearchMenuItem(this.children, _id); -} - -/** - * Applies the given onClick handler to all menu items which don't have a clicked - * handler assigned yet. - */ -egwMenu.prototype.setGlobalOnClick = function(_onClick) -{ - _egwSetMenuOnClick(this.children, _onClick); -} - -/** - * Constructor for the egwMenuItem. Each entry in a menu (including seperators) - * is represented by a menu item. - */ -export function egwMenuItem(_parent, _id) -{ - this.id = _id; - this.caption = ""; - this.checkbox = false; - this.checked = false; - this.groupIndex = 0; - this.enabled = true; - this.iconUrl = ""; - this.onClick = null; - this["default"] = false; - this.data = null; - this.shortcutCaption = null; - - this.children = []; - this.parent = _parent; -} - -/** - * Searches for the given item id within the element tree. - */ -egwMenuItem.prototype.getItem = function(_id) -{ - if (this.id === _id) - return this; - - return _egwSearchMenuItem(this.children, _id); -} - -/** - * Applies the given onClick handler to all menu items which don't have a clicked - * handler assigned yet. - */ -egwMenuItem.prototype.setGlobalOnClick = function(_onClick) -{ - this.onClick = _onClick; - _egwSetMenuOnClick(this.children, _onClick); -} - -/** - * Adds a new menu item to the list and returns a reference to that object. - * - * @param string _id is a unique identifier of the menu item. You can use the - * the getItem function to search a specific menu item inside the menu tree. The - * id may also be false, null or "", which makes sense for items like seperators, - * which you don't want to access anymore after adding them to the menu tree. - * @param string _caption is the caption of the newly generated menu item. Set the caption - * to "-" in order to create a sperator. - * @param string _iconUrl is the URL of the icon which should be prepended to the - * menu item. It may be false, null or "" if you don't want a icon to be displayed. - * @param function _onClick is the JS function which is being executed when the - * menu item is clicked. - * @returns egwMenuItem the newly generated menu item, which had been appended to the - * menu item list. - */ -egwMenuItem.prototype.addItem = function(_id, _caption, _iconUrl, _onClick) -{ - //Append the item to the list - var item = _egwGenMenuItem(this, _id, _caption, _iconUrl, _onClick); - this.children.push(item); - - return item; -} - - -//Setter functions for the menuitem properties - -egwMenuItem.prototype.set_id = function(_value) -{ - this.id = _value; -} - -egwMenuItem.prototype.set_caption = function(_value) -{ - //A value of "-" means that this element is a seperator. - this.caption = _value; -} - -egwMenuItem.prototype.set_checkbox = function(_value) -{ - this.checkbox = _value; -} - -egwMenuItem.prototype.set_checked = function(_value) -{ - if (_value && this.groupIndex > 0) - { - //Uncheck all other elements in this radio group - for (var i = 0; i < this.parent.children.length; i++) - { - var obj = this.parent.children[i]; - if (obj.groupIndex == this.groupIndex) - obj.checked = false; - } - } - this.checked = _value; -} - -egwMenuItem.prototype.set_groupIndex = function(_value) -{ - //If groupIndex is greater than 0 and the element is a checkbox, it is - //treated like a radio box - this.groupIndex = _value; -} - -egwMenuItem.prototype.set_enabled = function(_value) -{ - this.enabled = _value; -} - -egwMenuItem.prototype.set_onClick = function(_value) -{ - this.onClick = _value; -} - -egwMenuItem.prototype.set_iconUrl = function(_value) -{ - this.iconUrl = _value; -} - -egwMenuItem.prototype.set_default = function(_value) -{ - this["default"] = _value; -} - -egwMenuItem.prototype.set_data = function(_value) -{ - this.data = _value; -} - -egwMenuItem.prototype.set_hint = function(_value) -{ - this.hint = _value; -} - -egwMenuItem.prototype.set_shortcutCaption = function(_value) -{ - this.shortcutCaption = _value; -} - diff --git a/api/js/egw_action/egw_menu.ts b/api/js/egw_action/egw_menu.ts new file mode 100755 index 0000000000..686ce9405d --- /dev/null +++ b/api/js/egw_action/egw_menu.ts @@ -0,0 +1,534 @@ +/** + * eGroupWare egw_action framework - JS Menu abstraction + * + * @link http://www.egroupware.org + * @author Andreas Stöckel + * @copyright 2011 by Andreas Stöckel + * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License + * @package egw_action + * + */ +import {egwMenuImpl} from './egw_menu_dhtmlx'; +import {egw_registeredShortcuts, egw_shortcutIdx} from './egw_keymanager'; +import { + EGW_KEY_ARROW_DOWN, + EGW_KEY_ARROW_LEFT, + EGW_KEY_ARROW_RIGHT, + EGW_KEY_ARROW_UP, EGW_KEY_ENTER, + EGW_KEY_ESCAPE +} from "./egw_action_constants"; +//Global variable which is used to store the currently active menu so that it +//may be closed when another menu opens +export var _egw_active_menu: egwMenu = null; + +/** + * Internal function which generates a menu item with the given parameters as used + * in e.g. the egwMenu.addItem function. + */ +//TODO Icons: write PHP GD script which is capable of generating the menu icons in various states (disabled, highlighted) +function _egwGenMenuItem(_parent = null, _id = "", _caption = "", _iconUrl = "", _onClick = null) +{ + //Create a menu item with no parent (null) and set the given parameters + const item: egwMenuItem = new egwMenuItem(_parent, _id); + item.set_caption(_caption); + item.set_iconUrl(_iconUrl); + item.set_onClick(_onClick); + + return item; +} + +/** + * Internal function which parses the given menu tree in _elements and adds the + * elements to the given parent. + */ +function _egwGenMenuStructure(_elements: any[], _parent) +{ + const items: egwMenuItem[] = []; + + //Go through each object in the elements array + for (const obj of _elements) + { + //Go through each key of the current object + const item = new egwMenuItem(_parent, null); + for (const key in obj) + { + if (key == "children" && obj[key].constructor === Array) + { + //Recursively load the children. + item.children = _egwGenMenuStructure(obj[key], item); + } else + { + //Directly set the other keys + //TODO Sanity necessary checks here? + //TODO Implement menu item getters? + if (key == "id" || key == "caption" || key == "iconUrl" || + key == "checkbox" || key == "checked" || key == "groupIndex" || + key == "enabled" || key == "default" || key == "onClick" || + key == "hint" || key == "shortcutCaption") + { + item['set_' + key](obj[key]); + } + } + } + + items.push(item); + } + + return items; +} + +/** + * Internal function which searches for the given ID inside an element tree. + */ +function _egwSearchMenuItem(_elements: any[], _id: any): egwMenuItem +{ + for (const item1 of _elements) + { + if (item1.id === _id) + return item1; + + const item = _egwSearchMenuItem(item1.children, _id); + if (item) + return item; + } + + return null; +} + +/** + * Internal function which allows to set the onClick handler of multiple menu items + */ +function _egwSetMenuOnClick(_elements, _onClick) +{ + for (const item of _elements) + { + if (item.onClick === null) + { + item.onClick = _onClick; + } + _egwSetMenuOnClick(item.children, _onClick); + } +} + +/** + * replacement function for jquery trigger + * @param selector + * @param eventType + */ +function trigger(selector, eventType) +{ + if (typeof eventType === 'string' && typeof selector[eventType] === 'function') + { + selector[eventType](); + } else + { + const event = + typeof eventType === 'string' + ? new Event(eventType, {bubbles: true}) + : eventType; + selector.dispatchEvent(event); + } +} + + +/** + * Constructor for the egwMenu object. The egwMenu object is an abstract representation + * of a context/popup menu. The actual generation of the menu can be done by + * so-called menu implementations. Those are activated by simply including the JS file + * of such an implementation. + * + * The currently available implementation is the "egwDhtmlxMenu.js" which is based + * upon the dhtmlxmenu component. + */ +export class egwMenu +{ + //The "items" variable contains all menu items of the menu + children: egwMenuItem[] = []; + + //The "instance" variable contains the currently opened instance. There may + //only be one instance opened at a time. + instance: egwMenuImpl = null; // This is equivalent to iface in other classes and holds an egwMenuImpl + constructor() + { + } + + /** + * The private _checkImpl function checks whether a menu implementation is available. + * + * @returns bool whether a menu implementation is available. + */ + private _checkImpl() + { + return typeof egwMenuImpl == 'function'; + } + + /** + * Hides the menu if it is currently opened. Otherwise, nothing happens. + */ + public hide() + { + //Reset the currently active menu variable + if (_egw_active_menu == this) + _egw_active_menu = null; + + //Check whether a currently opened instance exists. If it does, close it. + if (this.instance != null) + { + this.instance.hide(); + this.instance = null; + } + } + + /** + * The showAtElement function shows the menu at the given screen position in a + * (hopefully) optimal orientation. There can only be one instance of the menu opened at + * one time and the menu implementation should care that there is only one menu + * opened globally at all. + * + * @param {number} _x is the x position at which the menu will be opened + * @param {number} _y is the y position at which the menu will be opened + * @param {boolean} _force if true, the menu will be reopened at the given position, + * even if it already had been opened. Defaults to false. + * @returns {boolean} whether the menu had been opened + */ + public showAt(_x: number, _y: number, _force: boolean = false) + { + //Hide any other currently active menu + if (_egw_active_menu != null) + { + if (_egw_active_menu == this && !_force) + { + this.hide(); + return false; + } else + { + _egw_active_menu.hide(); + } + } + + if (this.instance == null && this._checkImpl) + { + //Obtain a new egwMenuImpl object and pass this instance to it + this.instance = new egwMenuImpl(this.children); + + _egw_active_menu = this; + + this.instance.showAt(_x, _y, () => { + this.instance = null; + _egw_active_menu = null; + }); + return true; + } + + return false; + } + + /** + * Key handler to allow keyboard navigation of menu + * TODO does this work? + * + * @return {boolean} true if we dealt with the keypress + */ + public keyHandler(_keyCode, _shift, _ctrl, _alt) + { + // Let main key-handler deal with shortcuts + const idx = egw_shortcutIdx(_keyCode, _shift, _ctrl, _alt); + if (typeof egw_registeredShortcuts[idx] !== "undefined") + { + return false; + } + + //TODO change with shoelace + let current = this.instance.dhtmlxmenu.menuSelected; + if (current !== -1) + { + let find_func = function (child) { + if (child.id === current.replace(this.instance.dhtmlxmenu.idPrefix, "")) + { + return child; + } else if (child.children) + { + for (let i = 0; i < child.children.length; i++) + { + const result = find_func(child.children[i]); + if (result) return result; + } + } + return null; + }.bind(this); + current = find_func(this); + } else + { + current = this.children[0]; + trigger("#" + this.instance.dhtmlxmenu.idPrefix + current.id, "mouseover"); + return true; + } + + switch (_keyCode) + { + case EGW_KEY_ENTER: + trigger("#" + this.instance.dhtmlxmenu.idPrefix + current.id, "click"); + return true; + case EGW_KEY_ESCAPE: + this.hide(); + return true; + case EGW_KEY_ARROW_RIGHT: + if (current.children) + { + current = current.children[0]; + } + break; + case EGW_KEY_ARROW_LEFT: + if (current.parent && current.parent !== this) + { + current = current.parent; + } + break; + case EGW_KEY_ARROW_UP: + case EGW_KEY_ARROW_DOWN: + const direction = _keyCode === EGW_KEY_ARROW_DOWN ? 1 : -1; + let parent = current.parent; + let index = parent.children.indexOf(current); + let cont = false; + + // Don't run off ends, skip disabled + do + { + index += direction; + cont = !parent.children[index] || !parent.children[index].enabled || !parent.children[index].id; + } while (cont && index + direction < parent.children.length && index + direction >= 0); + if (index > parent.children.length - 1) + { + index = parent.children.length - 1; + } + if (index < 0) + { + index = 0; + } + current = parent.children[index]; + break; + default: + return false; + } + + if (current) + { + trigger("#" + this.instance.dhtmlxmenu.idPrefix + current.id, "mouseover"); + this.instance.dhtmlxmenu._redistribSubLevelSelection(this.instance.dhtmlxmenu.idPrefix + current.id, this.instance.dhtmlxmenu.idPrefix + (current.parent ? current.parent.id : this.instance.dhtmlxmenu.topId)); + } + return true; + } + + /** + * Adds a new menu item to the list and returns a reference to that object. + * + * @param {string} _id is a unique identifier of the menu item. You can use + * the getItem function to search a specific menu item inside the menu tree. The + * id may also be false, null or "", which makes sense for items like separators, + * which you don't want to access anymore after adding them to the menu tree. + * @param {string} _caption is the caption of the newly generated menu item. Set the caption + * to "-" in order to create a separator. + * @param {string} _iconUrl is the URL of the icon which should be prepended to the + * menu item. It may be false, null or "" if you don't want an icon to be displayed. + * @param {function} _onClick is the JS function which is being executed when the + * menu item is clicked. + * @returns {egwMenuItem} the newly generated menu item, which had been appended to the + * menu item list. + */ + public addItem(_id, _caption, _iconUrl, _onClick): egwMenuItem + { + //Append the item to the list + const item: egwMenuItem = new egwMenuItem(this, _id, _caption, _iconUrl, _onClick); + this.children.push(item); + + return item; + } + + /** + * Removes all elements from the menu structure. + */ + public clear() + { + this.children = []; + } + + /** + * Loads the menu structure from the given object tree. The object tree is an array + * of objects which may contain a subset of the menu item properties. The "children" + * property of such an object is interpreted as a new sub-menu tree and appended + * to that child. + * + * @param {array} _elements is an array of elements which should be added to the menu + */ + public loadStructure(_elements) + { + this.children = _egwGenMenuStructure(_elements, this); + } + + /** + * Searches for the given item id within the element tree. + */ + public getItem(_id) { + return _egwSearchMenuItem(this.children, _id); + } + + /** + * Applies the given onClick handler to all menu items which don't have a clicked + * handler assigned yet. + */ + setGlobalOnClick(_onClick) { + _egwSetMenuOnClick(this.children, _onClick); +} +} + + +/** + * Constructor for the egwMenuItem. Each entry in a menu (including separators) + * is represented by a menu item. + */ +export class egwMenuItem +{ + id: string; + + set_id(_value) + { + this.id = _value; + } + + caption = ""; + + set_caption(_value) + { + //A value of "-" means that this element is a separator. + this.caption = _value; + } + + checkbox = false; + + set_checkbox(_value) + { + this.checkbox = _value; + } + + checked = false; + + set_checked(_value) + { + if (_value && this.groupIndex > 0) + { + //Uncheck all other elements in this radio group + for (const menuItem of this.parent.children) + { + if (menuItem.groupIndex == this.groupIndex) + menuItem.checked = false; + } + } + this.checked = _value; + } + + groupIndex = 0; + enabled = true; + iconUrl = ""; + onClick = null; + default = false; + data = null; + shortcutCaption = null; + + children = []; + parent: egwMenu; + //is set for radio Buttons + _dhtmlx_grpid: string = ""; + //hint might get set somewhere + hint: string = ""; + + constructor(_parent, _id, _caption="", _iconUrl="", onClick=null) + { + this.parent = _parent; + this.id = _id; + this.caption = _caption; + this.iconUrl = _iconUrl; + this.onClick = onClick; + } + + /** + * Searches for the given item id within the element tree. + */ + getItem(_id) + { + if (this.id === _id) + return this; + + return _egwSearchMenuItem(this.children, _id); + } + + /** + * Applies the given onClick handler to all menu items which don't have a clicked + * handler assigned yet. + */ + setGlobalOnClick(_onClick) + { + this.onClick = _onClick; + _egwSetMenuOnClick(this.children, _onClick); + } + + /** + * Adds a new menu item to the list and returns a reference to that object. + * + * @param {string} _id is a unique identifier of the menu item. You can use + * the getItem function to search a specific menu item inside the menu tree. The + * id may also be false, null or "", which makes sense for items like separators, + * which you don't want to access anymore after adding them to the menu tree. + * @param {string} _caption is the caption of the newly generated menu item. Set the caption + * to "-" in order to create a separator. + * @param {string} _iconUrl is the URL of the icon which should be prepended to the + * menu item. It may be false, null or "" if you don't want an icon to be displayed. + * @param {function} _onClick is the JS function which is being executed when the + * menu item is clicked. + * @returns {egwMenuItem} the newly generated menu item, which had been appended to the + * menu item list. + */ + addItem(_id: string, _caption: string, _iconUrl: string, _onClick: any) + { + //Append the item to the list + const item = _egwGenMenuItem(this, _id, _caption, _iconUrl, _onClick); + this.children.push(item); + + return item; + } + + set_groupIndex(_value) { + //If groupIndex is greater than 0 and the element is a checkbox, it is + //treated like a radio box + this.groupIndex = _value; +} + + set_enabled (_value) { + this.enabled = _value; +} + + set_onClick (_value) { + this.onClick = _value; +} + + set_iconUrl (_value) { + this.iconUrl = _value; +} + + set_default (_value) { + this["default"] = _value; +} + + set_data (_value) { + this.data = _value; +} + + set_hint (_value) { + this.hint = _value; +} + + set_shortcutCaption (_value) { + this.shortcutCaption = _value; +} + +} + diff --git a/api/js/egw_action/egw_menu_dhtmlx.js b/api/js/egw_action/egw_menu_dhtmlx.js deleted file mode 100644 index cb39eba0c8..0000000000 --- a/api/js/egw_action/egw_menu_dhtmlx.js +++ /dev/null @@ -1,206 +0,0 @@ -/** - * eGroupWare egw_action framework - JS Menu abstraction - * - * @link http://www.egroupware.org - * @author Andreas Stöckel - * @copyright 2011 by Andreas Stöckel - * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License - * @package egw_action - * @version $Id$ - */ - -/*egw:uses - egw_menu; - /api/js/dhtmlxtree/codebase/dhtmlxcommon.js; - /api/js/dhtmlxMenu/sources/dhtmlxmenu.js; - /api/js/dhtmlxMenu/sources/ext/dhtmlxmenu_ext.js; -*/ - -// Need CSS, or it doesn't really work -//if(typeof egw == 'function') egw(window).includeCSS(egw.webserverUrl + "/api/js/egw_action/test/skins/dhtmlxmenu_egw.css"); - -/** - * - * @param {type} _structure - */ -export function egwMenuImpl(_structure) -{ - //Create a new dhtmlxmenu object - this.dhtmlxmenu = new dhtmlXMenuObject(); - this.dhtmlxmenu.setSkin("egw"); - this.dhtmlxmenu.renderAsContextMenu(); - // TODO: Keyboard navigation of the menu - - var self = this; - - //Attach the simple click handler - this.dhtmlxmenu.attachEvent("onClick", function(id) { - if (id) - { - var elem = self.dhtmlxmenu.getUserData(id, 'egw_menu'); - - if (elem && elem.onClick) - { - if (elem.checkbox) - { - self.dhtmlxmenu.setContextMenuHideAllMode(false); - } - - var res = elem.onClick(elem); - - if (elem.checkbox && (res === false || res === true)) - { - var checked = res; - if (elem.groupIndex != 0) - { - self.dhtmlxmenu.setRadioChecked(id, checked); - } - else - { - self.dhtmlxmenu.setCheckboxState(id, checked); - } - } - } - } - }); - - //Attach the radiobutton click handler - this.dhtmlxmenu.attachEvent("onRadioClick", function(group, idChecked, idClicked, zoneId, casState) { - if (idClicked) - { - var elem = self.dhtmlxmenu.getUserData(idClicked, 'egw_menu'); - if (elem) - { - elem.set_checked(true); - } - } - - return true; - }); - - //Attach the radiobutton click handler - this.dhtmlxmenu.attachEvent("onCheckboxClick", function(id, state, zoneId, casState) { - if (id) - { - var elem = self.dhtmlxmenu.getUserData(id, 'egw_menu'); - if (elem) - { - elem.set_checked(!state); - } - } - - return true; - }); - - - //Translate the given structure to the dhtmlx object structure - this._translateStructure(_structure, this.dhtmlxmenu.topId, 0); - - // Add disableIfNoEPL class to the relevant action's DOM - for(var i in this.dhtmlxmenu.idPull) - { - if (this.dhtmlxmenu.userData[i+'_egw_menu'] && - this.dhtmlxmenu.userData[i+'_egw_menu']['data'] && - this.dhtmlxmenu.userData[i+'_egw_menu']['data']['disableIfNoEPL']) - { - this.dhtmlxmenu.idPull[i].className += ' disableIfNoEPL'; - } - } -} - -egwMenuImpl.prototype._translateStructure = function(_structure, _parentId, _idCnt) -{ - //Initialize the counter which we will use to generate unique id's for all - //dhtmlx menu objects - var counter = 0; - var last_id = null; - - for (var i = 0; i < _structure.length; i++) - { - var elem = _structure[i]; - var id = elem.id || 'elem_' + (_idCnt + counter); - - counter++; - - //Check whether this element is a seperator - if (elem.caption == '-' && last_id != null) - { - //Add the separator next to last_id with the id "id" - this.dhtmlxmenu.addNewSeparator(last_id, id); - } - else - { - if (elem.checkbox && elem.groupIndex === 0) - { - //Add checkbox - this.dhtmlxmenu.addCheckbox("child", _parentId, i, id, - elem.caption, elem.checked, !elem.enabled); - } - else if (elem.checkbox && elem.groupIndex > 0) - { - //Add radiobox - elem._dhtmlx_grpid = "grp_" + _idCnt + '_' + elem.groupIndex; - this.dhtmlxmenu.addRadioButton("child", _parentId, i, id, - elem.caption, elem._dhtmlx_grpid, elem.checked, !elem.enabled); - } - else - { - var caption = elem.caption; - if (elem["default"]) - caption = "" + caption + ""; - this.dhtmlxmenu.addNewChild(_parentId, i, id, caption, !elem.enabled, - elem.iconUrl, elem.iconUrl); - } - - if (elem.shortcutCaption != null) - { - this.dhtmlxmenu.setHotKey(id, elem.shortcutCaption); - } - - if (elem.children.length > 0) - { - counter += this._translateStructure(elem.children, id, (_idCnt + counter)); - } - } - - //Set the actual egw menu as user data element - this.dhtmlxmenu.setUserData(id, 'egw_menu', elem); - - // Set the tooltip if one has been set - if (elem.hint) - { - this.dhtmlxmenu.setTooltip(id, elem.hint); - } - - var last_id = id; - } - - return counter; -}; - - -egwMenuImpl.prototype.showAt = function(_x, _y, _onHide) -{ - var self = this; - - if (_onHide) - { - this.dhtmlxmenu.attachEvent("onHide", function(id) { - if (id === null) - { - _onHide(); - } - }); - } - - var self = this; - window.setTimeout(function() { - self.dhtmlxmenu.showContextMenu(_x, _y); - // TODO: Get keybard focus - }, 0); -}; - -egwMenuImpl.prototype.hide = function() -{ - this.dhtmlxmenu.hide(); -}; diff --git a/api/js/egw_action/egw_menu_dhtmlx.ts b/api/js/egw_action/egw_menu_dhtmlx.ts new file mode 100644 index 0000000000..a00d52fcf3 --- /dev/null +++ b/api/js/egw_action/egw_menu_dhtmlx.ts @@ -0,0 +1,195 @@ +/** + * eGroupWare egw_action framework - JS Menu abstraction + * + * @link http://www.egroupware.org + * @author Andreas Stöckel + * @copyright 2011 by Andreas Stöckel + * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License + * @package egw_action + * @version $Id$ + */ + + +import {egwMenuItem} from "./egw_menu"; + +/** + * + * @param {type} _structure + */ +export class egwMenuImpl +{ + readonly dhtmlxmenu:any; + constructor(_structure) + { + //Create a new dhtmlxmenu object + // @ts-ignore //origin not easily ts compatible --> soon to be replaced by shoelace anyway + this.dhtmlxmenu = new dhtmlXMenuObject(); + this.dhtmlxmenu.setSkin("egw"); + this.dhtmlxmenu.renderAsContextMenu(); + // TODO: Keyboard navigation of the menu + + + //Attach the simple click handler + this.dhtmlxmenu.attachEvent("onClick", (id) => { + if (id) + { + const elem = this.dhtmlxmenu.getUserData(id, 'egw_menu'); + + if (elem && elem.onClick) + { + if (elem.checkbox) + { + this.dhtmlxmenu.setContextMenuHideAllMode(false); + } + + const res = elem.onClick(elem); + + if (elem.checkbox && (res === false || res === true)) + { + const checked = res; + if (elem.groupIndex != 0) + { + this.dhtmlxmenu.setRadioChecked(id, checked); + } else + { + this.dhtmlxmenu.setCheckboxState(id, checked); + } + } + } + } + }); + + //Attach the radiobutton click handler + this.dhtmlxmenu.attachEvent("onRadioClick", (group, idChecked, idClicked) => { + if (idClicked) + { + const elem = this.dhtmlxmenu.getUserData(idClicked, 'egw_menu'); + if (elem) + { + elem.set_checked(true); + } + } + + return true; + }); + + //Attach the radiobutton click handler + this.dhtmlxmenu.attachEvent("onCheckboxClick", (id, state) => { + if (id) + { + const elem = this.dhtmlxmenu.getUserData(id, 'egw_menu'); + if (elem) + { + elem.set_checked(!state); + } + } + + return true; + }); + + + //Translate the given structure to the dhtmlx object structure + this._translateStructure(_structure, this.dhtmlxmenu.topId, 0); + + // Add disableIfNoEPL class to the relevant action's DOM + for (const i in this.dhtmlxmenu.idPull) + { + if (this.dhtmlxmenu.userData[i + '_egw_menu'] && + this.dhtmlxmenu.userData[i + '_egw_menu']['data'] && + this.dhtmlxmenu.userData[i + '_egw_menu']['data']['disableIfNoEPL']) + { + this.dhtmlxmenu.idPull[i].className += ' disableIfNoEPL'; + } + } + } + + private _translateStructure(_structure:egwMenuItem[], _parentId: string, _idCnt: number) + { + //Initialize the counter which we will use to generate unique id's for all + //dhtmlx menu objects + let counter: number = 0; + let last_id = null; + + _structure.forEach((elem: egwMenuItem, i: number) => + { + const id = elem.id || 'elem_' + (_idCnt + counter); + + counter++; + + //Check whether this element is a separator + if (elem.caption == '-' && last_id != null) + { + //Add the separator next to last_id with the id "id" + this.dhtmlxmenu.addNewSeparator(last_id, id); + } else + { + if (elem.checkbox && elem.groupIndex === 0) + { + //Add checkbox + this.dhtmlxmenu.addCheckbox("child", _parentId, i, id, + elem.caption, elem.checked, !elem.enabled); + } else if (elem.checkbox && elem.groupIndex > 0) + { + //Add radiobox + elem._dhtmlx_grpid = "grp_" + _idCnt + '_' + elem.groupIndex; + this.dhtmlxmenu.addRadioButton("child", _parentId, i, id, + elem.caption, elem._dhtmlx_grpid, elem.checked, !elem.enabled); + } else + { + let caption = elem.caption; + if (elem.default) + caption = "" + caption + ""; + this.dhtmlxmenu.addNewChild(_parentId, i, id, caption, !elem.enabled, + elem.iconUrl, elem.iconUrl); + } + + if (elem.shortcutCaption != null) + { + this.dhtmlxmenu.setHotKey(id, elem.shortcutCaption); + } + + if (elem.children.length > 0) + { + counter += this._translateStructure(elem.children, id, (_idCnt + counter)); + } + } + + //Set the actual egw menu as user data element + this.dhtmlxmenu.setUserData(id, 'egw_menu', elem); + + // Set the tooltip if one has been set + if (elem.hint) + { + this.dhtmlxmenu.setTooltip(id, elem.hint); + } + + last_id = id; + }) + + return counter; + }; + + public showAt(_x, _y, _onHide) + { + if (_onHide) + { + this.dhtmlxmenu.attachEvent("onHide", (id) => { + if (id === null) + { + _onHide(); + } + }); + } + + window.setTimeout(() => { + this.dhtmlxmenu.showContextMenu(_x, _y); + // TODO: Get keyboard focus + }, 0); + }; + + public hide() + { + this.dhtmlxmenu.hide(); + }; + +} \ No newline at end of file diff --git a/api/js/egw_action/egw_stylesheet.js b/api/js/egw_action/egw_stylesheet.js deleted file mode 100644 index 95143c8168..0000000000 --- a/api/js/egw_action/egw_stylesheet.js +++ /dev/null @@ -1,93 +0,0 @@ -/** - * eGroupWare egw_action framework - egw action framework - * - * @link http://www.egroupware.org - * @author Andreas Stöckel - * @copyright 2011 by Andreas Stöckel - * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License - * @package egw_action - * @version $Id$ - */ - -/** - * Contains the egwDynStyleSheet class which allows dynamic generation of stylesheet - * rules - updating a single stylesheet rule is way more efficient than updating - * the element style of many objects. - */ - -var EGW_DYNAMIC_STYLESHEET = null; - -/** - * Main egwDynStyleSheet class - all egwDynStyleSheets share the same stylesheet - * which is dynamically inserted into the head section of the DOM-Tree. - * This stylesheet is created with the first egwDynStyleSheet class. - */ -function egwDynStyleSheet() -{ - // Check whether the EGW_DYNAMIC_STYLESHEET has already be created - if (!EGW_DYNAMIC_STYLESHEET) - { - var style = document.createElement("style"); - document.getElementsByTagName("head")[0].appendChild(style); - - this.styleSheet = style.sheet ? style.sheet : style.styleSheet; - this.selectors = {}; - this.selectorCount = 0; - - EGW_DYNAMIC_STYLESHEET = this; - - return this; - } - else - { - return EGW_DYNAMIC_STYLESHEET; - } -} - -/** - * Creates/Updates the given stylesheet rule. Example call: - * - * styleSheet.updateRule("#container", "background-color: blue; font-family: sans;") - * - * @param string _selector is the css selector to which the given rule should apply - * @param string _rule is the rule which is bound to the selector. - */ -egwDynStyleSheet.prototype.updateRule = function (_selector, _rule) -{ - var ruleObj = { - "index": this.selectorCount - } - - // Remove any existing rule first - if (typeof this.selectors[_selector] !== "undefined") - { - var ruleObj = this.selectors[_selector]; - if (typeof this.styleSheet.removeRule !== "undefined") - { - this.styleSheet.removeRule(ruleObj.index); - } - else - { - this.styleSheet.deleteRule(ruleObj.index); - } - - delete (this.selectors[_selector]); - } - else - { - this.selectorCount++; - } - - // Add the rule to the stylesheet - if (typeof this.styleSheet.addRule !== "undefined") - { - this.styleSheet.addRule(_selector, _rule, ruleObj.index); - } - else - { - this.styleSheet.insertRule(_selector + "{" + _rule + "}", ruleObj.index); - } - - this.selectors[_selector] = ruleObj; -} - diff --git a/api/js/egw_action/egw_stylesheet.ts b/api/js/egw_action/egw_stylesheet.ts new file mode 100755 index 0000000000..313c323aac --- /dev/null +++ b/api/js/egw_action/egw_stylesheet.ts @@ -0,0 +1,52 @@ +/** + * eGroupWare egw_action framework - egw action framework + * + * @link http://www.egroupware.org + * @author Andreas Stöckel + * @copyright 2011 by Andreas Stöckel + * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License + * @package egw_action + * @version $Id$ + */ + +/** + * Contains the egwDynStyleSheet class which allows dynamic generation of stylesheet + * rules - updating a single stylesheet rule is way more efficient than updating + * the element style of many objects. + */ +class EgwDynamicStyleSheet { + private readonly styleSheet: CSSStyleSheet; + //mapping of selectors to indices in the Stylesheet's CSSRuleList + private selectors: Map; + + //selector count is no longer needed, since insert rules returns the index to store in the map above + + constructor() { + const style = document.createElement("style"); + document.head.appendChild(style); + this.styleSheet = style.sheet; + this.selectors = new Map; + } + + /** + * Creates/Updates the given stylesheet rule. Example call: + * + * styleSheet.updateRule(".someCssClass", "background-color: blue; font-family: sans;") + * + * @param selector is the css selector to which the given rule should apply + * @param rule is the rule which is bound to the selector. + */ + public updateRule(selector: string, rule: string): void { + // Remove any existing rule first + if (this.selectors.has(selector)) { + let index: number = this.selectors.get(selector) + this.styleSheet.deleteRule(index) + } + + //Add the rule to the stylesheet + let index = this.styleSheet.insertRule(selector + "{" + rule + "}") + this.selectors.set(selector, index) + } +} + +export const EGW_DYNAMIC_STYLESHEET: EgwDynamicStyleSheet = new EgwDynamicStyleSheet(); \ No newline at end of file diff --git a/api/js/egw_action/test/imgs/ajax-loader.gif b/api/js/egw_action/test/imgs/ajax-loader.gif old mode 100644 new mode 100755 diff --git a/api/js/egw_action/test/imgs/arrows.png b/api/js/egw_action/test/imgs/arrows.png old mode 100644 new mode 100755 diff --git a/api/js/egw_action/test/imgs/arrows.svg b/api/js/egw_action/test/imgs/arrows.svg old mode 100644 new mode 100755 diff --git a/api/js/egw_action/test/imgs/dhxmenu_chrd.gif b/api/js/egw_action/test/imgs/dhxmenu_chrd.gif old mode 100644 new mode 100755 diff --git a/api/js/egw_action/test/imgs/dhxmenu_loader.gif b/api/js/egw_action/test/imgs/dhxmenu_loader.gif old mode 100644 new mode 100755 diff --git a/api/js/egw_action/test/imgs/dhxmenu_subar.gif b/api/js/egw_action/test/imgs/dhxmenu_subar.gif old mode 100644 new mode 100755 diff --git a/api/js/egw_action/test/imgs/dhxmenu_subselbg.gif b/api/js/egw_action/test/imgs/dhxmenu_subselbg.gif old mode 100644 new mode 100755 diff --git a/api/js/egw_action/test/imgs/dhxmenu_subselbg.png b/api/js/egw_action/test/imgs/dhxmenu_subselbg.png old mode 100644 new mode 100755 diff --git a/api/js/egw_action/test/imgs/dhxmenu_subsepbg.gif b/api/js/egw_action/test/imgs/dhxmenu_subsepbg.gif old mode 100644 new mode 100755 diff --git a/api/js/egw_action/test/imgs/dhxmenu_topbg.gif b/api/js/egw_action/test/imgs/dhxmenu_topbg.gif old mode 100644 new mode 100755 diff --git a/api/js/egw_action/test/imgs/dhxmenu_topselbg.gif b/api/js/egw_action/test/imgs/dhxmenu_topselbg.gif old mode 100644 new mode 100755 diff --git a/api/js/egw_action/test/imgs/dhxmenu_topsepbg.gif b/api/js/egw_action/test/imgs/dhxmenu_topsepbg.gif old mode 100644 new mode 100755 diff --git a/api/js/egw_action/test/imgs/dhxmenu_upar.gif b/api/js/egw_action/test/imgs/dhxmenu_upar.gif old mode 100644 new mode 100755 diff --git a/api/js/egw_action/test/imgs/focused_hatching.png b/api/js/egw_action/test/imgs/focused_hatching.png old mode 100644 new mode 100755 diff --git a/api/js/egw_action/test/imgs/focused_hatching.svg b/api/js/egw_action/test/imgs/focused_hatching.svg old mode 100644 new mode 100755 diff --git a/api/js/egw_action/test/imgs/header_overlay.png b/api/js/egw_action/test/imgs/header_overlay.png old mode 100644 new mode 100755 diff --git a/api/js/egw_action/test/imgs/header_overlay.svg b/api/js/egw_action/test/imgs/header_overlay.svg old mode 100644 new mode 100755 diff --git a/api/js/egw_action/test/imgs/mime16_directory.png b/api/js/egw_action/test/imgs/mime16_directory.png old mode 100644 new mode 100755 diff --git a/api/js/egw_action/test/imgs/non_loaded_bg.png b/api/js/egw_action/test/imgs/non_loaded_bg.png old mode 100644 new mode 100755 diff --git a/api/js/egw_action/test/imgs/non_loaded_bg.svg b/api/js/egw_action/test/imgs/non_loaded_bg.svg old mode 100644 new mode 100755 diff --git a/api/js/egw_action/test/imgs/select_overlay.png b/api/js/egw_action/test/imgs/select_overlay.png old mode 100644 new mode 100755 diff --git a/api/js/egw_action/test/imgs/select_overlay.svg b/api/js/egw_action/test/imgs/select_overlay.svg old mode 100644 new mode 100755 diff --git a/api/js/egw_action/test/imgs/selectcols.png b/api/js/egw_action/test/imgs/selectcols.png old mode 100644 new mode 100755 diff --git a/api/js/egw_action/test/js/dhtmlxcommon.js b/api/js/egw_action/test/js/dhtmlxcommon.js old mode 100644 new mode 100755 diff --git a/api/js/egw_action/test/js/dhtmlxmenu.js b/api/js/egw_action/test/js/dhtmlxmenu.js old mode 100644 new mode 100755 diff --git a/api/js/egw_action/test/js/dhtmlxmenu_ext.js b/api/js/egw_action/test/js/dhtmlxmenu_ext.js old mode 100644 new mode 100755 diff --git a/api/js/egw_action/test/js/jquery-ui.js b/api/js/egw_action/test/js/jquery-ui.js old mode 100644 new mode 100755 diff --git a/api/js/egw_action/test/js/jquery.js b/api/js/egw_action/test/js/jquery.js old mode 100644 new mode 100755 diff --git a/api/js/egw_action/test/skins/dhtmlxmenu_egw.css b/api/js/egw_action/test/skins/dhtmlxmenu_egw.css old mode 100644 new mode 100755 diff --git a/api/js/egw_action/test/test_action.html b/api/js/egw_action/test/test_action.html old mode 100644 new mode 100755 index 0ebf0cddd7..3ef23f1fb1 --- a/api/js/egw_action/test/test_action.html +++ b/api/js/egw_action/test/test_action.html @@ -1,408 +1,435 @@ - - - Test page for the egw action stuff + + + Test page for the egw action stuff - - - + - - - + - - - - - - - - - + - - - - - - - - - - -
Folder 1
File 1
File 2
File 3
File 4
File 5
- - - + jQuery("#performAction").click(function (e) { + if (!objectManager.executeActionImplementation(this, "popup")) + alert("Please select one or more objects."); + return false; + }); + } + + diff --git a/api/js/egw_action/test/test_menu.html b/api/js/egw_action/test/test_menu.html old mode 100644 new mode 100755 index 11458adc8a..7cc1b3564a --- a/api/js/egw_action/test/test_menu.html +++ b/api/js/egw_action/test/test_menu.html @@ -1,12 +1,12 @@ - + Test page for the egw menu - - + + - + +

Test for dynamically generating stylesheets and stylesheet rules

-
1: Style me!
-
2: Style me!
-
3: Style me!
-
1: Style me!
-
2: Style me!
-
3: Style me!
-
1: Style me!
-
2: Style me!
-
3: Style me!
-
1: Style me!
-
2: Style me!
-
3: Style me!
-
1: Style me!
-
2: Style me!
-
3: Style me!
-
1: Style me!
-
2: Style me!
-
3: Style me!
-
1: Style me!
-
2: Style me!
-
3: Style me!
-
1: Style me!
-
2: Style me!
-
3: Style me!
-
1: Style me!
-
2: Style me!
-
3: Style me!
-
1: Style me!
-
2: Style me!
-
3: Style me!
-
1: Style me!
-
2: Style me!
-
3: Style me!
-
1: Style me!
-
2: Style me!
-
3: Style me!
-
1: Style me!
-
2: Style me!
-
3: Style me!
-
1: Style me!
-
2: Style me!
-
3: Style me!
-
1: Style me!
-
2: Style me!
-
3: Style me!
-
1: Style me!
-
2: Style me!
-
3: Style me!
-
1: Style me!
-
2: Style me!
-
3: Style me!
-
1: Style me!
-
2: Style me!
-
3: Style me!
-
1: Style me!
-
2: Style me!
-
3: Style me!
-
1: Style me!
-
2: Style me!
-
3: Style me!
-
1: Style me!
-
2: Style me!
-
3: Style me!
-
1: Style me!
-
2: Style me!
-
3: Style me!
-
1: Style me!
-
2: Style me!
-
3: Style me!
-
1: Style me!
-
2: Style me!
-
3: Style me!
-
1: Style me!
-
2: Style me!
-
3: Style me!
- - - - - - - +
1: Style me!
+
2: Style me!
+
3: Style me!
+
1: Style me!
+
2: Style me!
+
3: Style me!
+
1: Style me!
+
2: Style me!
+
3: Style me!
+
1: Style me!
+
2: Style me!
+
3: Style me!
+
1: Style me!
+
2: Style me!
+
3: Style me!
+
1: Style me!
+
2: Style me!
+
3: Style me!
+
1: Style me!
+
2: Style me!
+
3: Style me!
+
1: Style me!
+
2: Style me!
+
3: Style me!
+
1: Style me!
+
2: Style me!
+
3: Style me!
+
1: Style me!
+
2: Style me!
+
3: Style me!
+
1: Style me!
+
2: Style me!
+
3: Style me!
+
1: Style me!
+
2: Style me!
+
3: Style me!
+
1: Style me!
+
2: Style me!
+
3: Style me!
+
1: Style me!
+
2: Style me!
+
3: Style me!
+
1: Style me!
+
2: Style me!
+
3: Style me!
+
1: Style me!
+
2: Style me!
+
3: Style me!
+
1: Style me!
+
2: Style me!
+
3: Style me!
+
1: Style me!
+
2: Style me!
+
3: Style me!
+
1: Style me!
+
2: Style me!
+
3: Style me!
+
1: Style me!
+
2: Style me!
+
3: Style me!
+
1: Style me!
+
2: Style me!
+
3: Style me!
+
1: Style me!
+
2: Style me!
+
3: Style me!
+
1: Style me!
+
2: Style me!
+
3: Style me!
+
1: Style me!
+
2: Style me!
+
3: Style me!
+
1: Style me!
+
2: Style me!
+
3: Style me!
+ + + + + + + - diff --git a/api/js/etemplate/et2_core_DOMWidget.ts b/api/js/etemplate/et2_core_DOMWidget.ts index e498cfdbfd..1876909df1 100644 --- a/api/js/etemplate/et2_core_DOMWidget.ts +++ b/api/js/etemplate/et2_core_DOMWidget.ts @@ -11,7 +11,7 @@ /*egw:uses et2_core_interfaces; et2_core_widget; - /api/js/egw_action/egw_action.js; + /api/js/egw_action/egw_action; */ import {ClassWithAttributes} from './et2_core_inheritance'; @@ -24,8 +24,8 @@ import { egw_getObjectManager, egwActionObject, egwActionObjectInterface -} from '../egw_action/egw_action.js'; -import {EGW_AI_DRAG_OUT, EGW_AI_DRAG_OVER} from '../egw_action/egw_action_constants.js'; +} from '../egw_action/egw_action'; +import {EGW_AI_DRAG_OUT, EGW_AI_DRAG_OVER} from '../egw_action/egw_action_constants'; import {egw} from "../jsapi/egw_global"; import {Et2Tab} from "./Layout/Et2Tabs/Et2Tab"; import {Et2TabPanel} from "./Layout/Et2Tabs/Et2TabPanel"; diff --git a/api/js/etemplate/et2_core_baseWidget.ts b/api/js/etemplate/et2_core_baseWidget.ts index 35467ded42..10ec627be6 100644 --- a/api/js/etemplate/et2_core_baseWidget.ts +++ b/api/js/etemplate/et2_core_baseWidget.ts @@ -21,7 +21,7 @@ import {et2_register_widget, et2_widget, WidgetConfig} from "./et2_core_widget"; import {et2_no_init} from "./et2_core_common"; // fixing circular dependencies by only importing type import type {et2_inputWidget} from "./et2_core_inputWidget"; -import {egwIsMobile} from "../egw_action/egw_action_common.js"; +import {egwIsMobile} from "../egw_action/egw_action_common"; /** * Class which manages the DOM node itself. The simpleWidget class is derrived diff --git a/api/js/etemplate/et2_dataview_controller.ts b/api/js/etemplate/et2_dataview_controller.ts index 6ce52810fa..062c3eb2d8 100644 --- a/api/js/etemplate/et2_dataview_controller.ts +++ b/api/js/etemplate/et2_dataview_controller.ts @@ -27,8 +27,8 @@ import {et2_dataview_row} from "./et2_dataview_view_row"; import {et2_dataview_grid} from "./et2_dataview_view_grid"; import {et2_arrayIntKeys, et2_bounds} from "./et2_core_common"; import {egw} from "../jsapi/egw_global"; -import {egwBitIsSet} from "../egw_action/egw_action_common.js"; -import {EGW_AO_STATE_NORMAL, EGW_AO_STATE_SELECTED} from "../egw_action/egw_action_constants.js"; +import {egwBitIsSet} from "../egw_action/egw_action_common"; +import {EGW_AO_STATE_NORMAL, EGW_AO_STATE_SELECTED} from "../egw_action/egw_action_constants"; /** * The fetch timeout specifies the time during which the controller tries to diff --git a/api/js/etemplate/et2_dataview_controller_selection.ts b/api/js/etemplate/et2_dataview_controller_selection.ts index 42ba515f7a..f5356c75bb 100644 --- a/api/js/etemplate/et2_dataview_controller_selection.ts +++ b/api/js/etemplate/et2_dataview_controller_selection.ts @@ -18,15 +18,15 @@ import {egw} from "../jsapi/egw_global"; import {et2_bounds} from "./et2_core_common"; import {et2_dataview_rowAOI} from "./et2_dataview_view_aoi"; -import {egwActionObjectInterface} from "../egw_action/egw_action.js"; +import {egwActionObjectInterface} from "../egw_action/egw_action"; import { EGW_AO_SHIFT_STATE_BLOCK, EGW_AO_SHIFT_STATE_MULTI, EGW_AO_STATE_FOCUSED, EGW_AO_STATE_NORMAL, EGW_AO_STATE_SELECTED -} from "../egw_action/egw_action_constants.js"; -import {egwBitIsSet, egwSetBit} from "../egw_action/egw_action_common.js"; +} from "../egw_action/egw_action_constants"; +import {egwBitIsSet, egwSetBit} from "../egw_action/egw_action_common"; import {Et2Dialog} from "./Et2Dialog/Et2Dialog"; /** diff --git a/api/js/etemplate/et2_dataview_view_aoi.ts b/api/js/etemplate/et2_dataview_view_aoi.ts index f052e26b0b..063fe28142 100644 --- a/api/js/etemplate/et2_dataview_view_aoi.ts +++ b/api/js/etemplate/et2_dataview_view_aoi.ts @@ -17,12 +17,12 @@ import { egwActionObjectInterface -} from "../egw_action/egw_action.js"; +} from "../egw_action/egw_action"; import {EGW_AO_SHIFT_STATE_MULTI, EGW_AO_STATE_FOCUSED, - EGW_AO_STATE_SELECTED} from '../egw_action/egw_action_constants.js'; -import {egwBitIsSet, egwGetShiftState, egwPreventSelect, egwSetBit, egwUnfocus, egwIsMobile} from "../egw_action/egw_action_common.js"; -import {_egw_active_menu} from "../egw_action/egw_menu.js"; + EGW_AO_STATE_SELECTED} from '../egw_action/egw_action_constants'; +import {egwBitIsSet, egwGetShiftState, egwPreventSelect, egwSetBit, egwUnfocus, egwIsMobile} from "../egw_action/egw_action_common"; +import {_egw_active_menu} from "../egw_action/egw_menu"; import {tapAndSwipe} from "../tapandswipe"; /** diff --git a/api/js/etemplate/et2_extension_nextmatch.ts b/api/js/etemplate/et2_extension_nextmatch.ts index ca944ae9b4..c0d5fe4fe6 100644 --- a/api/js/etemplate/et2_extension_nextmatch.ts +++ b/api/js/etemplate/et2_extension_nextmatch.ts @@ -68,7 +68,7 @@ import {et2_button} from "./et2_widget_button"; import {et2_template} from "./et2_widget_template"; import {egw} from "../jsapi/egw_global"; import {et2_compileLegacyJS} from "./et2_core_legacyJSFunctions"; -import {egwIsMobile} from "../egw_action/egw_action_common.js"; +import {egwIsMobile} from "../egw_action/egw_action_common"; import {Et2Dialog} from "./Et2Dialog/Et2Dialog"; import {Et2Select} from "./Et2Select/Et2Select"; import {loadWebComponent} from "./Et2Widget/Et2Widget"; diff --git a/api/js/etemplate/et2_extension_nextmatch_controller.ts b/api/js/etemplate/et2_extension_nextmatch_controller.ts index 48c9f7a215..5fc90a2011 100644 --- a/api/js/etemplate/et2_extension_nextmatch_controller.ts +++ b/api/js/etemplate/et2_extension_nextmatch_controller.ts @@ -35,10 +35,10 @@ import { egw_getObjectManager, egwActionObjectManager, egwActionObject -} from "../egw_action/egw_action.js"; -import {EGW_AO_FLAG_DEFAULT_FOCUS,EGW_AO_EXEC_SELECTED, EGW_AO_FLAG_IS_CONTAINER} from "../egw_action/egw_action_constants.js"; +} from "../egw_action/egw_action"; +import {EGW_AO_FLAG_DEFAULT_FOCUS,EGW_AO_EXEC_SELECTED, EGW_AO_FLAG_IS_CONTAINER} from "../egw_action/egw_action_constants"; import {nm_action} from "./et2_extension_nextmatch_actions.js"; -import {egwIsMobile} from "../egw_action/egw_action_common.js"; +import {egwIsMobile} from "../egw_action/egw_action_common"; /** * @augments et2_dataview_controller diff --git a/api/js/etemplate/et2_widget_grid.ts b/api/js/etemplate/et2_widget_grid.ts index 44a284572d..de5ec5e60f 100644 --- a/api/js/etemplate/et2_widget_grid.ts +++ b/api/js/etemplate/et2_widget_grid.ts @@ -20,7 +20,7 @@ import {et2_IAligned, et2_IDetachedDOM, et2_IResizeable} from "./et2_core_interf import {et2_register_widget, et2_widget, WidgetConfig} from "./et2_core_widget"; import {ClassWithAttributes} from "./et2_core_inheritance"; import {et2_action_object_impl, et2_DOMWidget} from "./et2_core_DOMWidget"; -import {egw_getAppObjectManager, egwActionObject} from '../egw_action/egw_action.js'; +import {egw_getAppObjectManager, egwActionObject} from '../egw_action/egw_action'; import {et2_directChildrenByTagName, et2_filteredNodeIterator, et2_readAttrWithDefault} from "./et2_core_xml"; import {egw} from "../jsapi/egw_global"; import Sortable from 'sortablejs/modular/sortable.complete.esm.js'; diff --git a/api/js/etemplate/et2_widget_portlet.ts b/api/js/etemplate/et2_widget_portlet.ts index f41d059154..28fbe0b8e4 100644 --- a/api/js/etemplate/et2_widget_portlet.ts +++ b/api/js/etemplate/et2_widget_portlet.ts @@ -26,7 +26,7 @@ import { egwAction, egwActionObject, egwActionObjectInterface -} from "../egw_action/egw_action.js"; +} from "../egw_action/egw_action"; import {Et2Dialog} from "./Et2Dialog/Et2Dialog"; import {ColorTranslator} from 'colortranslator'; /** diff --git a/api/js/etemplate/et2_widget_toolbar.ts b/api/js/etemplate/et2_widget_toolbar.ts index 75d6ef9255..d49b5cb8da 100644 --- a/api/js/etemplate/et2_widget_toolbar.ts +++ b/api/js/etemplate/et2_widget_toolbar.ts @@ -17,10 +17,10 @@ import {et2_DOMWidget} from "./et2_core_DOMWidget"; import {et2_register_widget, WidgetConfig} from "./et2_core_widget"; import {ClassWithAttributes} from "./et2_core_inheritance"; -import {egw_getObjectManager, egwActionObject, egwActionObjectManager} from '../egw_action/egw_action.js'; +import {egw_getObjectManager, egwActionObject, egwActionObjectManager} from '../egw_action/egw_action'; import {et2_IInput} from "./et2_core_interfaces"; import {egw} from "../jsapi/egw_global"; -import {egwIsMobile} from "../egw_action/egw_action_common.js"; +import {egwIsMobile} from "../egw_action/egw_action_common"; import {Et2Dialog} from "./Et2Dialog/Et2Dialog"; import {Et2DropdownButton} from "./Et2DropdownButton/Et2DropdownButton"; import {loadWebComponent} from "./Et2Widget/Et2Widget"; diff --git a/api/js/etemplate/et2_widget_tree.ts b/api/js/etemplate/et2_widget_tree.ts index dbf0cc1945..6c18f23d82 100644 --- a/api/js/etemplate/et2_widget_tree.ts +++ b/api/js/etemplate/et2_widget_tree.ts @@ -13,7 +13,7 @@ /*egw:uses et2_core_inputWidget; - /api/js/egw_action/egw_dragdrop_dhtmlx_tree.js; + /api/js/egw_action/egw_dragdrop_dhtmlx_tree.ts; /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; @@ -26,10 +26,10 @@ import {et2_inputWidget} from "./et2_core_inputWidget"; import {ClassWithAttributes} from "./et2_core_inheritance"; import {et2_no_init} from "./et2_core_common"; import {egw} from "../jsapi/egw_global"; -import {egw_getAppObjectManager, egw_getObjectManager, egwActionObject} from "../egw_action/egw_action.js"; -import {EGW_AO_FLAG_IS_CONTAINER} from "../egw_action/egw_action_constants.js"; -import {dhtmlxtreeItemAOI} from "../egw_action/egw_dragdrop_dhtmlx_tree.js"; -import {egwIsMobile} from "../egw_action/egw_action_common.js"; +import {egw_getAppObjectManager, egw_getObjectManager, egwActionObject} from "../egw_action/egw_action"; +import {EGW_AO_FLAG_IS_CONTAINER} from "../egw_action/egw_action_constants"; +import {dhtmlxtreeItemAOI} from "../egw_action/./egw_dragdrop_dhtmlx_tree"; +import {egwIsMobile} from "../egw_action/egw_action_common"; /* no module, but egw:uses is ignored, so adding it here commented out import '../../../api/js/dhtmlxtree/sources/dhtmlxtree.js'; diff --git a/api/js/etemplate/et2_widget_vfs.ts b/api/js/etemplate/et2_widget_vfs.ts index 18298407e1..a091cba39c 100644 --- a/api/js/etemplate/et2_widget_vfs.ts +++ b/api/js/etemplate/et2_widget_vfs.ts @@ -28,9 +28,9 @@ import {et2_inputWidget} from "./et2_core_inputWidget"; import {et2_IDetachedDOM} from "./et2_core_interfaces"; import {et2_no_init} from "./et2_core_common"; import {egw} from "../jsapi/egw_global"; -import {egw_getAppObjectManager, egwActionObject} from "../egw_action/egw_action.js"; -import {egw_keyHandler} from '../egw_action/egw_keymanager.js'; -import {EGW_KEY_ENTER} from '../egw_action/egw_action_constants.js'; +import {egw_getAppObjectManager, egwActionObject} from "../egw_action/egw_action"; +import {egw_keyHandler} from '../egw_action/egw_keymanager'; +import {EGW_KEY_ENTER} from '../egw_action/egw_action_constants'; import {Et2Dialog} from "./Et2Dialog/Et2Dialog"; import type {Et2VfsMime} from "./Vfs/Et2VfsMime"; import type {Et2VfsGid, Et2VfsUid} from "./Et2Vfs/Et2VfsUid"; diff --git a/api/js/etemplate/etemplate2.ts b/api/js/etemplate/etemplate2.ts index 51e352fbaa..19f014805b 100644 --- a/api/js/etemplate/etemplate2.ts +++ b/api/js/etemplate/etemplate2.ts @@ -21,7 +21,7 @@ import {et2_compileLegacyJS} from "./et2_core_legacyJSFunctions"; import {et2_loadXMLFromURL} from "./et2_core_xml"; import {et2_nextmatch, et2_nextmatch_header_bar} from "./et2_extension_nextmatch"; import '../jsapi/egw_json.js'; -import {egwIsMobile} from "../egw_action/egw_action_common.js"; +import {egwIsMobile} from "../egw_action/egw_action_common"; import './Layout/Et2Box/Et2Box'; import './Layout/Et2Details/Et2Details'; import './Layout/Et2Tabs/Et2Tab'; diff --git a/api/js/etemplate/test/test_dataview.html b/api/js/etemplate/test/test_dataview.html index 75df93719d..cd2a84a598 100644 --- a/api/js/etemplate/test/test_dataview.html +++ b/api/js/etemplate/test/test_dataview.html @@ -8,8 +8,8 @@ - - + +