converted egw_action from javascript to typescript

classes are now uppercase and in their own files. lowercase classes are deprecated.
Interfaces are now actual interfaces that should be implemented instead of creating and returning an ai Object every time
This commit is contained in:
milan 2023-07-10 16:02:30 +02:00
parent 2f56677cc8
commit 5e3c67a5cf
100 changed files with 6331 additions and 7233 deletions

View File

@ -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";

0
api/js/egw_action/Class Diagram.graphml Normal file → Executable file
View File

0
api/js/egw_action/Class Diagram.png Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 44 KiB

0
api/js/egw_action/Class Diagram.svg Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

View File

@ -0,0 +1,720 @@
/**
* EGroupware egw_action framework - egw action framework
*
* @link https://www.egroupware.org
* @author Andreas Stöckel <as@stylite.de>
* @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;
}

View File

@ -0,0 +1,47 @@
/**
* EGroupware egw_action framework - egw action framework
*
* @link https://www.egroupware.org
* @author Andreas Stöckel <as@stylite.de>
* @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;
}

View File

@ -0,0 +1,52 @@
/**
* EGroupware egw_action framework - egw action framework
*
* @link https://www.egroupware.org
* @author Andreas Stöckel <as@stylite.de>
* @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!";
};
}

View File

@ -0,0 +1,27 @@
/**
* EGroupware egw_action framework - egw action framework
*
* @link https://www.egroupware.org
* @author Andreas Stöckel <as@stylite.de>
* @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;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,85 @@
/**
* EGroupware egw_action framework - egw action framework
*
* @link https://www.egroupware.org
* @author Andreas Stöckel <as@stylite.de>
* @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;
}

View File

@ -0,0 +1,32 @@
/**
* EGroupware egw_action framework - egw action framework
*
* @link https://www.egroupware.org
* @author Andreas Stöckel <as@stylite.de>
* @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;
}
}
}

View File

@ -0,0 +1,39 @@
/**
* EGroupware egw_action framework - egw action framework
*
* @link https://www.egroupware.org
* @author Andreas Stöckel <as@stylite.de>
* @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;
}
}

View File

@ -0,0 +1,67 @@
/**
* EGroupware egw_action framework - egw action framework
*
* @link https://www.egroupware.org
* @author Andreas Stöckel <as@stylite.de>
* @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];
}
};
}

View File

@ -0,0 +1,302 @@
/**
* EGroupware egw_action framework - egw action framework
*
* @link https://www.egroupware.org
* @author Andreas Stöckel <as@stylite.de>
* @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 {
}

View File

@ -0,0 +1,120 @@
/**
* EGroupware egw_action framework - egw action framework
*
* @link https://www.egroupware.org
* @author Andreas Stöckel <as@stylite.de>
* @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 {
}

View File

@ -0,0 +1,744 @@
/**
* EGroupware egw_action framework - egw action framework
*
* @link https://www.egroupware.org
* @author Andreas Stöckel <as@stylite.de>
* @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 <a> 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;
}

View File

@ -0,0 +1,291 @@
/**
* EGroupware egw_action framework - egw action framework
*
* @link https://www.egroupware.org
* @author Andreas Stöckel <as@stylite.de>
* @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
}

View File

@ -0,0 +1,41 @@
/**
* EGroupware egw_action framework - egw action framework
*
* @link https://www.egroupware.org
* @author Andreas Stöckel <as@stylite.de>
* @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
}
}

View File

@ -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 <as@stylite.de>
* @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 <as@stylite.de>
* @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 <as@stylite.de>
* @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 <alexaholic [at) gmail (dot] com>
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 <raph (at] n3rd [dot) org> (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 <as@stylite.de>
* @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 <as@stylite.de>
* @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 <as@stylite.de>
* @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 <as@stylite.de>
* @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 <as@stylite.de>
* @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 <as@stylite.de>
* @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;

File diff suppressed because it is too large Load Diff

410
api/js/egw_action/egw_action.ts Executable file
View File

@ -0,0 +1,410 @@
/**
* EGroupware egw_action framework - egw action framework
*
* @link https://www.egroupware.org
* @author Andreas Stöckel <as@stylite.de>
* @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 = <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;
}

View File

@ -1,520 +0,0 @@
/**
* eGroupWare egw_action framework - egw action framework
*
* @link http://www.egroupware.org
* @author Andreas Stöckel <as@stylite.de>
* @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 <alexaholic [at) gmail (dot] com>
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 <raph (at] n3rd [dot) org> (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('');
}

View File

@ -0,0 +1,487 @@
/**
* eGroupWare egw_action framework - egw action framework
*
* @link http://www.egroupware.org
* @author Andreas Stöckel <as@stylite.de>
* @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 <alexaholic [at) gmail (dot] com>
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 <raph (at] n3rd [dot) org> (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('');
}

View File

@ -1,24 +1,43 @@
// noinspection JSUnusedGlobalSymbols
/**
* eGroupWare egw_action framework - egw action framework
*
* @link http://www.egroupware.org
* @author Andreas Stöckel <as@stylite.de>
* @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]

View File

@ -1,737 +0,0 @@
/**
* eGroupWare egw_action framework - egw action framework
*
* @link http://www.egroupware.org
* @author Andreas Stöckel <as@stylite.de>
* @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;
}

View File

@ -1,941 +0,0 @@
/**
* eGroupWare egw_action framework - egw action framework
*
* @link http://www.egroupware.org
* @author Andreas Stöckel <as@stylite.de>
* @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 <a> 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;
}

View File

@ -0,0 +1,12 @@
/**
* eGroupWare egw_action framework - egw action framework
*
* @link http://www.egroupware.org
* @author Andreas Stöckel <as@stylite.de>
* @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

View File

@ -1,74 +0,0 @@
/**
* EGroupware egw_dragdrop_dhtmlxmenu - egw action framework
*
* @link https://www.egroupware.org
* @author Andreas Stöckel <as@stylite.de>
* @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;
}

View File

@ -0,0 +1,82 @@
/**
* EGroupware egw_dragdrop_dhtmlxmenu - egw action framework
*
* @link https://www.egroupware.org
* @author Andreas Stöckel <as@stylite.de>
* @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;
}
}

View File

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

View File

@ -1,524 +0,0 @@
/**
* eGroupWare egw_action framework - JS Menu abstraction
*
* @link http://www.egroupware.org
* @author Andreas Stöckel <as@stylite.de>
* @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;
}

534
api/js/egw_action/egw_menu.ts Executable file
View File

@ -0,0 +1,534 @@
/**
* eGroupWare egw_action framework - JS Menu abstraction
*
* @link http://www.egroupware.org
* @author Andreas Stöckel <as@stylite.de>
* @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;
}
}

View File

@ -1,206 +0,0 @@
/**
* eGroupWare egw_action framework - JS Menu abstraction
*
* @link http://www.egroupware.org
* @author Andreas Stöckel <as@stylite.de>
* @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 = "<b>" + caption + "</b>";
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();
};

View File

@ -0,0 +1,195 @@
/**
* eGroupWare egw_action framework - JS Menu abstraction
*
* @link http://www.egroupware.org
* @author Andreas Stöckel <as@stylite.de>
* @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 = "<b>" + caption + "</b>";
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();
};
}

View File

@ -1,93 +0,0 @@
/**
* eGroupWare egw_action framework - egw action framework
*
* @link http://www.egroupware.org
* @author Andreas Stöckel <as@stylite.de>
* @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;
}

View File

@ -0,0 +1,52 @@
/**
* eGroupWare egw_action framework - egw action framework
*
* @link http://www.egroupware.org
* @author Andreas Stöckel <as@stylite.de>
* @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<string, number>;
//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();

0
api/js/egw_action/test/imgs/ajax-loader.gif Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

0
api/js/egw_action/test/imgs/arrows.png Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 393 B

After

Width:  |  Height:  |  Size: 393 B

0
api/js/egw_action/test/imgs/arrows.svg Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

0
api/js/egw_action/test/imgs/dhxmenu_chrd.gif Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

0
api/js/egw_action/test/imgs/dhxmenu_loader.gif Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

0
api/js/egw_action/test/imgs/dhxmenu_subar.gif Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

0
api/js/egw_action/test/imgs/dhxmenu_subselbg.gif Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 52 B

After

Width:  |  Height:  |  Size: 52 B

0
api/js/egw_action/test/imgs/dhxmenu_subselbg.png Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 433 B

After

Width:  |  Height:  |  Size: 433 B

0
api/js/egw_action/test/imgs/dhxmenu_subsepbg.gif Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

0
api/js/egw_action/test/imgs/dhxmenu_topbg.gif Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 66 B

After

Width:  |  Height:  |  Size: 66 B

0
api/js/egw_action/test/imgs/dhxmenu_topselbg.gif Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 45 B

After

Width:  |  Height:  |  Size: 45 B

0
api/js/egw_action/test/imgs/dhxmenu_topsepbg.gif Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 68 B

After

Width:  |  Height:  |  Size: 68 B

0
api/js/egw_action/test/imgs/dhxmenu_upar.gif Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

0
api/js/egw_action/test/imgs/focused_hatching.png Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 202 B

After

Width:  |  Height:  |  Size: 202 B

0
api/js/egw_action/test/imgs/focused_hatching.svg Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 6.3 KiB

After

Width:  |  Height:  |  Size: 6.3 KiB

0
api/js/egw_action/test/imgs/header_overlay.png Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 431 B

After

Width:  |  Height:  |  Size: 431 B

0
api/js/egw_action/test/imgs/header_overlay.svg Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

0
api/js/egw_action/test/imgs/mime16_directory.png Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 695 B

After

Width:  |  Height:  |  Size: 695 B

0
api/js/egw_action/test/imgs/non_loaded_bg.png Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 408 B

After

Width:  |  Height:  |  Size: 408 B

0
api/js/egw_action/test/imgs/non_loaded_bg.svg Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 6.1 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

0
api/js/egw_action/test/imgs/select_overlay.png Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 377 B

After

Width:  |  Height:  |  Size: 377 B

0
api/js/egw_action/test/imgs/select_overlay.svg Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

0
api/js/egw_action/test/imgs/selectcols.png Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 215 B

After

Width:  |  Height:  |  Size: 215 B

0
api/js/egw_action/test/js/dhtmlxcommon.js Normal file → Executable file
View File

0
api/js/egw_action/test/js/dhtmlxmenu.js Normal file → Executable file
View File

0
api/js/egw_action/test/js/dhtmlxmenu_ext.js Normal file → Executable file
View File

0
api/js/egw_action/test/js/jquery-ui.js vendored Normal file → Executable file
View File

0
api/js/egw_action/test/js/jquery.js vendored Normal file → Executable file
View File

0
api/js/egw_action/test/skins/dhtmlxmenu_egw.css Normal file → Executable file
View File

761
api/js/egw_action/test/test_action.html Normal file → Executable file
View File

@ -1,408 +1,435 @@
<html>
<head>
<title>Test page for the egw action stuff</title>
<html lang="en">
<head>
<title>Test page for the egw action stuff</title>
<!-- Basic action stuff -->
<script src="../egw_action.js"></script>
<script src="../egw_action_common.js"></script>
<!-- Basic action stuff -->
<!-- JQuery is just used in this example. The egw_action scripts do not
need JQuery! -->
<script src="js/jquery.js"></script>
<script src="js/jquery-ui.js"></script>
<script type="module" src="/egroupware/api/js/jsapi/egw.min.js?1678345504"
id="egw_script_id"
data-app-header="EGroupware" data-url="http://localhost:8080/egroupware"
data-include="[&quot;api\/js\/dhtmlxtree\/codebase\/dhtmlxcommon.js&quot;,&quot;api\/js\/dhtmlxMenu\/sources\/dhtmlxmenu.js&quot;,&quot;api\/js\/dhtmlxMenu\/sources\/ext\/dhtmlxmenu_ext.js&quot;,&quot;api\/js\/dhtmlxtree\/sources\/dhtmlxtree.js&quot;,&quot;api\/js\/dhtmlxtree\/sources\/ext\/dhtmlxtree_json.js&quot;]"></script>
<!-- Popup stuff -->
<link rel="stylesheet" type="text/css" href="skins/dhtmlxmenu_egw.css">
<script src="js/dhtmlxcommon.js"></script>
<script src="js/dhtmlxmenu.js"></script>
<script src="js/dhtmlxmenu_ext.js"></script>
<script src="../egw_action_popup.js"></script>
<script src="../egw_action_dragdrop.js"></script>
<script src="../egw_menu.js"></script>
<script src="../egw_menu_dhtmlx.js"></script>
<!-- JQuery is just used in this example. The egw_action scripts do not
need JQuery! -->
<style>
body, table {
font-family: Arial, sans-serif;
font-size: 10pt;
}
.listbox {
width: 250px;
border: 2px groove gray;
margin: 5px;
border-collapse: collapse;
}
<!-- Popup stuff -->
<link rel="stylesheet" type="text/css" href="skins/dhtmlxmenu_egw.css">
.listbox tr {
-moz-user-select: none;
-khtml-user-select: none;
user-select: none;
cursor: default;
padding: 2px;
}
<style>
body, table {
font-family: Arial, sans-serif;
font-size: 10pt;
}
.listbox tr.odd {
background-color: #eeeeee;
}
.listBox {
width: 250px;
border: 2px groove gray;
margin: 5px;
border-collapse: collapse;
}
.listbox tr.selected {
background-color: #2b5d9b;
color: white;
}
.listBox tr {
-moz-user-select: none;
user-select: none;
cursor: default;
padding: 2px;
}
.listbox tr.odd.selected {
background-color: #234b7d !important;
}
.listBox tr.odd {
background-color: #eeeeee;
}
.listbox .focused {
border: 1px dotted black;
padding: 1px;
}
.listBox tr.selected {
background-color: #2b5d9b;
color: white;
}
.listbox tr.draggedOver {
color: red !important;
}
.listBox tr.odd.selected {
background-color: #234b7d !important;
}
.egw_action_ddHelper {
padding: 5px 5px 5px 26px;
background-image: url(imgs/page.png);
background-position: 5px 5px;
background-repeat: no-repeat;
-webkit-border-radius: 5px;
-moz-border-radius: 5px;
border-radius: 5px;
text-shadow: white 0px 0px 5px;
}
</style>
</head>
<body>
<table class="listbox" id="lb1">
<tr id="folder1"><td><img src="imgs/folder.png"/></td><td style="width: 200px">Folder 1</td><td><input type="checkbox"></td></tr>
<tr id="file1"><td><img src="imgs/page.png"/></td><td style="width: 200px">File 1</td><td><input type="checkbox"></td></tr>
<tr id="file2"><td><img src="imgs/page.png"/></td><td style="width: 200px">File 2</td><td><input type="checkbox"></td></tr>
<tr id="file3"><td><img src="imgs/page.png"/></td><td style="width: 200px">File 3</td><td><input type="checkbox"></td></tr>
<tr id="file4"><td><img src="imgs/page.png"/></td><td style="width: 200px">File 4</td><td><input type="checkbox"></td></tr>
<tr id="file5"><td><img src="imgs/page.png"/></td><td style="width: 200px">File 5</td><td><input type="checkbox"></td></tr>
</table>
<button id="performAction">Perform action...</button><button id="selectAll">Select All</button>
<script>
var actionManager = null;
var objectManager = null;
.listBox .focused {
border: 1px dotted black;
padding: 1px;
}
$j(document).ready(function() {
init();
});
.listBox tr.draggedOver {
color: red !important;
}
// An action object interface for the listbox - "inherits" from
// egwActionObjectInterface
function listboxItemAOI(_node)
{
var aoi = new egwActionObjectInterface();
.egw_action_ddHelper {
padding: 5px 5px 5px 26px;
background-image: url(imgs/page.png);
background-position: 5px 5px;
background-repeat: no-repeat;
-webkit-border-radius: 5px;
-moz-border-radius: 5px;
border-radius: 5px;
text-shadow: white 0 0 5px;
}
</style>
</head>
<body>
<table class="listBox" id="lb1">
<tr id="folder1">
<td><img src="imgs/folder.png" alt="folder png"/></td>
<td style="width: 200px">Folder 1</td>
<td><label>
<input type="checkbox">
</label></td>
</tr>
<tr id="file1">
<td><img src="imgs/page.png" alt="page png"/></td>
<td style="width: 200px">File 1</td>
<td><label>
<input type="checkbox">
</label></td>
</tr>
<tr id="file2">
<td><img src="imgs/page.png" alt="page png"/></td>
<td style="width: 200px">File 2</td>
<td><label>
<input type="checkbox">
</label></td>
aoi.node = _node;
aoi.checkBox = ($j(":checkbox", aoi.node))[0];
aoi.checkBox.checked = false;
</tr>
<tr id="file3">
<td><img src="imgs/page.png" alt="page"/></td>
<td style="width: 200px">File 3</td>
<td><input type="checkbox"></td>
</tr>
<tr id="file4">
<td><img src="imgs/page.png" alt="page"/></td>
<td style="width: 200px">File 4</td>
<td><input type="checkbox"></td>
</tr>
<tr id="file5">
<td><img src="imgs/page.png" alt="page"/></td>
<td style="width: 200px">File 5</td>
<td><input type="checkbox"></td>
</tr>
</table>
<button id="performAction">Perform action...</button>
<button id="selectAll">Select All</button>
<script type="module">
import {EgwActionManager, EgwActionObjectManager} from "../egw_action.min.js";
import {
EGW_AO_STATE_SELECTED,
EGW_AO_STATE_FOCUSED,
EGW_AO_SHIFT_STATE_MULTI,
EGW_AO_SHIFT_STATE_NONE,
EGW_AO_SHIFT_STATE_BLOCK,
} from '../egw_action_constants';
import {egwBitIsSet, egwSetBit} from '../egw_action_common';
import {EgwActionObjectBase} from "../../egw_action/egw_action.min.js";
aoi.doGetDOMNode = function() {
return aoi.node;
}
var actionManager = null;
var objectManager = null;
function getShiftState(e)
{
var state = EGW_AO_SHIFT_STATE_NONE;
state = egwSetBit(state, EGW_AO_SHIFT_STATE_MULTI, e.ctrkKey || e.metaKey);
state = egwSetBit(state, EGW_AO_SHIFT_STATE_BLOCK, e.shiftKey);
return state;
}
jQuery(document).ready(function () {
init();
});
// Now append some action code to the node
$j(_node).click(function(e) {
if (e.target != aoi.checkBox)
{
var selected = egwBitIsSet(aoi.getState(), EGW_AO_STATE_SELECTED);
var state = getShiftState(e);
// An action object interface for the listbox - "inherits" from
// egwActionObjectInterface
function listboxItemAOI(_node) {
var aoi = new EgwActionObjectBase();
aoi.node = _node;
aoi.checkBox = aoi.node.querySelector('[type="checkbox"]')//(jQuery(":checkbox", aoi.node))[0];
aoi.checkBox.checked = false;
aoi.doGetDOMNode = function () {
return aoi.node;
}
function getShiftState(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;
}
// Now append some action code to the node
jQuery(_node).click(function (e) {
if (e.target != aoi.checkBox) {
var selected = egwBitIsSet(aoi.getState(), EGW_AO_STATE_SELECTED);
var state = getShiftState(e);
// "Normal" Listbox behaviour
aoi.updateState(EGW_AO_STATE_SELECTED,
!egwBitIsSet(state, EGW_AO_SHIFT_STATE_MULTI) || !selected,
state);
aoi.updateState(EGW_AO_STATE_SELECTED,
!egwBitIsSet(state, EGW_AO_SHIFT_STATE_MULTI) || !selected,
state);
// "PHPMyAdmin" Listbox behaviour
// aoi.doSetState(egwSetBit(aoi.getState(), EGW_AO_STATE_SELECTED,
// !selected), false, EGW_AO_SHIFT_STATE_MULTI);
}
});
}
});
$j(aoi.checkBox).change(function() {
aoi.updateState(EGW_AO_STATE_SELECTED, this.checked, EGW_AO_SHIFT_STATE_MULTI);
});
aoi.checkBox.addEventListener("change",(event)=> {
aoi.updateState(EGW_AO_STATE_SELECTED, event.target.checked, EGW_AO_SHIFT_STATE_MULTI);
},);
aoi.doTriggerEvent = function(_event) {
if (_event == EGW_AI_DRAG_OVER)
{
$j(this.node).addClass("draggedOver");
}
if (_event == EGW_AI_DRAG_OUT)
{
$j(this.node).removeClass("draggedOver");
}
}
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) {
var selected = egwBitIsSet(_state, EGW_AO_STATE_SELECTED);
this.checkBox.checked = selected;
$j(this.node).toggleClass('focused',
egwBitIsSet(_state, EGW_AO_STATE_FOCUSED));
$j(this.node).toggleClass('selected',
selected);
}
aoi.doSetState = function (_state) {
var selected = egwBitIsSet(_state, EGW_AO_STATE_SELECTED);
this.checkBox.checked = selected;
jQuery(this.node).toggleClass('focused',
egwBitIsSet(_state, EGW_AO_STATE_FOCUSED));
jQuery(this.node).toggleClass('selected',
selected);
}
return aoi;
}
return aoi;
}
function alertClicked(_action, _senders, _target)
{
var ids = "";
for (var i = 0; i < _senders.length; i++)
ids += _senders[i].id + ((i < _senders.length - 1) ? ", " : "");
function alertClicked(_action, _senders, _target) {
var ids = "";
for (var i = 0; i < _senders.length; i++)
ids += _senders[i].id + ((i < _senders.length - 1) ? ", " : "");
alert("Action '" + _action.caption + "' executed on elements '"
+ ids + "', target '" + (_target ? _target.id : "null") + "'");
}
alert("Action '" + _action.caption + "' executed on elements '"
+ ids + "', target '" + (_target ? _target.id : "null") + "'");
}
function returnDDHelper(_action, _senders)
{
var text = [];
for (var i = 0; i < _senders.length; i++)
{
text.push(_senders[i].id);
}
function returnDDHelper(_action, _senders) {
var text = [];
for (var i = 0; i < _senders.length; i++) {
text.push(_senders[i].id);
}
return $j("<div class=\"ddhelper\">" + _senders.length + " (" + text.join(", ") + ") Elements selected </div>")
}
return jQuery("<div class=\"ddhelper\">" + _senders.length + " (" + text.join(", ") + ") Elements selected </div>")
}
function init()
{
//Initialize the action manager and add some actions to it
actionManager = new egwActionManager();
objectManager = new egwActionObjectManager("", actionManager);
function init() {
//Initialize the action manager and add some actions to it
actionManager = new EgwActionManager();
objectManager = new EgwActionObjectManager("", actionManager);
actionManager.updateActions(
[
{
"id": "folder_open",
"iconUrl": "imgs/folder.png",
"caption": "Open folder",
"onExecute": alertClicked,
"allowOnMultiple": false,
"type": "popup",
"default": true
},
{
"id": "file_view",
"iconUrl": "imgs/view.png",
"caption": "View",
"onExecute": alertClicked,
"allowOnMultiple": false,
"type": "popup",
"default": true
},
{
"id": "file_preview",
"iconUrl": "imgs/preview.png",
"caption": "Preview",
"onExecute": alertClicked,
"allowOnMultiple": false,
"type": "popup"
},
{
"id": "file_delete",
"iconUrl": "imgs/delete.png",
"caption": "Delete",
"onExecute": alertClicked,
"type": "popup",
"group": 2
},
{
"id": "file_edit",
"iconUrl": "imgs/edit.png",
"caption": "Edit file",
"onExecute": alertClicked,
"allowOnMultiple": false,
"type": "popup"
},
{
"id": "file_compress",
"iconUrl": "imgs/compress.png",
"caption": "Create ZIP archive",
"onExecute": alertClicked,
"type": "popup",
"group": 1,
"order": 1
},
{
"id": "file_email",
"iconUrl": "imgs/email.png",
"caption": "E-Mail",
"onExecute": alertClicked,
"allowOnMultiple": false,
"type": "popup",
"group": 1,
"order": 0
},
{
"id": "file_compress_email",
"caption": "Create ZIP and E-Mail",
"onExecute": alertClicked,
"type": "popup",
"group": 1,
"order": 2,
"allowOnMultiple": "only",
"hint": "Compresses multiple files and mails them"
},
{
"id": "send_to",
"caption": "Send to",
"type": "popup",
"group": 10,
"children":
[
{
"id": "send_to_1",
"caption": "Folder 1",
"onExecute": alertClicked,
"type": "popup"
},
{
"id": "send_to_2",
"caption": "Folder 2",
"onExecute": alertClicked,
"type": "popup"
},
{
"id": "send_to_3",
"caption": "Folder 3",
"onExecute": alertClicked,
"type": "popup"
},
{
"id": "send_to_add",
"caption": "Add target",
"onExecute": alertClicked,
"type": "popup",
"group": -1
}
]
},
actionManager.updateActions(
[
{
"id": "folder_open",
"iconUrl": "imgs/folder.png",
"caption": "Open folder",
"onExecute": alertClicked,
"allowOnMultiple": false,
"type": "popup",
"default": true
},
{
"id": "file_view",
"iconUrl": "imgs/view.png",
"caption": "View",
"onExecute": alertClicked,
"allowOnMultiple": false,
"type": "popup",
"default": true
},
{
"id": "file_preview",
"iconUrl": "imgs/preview.png",
"caption": "Preview",
"onExecute": alertClicked,
"allowOnMultiple": false,
"type": "popup"
},
{
"id": "file_delete",
"iconUrl": "imgs/delete.png",
"caption": "Delete",
"onExecute": alertClicked,
"type": "popup",
"group": 2
},
{
"id": "file_edit",
"iconUrl": "imgs/edit.png",
"caption": "Edit file",
"onExecute": alertClicked,
"allowOnMultiple": false,
"type": "popup"
},
{
"id": "file_compress",
"iconUrl": "imgs/compress.png",
"caption": "Create ZIP archive",
"onExecute": alertClicked,
"type": "popup",
"group": 1,
"order": 1
},
{
"id": "file_email",
"iconUrl": "imgs/email.png",
"caption": "E-Mail",
"onExecute": alertClicked,
"allowOnMultiple": false,
"type": "popup",
"group": 1,
"order": 0
},
{
"id": "file_compress_email",
"caption": "Create ZIP and E-Mail",
"onExecute": alertClicked,
"type": "popup",
"group": 1,
"order": 2,
"allowOnMultiple": "only",
"hint": "Compresses multiple files and mails them"
},
{
"id": "send_to",
"caption": "Send to",
"type": "popup",
"group": 10,
"children":
[
{
"id": "send_to_1",
"caption": "Folder 1",
"onExecute": alertClicked,
"type": "popup"
},
{
"id": "send_to_2",
"caption": "Folder 2",
"onExecute": alertClicked,
"type": "popup"
},
{
"id": "send_to_3",
"caption": "Folder 3",
"onExecute": alertClicked,
"type": "popup"
},
{
"id": "send_to_add",
"caption": "Add target",
"onExecute": alertClicked,
"type": "popup",
"group": -1
}
]
},
{
"id": "file_drag",
"type": "drag",
"dragType": "file"
},
{
"id": "folder_drop",
"type": "drop",
"caption": "Move files here",
"iconUrl": "imgs/move.png",
"acceptedTypes": "file",
"onExecute": alertClicked,
"children":
[
{
"id": "sub1",
"type": "popup",
"caption": "Use insecure but super fast move algorithm"
},
{
"id": "sub2",
"type": "popup",
"caption": "Use standard move algorithm",
"default": true
},
{
"id": "sub3",
"type": "popup",
"caption": "Only simulate moving"
}
],
"default": true
},
{
"id": "folder_drop2",
"type": "drop",
"caption": "Copy files here",
"iconUrl": "imgs/copy.png",
"onExecute": alertClicked,
"acceptedTypes": "file"
},
{
"id": "chk1",
"type": "popup",
"checkbox": true,
"checked": true,
"caption": "Test1"
},
{
"id": "chk2",
"type": "popup",
"checkbox": true,
"checked": false,
"caption": "Test2",
"onExecute": function(_action) {
_action.checked = true;
}
}
]
);
{
"id": "file_drag",
"type": "drag",
"dragType": "file"
},
{
"id": "folder_drop",
"type": "drop",
"caption": "Move files here",
"iconUrl": "imgs/move.png",
"acceptedTypes": "file",
"onExecute": alertClicked,
"children":
[
{
"id": "sub1",
"type": "popup",
"caption": "Use insecure but super fast move algorithm"
},
{
"id": "sub2",
"type": "popup",
"caption": "Use standard move algorithm",
"default": true
},
{
"id": "sub3",
"type": "popup",
"caption": "Only simulate moving"
}
],
"default": true
},
{
"id": "folder_drop2",
"type": "drop",
"caption": "Copy files here",
"iconUrl": "imgs/copy.png",
"onExecute": alertClicked,
"acceptedTypes": "file"
},
{
"id": "chk1",
"type": "popup",
"checkbox": true,
"checked": true,
"caption": "Test1"
},
{
"id": "chk2",
"type": "popup",
"checkbox": true,
"checked": false,
"caption": "Test2",
"onExecute": function (_action) {
_action.checked = true;
}
}
]
);
//Links which will be assigned to each listbox item
var listboxFileLinks = [
{"actionId": "file_view", "enabled": true},
{"actionId": "file_preview", "enabled": true},
{"actionId": "file_edit", "enabled": true},
{"actionId": "file_email", "enabled": true},
{"actionId": "file_compress_email", "enabled": true},
{"actionId": "file_compress", "enabled": true},
{"actionId": "file_delete", "enabled": true},
{"actionId": "send_to", "enabled": true},
"file_drag",
"chk1",
"chk2"
];
//Links which will be assigned to each listbox item
var listboxFileLinks = [
{"actionId": "file_view", "enabled": true},
{"actionId": "file_preview", "enabled": true},
{"actionId": "file_edit", "enabled": true},
{"actionId": "file_email", "enabled": true},
{"actionId": "file_compress_email", "enabled": true},
{"actionId": "file_compress", "enabled": true},
{"actionId": "file_delete", "enabled": true},
{"actionId": "send_to", "enabled": true},
"file_drag",
"chk1",
"chk2"
];
var listboxFolderLinks = [
{"actionId": "folder_open", "enabled": true},
{"actionId": "file_compress_email", "enabled": true},
{"actionId": "file_compress", "enabled": true},
{"actionId": "file_delete", "enabled": true},
"send_to",
"folder_drop", "folder_drop2"
];
var listboxFolderLinks = [
{"actionId": "folder_open", "enabled": true},
{"actionId": "file_compress_email", "enabled": true},
{"actionId": "file_compress", "enabled": true},
{"actionId": "file_delete", "enabled": true},
"send_to",
"folder_drop", "folder_drop2"
];
$j('#lb1 tr:odd').addClass('odd');
jQuery('#lb1 tr:odd').addClass('odd');
//Create an object representation for each listbox-row and append
//each to its own listboxItemAOI
$j('#lb1 tr').each(function(index, elem) {
var obj = objectManager.addObject(elem.id, new listboxItemAOI(elem));
//Apply the links to the actions
if (elem.id.substr(0,4) == "file")
obj.updateActionLinks(listboxFileLinks);
else
obj.updateActionLinks(listboxFolderLinks);
});
//Create an object representation for each listbox-row and append
//each to its own listboxItemAOI
jQuery('#lb1 tr').each(function (index, elem) {
var obj = objectManager.addObject(elem.id, new listboxItemAOI(elem));
//Apply the links to the actions
if (elem.id.substr(0, 4) == "file")
obj.updateActionLinks(listboxFileLinks);
else
obj.updateActionLinks(listboxFolderLinks);
});
$j("#selectAll").click(function() {
objectManager.toggleAllSelected();
});
jQuery("#selectAll").click(function () {
objectManager.toggleAllSelected();
});
$j("#performAction").click(function(e) {
if (!objectManager.executeActionImplementation(this, "popup"))
alert("Please select one or more objects.");
return false;
});
}
</script>
</body>
jQuery("#performAction").click(function (e) {
if (!objectManager.executeActionImplementation(this, "popup"))
alert("Please select one or more objects.");
return false;
});
}
</script>
</body>
</html>

6
api/js/egw_action/test/test_menu.html Normal file → Executable file
View File

@ -1,12 +1,12 @@
<html>
<html lang="en">
<head>
<title>Test page for the egw menu</title>
<link rel="stylesheet" type="text/css" href="skins/dhtmlxmenu_egw.css">
<script src="js/dhtmlxcommon.js"></script>
<script src="js/dhtmlxmenu.js"></script>
<script src="js/dhtmlxmenu_ext.js"></script>
<script src="../egw_menu.js"></script>
<script src="../egw_menu_dhtmlx.js"></script>
<script src="../egw_menu.ts"></script>
<script src="../egw_menu_dhtmlx.ts"></script>
</head>
<body style="width:95%; height:95%; font-family: sans-serif">
<script>

176
api/js/egw_action/test/test_stylesheet_editing.html Normal file → Executable file
View File

@ -1,97 +1,97 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xml:lang="en" xmlns="http://www.w3.org/1999/xhtml">
<!DOCTYPE html>
<html lang="en">
<head>
<title>Stylesheet Test</title>
<script src="js/jquery.js"></script>
<script src="../egw_stylesheet.js"></script>
<script src="../egw_stylesheet.ts"></script>
<script >
const s = new egwDynStyleSheet();
</script>
</head>
<body>
<h1>Test for dynamically generating stylesheets and stylesheet rules</h1>
<div id="container">1: Style me!</div>
<div id="container2">2: Style me!</div>
<div id="container3">3: Style me!</div>
<div id="container">1: Style me!</div>
<div id="container2">2: Style me!</div>
<div id="container3">3: Style me!</div>
<div id="container">1: Style me!</div>
<div id="container2">2: Style me!</div>
<div id="container3">3: Style me!</div>
<div id="container">1: Style me!</div>
<div id="container2">2: Style me!</div>
<div id="container3">3: Style me!</div>
<div id="container">1: Style me!</div>
<div id="container2">2: Style me!</div>
<div id="container3">3: Style me!</div>
<div id="container">1: Style me!</div>
<div id="container2">2: Style me!</div>
<div id="container3">3: Style me!</div>
<div id="container">1: Style me!</div>
<div id="container2">2: Style me!</div>
<div id="container3">3: Style me!</div>
<div id="container">1: Style me!</div>
<div id="container2">2: Style me!</div>
<div id="container3">3: Style me!</div>
<div id="container">1: Style me!</div>
<div id="container2">2: Style me!</div>
<div id="container3">3: Style me!</div>
<div id="container">1: Style me!</div>
<div id="container2">2: Style me!</div>
<div id="container3">3: Style me!</div>
<div id="container">1: Style me!</div>
<div id="container2">2: Style me!</div>
<div id="container3">3: Style me!</div>
<div id="container">1: Style me!</div>
<div id="container2">2: Style me!</div>
<div id="container3">3: Style me!</div>
<div id="container">1: Style me!</div>
<div id="container2">2: Style me!</div>
<div id="container3">3: Style me!</div>
<div id="container">1: Style me!</div>
<div id="container2">2: Style me!</div>
<div id="container3">3: Style me!</div>
<div id="container">1: Style me!</div>
<div id="container2">2: Style me!</div>
<div id="container3">3: Style me!</div>
<div id="container">1: Style me!</div>
<div id="container2">2: Style me!</div>
<div id="container3">3: Style me!</div>
<div id="container">1: Style me!</div>
<div id="container2">2: Style me!</div>
<div id="container3">3: Style me!</div>
<div id="container">1: Style me!</div>
<div id="container2">2: Style me!</div>
<div id="container3">3: Style me!</div>
<div id="container">1: Style me!</div>
<div id="container2">2: Style me!</div>
<div id="container3">3: Style me!</div>
<div id="container">1: Style me!</div>
<div id="container2">2: Style me!</div>
<div id="container3">3: Style me!</div>
<div id="container">1: Style me!</div>
<div id="container2">2: Style me!</div>
<div id="container3">3: Style me!</div>
<div id="container">1: Style me!</div>
<div id="container2">2: Style me!</div>
<div id="container3">3: Style me!</div>
<div id="container">1: Style me!</div>
<div id="container2">2: Style me!</div>
<div id="container3">3: Style me!</div>
<div id="container">1: Style me!</div>
<div id="container2">2: Style me!</div>
<div id="container3">3: Style me!</div>
<div id="container">1: Style me!</div>
<div id="container2">2: Style me!</div>
<div id="container3">3: Style me!</div>
<button onclick="stylesheet.updateRule('#container', 'background-color: blue');">1 Blue</button>
<button onclick="stylesheet.updateRule('#container', 'background-color: red');">1 Red</button>
<button onclick="stylesheet.updateRule('#container2', 'background-color: blue');">2 Blue</button>
<button onclick="stylesheet.updateRule('#container2', 'background-color: red');">2 Red</button>
<button onclick="stylesheet.updateRule('#container3', 'background-color: blue');">3 Blue</button>
<button onclick="stylesheet.updateRule('#container3', 'background-color: red');">3 Red</button>
<button onclick="stylesheet.updateRule('div', 'display: inline; width: 100px; font-family: Arial, Helvetica, sans;');">Div</button>
<div class="container">1: Style me!</div>
<div class="container2">2: Style me!</div>
<div class="container3">3: Style me!</div>
<div class="container">1: Style me!</div>
<div class="container2">2: Style me!</div>
<div class="container3">3: Style me!</div>
<div class="container">1: Style me!</div>
<div class="container2">2: Style me!</div>
<div class="container3">3: Style me!</div>
<div class="container">1: Style me!</div>
<div class="container2">2: Style me!</div>
<div class="container3">3: Style me!</div>
<div class="container">1: Style me!</div>
<div class="container2">2: Style me!</div>
<div class="container3">3: Style me!</div>
<div class="container">1: Style me!</div>
<div class="container2">2: Style me!</div>
<div class="container3">3: Style me!</div>
<div class="container">1: Style me!</div>
<div class="container2">2: Style me!</div>
<div class="container3">3: Style me!</div>
<div class="container">1: Style me!</div>
<div class="container2">2: Style me!</div>
<div class="container3">3: Style me!</div>
<div class="container">1: Style me!</div>
<div class="container2">2: Style me!</div>
<div class="container3">3: Style me!</div>
<div class="container">1: Style me!</div>
<div class="container2">2: Style me!</div>
<div class="container3">3: Style me!</div>
<div class="container">1: Style me!</div>
<div class="container2">2: Style me!</div>
<div class="container3">3: Style me!</div>
<div class="container">1: Style me!</div>
<div class="container2">2: Style me!</div>
<div class="container3">3: Style me!</div>
<div class="container">1: Style me!</div>
<div class="container2">2: Style me!</div>
<div class="container3">3: Style me!</div>
<div class="container">1: Style me!</div>
<div class="container2">2: Style me!</div>
<div class="container3">3: Style me!</div>
<div class="container">1: Style me!</div>
<div class="container2">2: Style me!</div>
<div class="container3">3: Style me!</div>
<div class="container">1: Style me!</div>
<div class="container2">2: Style me!</div>
<div class="container3">3: Style me!</div>
<div class="container">1: Style me!</div>
<div class="container2">2: Style me!</div>
<div class="container3">3: Style me!</div>
<div class="container">1: Style me!</div>
<div class="container2">2: Style me!</div>
<div class="container3">3: Style me!</div>
<div class="container">1: Style me!</div>
<div class="container2">2: Style me!</div>
<div class="container3">3: Style me!</div>
<div class="container">1: Style me!</div>
<div class="container2">2: Style me!</div>
<div class="container3">3: Style me!</div>
<div class="container">1: Style me!</div>
<div class="container2">2: Style me!</div>
<div class="container3">3: Style me!</div>
<div class="container">1: Style me!</div>
<div class="container2">2: Style me!</div>
<div class="container3">3: Style me!</div>
<div class="container">1: Style me!</div>
<div class="container2">2: Style me!</div>
<div class="container3">3: Style me!</div>
<div class="container">1: Style me!</div>
<div class="container2">2: Style me!</div>
<div class="container3">3: Style me!</div>
<div class="container">1: Style me!</div>
<div class="container2">2: Style me!</div>
<div class="container3">3: Style me!</div>
<button onclick="s.updateRule('.container', 'background-color: blue');">1 Blue</button>
<button onclick="s.updateRule('.container', 'background-color: red');">1 Red</button>
<button onclick="s.updateRule('.container2', 'background-color: blue');">2 Blue</button>
<button onclick="s.updateRule('.container2', 'background-color: red');">2 Red</button>
<button onclick="s.updateRule('.container3', 'background-color: blue');">3 Blue</button>
<button onclick="s.updateRule('.container3', 'background-color: red');">3 Red</button>
<button onclick="s.updateRule('div', 'display: inline; width: 100px; font-family: Arial, Helvetica, sans;');">Div</button>
<script>
var stylesheet = new egwDynStyleSheet();
</script>
</body>
</html>

View File

@ -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";

View File

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

View File

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

View File

@ -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";
/**

View File

@ -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";
/**

View File

@ -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";

View File

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

View File

@ -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';

View File

@ -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';
/**

View File

@ -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";

View File

@ -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';

View File

@ -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";

View File

@ -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';

View File

@ -8,8 +8,8 @@
<script src="../../jquery/jquery.tools.min.js"></script>
<script src="../../jquery/jquery-ui.js"></script>
<script src="../../egw_action/egw_action.js"></script>
<script src="../../egw_action/egw_action_common.js"></script>
<script src="../../egw_action/egw_action.ts"></script>
<script src="../../egw_action/egw_action_common.ts"></script>
<script>
// Create the egw object template

View File

@ -12,7 +12,7 @@
egw_inheritance.js;
*/
import '../egw_action/egw_action_common.js';
import '../egw_action/egw_action_common';
import '../jsapi/egw_inheritance.js';
import '../etemplate/etemplate2'; // otherwise et2_load json-response-handler is not (yet) available

View File

@ -17,7 +17,7 @@ import "../../../vendor/bower-asset/jquery/dist/jquery.min.js";
import "../jquery/jquery.noconflict.js";
import "../jquery/mousewheel/mousewheel.js";
import '../jsapi/egw_inheritance.js';
import {EGW_KEY_ENTER, EGW_KEY_SPACE} from '../egw_action/egw_action_constants.js';
import {EGW_KEY_ENTER, EGW_KEY_SPACE} from '../egw_action/egw_action_constants';
import interact from "@interactjs/interactjs";
/**

View File

@ -20,6 +20,7 @@ import {et2_valueWidget} from "../etemplate/et2_core_valueWidget";
import {nm_action} from "../etemplate/et2_extension_nextmatch_actions";
import {Et2Dialog} from "../etemplate/Et2Dialog/Et2Dialog";
import {Et2Favorites} from "../etemplate/Et2Favorites/Et2Favorites";
import {EgwAction} from "../egw_action/EgwAction";
/**
* Type for push-message
@ -2184,6 +2185,9 @@ export abstract class EgwApp
{
return EgwApp._instances[Symbol.iterator]();
}
//TODO check if this makes any sense
confirm:(param: EgwAction, _senders:any, _target:any)=>any=undefined
}
// EgwApp need to be global on window, as it's used to iterate through all EgwApp instances

View File

@ -23,7 +23,19 @@ declare var egw : Iegw;
/**
* Interface for global egw with window global or local methods or as function returning an object allowing also application local methods
*/
declare interface Iegw extends IegwWndLocal { (_app? : string|Window, _wnd? : Window) : IegwAppLocal }
declare interface Iegw extends IegwWndLocal {
(_app?: string | Window, _wnd?: Window) : IegwAppLocal,
/**
* Copy text to the clipboard
*
* @param text Actual text to copy. Usually target_element.value
* @param target_element Optional, but useful for fallback copy attempts
* @param event Optional, but if you have an event we can try some fallback options with it
*
* @returns {Promise<undefined|boolean>|Promise<void>}
*/
copyTextToClipboard:(text, target_element, event)=>any
}
/**
* Return type for egw.app() call
@ -863,7 +875,7 @@ declare interface IegwWndLocal extends IegwGlobal
* @param {object} _context
* @returns {mixed|Promise}
*/
applyFunc(_func : string|Function, args : Array<any>, _context? : Object) : Promise<any>|any
applyFunc(_func : string|Function, args : IArguments, _context? : Object) : Promise<any>|any
/**
* Registers a new handler plugin.

View File

@ -25,7 +25,9 @@ egw.extend('preferences', egw.MODULE_GLOBAL, function()
*
* @access: private, use egw.preferences() or egw.set_perferences()
*/
var prefs = {};
var prefs = {
common:{texsize:12}
};
var grants = {};
/**

15
api/src/Framework/Bundle.php Normal file → Executable file
View File

@ -249,14 +249,13 @@ class Bundle
$inc_mgr->include_js_file('/api/js/dhtmlxtree/sources/dhtmlxtree.js');
$inc_mgr->include_js_file('/api/js/dhtmlxtree/sources/ext/dhtmlxtree_json.js');
// actions
$inc_mgr->include_js_file('/api/js/egw_action/egw_action_constants.js');
$inc_mgr->include_js_file('/api/js/egw_action/egw_action.js');
$inc_mgr->include_js_file('/api/js/egw_action/egw_keymanager.js');
$inc_mgr->include_js_file('/api/js/egw_action/egw_action_popup.js');
$inc_mgr->include_js_file('/api/js/egw_action/egw_action_dragdrop.js');
$inc_mgr->include_js_file('/api/js/egw_action/egw_dragdrop_dhtmlx_tree.js');
$inc_mgr->include_js_file('/api/js/egw_action/egw_menu.js');
$inc_mgr->include_js_file('/api/js/egw_action/egw_menu_dhtmlx.js');
//TODO there are more and different files that need to be included
$inc_mgr->include_js_file('/api/js/egw_action/egw_action_constants');
$inc_mgr->include_js_file('/api/js/egw_action/egw_action');
$inc_mgr->include_js_file('/api/js/egw_action/egw_keymanager');
$inc_mgr->include_js_file('/api/js/egw_action/egw_dragdrop_dhtmlx_tree.ts');
$inc_mgr->include_js_file('/api/js/egw_action/egw_menu');
$inc_mgr->include_js_file('/api/js/egw_action/egw_menu_dhtmlx');
// include choosen in api, as old eTemplate uses it and fail if it pulls in half of et2
$inc_mgr->include_js_file('/api/js/jquery/chosen/chosen.jquery.js');
$bundles['api'] = $inc_mgr->get_included_files();

View File

@ -39,8 +39,8 @@ import {et2_nextmatch} from "../../api/js/etemplate/et2_extension_nextmatch";
import {et2_iframe} from "../../api/js/etemplate/et2_widget_iframe";
// @ts-ignore
import {date} from "../../api/js/etemplate/lib/date.js";
import {sprintf} from "../../api/js/egw_action/egw_action_common.js";
import {egw_registerGlobalShortcut, egw_unregisterGlobalShortcut} from "../../api/js/egw_action/egw_keymanager.js";
import {sprintf} from "../../api/js/egw_action/egw_action_common";
import {egw_registerGlobalShortcut, egw_unregisterGlobalShortcut} from "../../api/js/egw_action/egw_keymanager";
import {egw, egw_getFramework} from "../../api/js/jsapi/egw_global";
import {et2_number} from "../../api/js/etemplate/et2_widget_number";
import {et2_template} from "../../api/js/etemplate/et2_widget_template";

View File

@ -22,7 +22,7 @@ import {ClassWithAttributes} from "../../api/js/etemplate/et2_core_inheritance";
import {et2_IDetachedDOM, et2_IResizeable} from "../../api/js/etemplate/et2_core_interfaces";
import {et2_no_init} from "../../api/js/etemplate/et2_core_common";
import {egw} from "../../api/js/jsapi/egw_global";
import {egwIsMobile, sprintf} from "../../api/js/egw_action/egw_action_common.js";
import {egwIsMobile, sprintf} from "../../api/js/egw_action/egw_action_common";
import {CalendarApp} from "./app";
import {et2_calendar_view} from "./et2_widget_view";
import flatpickr from "flatpickr";

View File

@ -21,7 +21,7 @@ import {et2_calendar_daycol} from "./et2_widget_daycol";
import {et2_calendar_planner_row} from "./et2_widget_planner_row";
import {et2_IDetachedDOM} from "../../api/js/etemplate/et2_core_interfaces";
import {et2_activateLinks, et2_insertLinkText, et2_no_init} from "../../api/js/etemplate/et2_core_common";
import {egw_getAppObjectManager, egwActionObject} from '../../api/js/egw_action/egw_action.js';
import {egw_getAppObjectManager, egwActionObject} from '../../api/js/egw_action/egw_action';
import {egw} from "../../api/js/jsapi/egw_global";
import {et2_container} from "../../api/js/etemplate/et2_core_baseWidget";
import {Et2Dialog} from "../../api/js/etemplate/Et2Dialog/Et2Dialog";

View File

@ -22,17 +22,17 @@ import {et2_action_object_impl} from "../../api/js/etemplate/et2_core_DOMWidget"
import {et2_calendar_event} from "./et2_widget_event";
import {et2_calendar_planner_row} from "./et2_widget_planner_row";
import {egw} from "../../api/js/jsapi/egw_global";
import {egw_getObjectManager, egwActionObject} from "../../api/js/egw_action/egw_action.js";
import {egw_getObjectManager, egwActionObject} from "../../api/js/egw_action/egw_action";
import {
EGW_AI_DRAG_ENTER,
EGW_AI_DRAG_OUT,
EGW_AO_FLAG_IS_CONTAINER
} from "../../api/js/egw_action/egw_action_constants.js";
} from "../../api/js/egw_action/egw_action_constants";
import {et2_IDetachedDOM, et2_IPrint, et2_IResizeable} from "../../api/js/etemplate/et2_core_interfaces";
import {et2_compileLegacyJS} from "../../api/js/etemplate/et2_core_legacyJSFunctions";
import {et2_no_init} from "../../api/js/etemplate/et2_core_common";
import {CalendarApp} from "./app";
import {sprintf} from "../../api/js/egw_action/egw_action_common.js";
import {sprintf} from "../../api/js/egw_action/egw_action_common";
import {et2_dataview_grid} from "../../api/js/etemplate/et2_dataview_view_grid";
import {formatDate, formatTime} from "../../api/js/etemplate/Et2Date/Et2Date";
import interact from "@interactjs/interactjs/index";

View File

@ -20,8 +20,8 @@ import {et2_valueWidget} from "../../api/js/etemplate/et2_core_valueWidget";
import {ClassWithAttributes} from "../../api/js/etemplate/et2_core_inheritance";
import {et2_action_object_impl} from "../../api/js/etemplate/et2_core_DOMWidget";
import {et2_calendar_planner} from "./et2_widget_planner";
import {egw_getObjectManager, egwActionObject} from "../../api/js/egw_action/egw_action.js";
import {EGW_AI_DRAG_ENTER, EGW_AI_DRAG_OUT} from "../../api/js/egw_action/egw_action_constants.js";
import {egw_getObjectManager, egwActionObject} from "../../api/js/egw_action/egw_action";
import {EGW_AI_DRAG_ENTER, EGW_AI_DRAG_OUT} from "../../api/js/egw_action/egw_action_constants";
import {et2_IResizeable} from "../../api/js/etemplate/et2_core_interfaces";
import {egw} from "../../api/js/jsapi/egw_global";
import {et2_calendar_view} from "./et2_widget_view";

View File

@ -23,10 +23,10 @@ import {egw} from "../../api/js/jsapi/egw_global";
import {et2_no_init} from "../../api/js/etemplate/et2_core_common";
import {et2_IDetachedDOM, et2_IPrint, et2_IResizeable} from "../../api/js/etemplate/et2_core_interfaces";
import {et2_calendar_event} from "./et2_widget_event";
import {egw_getObjectManager, egwActionObject} from "../../api/js/egw_action/egw_action.js";
import {egw_getObjectManager, egwActionObject} from "../../api/js/egw_action/egw_action";
import {et2_compileLegacyJS} from "../../api/js/etemplate/et2_core_legacyJSFunctions";
import {Et2Dialog} from "../../api/js/etemplate/Et2Dialog/Et2Dialog";
import {EGW_AI_DRAG_ENTER, EGW_AI_DRAG_OUT} from "../../api/js/egw_action/egw_action_constants.js";
import {EGW_AI_DRAG_ENTER, EGW_AI_DRAG_OUT} from "../../api/js/egw_action/egw_action_constants";
import {formatDate, formatTime, parseTime} from "../../api/js/etemplate/Et2Date/Et2Date";
import interact from "@interactjs/interactjs/index";
import type {InteractEvent} from "@interactjs/core/InteractEvent";

View File

@ -7,6 +7,7 @@
```
curl https://raw.githubusercontent.com/EGroupware/egroupware/master/doc/docker/docker-compose.yml > docker-compose.yml
curl https://raw.githubusercontent.com/EGroupware/egroupware/master/doc/docker/nginx.conf > nginx.conf
curl https://raw.githubusercontent.com/EGroupware/egroupware/master/doc/docker/Dockerfile > Dockerfile
# edit docker-compose.yml or nginx.conf, by default it will run on http://localhost:8080/
# create a few directories upfront, otherwise the containers won't start up:
mkdir data # this is where egroupware data is stored, it's by default a subdir of the directory of docker-compose.yml
@ -15,6 +16,8 @@ mkdir -p data/default/rocketchat/dump # rocket.chat dumps
mkdir -p data/default/rocketchat/uploads # rocket.chat uploads
mkdir sources # egroupware sources will show up in this folder
docker-compose up -d
# grand access rights to source sub folders
#remove sources/egroupware/swolepush --> egw install complains that /egroupware has to be empty
```
## More information
The provided docker-compose.yml will run the following container:

View File

@ -64,11 +64,11 @@ services:
#environment:
#- LANG=de
volumes:
- sources:/var/www
- data:/var/lib/egroupware
- sessions:/var/lib/php/sessions
- cache:/root
- push-config:/var/lib/egroupware-push
- $PWD/sources:/var/www
- $PWD/data:/var/lib/egroupware
- $PWD/sessions:/var/lib/php/sessions
- $PWD/cache:/root
- $PWD/push-config:/var/lib/egroupware-push
# if you want to use the host database:
# 1. comment out the whole db service below AND
# 2. set EGW_DB_HOST=localhost AND
@ -110,7 +110,7 @@ services:
#- EGW_EXTRA_APP_OLDAPI=https://github.com/EGroupware/phpgwapi.git https://github.com/EGroupware/etemplate.git
#
# XDEBUG_REMOTE_HOST need to be set, if the host running the IDE is different from 172.17.0.1 (Mac can use docker.for.mac.localhost)
- XDEBUG_REMOTE_HOST=docker.for.mac.localhost
- XDEBUG_REMOTE_HOST=172.17.0.1
restart: always
depends_on:
- db
@ -123,7 +123,7 @@ services:
nginx:
image: nginx:stable-alpine
volumes:
- sources:/var/www:ro
- $PWD/sources:/var/www:ro
# to add a certificate create a certificate.pem containing (in that order)
# 1. private key
# 2. public key
@ -134,8 +134,8 @@ services:
- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
ports:
# if no webserver is running on the host, change (first) number to 80 and 443
- "8080:80"
- "4443:443"
- "80:80"
- "443:443"
depends_on:
- egroupware
- push
@ -150,8 +150,9 @@ services:
environment:
#- MYSQL_ROOT=root
- MYSQL_ROOT_PASSWORD=secret
- MARIADB_AUTO_UPGRADE=true
#- MARIADB_AUTO_UPGRADE=true
volumes:
#for WSL2 no $PWD/ here! otherwhise connection is denied
- db:/var/lib/mysql
# to add an own persistent configuration
#- ./mariadb.cnf:/etc/mysql/mariadb.conf.d/egroupware.cnf
@ -167,9 +168,10 @@ services:
command:
- /var/www/server.php
volumes:
- sources-push:/var/www
- sessions:/var/lib/php/sessions
- push-config:/var/lib/egroupware-push
# push server needs this source
- $PWD/sources/egroupware/swoolepush:/var/www
- $PWD/sessions:/var/lib/php/sessions
- $PWD/push-config:/var/lib/egroupware-push
container_name: egroupware-push
restart: always
depends_on:
@ -204,9 +206,9 @@ services:
image: "quay.io/egroupware/collabora-key:stable"
#image: collabora/code:latest
volumes:
- collabora-config:/etc/loolwsd
# $PWD/collabora-config:/etc/loolwsd
# support for Collabora/CODE 21.11+
- collabora-config:/etc/coolwsd
- $PWD/data/default/loolwsd:/etc/coolwsd
restart: always
container_name: collabora-key
# set the ip-address of your docker host AND your official DNS name so Collabora
@ -221,15 +223,15 @@ services:
image: "quay.io/egroupware/collabora-key:latest"
command: bash -c "test -f /tmp/coolwsd/coolwsd.xml || (cp -p /etc/coolwsd/* /tmp/coolwsd && cd /tmp/coolwsd && ln -s coolwsd.conf loolwsd.conf)"
volumes:
- collabora-config:/tmp/coolwsd
- $PWD/data/default/loolwsd:/tmp/coolwsd
# Rocket.Chat server
rocketchat:
image: rocketchat/rocket.chat:latest
image: quay.io/egroupware/rocket.chat:latest
command: bash -c 'for i in `seq 1 30`; do node main.js && s=$$? && break || s=$$?; echo "Tried $$i times. Waiting 5 secs..."; sleep 5; done; (exit $$s)'
restart: unless-stopped
volumes:
- rocketchat-uploads:/app/uploads
- $PWD/data/default/rocketchat/uploads:/app/uploads
# if EGroupware uses a certificate from a private CA, OAuth authentication will fail, you need to:
# - have the CA certificate stored at /etc/egroupware-docker/private-ca.crt
# - uncomment the next 2 lines about the private CA:
@ -253,17 +255,17 @@ services:
# MongoDB for Rocket.Chat
mongo:
image: mongo:4.2
image: mongo:5.0
restart: unless-stopped
volumes:
- mongo:/data/db
- rocketchat-dumps:/dump
command: mongod --smallfiles --oplogSize 128 --replSet rs0 --storageEngine=mmapv1
- $PWD/data/default/rocketchat/dump:/dump
command: mongod --oplogSize 128 --replSet rs0
container_name: rocketchat-mongo
# this container's job is just run the command to initialize the replica set.
# it will run the command and remove himself (it will not stay running)
mongo-init-replica:
image: mongo:4.2
image: mongo:5.0
command: 'bash -c "for i in `seq 1 30`; do mongo mongo/rocketchat --eval \"rs.initiate({ _id: ''rs0'', members: [ { _id: 0, host: ''localhost:27017'' } ]})\" && s=$$? && break || s=$$?; echo \"Tried $$i times. Waiting 5 secs...\"; sleep 5; done; (exit $$s)"'
depends_on:
- mongo

View File

@ -1,19 +1,6 @@
version: '3'
volumes:
# NOTE: all directories referenced by "device" entries below need to be created manually before starting the containers
# egroupware sources
sources:
driver_opts:
type: none
o: bind
device: $PWD/sources/
# sources for push server, swoolepush is a subdirectory of egroupware sources (within volume "sources")
sources-push:
driver_opts:
type: none
o: bind
device: $PWD/sources/swoolepush
db:
# data directory: here are the files stored (/var/lib/egroupware by default)
data:
driver_opts:
type: none
@ -23,19 +10,24 @@ volumes:
#device: /var/lib/egroupware
# otherwise data is stored in data subdirectory of the current directory
device: $PWD/data
# extra sources with apps not part of egroupware container
#extra:
# driver_opts:
# type: none
# o: bind
# # location of deprecated EGroupware packages like Wiki, SiteMgr, KnowledgeBase
# device: /usr/share/egroupware
# #device: $PWD/extra
# sources directory or document root mounted as /var/www inside the container
sources:
driver_opts:
type: none
o: bind
# use this if you have an existing document root with an egroupware directory inside
#device: /var/www
# otherwise sources/document is stored in sources subdirectory of current directory
device: $PWD/sources
# sources for push server, swoolpush subdirectory of egroupware
sources-push:
driver_opts:
type: none
o: bind
device: $PWD/sources/egroupware/swoolepush
# volume to store config.inc.php file / token shared between egroupware and push container
push-config:
sessions:
# collabora-config directory, initially filled by collabora-init container
# additionally some more configuration files are needed in order for collabora to work, which are _not_ generated by the collabora-init container
# collabora-config
collabora-config:
driver_opts:
type: none
@ -45,6 +37,11 @@ volumes:
#device: /var/lib/egroupware/default/loolwsd
# otherwise data is stored in data subdirectory of the current directory
device: $PWD/data/default/loolwsd
# for Mac and Windows, do NOT use a directory for the DB, as the Docker host is in a VM!
db:
sessions:
# cache files from compose, npm and yarn (actually /root inside the container)
cache:
# store Rocket.Chat MongoDB on an (internal) Volume
mongo:
# directory to store MongoDB dumps
@ -58,20 +55,20 @@ volumes:
type: none
o: bind
device: $PWD/data/default/rocketchat/uploads
services:
egroupware:
image: egroupware/egroupware:latest
# EPL image: download.egroupware.org/egroupware/epl:20.1
# you can also use tags like: 7.4, 8.0 or 8.1 depending on the PHP version you want to use
image: egroupware/development:latest
# setting a default language for a new installation
#environment:
#- LANG=de
volumes:
- sources:/usr/share/egroupware
# extra-sources rsync from entry-point into sources
#- extra:/usr/share/egroupware-extra
- data:/var/lib/egroupware
- sessions:/var/lib/php/sessions
- push-config:/var/lib/egroupware-push
- $PWD/sources:/var/www
- $PWD/data:/var/lib/egroupware
- $PWD/sessions:/var/lib/php/sessions
- $PWD/cache:/root
- $PWD/push-config:/var/lib/egroupware-push
# if you want to use the host database:
# 1. comment out the whole db service below AND
# 2. set EGW_DB_HOST=localhost AND
@ -84,100 +81,121 @@ services:
# multiple certificates (eg. a chain) have to be single files in a directory, with one named private-ca.crt!
#- /etc/egroupware-docker/private-ca.crt:/usr/local/share/ca-certificates/private-ca.crt:ro
environment:
# MariaDB/MySQL host to use: for internal service use "db", for host database (socket bind-mounted into container) use "localhost"
- EGW_DB_HOST=db
# grant host is needed for NOT using localhost / unix domain socket for MySQL/MariaDB
- EGW_DB_GRANT_HOST=172.%
# for internal db service you should to specify a root password here AND in db service
# a database "egroupware" with a random password is created for you on installation (password is stored in header.inc.php in data directory)
#- EGW_DB_ROOT=root
- EGW_DB_ROOT_PW=secret
# alternativly you can specify an already existing database with full right by the given user!
#- EGW_DB_NAME=egroupware
#- EGW_DB_USER=egroupware
#- EGW_DB_PASS=
# further post_install.php arguments can be passed as a single enviroment variable with space separated assignments
# "<name1>=<value1> <name2>=<value2>" see https://github.com/EGroupware/egroupware/blob/master/doc/rpm-build/post_install.php#L17
# to configure eg. LDAP for authentication and account storage use
#- EGW_POST_INSTALL='account-auth=ldap,ldap ldap_base=ou=egroupware,dc=example,dc=org ldap_host=tls://ldap.example.org ldap_admin=cn=admin,$base ldap_admin_pw=secret ldap_context=cn=users,$base ldap_group_context=cn=groups,$base'
#
# MariaDB/MySQL host to use: for internal service use "db", for host database (socket bind-mounted into container) use "localhost"
- EGW_DB_HOST=db
# grant host is needed for NOT using localhost / unix domain socket for MySQL/MariaDB
- EGW_DB_GRANT_HOST=172.%
# for internal db service you should to specify a root password here AND in db service
# a database "egroupware" with a random password is created for you on installation (password is stored in header.inc.php in data directory)
#- EGW_DB_ROOT=root
- EGW_DB_ROOT_PW=secret
# alternativly you can specify an already existing database with full right by the given user!
#- EGW_DB_NAME=egroupware
#- EGW_DB_USER=egroupware
#- EGW_DB_PASS=
#
# further post_install.php arguments can be passed as a single enviroment variable with space separated assignments
# "<name1>=<value1> <name2>=<value2>" see https://github.com/EGroupware/egroupware/blob/master/doc/rpm-build/post_install.php#L17
# to configure eg. LDAP for authentication and account storage use
#- EGW_POST_INSTALL='account-auth=ldap,ldap ldap_base=ou=egroupware,dc=example,dc=org ldap_host=tls://ldap.example.org ldap_admin=cn=admin,$base ldap_admin_pw=secret ldap_context=cn=users,$base ldap_group_context=cn=groups,$base'
#
# extra non-default apps (need to start with EGW_EXTRA_APP!)
#
# EPL apps (need extra credentials!)
#- EGW_EXTRA_APPS_EPL=https://github.com/EGroupwareGmbH/epl.git https://github.com/EGroupwareGmbH/esyncpro.git https://github.com/EGroupwareGmbH/policy.git https://github.com/EGroupwareGmbH/webauthn.git
# old Wiki
#- EGW_EXTRA_APP_WIKI=https://github.com/EGroupware/wiki.git
# old API and eTemplate(1), required for upgrades from before 14.3
#- EGW_EXTRA_APP_OLDAPI=https://github.com/EGroupware/phpgwapi.git https://github.com/EGroupware/etemplate.git
#
# XDEBUG_REMOTE_HOST need to be set, if the host running the IDE is different from 172.17.0.1 (Mac can use docker.for.mac.localhost)
- XDEBUG_REMOTE_HOST=172.17.0.1
restart: always
depends_on:
- db
- db
container_name: egroupware
# set the ip-address of your docker host AND your official DNS name so EGroupware
# can access Rocket.Chat or Collabora without the need to go over your firewall
#extra_hosts:
#- "my.host.name:ip-address"
# push server using phpswoole
push:
image: phpswoole/swoole:php8.1-alpine
command:
- /var/www/server.php
environment:
EGW_MAX_PUSH_USERS: 1024
volumes:
- sources-push:/var/www
- sessions:/var/lib/php/sessions
- push-config:/var/lib/egroupware-push
container_name: egroupware-push
restart: always
# as we get our sources from there
depends_on:
- egroupware
extra_hosts:
- "devbox.egroupware.org:172.17.0.1"
nginx:
image: nginx:stable-alpine
volumes:
- sources:/usr/share/egroupware:ro
# to add a certificate create a certificate.pem containing (in that order)
# 1. private key
# 2. public key
# 3. (optional) chain certificates
# uncomment to the next line
# ./certificate.pem:/etc/ssl/private/certificate.pem
# AND uncomment the three lines starting with "listen 443", "ssl_certificate", "ssl_certificate_key" in nginx.conf
- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
- $PWD/sources:/var/www:ro
# to add a certificate create a certificate.pem containing (in that order)
# 1. private key
# 2. public key
# 3. (optional) chain certificates
# uncomment to the next line
# ./certificate.pem:/etc/ssl/private/certificate.pem
# AND uncomment the three lines starting with "listen 443", "ssl_certificate", "ssl_certificate_key" in nginx.conf
- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
ports:
# if no webserver is running on the host, change (first) number to 80 or 443
- "8080:80"
- "4443:443"
# if no webserver is running on the host, change (first) number to 80 and 443
- "8080:80"
- "4443:443"
depends_on:
- egroupware
- collabora-key
- rocketchat
- egroupware
- push
- collabora-key
- rocketchat
container_name: egroupware-nginx
restart: always
# run an own MariaDB:10.4 (you can use EGroupware's database backup and restore to add your existing database)
# run an own MariaDB:10.6 (you can use EGroupware's database backup and restore to add your existing database)
db:
image: mariadb:10.6
environment:
#- MYSQL_ROOT=root
- MYSQL_ROOT_PASSWORD=secret
- MARIADB_AUTO_UPGRADE=true
#- MYSQL_ROOT=root
- MYSQL_ROOT_PASSWORD=secret
#- MARIADB_AUTO_UPGRADE=true
volumes:
- db:/var/lib/mysql
- db:/var/lib/mysql
# to add an own persistent configuration
#- ./mariadb.cnf:/etc/mysql/mariadb.conf.d/egroupware.cnf
container_name: egroupware-db
restart: always
# make mysql also available on the host
#ports:
#- "3306:3306"
# push server using phpswoole
push:
image: phpswoole/swoole:latest-alpine
command:
- /var/www/server.php
volumes:
- $PWD/sources/egroupware/swoolepush:/var/www
- $PWD/sessions:/var/lib/php/sessions
- $PWD/push-config:/var/lib/egroupware-push
container_name: egroupware-push
restart: always
depends_on:
- egroupware
# automatic updates of all containers daily at 4am
# see https://containrrr.github.io/watchtower for more information
watchtower:
image: containrrr/watchtower
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- /var/run/docker.sock:/var/run/docker.sock
# For automatic EPL Updates (not necessary for CE!) you need to pass docker
# credentials into watchtower after running: docker login download.egroupware.org
#- /root/.docker/config.json:/config.json:ro
environment:
- WATCHTOWER_CLEANUP=true # delete old image after update to not fill up the disk
- WATCHTOWER_CLEANUP=true # delete old image after update to not fill up the disk
# for email notifications add your email and mail-server here
#- WATCHTOWER_NOTIFICATIONS=email
#- WATCHTOWER_NOTIFICATIONS_LEVEL=info # possible values: panic, fatal, error, warn, info or debug
#- WATCHTOWER_NOTIFICATION_EMAIL_FROM=watchtower@my-domain.com
#- WATCHTOWER_NOTIFICATION_EMAIL_TO=me@my-domain.com"
#- WATCHTOWER_NOTIFICATION_EMAIL_SERVER=mail.my-domain.com # if you give your MX here, you need no user/password
#- WATCHTOWER_NOTIFICATION_EMAIL_FROM="watchtower@my-domain.com"
#- WATCHTOWER_NOTIFICATION_EMAIL_TO="me@my-domain.com"
#- WATCHTOWER_NOTIFICATION_EMAIL_SERVER="mail.my-domain.com" # if you give your MX here, you need no user/password
#- WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PORT=25
#- WATCHTOWER_NOTIFICATION_EMAIL_SERVER_USER=watchtower@my-domain.com
#- WATCHTOWER_NOTIFICATION_EMAIL_SERVER_USER="watchtower@my-domain.com"
#- WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PASSWORD="secret"
command: --schedule "0 0 4 * * *"
container_name: egroupware-watchtower
@ -188,22 +206,26 @@ services:
image: "quay.io/egroupware/collabora-key:stable"
#image: collabora/code:latest
volumes:
- collabora-config:/etc/coolwsd
#- collabora-config:/etc/loolwsd
# support for Collabora/CODE 21.11+
- $PWD/data/default/loolwsd:/etc/coolwsd
restart: always
container_name: collabora-key
# set the ip-address of your docker host AND your official DNS name so Collabora
# can access EGroupware without the need to go over your firewall
#extra_hosts:
#- "my.host.name:ip-address"
extra_hosts:
- "devbox.egroupware.org:172.17.0.1"
depends_on:
- collabora-init
# initialise the collabora-config volume
collabora-init:
image: "quay.io/egroupware/collabora-key:latest"
command: bash -c 'test -f /tmp/coolwsd/coolwsd.xml || (cp -p /etc/coolwsd/* /tmp/coolwsd/; sed "s/<enable type=\"bool\" desc=\"Controls whether SSL encryption between coolwsd and the network is enabled (do not disable for production deployment). If default is false, must first be compiled with SSL support to enable.\" default=\"true\">true</<enable type=\"bool\" desc=\"Controls whether SSL encryption between coolwsd and the network is enabled (do not disable for production deployment). If default is false, must first be compiled with SSL support to enable.\" default=\"true\">false</g" < /etc/coolwsd/coolwsd.xml > /tmp/coolwsd/coolwsd.xml)'
command: bash -c "test -f /tmp/coolwsd/coolwsd.xml || (cp -p /etc/coolwsd/* /tmp/coolwsd && cd /tmp/coolwsd && ln -s coolwsd.conf loolwsd.conf)"
volumes:
- collabora-config:/tmp/coolwsd
- $PWD/data/default/loolwsd:/tmp/coolwsd
# Rocket.Chat server
rocketchat:
@ -211,7 +233,7 @@ services:
command: bash -c 'for i in `seq 1 30`; do node main.js && s=$$? && break || s=$$?; echo "Tried $$i times. Waiting 5 secs..."; sleep 5; done; (exit $$s)'
restart: unless-stopped
volumes:
- rocketchat-uploads:/app/uploads
- $PWD/data/default/rocketchat/uploads:/app/uploads
# if EGroupware uses a certificate from a private CA, OAuth authentication will fail, you need to:
# - have the CA certificate stored at /etc/egroupware-docker/private-ca.crt
# - uncomment the next 2 lines about the private CA:
@ -232,24 +254,41 @@ services:
# can access EGroupware without the need to go over your firewall
#extra_hosts:
#- "my.host.name:ip-address"
extra_hosts:
- "devbox.egroupware.org:172.17.0.1"
# MongoDB for Rocket.Chat
mongo:
image: mongo:4.0
image: mongo:5.0
restart: unless-stopped
volumes:
- mongo:/data/db
- rocketchat-dumps:/dump
command: mongod --smallfiles --oplogSize 128 --replSet rs0 --storageEngine=mmapv1
- $PWD/data/default/rocketchat/dump:/dump
command: mongod --oplogSize 128 --replSet rs0 -
container_name: rocketchat-mongo
# this container's job is just run the command to initialize the replica set.
# it will run the command and remove himself (it will not stay running)
mongo-init-replica:
image: mongo:4.0
image: mongo:5.0
command: 'bash -c "for i in `seq 1 30`; do mongo mongo/rocketchat --eval \"rs.initiate({ _id: ''rs0'', members: [ { _id: 0, host: ''localhost:27017'' } ]})\" && s=$$? && break || s=$$?; echo \"Tried $$i times. Waiting 5 secs...\"; sleep 5; done; (exit $$s)"'
depends_on:
- mongo
# phpMyAdmin
phpmyadmin:
restart: unless-stopped
image: phpmyadmin
container_name: phpmyadmin
hostname: phpmyadmin
# pre 20.1 installs run MariaDB on the host and need to pass the socket (to use egroupware user and it's password only valid on localhost)
#volumes:
# - /var/run/mysqld/mysqld.sock:/tmp/mysql.sock
environment:
# PMA_HOST: use localhost, if you use a socket (pre 20.1 install) or db for 20.1+ installations
- PMA_HOST=db
# phpMyAdmin needs the full URL incl. protocol, domain, path and a trailing slash!
- PMA_ABSOLUTE_URI=http://localhost/phpmyadmin/
# Portainer: Docker GUI (needs to be enabled in nginx.conf too!)
# portainer:
# image: portainer/portainer
@ -261,4 +300,4 @@ services:
# volumes:
# - /var/run/docker.sock:/var/run/docker.sock
# - portainer_data:/data
# container_name: portainer
# container_name: portainer

View File

@ -21,7 +21,7 @@ import {egw} from "../../api/js/jsapi/egw_global";
import {et2_selectbox} from "../../api/js/etemplate/et2_widget_selectbox";
import {et2_textbox} from "../../api/js/etemplate/et2_widget_textbox";
import {MIME_REGEX} from "../../api/js/etemplate/Expose/ExposeMixin";
import {egwAction} from "../../api/js/egw_action/egw_action.js";
import {egwAction} from "../../api/js/egw_action/egw_action";
/**
* UI for filemanager

View File

@ -15,14 +15,14 @@ import {AppJS} from "../../api/js/jsapi/app_base.js";
import {et2_createWidget} from "../../api/js/etemplate/et2_core_widget";
import {et2_dialog} from "../../api/js/etemplate/et2_widget_dialog";
import {et2_button} from "../../api/js/etemplate/et2_widget_button";
import {egw_getObjectManager} from '../../api/js/egw_action/egw_action.js';
import {egwIsMobile, egwSetBit} from "../../api/js/egw_action/egw_action_common.js";
import {EGW_AO_FLAG_DEFAULT_FOCUS} from "../../api/js/egw_action/egw_action_constants.js";
import {egw_getObjectManager} from '../../api/js/egw_action/egw_action';
import {egwIsMobile, egwSetBit} from "../../api/js/egw_action/egw_action_common";
import {EGW_AO_FLAG_DEFAULT_FOCUS} from "../../api/js/egw_action/egw_action_constants";
import {
egw_keycode_translation_function,
egw_keycode_makeValid,
egw_keyHandler
} from "../../api/js/egw_action/egw_keymanager.js";
} from "../../api/js/egw_action/egw_keymanager";
import {Et2UrlEmailReadonly} from "../../api/js/etemplate/Et2Url/Et2UrlEmailReadonly";
import {Et2SelectEmail} from "../../api/js/etemplate/Et2Select/Et2SelectEmail";
/* required dependency, commented out because no module, but egw:uses is no longer parsed

10
package-lock.json generated
View File

@ -7019,7 +7019,7 @@
"version": "2.79.1",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.1.tgz",
"integrity": "sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==",
"dev": true,
"devOptional": true,
"bin": {
"rollup": "dist/bin/rollup"
},
@ -9217,7 +9217,8 @@
"version": "1.10.11",
"resolved": "https://registry.npmjs.org/@interactjs/core/-/core-1.10.11.tgz",
"integrity": "sha512-aJ50ccVeszpJt7wPH7Yfqm7f1aG1SA94qd90P0NaESh5/QUXn4CESO6igobo4DFHQ5z+1Rfdl8aphP4JxlH4gw==",
"dev": true
"dev": true,
"requires": {}
},
"@interactjs/dev-tools": {
"version": "1.10.11",
@ -13289,7 +13290,7 @@
"version": "2.79.1",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.1.tgz",
"integrity": "sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==",
"dev": true,
"devOptional": true,
"requires": {
"fsevents": "~2.3.2"
}
@ -13936,7 +13937,8 @@
"version": "7.5.3",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.5.3.tgz",
"integrity": "sha512-kQ/dHIzuLrS6Je9+uv81ueZomEwH0qVYstcAQ4/Z93K8zeko9gtAbttJWzoC5ukqXY1PpoouV3+VSOqEAFt5wg==",
"dev": true
"dev": true,
"requires": {}
},
"yallist": {
"version": "4.0.0",