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
@ -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
0
api/js/egw_action/Class Diagram.png
Normal file → Executable 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
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
720
api/js/egw_action/EgwAction.ts
Normal 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;
|
||||
}
|
47
api/js/egw_action/EgwActionImplementation.ts
Normal 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;
|
||||
}
|
52
api/js/egw_action/EgwActionLink.ts
Normal 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!";
|
||||
};
|
||||
}
|
27
api/js/egw_action/EgwActionManager.ts
Normal 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;
|
||||
}
|
||||
}
|
1130
api/js/egw_action/EgwActionObject.ts
Normal file
85
api/js/egw_action/EgwActionObjectInterface.ts
Normal 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;
|
||||
}
|
32
api/js/egw_action/EgwActionObjectManager.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
39
api/js/egw_action/EgwDragAction.ts
Normal 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;
|
||||
}
|
||||
}
|
67
api/js/egw_action/EgwDropAction.ts
Normal 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];
|
||||
}
|
||||
};
|
||||
}
|
302
api/js/egw_action/EgwDropActionImplementation.ts
Normal 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 {
|
||||
}
|
120
api/js/egw_action/EgwPopupAction.ts
Normal 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 {
|
||||
}
|
744
api/js/egw_action/EgwPopupActionImplementation.ts
Normal 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;
|
||||
}
|
291
api/js/egw_action/egwDragActionImplementation.ts
Normal 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
|
||||
}
|
41
api/js/egw_action/egwGlobal.ts
Normal 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
|
||||
}
|
||||
}
|
884
api/js/egw_action/egw_action.d.ts
vendored
@ -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;
|
410
api/js/egw_action/egw_action.ts
Executable 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;
|
||||
}
|
||||
|
@ -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('');
|
||||
}
|
487
api/js/egw_action/egw_action_common.ts
Executable 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('');
|
||||
}
|
33
api/js/egw_action/egw_action_constants.js → api/js/egw_action/egw_action_constants.ts
Normal file → Executable 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]
|
||||
|
@ -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;
|
||||
}
|
@ -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;
|
||||
}
|
12
api/js/egw_action/egw_action_popup.ts
Normal 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
|
@ -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;
|
||||
}
|
||||
|
82
api/js/egw_action/egw_dragdrop_dhtmlx_tree.ts
Executable 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;
|
||||
}
|
||||
}
|
||||
|
167
api/js/egw_action/egw_keymanager.js → api/js/egw_action/egw_keymanager.ts
Normal file → Executable 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;
|
@ -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
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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();
|
||||
};
|
195
api/js/egw_action/egw_menu_dhtmlx.ts
Normal 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();
|
||||
};
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
52
api/js/egw_action/egw_stylesheet.ts
Executable 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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
0
api/js/egw_action/test/js/dhtmlxmenu.js
Normal file → Executable file
0
api/js/egw_action/test/js/dhtmlxmenu_ext.js
Normal file → Executable file
0
api/js/egw_action/test/js/jquery-ui.js
vendored
Normal file → Executable file
0
api/js/egw_action/test/js/jquery.js
vendored
Normal file → Executable file
0
api/js/egw_action/test/skins/dhtmlxmenu_egw.css
Normal file → Executable file
761
api/js/egw_action/test/test_action.html
Normal file → Executable 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="["api\/js\/dhtmlxtree\/codebase\/dhtmlxcommon.js","api\/js\/dhtmlxMenu\/sources\/dhtmlxmenu.js","api\/js\/dhtmlxMenu\/sources\/ext\/dhtmlxmenu_ext.js","api\/js\/dhtmlxtree\/sources\/dhtmlxtree.js","api\/js\/dhtmlxtree\/sources\/ext\/dhtmlxtree_json.js"]"></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
@ -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
@ -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>
|
||||
|
@ -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";
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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";
|
||||
|
||||
/**
|
||||
|
@ -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";
|
||||
|
||||
/**
|
||||
|
@ -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";
|
||||
|
@ -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
|
||||
|
@ -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';
|
||||
|
@ -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';
|
||||
/**
|
||||
|
@ -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";
|
||||
|
@ -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';
|
||||
|
@ -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";
|
||||
|
@ -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';
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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";
|
||||
|
||||
/**
|
||||
|
@ -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
|
||||
|
16
api/js/jsapi/egw_global.d.ts
vendored
@ -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.
|
||||
|
@ -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
@ -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();
|
||||
|
@ -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";
|
||||
|
@ -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";
|
||||
|
@ -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";
|
||||
|
@ -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";
|
||||
|
@ -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";
|
||||
|
@ -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";
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
@ -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",
|
||||
|