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 @@ - - + +