From e2c9cd49cca4a2357a471c226998a0c0fbf81277 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20St=C3=B6ckel?= Date: Wed, 23 Feb 2011 18:24:16 +0000 Subject: [PATCH] Initial commit of the current JS implementation of the egw_action system, which will later be used to create context sensitive menus and drag and drop capabilities. The egw_menu.js/egw_menu_dhtml.js files are fully functional (see test in the test dir), implementation of egw_action.js has just started and not even a single line of that has been tested yet. --- phpgwapi/js/egw_action/egw_action.js | 474 ++++ phpgwapi/js/egw_action/egw_action_common.js | 39 + phpgwapi/js/egw_action/egw_menu.js | 377 +++ phpgwapi/js/egw_action/egw_menu_dhtmlx.js | 147 ++ .../js/egw_action/test/imgs/dhxmenu_chrd.gif | Bin 0 -> 2509 bytes .../egw_action/test/imgs/dhxmenu_loader.gif | Bin 0 -> 1683 bytes .../js/egw_action/test/imgs/dhxmenu_subar.gif | Bin 0 -> 1188 bytes .../egw_action/test/imgs/dhxmenu_subselbg.gif | Bin 0 -> 52 bytes .../egw_action/test/imgs/dhxmenu_subselbg.png | Bin 0 -> 433 bytes .../egw_action/test/imgs/dhxmenu_subsepbg.gif | Bin 0 -> 1094 bytes .../js/egw_action/test/imgs/dhxmenu_topbg.gif | Bin 0 -> 66 bytes .../egw_action/test/imgs/dhxmenu_topselbg.gif | Bin 0 -> 45 bytes .../egw_action/test/imgs/dhxmenu_topsepbg.gif | Bin 0 -> 68 bytes .../js/egw_action/test/imgs/dhxmenu_upar.gif | Bin 0 -> 1930 bytes phpgwapi/js/egw_action/test/imgs/disk.png | Bin 0 -> 620 bytes phpgwapi/js/egw_action/test/imgs/folder.png | Bin 0 -> 537 bytes phpgwapi/js/egw_action/test/imgs/page.png | Bin 0 -> 635 bytes phpgwapi/js/egw_action/test/imgs/wrench.png | Bin 0 -> 610 bytes .../js/egw_action/test/js/dhtmlxcommon.js | 928 +++++++ phpgwapi/js/egw_action/test/js/dhtmlxmenu.js | 2190 +++++++++++++++++ .../js/egw_action/test/js/dhtmlxmenu_ext.js | 18 + .../egw_action/test/skins/dhtmlxmenu_egw.css | 360 +++ phpgwapi/js/egw_action/test/test_menu.html | 187 ++ 23 files changed, 4720 insertions(+) create mode 100644 phpgwapi/js/egw_action/egw_action.js create mode 100644 phpgwapi/js/egw_action/egw_action_common.js create mode 100644 phpgwapi/js/egw_action/egw_menu.js create mode 100644 phpgwapi/js/egw_action/egw_menu_dhtmlx.js create mode 100644 phpgwapi/js/egw_action/test/imgs/dhxmenu_chrd.gif create mode 100644 phpgwapi/js/egw_action/test/imgs/dhxmenu_loader.gif create mode 100644 phpgwapi/js/egw_action/test/imgs/dhxmenu_subar.gif create mode 100644 phpgwapi/js/egw_action/test/imgs/dhxmenu_subselbg.gif create mode 100644 phpgwapi/js/egw_action/test/imgs/dhxmenu_subselbg.png create mode 100644 phpgwapi/js/egw_action/test/imgs/dhxmenu_subsepbg.gif create mode 100644 phpgwapi/js/egw_action/test/imgs/dhxmenu_topbg.gif create mode 100644 phpgwapi/js/egw_action/test/imgs/dhxmenu_topselbg.gif create mode 100644 phpgwapi/js/egw_action/test/imgs/dhxmenu_topsepbg.gif create mode 100644 phpgwapi/js/egw_action/test/imgs/dhxmenu_upar.gif create mode 100755 phpgwapi/js/egw_action/test/imgs/disk.png create mode 100755 phpgwapi/js/egw_action/test/imgs/folder.png create mode 100755 phpgwapi/js/egw_action/test/imgs/page.png create mode 100755 phpgwapi/js/egw_action/test/imgs/wrench.png create mode 100644 phpgwapi/js/egw_action/test/js/dhtmlxcommon.js create mode 100644 phpgwapi/js/egw_action/test/js/dhtmlxmenu.js create mode 100644 phpgwapi/js/egw_action/test/js/dhtmlxmenu_ext.js create mode 100644 phpgwapi/js/egw_action/test/skins/dhtmlxmenu_egw.css create mode 100644 phpgwapi/js/egw_action/test/test_menu.html diff --git a/phpgwapi/js/egw_action/egw_action.js b/phpgwapi/js/egw_action/egw_action.js new file mode 100644 index 0000000000..8653efe7fa --- /dev/null +++ b/phpgwapi/js/egw_action/egw_action.js @@ -0,0 +1,474 @@ +/** + * eGroupWare egw_action framework - egw action framework + * + * @link http://www.egroupware.org + * @author Andreas Stöckel + * @copyright 2011 by Andreas Stöckel + * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License + * @package egw_action + * @version $Id:$ + */ + +// XXX WARNING: UNTESTED, UNFINISHED, NOT (YET) WORKING CODE! XXX + +/** egwActionHandler Interface **/ + +/** + * Constructor for the egwActionHandler interface which (at least) should have the + * execute function implemented. + */ +function egwActionHandler(_executeEvent) +{ + //Copy the executeEvent parameter + this.execute = _executeEvent; +} + + +/** egwAction Object **/ + +/** + * Associative array where action classes may register themselves + */ +var _egwActionClasses = + { + "default": + { + "actionConstructor": egwAction, + "implementationConstructor": null + } + } + +function egwAction(_id, _handler, _caption, _icon, _onExecute, _allowOnMultiple) +{ + //Default and check the values + if (typeof _id != "string" || !_id) + throw "egwAction _id must be a non-empty string!"; + if (typeof _handler == "undefined") + this.handler = null; + if (typeof _label == "undefined") + _label = ""; + if (typeof _icon == "undefined") + _icon = ""; + if (typeof _onExecute == "undefined") + _onExecute = null; + if (typeof _allowOnMultiple == "undefined") + _allowOnMultiple = true; + + this.id = _id; + this.caption = _caption; + this.icon = _icon; + this.allowOnMultiple = _allowOnMultiple; + this.type = "default"; //All derived classes have to override this! + + this.execJSFnct = null; + this.execHandler = false; + this.set_onExecute(_onExecute); +} + +/** + * 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 + * //TODO: With DragDrop we don't only have senders but also one(!) target. + */ +egwAction.prototype.execute = function(_senders) +{ + if (this.execJSFnct && typeof this.execJSFnct == "function") + { + this.execJSFnct(this, _senders); + } + else if (this.execHandler) + { + this.handler.execute(this, _senders); + } +} + +/** + * 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 functino 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 + */ +egwAction.prototype.set_onExecute(_value) +{ + //Reset the onExecute handlers + this.execJSFnct = null; + this.execHandler = false; + + if (typeof _value == "string") + { + // Check whether the given string contains a javaScript function which + // should be called upon executing the action + if (_value.substr(0, 11) == "javaScript:") + { + //Check whether the given function exists + var fnct = _value.substr(11); + if (typeof window[fnct] == "function") + { + this.execJSFnct = window[fnct]; + } + } + } + else if (typeof _value == "boolean") + { + // There is no direct reference to the PHP code which should be executed, + // as the PHP code has knowledge about this. + this.execHandler = _value; + } + else if (typeof _value == "function") + { + //The JS function has been passed directly + this.execJSFnct = _value; + } +} + +egwAction.prototype.set_caption(_value) +{ + this.caption = _value; +} + +egwAction.prototype.set_icon(_value) +{ + this.icon = _value; +} + +egwAction.prototype.set_allowOnMultiple(_value) +{ + this.allowOnMultiple = _value; +} + + +/** egwActionManager Object **/ + +/** + * egwActionManager manages a list of actions, provides functions to add new + * actions or to update them via JSON. + */ +function egwActionManager(_handler) +{ + //Preset the handler parameter to null + if (typeof _handler == "undefined") + _handler = null; + + this.handler = _handler; + this.actions = []; +} + +egwActionManager.prototype.addAction = function(_type, _id, _caption, _icon, + _onExecute, _allowOnMultiple) +{ + //Get the constructor for the given action type + if (!_type) + _type = "default"; + var constructor = _egwActionClasses[_type].actionConstructor; + + if (typeof constructor == "function") + { + var action = new constructor(_id, this.handler, _caption, _icon, _onExecute, + _allowOnMultiple); + this.actions.push[action]; + + return action; + } + else + throw "Given action type not registered."; +} + +egwActionManager.prototype.updateActions = function(_actions) +{ + for (var i = 0 ; i < _actions.length; i++) + { + //Check whether the given action is already part of this action manager instance + var elem = _actions[i]; + if (typeof elem == "object" && typeof elem.id == "string" && elem.id) + { + //Check whether the action already exists, and if no, add it to the + //actions list + var action = this.getAction(elem.id); + if (!action) + { + if (typeof elem.type == "undefined") + elem.type = "default"; + + var constructor = _egwActionClasses[elem.type].actionConstructor; + + if (typeof constructor == "function") + action = new constructor(elem.id, this.handler); + else + throw "Given action type not registered."; + + this.actions.push(action); + } + + //Update the actions by calling the corresponding setter functions + //TODO: Hirachical actions will need a reference to their parent - + // this parent is has to be translated to a js object + //TODO: Maby the setter, JSON, update stuff should somehow be moved + // to a own base class. + egwActionStoreJSON(elem, action, true); + } + } +} + +egwActionManager.prototype.getAction = function(_id) +{ + for (var i = 0; i < this.actions.length; i++) + { + if (this.actions[i].id == _id) + return this.actions[i]; + } + + return null; +} + + +/** 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. + * + * @param object _action is the parent egwAction object for that instance. + */ +function egwActionImplementation(_action) +{ + this.action = _action; + + this.doRegisterAction = null; + this.doUnregisterAction = null; +} + +/** + * Injects the implementation code into the DOM tree by using the supplied + * actionObjectInterface. + * + * @returns true if the Action had been successfully registered, false if it + * had not. + */ +egwActionImplementation.registerAction = function(_actionObjectInterface) +{ + if (this.doRegisterAction == null) + { + throw "Abstract function call: registerAction"; + } + else + { + return this.doRegisterAction(_action, _actionObjectInterface); + } +} + +/** + * Unregister action will be called before an actionObjectInterface is destroyed, + * which gives the egwActionImplementation the opportunity to remove the previously + * injected code. + * + * @returns true if the Action had been successfully unregistered, false if it + * had not. + */ +egwActionImplementation.unregisterAction = function(_actionObjectInterface) +{ + if (this.doUnregisterAction == null) + { + throw "Abstract function call: unregisterAction"; + } + else + { + return this.doUnregisterAction(_action, _actionObjectInterface); + } +} + + +/** 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. + */ +function egwActionLink(_manager) +{ + this.enabled = true; + this.actionId = ""; + this.actionObj = null; + this.manager = _manager; +} + +egwActionLink.prototype.updateLink = function (_data) +{ + egwActionStoreJSON(_data, this, true); +} + +egwActionLink.prototype.set_enabled = function(_value) +{ + this.enabled = _value; +} + +egwActionLink.prototype.set_actionId = function(_value) +{ + this.actionId = _value; + this.actionObj = this.manager.getAction(_value); + + if (!this.actionObj) + throw "Given action object does not exist!" +} + + +/** egwActionObject Object **/ + +/** + * 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 object _parent is the parent object in the hirachy. This may be set to NULL + * @param object _manager is the action manager this object is connected to + * @param object _interaction is the egwActionObjectInterface which connects + * this object to the DOM tree. + */ +function egwActionObject(_id, _parent, _manager, _interaction) +{ + this.id = _id; + this.parent = _parent; + this.interaction = _interaction; + this.children = []; + this.actionLinks = []; + this.manager = _manager; +} + +/** + * Updates the actionLinks of the given ActionObject. + * + * @param array _actionLinks contains the information about the actionLinks which + * should be updated as an array of objects. Example + * [ + * { + * "actionId": "file_delete", + * "enabled": true + * } + * ] + * If an supplied link doesn't exist yet, it will be created (if _doCreate is true) + * and added to the list. Otherwise the information will just be updated. + * @param boolean _recursive If true, the settings will be applied to all child + * object (default false) + * @param boolean _doCreate If true, not yet existing links will be created (default true) + */ +egwActionObject.prototype.updateActionLinks = function(_actionLinks, _recursive, _doCreate) +{ + if (typeof _recursive == "undefined") + _recursive = false; + if (typeof _doCreate == "undefined") + _doCreate = true; + + for (var i = 0; i < _actionLinks.length; i++) + { + var elem = _actionLinks[i]; + if (typeof elem.actionId != "undefined" && elem.actionId) + { + //Get the action link object, if it doesn't exists yet, create it + var actionLink = this.getActionLink(elem.actionId); + if (!actionLink && _doCreate) + { + actionLink = new egwActionLink(this.manager); + } + + //Set the supplied data + if (actionLink) + { + actionLink.updateLink(elem); + } + } + } + + if (_recursive) + { + for (var i = 0; i < this.children.length; i++) + { + this.children[i].updateActionLinks(_actionLinks, true, _doCreate); + } + } +} + +/** + * Returns the action link, which contains the association to the action with + * the given actionId. + * + * @param string _actionId name of the action associated to the link + */ +egwActionObject.prototype.getActionLink = function(_actionId) +{ + for (var i = 0; i < this.actionLinks.length; i++) + { + if (this.actionLinks[i].actionObj.id == _actionId) + { + return this.actionLinks[i]; + } + } + + return null; +} + +/** + * Returns all actions associated to the object tree, grouped by type. + * + * @param function _test gets an egwActionObject and should return, whether the + * actions of this object are added to the result. Defaults to a "always true" + * function. + * @param object _groups is an internally used parameter, may be omitted. + */ +egwActionObject.prototype.getActionImplementationGroups = function(_test, _groups) +{ + // If the _groups parameter hasn't been given preset it to an empty object + // (associative array). + if (typeof _groups == "undefined") + _groups = {}; + if (typeof _test == "undefined") + _test = function(_obj) {return true}; + + for (var i = 0; i < this.actionLinks.length; i++) + { + var action = this.actionsLink[i].actionObj; + if (typeof action != "undefined" && _test(this)) + { + if (typeof _groups[action.type] == "undefined") + { + _groups[action.type] = []; + } + + _groups[action.type].push( + { + "object": this, + "link": this.actionLinks[i] + } + ); + } + } + + // Recursively add the actions of the children to the result (as _groups is) + // a object, only the reference is passed. + for (var i = 0; i < this.children.length; i++) + { + this.children[i].getActionImplementationGroups(_test, _groups); + } + + return _groups; +} + diff --git a/phpgwapi/js/egw_action/egw_action_common.js b/phpgwapi/js/egw_action/egw_action_common.js new file mode 100644 index 0000000000..4fe02a2e61 --- /dev/null +++ b/phpgwapi/js/egw_action/egw_action_common.js @@ -0,0 +1,39 @@ +/** + * eGroupWare egw_action framework - egw action framework + * + * @link http://www.egroupware.org + * @author Andreas Stöckel + * @copyright 2011 by Andreas Stöckel + * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License + * @package egw_action + * @version $Id:$ + */ + +/** + * Sets properties given in _data in _obj. Checks whether the property keys + * exists and if corresponding setter functions are available. Properties starting + * with "_" are ignored. + * + * @param object _data may be an object with data that will be stored inside the + * given object. + * @param object _obj is the object where the data will be stored. + */ +function egwActionStoreJSON(_data, _obj, _setterOnly) +{ + for (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]; + } + } + } +} + diff --git a/phpgwapi/js/egw_action/egw_menu.js b/phpgwapi/js/egw_action/egw_menu.js new file mode 100644 index 0000000000..55535589f3 --- /dev/null +++ b/phpgwapi/js/egw_action/egw_menu.js @@ -0,0 +1,377 @@ +/** + * eGroupWare egw_action framework - JS Menu abstraction + * + * @link http://www.egroupware.org + * @author Andreas Stöckel + * @copyright 2011 by Andreas Stöckel + * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License + * @package egw_action + * @version $Id:$ + */ + +//Global variable which is used to store the currently active menu so that it +//may be closed when another menu openes +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, _label, _iconUrl, _onClick) +{ + //Preset the parameters + if (typeof _parent == "undefined") + _parent = null; + if (typeof _id == "undefined") + _id = ""; + if (typeof _label == "undefined") + _label = ""; + 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(_label); + 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 (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") + { + 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. + */ +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; +} + +/** + * 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 _label is the label of the newly generated menu item. Set the label + * 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, _label, _iconUrl, _onClick) +{ + //Append the item to the list + var item = _egwGenMenuItem(this, _id, _label, _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. + */ +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.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); +} + +//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; +} + + diff --git a/phpgwapi/js/egw_action/egw_menu_dhtmlx.js b/phpgwapi/js/egw_action/egw_menu_dhtmlx.js new file mode 100644 index 0000000000..c0bb6fece3 --- /dev/null +++ b/phpgwapi/js/egw_action/egw_menu_dhtmlx.js @@ -0,0 +1,147 @@ +/** + * eGroupWare egw_action framework - JS Menu abstraction + * + * @link http://www.egroupware.org + * @author Andreas Stöckel + * @copyright 2011 by Andreas Stöckel + * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License + * @package egw_action + * @version $Id:$ + */ + +function egwMenuImpl(_structure) +{ + //Create a new dhtmlxmenu object + this.dhtmlxmenu = new dhtmlXMenuObject(); + this.dhtmlxmenu.setSkin("egw"); + this.dhtmlxmenu.renderAsContextMenu(); + + 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) + { + elem.onClick(elem); + } + } + }); + + //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); +} + +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 id = 'elem_' + (_idCnt + counter); + var elem = _structure[i]; + + counter++; + + //Check whether this element is a seperator + if (elem.caption == '-' && last_id != null) + { + //Add the separator next to last_id with the id "id" + this.dhtmlxmenu.addNewSeparator(last_id, id); + } + else + { + if (elem.checkbox && elem.groupIndex === 0) + { + //Add checkbox + this.dhtmlxmenu.addCheckbox("child", _parentId, i, id, + elem.caption, elem.checked, !elem.enabled); + } + else if (elem.checkbox && elem.groupIndex > 0) + { + //Add radiobox + elem._dhtmlx_grpid = "grp_" + _idCnt + '_' + elem.groupIndex; + this.dhtmlxmenu.addRadioButton("child", _parentId, i, id, + elem.caption, elem._dhtmlx_grpid, elem.checked, !elem.enabled); + } + else + { + var caption = elem.caption; + if (elem.default) + caption = "" + caption + "" + this.dhtmlxmenu.addNewChild(_parentId, i, id, caption, !elem.enabled, + elem.iconUrl, elem.iconUrl); + } + + if (elem.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); + + 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(); + } + }); + } + this.dhtmlxmenu.showContextMenu(_x, _y); +} + +egwMenuImpl.prototype.hide = function() +{ + this.dhtmlxmenu.hide(); +} + + diff --git a/phpgwapi/js/egw_action/test/imgs/dhxmenu_chrd.gif b/phpgwapi/js/egw_action/test/imgs/dhxmenu_chrd.gif new file mode 100644 index 0000000000000000000000000000000000000000..704f564ce59db65cb28f61908d615939242c4658 GIT binary patch literal 2509 zcmb_cX+Tp~7EVY6WZz_Q(0Pfds6buPz z#1x1gWF#0O9vy9JYPxvw;_~wH+qZ95S6AP!y_d(x$VgIBQdLzI1OgF>L@O&RYpZMW zkS)oUmX;(E$+B#DczC$GyL)h8aC&-LCX>z0&dtxw3x&e!>gvgf$@%&DrNyP5o}RI> zv97MJg@uKgnVG)6zTV#6)svX@xc+`!AP`JUOk`wa3=9l-dwW+@RFsvKwYIhn4Gnd6b`B2>r>3SZE-V%l z6!7_csZ^SkmG%7j^OlyDy1Kgh`ug15+~J|2`}gmoP^h%Dw6?Z3>5McbB_%U6bL8Df zO-)TnNr{7lLwkF>cvL(-K0Y})DHe-orL+D0{j;;P{{H?T2-R#wKx$IFe$ zx8?s5h;?;;FK@t&$R^6)jt3$bL{cb)4FpnxsWd0#^s_1?KqWgNk7K<+F9v}ULiLDb zQjSI*@gqfsk{rm$1I~bB1TG?s5k_GXfrzj(G!`zx2}vZ=11UK9Sh0*k0v{mkP$wi_ z@c~feB+!`@0BZ$Wl0aJ!uti&;t!?b=>?{Bbhz3z028G60qU~_j7#s)!9|I}R#v}*f zeBInX=915xkRfa~1BXI|hlg8*TU*hY!6>wRLlqhrjHMi5$vQ`46C*5XtQ{K)ZWI=Y zNoBC9bQ+*gBnHyYvYn7}r~fV?jPXg9#hKe9EP-rVqQPKv`%jc-MgE6Cnx*1;MUC>{Oi}UGBbWkPrG_0 zHRW=0(xr=u3Gs2UF+6Vc1P;qK@>76kQnf{ zQ{R1i^29g($B%vOchuL%`>P{fhrj&7)8ml)K{tXc-sOO^ljDAzgT0+C*2Wqm|Dmib zE%xo*W4?RWPO}|I1n}4G+f4tm)x_AyaEpPyp019zmZpZf+GbT1XoZ$=|5#8t6jU!=ksm|uJhn`a_`>F zxp)6g=EH(u55p2eyg``(zQlu9{OIwMa>Hj_xKX{4UgL{;c#iJt=9bqlg|hnG&E2>0gA^?s5{Vo0quC|(~U?ng+`dQ)+j}5nWya`wA@zlylNiYz~4+3DY<@Ql_CM} z#Js*ma3qUNpagD?$K~Vzp+KiD4sgi?eX4n!(_3%p|NuFpkAzMePOLT#$Xd@4udJ(`q8V`IC z)si}RD6t@8ANh@_(>lZzTFXT zIoK&93KNu?)%nH}{0va9r{n=L1p6s)XH}nk7i^5fmQh{4v-Qofq;0Ov54`&#EtHZ~ z{neS80#8jNtWB}c`51fjNo5@kZw^e$0EECz?D6sX1|6j?CK}s~5D3o+xQL}~93X`K zicf*Z>4J5_O|~79@1VQ2kera6cQ`Ia_6>{+8us~iq#!$9G0ZS%qJ#8f;aQ&9Is|Jv zP_2v2Kc=d`n+oEg$V7i3d@G1oO(*$>#Aw^TYsP2rYKwkL*JihGOVF=P*`<6k&s?N- z7$M2zV}gQ)42_J7qgD1959Hk1ZTbrY<$madFe5*DmT_ zrT*=aZ8ivR)6D*8P3KIgf%h$5Kg2OleJ{^nJDV2=Bzg>P)-lI7=(!WmaWphb&FRoD z?dQ2_MwQDD%_yUny*=l&i^BU3KQI)Z_uTHh>4a~H1PV_fNG>S*9>zBeBAWUmhOdNz z+;@R03nI8CKVW&Y=3&W&w_J;s4iX|r#(`@KX7@+_vKq&x<&HrIOes|Mwc;KJt%@gcJ-lkFV+WSynn%E0%s(R+pOmnWWRdjgRC Fe*oZCKL!8* literal 0 HcmV?d00001 diff --git a/phpgwapi/js/egw_action/test/imgs/dhxmenu_loader.gif b/phpgwapi/js/egw_action/test/imgs/dhxmenu_loader.gif new file mode 100644 index 0000000000000000000000000000000000000000..9ef515f1e3e696287044739512d372b680994cf8 GIT binary patch literal 1683 zcmaJ>eOwcD7@vvY3!2t63$x?s#TMIcV`FYI6?dCZjRq_=Df8HF+coZGcQ=_3L?ILg zg+LSmMMM+@A`p@gF&u+{tf`r%wcg)fq_P)#vl}t&kG9|EbD#VDp8I~E@ALeg=bp`} zy0uCqiWkK@q2Sr=c89}JTwL7U-R*QbhlYllo14eS$A^c9D=I4b`uZj&CUSFgtE#Fx zIyx*C3y$OE<>g+lcW`hJ!?2o~n)deg($dn_*4Cz`rqR*S{QUf)qN2LGx}Kh%va+(u z%1W2Z+I}oYip~mttCm4VVJS8F{{;DP*Bj@+dDZq z+1S|F(9qD*(o$Gh*nWD77h=U;Sd^wUW@?fRTB!({JBNdDrfqry03$5Iy^}V$D8yt( zSs)v=VuTtzJ1_(Sm{|>OmFporr9o|2N*;}7=A{`;d3KZ13~pEtB)L>BoWfBS0bFl>31T-}yjRJBJBs4*Z5RfPlNfKlVg(4o1LJ~*} zNyQSWP@+&JNL3I7%q);oqs0Q7lFhK)@rCO*&aM$VK{Z3OF_EmS9BF>4;1uE_NZ5SR#S~ zDFuOgeRwF2&!QPN1HBmU|2k%jyD3zhfik3%HlZ9mE+9pzG&G8^ByA+goZu}QY$QuE zHj)A~nQ~w&MwrQ5W*V*6t5OMuMF? z&*5SB&})OQ4)h;-rSD+x%RSv)2Riq6w7>LX+Y8UPKG*VW^D|9PKh@Y!|76`0kJmm{ zQ(d*M^3g{s${*fa_Rxc+C3}h=xWDMW!h(DA^LD#(cR5*xc2FdN=h*MTY*q_uHf19_ z@7{6Oomt!O*tT`c?Z(WEbi-|F`de?gIW=XoP77-`C2!oYUY)c~#Z@~|E=!PdKa@xq zf0N+GH8)&;-L-LGEO5>0t5?Nbb>+${RxIaVe%Z36mo8Zxy=dWr`B9PcBIaJgjfU9s z5epXb6ukJrFbNDmUgT z%HWUm#UKKiEJEw)aD<~A0QgIY5skxO@JIV%b_!N4nD0HRr@~QuApaNvXYRA4`$1p5 zS+Hl`4(%ynSjy5k{(fR)o1-M%FY?7?3!;ZxN)4{^3rViZPPR-@vbR&Psj#ZZEyztR zNmQuF&B-gas<2f`Ovz75wF0t1!um=IU?nBlwn~m52?day&iO^D3Z{Any2%D+1`1|| zdWOa(=H}))3PuKo2Koj@`i4fjhUQkrMpgy}3Q(W~w5=#5%__*n4QdyVXRDM^Qc_^0 zuU}qXu2*iXmtT~wZ)j<02{OaTNEfI=x41H|B(Xv_uUHvof=g;~a#3bMNoIbY0?5R~ zr2NtnTP2`NAzsKWfE$}vtOxdvUUGh}ennz|zM-B0$V)JVzP|XC=H|jx7ncO3BHWAB z;NpiyW)Z+ZoqU2Pda%GTJ1y;^Qsfi`|MIrh5Ij~R+$jC3rFV4s>P;d@5RS3__ zOUW-U2CMeE=kNwPW5!LRRZeQ%gju%ayBtCH#BfDHg$2fFf?>=vovrqurzfu zaP^Az79*T`^?{Dj2SqDVG{b~|X$QoFCt4r}p6pZefN8x5n5Y^5|NHyr_phHn zzJL4r<@2YHAKt%v`{wnlmoJ__d-~+@qlXXf-@AL~_N|*Yu3x))1N{AbeZ0LqJ>1<~U7Vd99qjFFZLF;S;1CSOWlIB(CrP literal 0 HcmV?d00001 diff --git a/phpgwapi/js/egw_action/test/imgs/dhxmenu_subselbg.gif b/phpgwapi/js/egw_action/test/imgs/dhxmenu_subselbg.gif new file mode 100644 index 0000000000000000000000000000000000000000..6f9b96036c3df89b74610193e180072111e933fa GIT binary patch literal 52 zcmZ?wbhEHbWMq(GSjfb%=GKS*|Np-M1I3>#j9d&%3_1)z0Fq~5V&&;uxjK-+8UPM$ B5m^8L literal 0 HcmV?d00001 diff --git a/phpgwapi/js/egw_action/test/imgs/dhxmenu_subselbg.png b/phpgwapi/js/egw_action/test/imgs/dhxmenu_subselbg.png new file mode 100644 index 0000000000000000000000000000000000000000..247435c0e6c5aa7e859bac3e9be7d90983d70cbd GIT binary patch literal 433 zcmV;i0Z#sjP)Px#32;bRa{vGf6951U69E94oEQKA00(qQO+^RW0v8PeE0u9_n*aa-LrFwIR4C6) zlRK6pF$@C>dbdMZ`+r}QfJ1$LX4}hYHqGUPpk()q zS{8GD4MK1`P~k`-a4wM;cgK!mF1QlB7x$>1bHh_Zu{AIS~%vWq#R(e%%}cw?5K-xJ`P)9Ksxqm_JJP zUG7-^4Awia&wW$RCx|7e(0uAJR-#m1SGP`&uixKBH1LE?pAvitW5V{^wY;u9?Xr(^ zDU1bgD)|WG;Ci`N)&TDLc!X9LX~3>fT)Uo|g+j`d>PSMjIIg#cFVINM%8) zeo$(0erZv1Dp0vH$f^P>=c3falKi5O{QMkPCP(UIa|kjQ{`r{qy_R&mZ5vef{$J z)5j0*-@SeF`qj%9&!0Vg^7zri2lwyYy>t84%^TORUA=Po(!~qs&z(JU`qar2$B!L7 za`@1}1N-;w-Lrew&K=vgZQZhY)5ZeMTG_VdAT{+S(zE>X{jm6 zNr?&Zaj`McQIQehVWAmo_rKzE=rmCW>q^KY-Co3Z@B`F~;CMqH&FX#K^#)_>%=l VGBW5e00F4XU|?eINn~ZP1_0;@fbjqT literal 0 HcmV?d00001 diff --git a/phpgwapi/js/egw_action/test/imgs/dhxmenu_topbg.gif b/phpgwapi/js/egw_action/test/imgs/dhxmenu_topbg.gif new file mode 100644 index 0000000000000000000000000000000000000000..68463e8a1bb405b8664d508c3f8d59deccc8c109 GIT binary patch literal 66 zcmZ?wbhEHbWMmLwXkcV`^XAR}|Nj;L2|5?0CYGe8D3oWGWGJ|M`UWs4{$yd~Vqj#@ SVE_V#FEq$h4Rdj31pDc`A42%pq U3_t)<&cMXh!#{C#IxB-U0JBOFY5)KL literal 0 HcmV?d00001 diff --git a/phpgwapi/js/egw_action/test/imgs/dhxmenu_upar.gif b/phpgwapi/js/egw_action/test/imgs/dhxmenu_upar.gif new file mode 100644 index 0000000000000000000000000000000000000000..e96db885000f5e4f40a9a533438ad91d29f7d368 GIT binary patch literal 1930 zcmbtUSwIt46rBJjAgLlEP}gxpQ6$MEY$k6~ZF_tB?c29&YHG^M%R4$c>g(%=hK5Q?N*+IcJUl$C z*Xy-f?ZCjmix)4($H(v7xl>nHS5;M2U0ppoI$B&@+||`JHa2EvW>#8S+SJq(9v&_h ziz6Z;$YgRwMMY0fPew*Yb93{RD_2reQ)6Ocy1TpY-@h*q2pSt3U%h(O+uKVd5=A1B zv9YmEr|awMd-Uj0TU%RBPL8jyZ)j*}TwL64ZPmoHytWo6~& z=C-u7*xTFxwQu;={m-#@!hExT0LbNIeEjXcS}EfTA`lD=Lw1Oz9?&nv~pmGFsp~4ga>JNe zAUvB~7|sgh`n<`7M;?#}!(=QPEh;LC8s$Vq0d2KWN&4q3hL{2r~*_%Q}SgrIu$mwGzs+g|8J;7 z@)oVY0+GM#{a0ZHZ;uS21tJP`w_JeW@Fj*+G8RXU@G(@*L(yH6yBHusF;pQ!Wgush z3m7Dp3ehOVYqY;V%TKDn_)-Dl$Mt}4K2d-OvyDEK_bI5a}l{X5)t~yQ3?3EVU~ExTDU$_>b>C# zr>+H_l1syjp&5?#AIChognP#@eQR1gcx!t^ihEs-o0>53$M3)W`f7aa<%{Q|BhQ`= z|MFyL@aM+^{f{0#_^I#5-ku-s-|OzWd#AIby{)yS`L_Q1rtcaX>g#TOTU&Fpx~j6G zysWgOxadaV^=nrPuH@%k{wDY9uP)_W%)apD7w6Aqoz-P#oJmhRotkng`Q(Y?pC8j6 zJ(84oIN{L2_yhYj>V0vsdt*L}{#3O`85Ox3Q^-G&p;F1Nh@E25j&MXM2;+x-ygg*w z*5EChgFgC@w<&OAzy}-r*RNaa=ex$oo6GTHd#--p!~H!LbCsK`i?b60|DmW9$CVB% zmfJ5|YPSSh3@%!@VE(+h@7mf}&#{_qIm=?^jCaW9)6Gmt#AznRMg#y@JZ^$NMS!E> zhB4gM0BIst1gJIp55&id_8m-2QYRcab|~S*$&;Go)YEAxX&LE9Gqo$778_m2zL;|f zT)I*{#}OhFT)lR^a6WU6+WR9nd$Kjjq^9=UTO=E=nOlG?wJpi~Y*vQaoKI_OKdqkk z;m6%N%LM_K`Y(o%ZO^J_umcN6u5Gkv{r>Q5t0kt>iPMOt^L=HW1Y@G8&^Ah}B|6YI zu$B6YT@YZCQK8#^&aXzQfq0=ZPj(z}Q7N;;2wIRcbDQP)5@j!mq`E3}9Dtq9?<14k zhkXO!xFnL*@Pkb&;@HzM&&0vdlz{~%i$=6|>>HzVW6g{$#*9UJO`Of_?BIllH_cg; zuQA^el ztaV<8ES(ch)HPmJTj~xj2lmCcY$ss-d34|i<1(9&P0tq^<%Vor=czkKFR~)r6&m-1 znx{03M0;w}P`wHCyOB+Ij6Tt;ObZMaS`gd|nmEoW6V9T~H+MJzd|s z^YP1Hc07G_>)Lgir!F1{Qn4GcTg%?koHo<=1qRN{}nPDolOeI^o4N5I>! zU$N=L=sg~ zDx#dOA*B0N~cqPsWI(^rbbkh)DS0_H_UN0C4l_kvWIm2#Kyy6%BCh z(yIUf003&1xdx>t$*eR2ZvXxT0001Z_R$y3Iju92q*wg58};}zm(OaAH=p|y0002M zh5O5#fxp|~jc?yi@+7$`d4Q6Hl%z;WiWG??NXR{Hx%)pMd~SE0000OQI literal 0 HcmV?d00001 diff --git a/phpgwapi/js/egw_action/test/imgs/folder.png b/phpgwapi/js/egw_action/test/imgs/folder.png new file mode 100755 index 0000000000000000000000000000000000000000..784e8fa48234f4f64b6922a6758f254ee0ca08ec GIT binary patch literal 537 zcmV+!0_OdRP)x(K@^6+>g^d@v4;gkbWsEoXE%32*i1tcpTNXd5CcIl)ECgqz|2rE6EW}s7R?kl za1q`0GCkMruC6-2LANtwVlsgzsp4?{@7$`KBv!G66>Vie3h?3OmEEkjwdLG0PgLVi z`!N((f$A@n17Ldj#`};0I3@iHJ5M{#IZz|UIYRm4(!uV7eYIYIwQf&}_2J~}>pQ^n z6o8--^T(=hkBNQ_k{-_GWE;FMW7!p}f{NG3nHZ{D5<3d8&tLh%a4AqqnjMkr3m&fkMdECD3N5}Unig5wy40;>lo4j~k+e}v)` zR6)J8Mk*u=SpB`p6o)7j?S0T@9?bz#m@l>gc*zk__|*!FMcHwP!gwLJvS~9c0px8E zW0)+jEP);68^d)m`eN0o>(5%D`Q(1;j>g@G;xlf`0VBQ`PFY?6)!N&f?*K}$p; zB!U=NBn{eB8${1}&-2_L*HuZp@ZP1@clS@cHp)4iM1ewzw59vko7eMM{e9z|%NNdX z0V;`?KKSzTCvTm5bc{L^CIKLUxc2X{i{ISz$8Sgf{q)1nXTP{`{s?9mQ$4&hPiKC- zY8q7(Y1Xu5iCf33=O4Vy(+|zQ?rW#gkKB0f%}?+6{G*qT22|DQB-73`YzA{N4W^=s zq0kQYcbtFfz zLz)H<&|z(Y4k^jb z4`0v}DG1te)wmeb(>p90leRz?_mO+^JKy=v&2<29Od6?F%9%(c8los#f*@G`-%W&* z$)uBj2i@u-@SgX}gtyWPe6d*|w6h%R? zScK2#Yn%$sum0cy>90DmY*i{1XqpClEtktsRTZ)lCUe z<FogV^*tm>8*AlX za4oiR!&85LrobG57qUHUX#{>Vz(RHpB5|@>9O6N$jqB8>%($0wxE5R3)b>Y~xtCo$ zCgEk&A?_#IxHdN)9tqre^o{ho4{?hmPuf@^@I3-wncaRd%|~O3xbrKY=&TiwPYkJroM{;WUQTuMY8vp-1) + _isChrome=true; + +if ((navigator.userAgent.indexOf('Safari') != -1)||(navigator.userAgent.indexOf('Konqueror') != -1)){ + var _KHTMLrv = parseFloat(navigator.userAgent.substr(navigator.userAgent.indexOf('Safari')+7, 5)); + + if (_KHTMLrv > 525){ //mimic FF behavior for Safari 3.1+ + _isFF=true; + var _FFrv = 1.9; + } else + _isKHTML=true; +} else if (navigator.userAgent.indexOf('Opera') != -1){ + _isOpera=true; + _OperaRv=parseFloat(navigator.userAgent.substr(navigator.userAgent.indexOf('Opera')+6, 3)); +} + + +else if (navigator.appName.indexOf("Microsoft") != -1){ + _isIE=true; + if (navigator.appVersion.indexOf("MSIE 8.0")!= -1 && document.compatMode != "BackCompat") _isIE=8; + if (navigator.appVersion.indexOf("MSIE 9.0")!= -1 && document.compatMode != "BackCompat") _isIE=8; +} else { + _isFF=true; + var _FFrv = parseFloat(navigator.userAgent.split("rv:")[1]) +} + + +//multibrowser Xpath processor +dtmlXMLLoaderObject.prototype.doXPath=function(xpathExp, docObj, namespace, result_type){ + if (_isKHTML || (!_isIE && !window.XPathResult)) + return this.doXPathOpera(xpathExp, docObj); + + if (_isIE){ //IE + if (!docObj) + if (!this.xmlDoc.nodeName) + docObj=this.xmlDoc.responseXML + else + docObj=this.xmlDoc; + + if (!docObj) + dhtmlxError.throwError("LoadXML", "Incorrect XML", [ + (docObj||this.xmlDoc), + this.mainObject + ]); + + if (namespace != null) + docObj.setProperty("SelectionNamespaces", "xmlns:xsl='"+namespace+"'"); // + + if (result_type == 'single'){ + return docObj.selectSingleNode(xpathExp); + } + else { + return docObj.selectNodes(xpathExp)||new Array(0); + } + } else { //Mozilla + var nodeObj = docObj; + + if (!docObj){ + if (!this.xmlDoc.nodeName){ + docObj=this.xmlDoc.responseXML + } + else { + docObj=this.xmlDoc; + } + } + + if (!docObj) + dhtmlxError.throwError("LoadXML", "Incorrect XML", [ + (docObj||this.xmlDoc), + this.mainObject + ]); + + if (docObj.nodeName.indexOf("document") != -1){ + nodeObj=docObj; + } + else { + nodeObj=docObj; + docObj=docObj.ownerDocument; + } + var retType = XPathResult.ANY_TYPE; + + if (result_type == 'single') + retType=XPathResult.FIRST_ORDERED_NODE_TYPE + var rowsCol = new Array(); + var col = docObj.evaluate(xpathExp, nodeObj, function(pref){ + return namespace + }, retType, null); + + if (retType == XPathResult.FIRST_ORDERED_NODE_TYPE){ + return col.singleNodeValue; + } + var thisColMemb = col.iterateNext(); + + while (thisColMemb){ + rowsCol[rowsCol.length]=thisColMemb; + thisColMemb=col.iterateNext(); + } + return rowsCol; + } +} + +function _dhtmlxError(type, name, params){ + if (!this.catches) + this.catches=new Array(); + + return this; +} + +_dhtmlxError.prototype.catchError=function(type, func_name){ + this.catches[type]=func_name; +} +_dhtmlxError.prototype.throwError=function(type, name, params){ + if (this.catches[type]) + return this.catches[type](type, name, params); + + if (this.catches["ALL"]) + return this.catches["ALL"](type, name, params); + + alert("Error type: "+arguments[0]+"\nDescription: "+arguments[1]); + return null; +} + +window.dhtmlxError=new _dhtmlxError(); + + +//opera fake, while 9.0 not released +//multibrowser Xpath processor +dtmlXMLLoaderObject.prototype.doXPathOpera=function(xpathExp, docObj){ + //this is fake for Opera + var z = xpathExp.replace(/[\/]+/gi, "/").split('/'); + var obj = null; + var i = 1; + + if (!z.length) + return []; + + if (z[0] == ".") + obj=[docObj]; else if (z[0] == ""){ + obj=(this.xmlDoc.responseXML||this.xmlDoc).getElementsByTagName(z[i].replace(/\[[^\]]*\]/g, "")); + i++; + } else + return []; + + for (i; i < z.length; i++)obj=this._getAllNamedChilds(obj, z[i]); + + if (z[i-1].indexOf("[") != -1) + obj=this._filterXPath(obj, z[i-1]); + return obj; +} + +dtmlXMLLoaderObject.prototype._filterXPath=function(a, b){ + var c = new Array(); + var b = b.replace(/[^\[]*\[\@/g, "").replace(/[\[\]\@]*/g, ""); + + for (var i = 0; i < a.length; i++) + if (a[i].getAttribute(b)) + c[c.length]=a[i]; + + return c; +} +dtmlXMLLoaderObject.prototype._getAllNamedChilds=function(a, b){ + var c = new Array(); + + if (_isKHTML) + b=b.toUpperCase(); + + for (var i = 0; i < a.length; i++)for (var j = 0; j < a[i].childNodes.length; j++){ + if (_isKHTML){ + if (a[i].childNodes[j].tagName&&a[i].childNodes[j].tagName.toUpperCase() == b) + c[c.length]=a[i].childNodes[j]; + } + + else if (a[i].childNodes[j].tagName == b) + c[c.length]=a[i].childNodes[j]; + } + + return c; +} + +function dhtmlXHeir(a, b){ + for (var c in b) + if (typeof (b[c]) == "function") + a[c]=b[c]; + return a; +} + +function dhtmlxEvent(el, event, handler){ + if (el.addEventListener) + el.addEventListener(event, handler, false); + + else if (el.attachEvent) + el.attachEvent("on"+event, handler); +} + +//============= XSL Extension =================================== + +dtmlXMLLoaderObject.prototype.xslDoc=null; +dtmlXMLLoaderObject.prototype.setXSLParamValue=function(paramName, paramValue, xslDoc){ + if (!xslDoc) + xslDoc=this.xslDoc + + if (xslDoc.responseXML) + xslDoc=xslDoc.responseXML; + var item = + this.doXPath("/xsl:stylesheet/xsl:variable[@name='"+paramName+"']", xslDoc, + "http:/\/www.w3.org/1999/XSL/Transform", "single"); + + if (item != null) + item.firstChild.nodeValue=paramValue +} +dtmlXMLLoaderObject.prototype.doXSLTransToObject=function(xslDoc, xmlDoc){ + if (!xslDoc) + xslDoc=this.xslDoc; + + if (xslDoc.responseXML) + xslDoc=xslDoc.responseXML + + if (!xmlDoc) + xmlDoc=this.xmlDoc; + + if (xmlDoc.responseXML) + xmlDoc=xmlDoc.responseXML + + //MOzilla + if (!_isIE){ + if (!this.XSLProcessor){ + this.XSLProcessor=new XSLTProcessor(); + this.XSLProcessor.importStylesheet(xslDoc); + } + var result = this.XSLProcessor.transformToDocument(xmlDoc); + } else { + var result = new ActiveXObject("Msxml2.DOMDocument.3.0"); + try{ + xmlDoc.transformNodeToObject(xslDoc, result); + }catch(e){ + result = xmlDoc.transformNode(xslDoc); + } + } + return result; +} + +dtmlXMLLoaderObject.prototype.doXSLTransToString=function(xslDoc, xmlDoc){ + var res = this.doXSLTransToObject(xslDoc, xmlDoc); + if(typeof(res)=="string") + return res; + return this.doSerialization(res); +} + +dtmlXMLLoaderObject.prototype.doSerialization=function(xmlDoc){ + if (!xmlDoc) + xmlDoc=this.xmlDoc; + if (xmlDoc.responseXML) + xmlDoc=xmlDoc.responseXML + if (!_isIE){ + var xmlSerializer = new XMLSerializer(); + return xmlSerializer.serializeToString(xmlDoc); + } else + return xmlDoc.xml; +} + +/** +* @desc: +* @type: private +*/ +dhtmlxEventable=function(obj){ + obj.attachEvent=function(name, catcher, callObj){ + name='ev_'+name.toLowerCase(); + if (!this[name]) + this[name]=new this.eventCatcher(callObj||this); + + return(name+':'+this[name].addEvent(catcher)); //return ID (event name & event ID) + } + obj.callEvent=function(name, arg0){ + name='ev_'+name.toLowerCase(); + if (this[name]) + return this[name].apply(this, arg0); + return true; + } + obj.checkEvent=function(name){ + return (!!this['ev_'+name.toLowerCase()]) + } + obj.eventCatcher=function(obj){ + var dhx_catch = []; + var z = function(){ + var res = true; + for (var i = 0; i < dhx_catch.length; i++){ + if (dhx_catch[i] != null){ + var zr = dhx_catch[i].apply(obj, arguments); + res=res&&zr; + } + } + return res; + } + z.addEvent=function(ev){ + if (typeof (ev) != "function") + ev=eval(ev); + if (ev) + return dhx_catch.push(ev)-1; + return false; + } + z.removeEvent=function(id){ + dhx_catch[id]=null; + } + return z; + } + obj.detachEvent=function(id){ + if (id != false){ + var list = id.split(':'); //get EventName and ID + this[list[0]].removeEvent(list[1]); //remove event + } + } + obj.detachAllEvents = function(){ + for (var name in this){ + if (name.indexOf("ev_")==0) + delete this[name]; + } + } +} diff --git a/phpgwapi/js/egw_action/test/js/dhtmlxmenu.js b/phpgwapi/js/egw_action/test/js/dhtmlxmenu.js new file mode 100644 index 0000000000..85a88233e2 --- /dev/null +++ b/phpgwapi/js/egw_action/test/js/dhtmlxmenu.js @@ -0,0 +1,2190 @@ +//v.2.6 build 100722 + +/* +Copyright DHTMLX LTD. http://www.dhtmlx.com +You allowed to use this component or parts of it under GPL terms +To use it on other terms or get Professional edition of the component please contact us at sales@dhtmlx.com +*/ +/** +* @desc: a constructor, creates a new dhtmlxMenu object, baseId defines a base object for the top menu level +* @param: baseId - id of the html element to which a menu will be attached, in case of a contextual menu - if specified, will used as a contextual zone +* @type: public +*/ +function dhtmlXMenuObject(baseId, skin) { + var main_self = this; + + this.addBaseIdAsContextZone = null; + + this.isDhtmlxMenuObject = true; + + // skin settings + this.skin = (skin!=null?skin:"dhx_skyblue"); + this.imagePath = ""; + + // iframe + this._isIE6 = false; + if (_isIE) this._isIE6 = (window.XMLHttpRequest==null?true:false); + + if (baseId == null) { + this.base = document.body; + } else { + if (document.getElementById(baseId) != null) { + this.base = document.getElementById(baseId); + while (this.base.childNodes.length > 0) { this.base.removeChild(this.base.childNodes[0]); } + this.base.className += " dhtmlxMenu_"+this.skin+"_Middle dir_left"; + this.base._autoSkinUpdate = true; + // preserv default oncontextmenu for future restorin in case of context menu + if (this.base.oncontextmenu) this.base._oldContextMenuHandler = this.base.oncontextmenu; + // + this.addBaseIdAsContextZone = baseId; + this.base.onselectstart = function(e) { e = e || event; e.returnValue = false; return false; } + this.base.oncontextmenu = function(e) { e = e || event; e.returnValue = false; return false; } + } else { + this.base = document.body; + } + } + // this.topId = topId; + this.topId = "dhxWebMenuTopId"; + + // + if (!this.extendedModule) { + // add notify for menu + var t = function(){alert(this.i18n.dhxmenuextalert);}; + var extMethods = new Array("setItemEnabled", "setItemDisabled", "isItemEnabled", "_changeItemState", "getItemText", "setItemText", + "loadFromHTML", "hideItem", "showItem", "isItemHidden", "_changeItemVisible", "setUserData", "getUserData", + "setOpenMode", "setWebModeTimeout", "enableDynamicLoading", "_updateLoaderIcon", "getItemImage", "setItemImage", + "clearItemImage", "setAutoShowMode", "setAutoHideMode", "setContextMenuHideAllMode", "getContextMenuHideAllMode", + "setVisibleArea", "setTooltip", "getTooltip", "setHotKey", "getHotKey", "setItemSelected", "setTopText", "setRTL", + "setAlign", "setHref", "clearHref", "getCircuit", "_clearAllSelectedSubItemsInPolygon", "_checkArrowsState", + "_addUpArrow", "_addDownArrow", "_removeUpArrow", "_removeDownArrow", "_isArrowExists", "_doScrollUp", "_doScrollDown", + "_countPolygonItems", "setOverflowHeight", "_getRadioImgObj", "_setRadioState", "_radioOnClickHandler", + "getRadioChecked", "setRadioChecked", "addRadioButton", "_getCheckboxState", "_setCheckboxState", "_readLevel", + "_updateCheckboxImage", "_checkboxOnClickHandler", "setCheckboxState", "getCheckboxState", "addCheckbox", "serialize"); + for (var q=0; q