diff --git a/phpgwapi/js/egw_action/egw_action.js b/phpgwapi/js/egw_action/egw_action.js index 66b9ff65b1..b32f5d3142 100644 --- a/phpgwapi/js/egw_action/egw_action.js +++ b/phpgwapi/js/egw_action/egw_action.js @@ -35,13 +35,11 @@ _egwActionClasses["default"] = { "implementation": null }; -function egwAction(_id, _handler, _caption, _iconUrl, _onExecute, _allowOnMultiple) +function egwAction(_parent, _id, _caption, _iconUrl, _onExecute, _allowOnMultiple) { //Default and check the values - if (typeof _id != "string" || !_id) + if (_parent && (typeof _id != "string" || !_id)) throw "egwAction _id must be a non-empty string!"; - if (typeof _handler == "undefined") - this.handler = null; if (typeof _caption == "undefined") _caption = ""; if (typeof _iconUrl == "undefined") @@ -56,13 +54,138 @@ function egwAction(_id, _handler, _caption, _iconUrl, _onExecute, _allowOnMultip this.iconUrl = _iconUrl; this.allowOnMultiple = _allowOnMultiple; this.enabled = true; + this.type = "default"; //All derived classes have to override this! + this.canHaveChildren = false; //Has to be overwritten by inherited action classes + this.parent = _parent; + this.children = []; this.execJSFnct = null; this.execHandler = false; this.set_onExecute(_onExecute); } +/** + * Searches for a specific action with the given id + */ +egwAction.prototype.getActionById = function(_id) +{ + // 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 (var i = 0; i < this.children.length; i++) + { + var elem = this.children[i].getActionById(_id); + if (elem) + { + return elem; + } + } + } + + return null; +} + +/** + * Adds a new action to the child elements. + */ +egwAction.prototype.addAction = function(_type, _id, _caption, _iconUrl, + _onExecute, _allowOnMultiple) +{ + //Get the constructor for the given action type + if (!_type) + { + _type = "default"; + } + + // Only allow adding new actions, if this action class allows it. + if (this.canHaveChildren) + { + var constructor = _egwActionClasses[_type].actionConstructor; + + if (typeof constructor == "function") + { + var action = new constructor(this, _id, _caption, _iconUrl, _onExecute, + _allowOnMultiple); + this.children.push(action); + + return action; + } + else + { + throw "Given action type not registered."; + } + } + else + { + throw "This action does not allow child elements!" + } +} + +/** + * Updates the children of this element + */ +egwAction.prototype.updateActions = function(_actions) +{ + if (this.canHaveChildren) + { + 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.getActionById(elem.id); + if (!action) + { + if (typeof elem.type == "undefined") + elem.type = "default"; + + var constructor = null; + + // Check whether the given type is inside the "canHaveChildren" + // array + if (this.canHaveChildren !== true && this.canHaveChildren.indexOf(elem.type) == -1) + { + throw "This child type '" + elem.type + "' is not allowed!" + } + + if (typeof _egwActionClasses[elem.type] != "undefined") + constructor = _egwActionClasses[elem.type].actionConstructor; + + if (typeof constructor == "function" && constructor) + action = new constructor(this, elem.id); + else + throw "Given action type \"" + elem.type + "\" not registered."; + + this.children.push(action); + } + + action.updateAction(elem); + + // Add sub-actions to the action + if (elem.children) + { + action.updateActions(elem.children); + } + } + } + } + else + { + throw "This action element cannot have children!"; + } +} + /** * Executes this action by using the method specified in the onExecute setter. * @@ -77,7 +200,7 @@ egwAction.prototype.execute = function(_senders) } else if (this.execHandler) { - this.handler.execute(this, _senders); + //this.handler.execute(this, _senders); TODO } } @@ -154,89 +277,132 @@ egwAction.prototype.updateAction = function(_data) } +function _egwActionTreeContains(_tree, _elem) +{ + for (var i = 0; i < _tree.length; i++) + { + if (_tree[i].action == _elem) + { + return _tree[i]; + } + + if (typeof _tree[i].children != "undefined") + { + var elem = _egwActionTreeContains(_tree[i].children, _elem); + if (elem) + { + return elem; + } + } + } + + return null; +} + +/** + * The appendToGraph function generates an action tree which automatically contains + * all parent elements. If the appendToGraph function is called for a + * + * @param array _tree contains the tree structure - pass an object containing + * the empty array "root" to this function {"root": []}. The result will be stored in + * this array. + * @param boolean _addChildren is used internally to prevent parent elements from + * adding their children automatically to the tree. + */ +egwAction.prototype.appendToTree = function(_tree, _addChildren) +{ + if (typeof _addChildren == "undefined") + { + _addChildren = true; + } + + if (typeof _addParent == "undefined") + { + _addParent = true; + } + + // Preset some variables + var root = _tree.root; + var parent_cntr = null; + var cntr = { + "action": this, + "children": [] + }; + + + if (this.parent) + { + // Check whether the parent container has already been added to the tree + parent_cntr = _egwActionTreeContains(root, this.parent); + + if (!parent_cntr) + { + parent_cntr = this.parent.appendToTree(_tree, false); + } + + // Check whether this element has already been added to the parent container + var added = false; + for (var i = 0; i < parent_cntr.children.length; i++) + { + if (parent_cntr.children[i].action == this) + { + cntr = parent_cntr.children[i]; + added = true; + break; + } + } + + if (!added) + { + parent_cntr.children.push(cntr); + } + } + else + { + var added = false; + for (var i = 0; i < root.length; i++) + { + if (root[i].action == this) + { + cntr = root[i]; + added = true; + break; + } + } + + if (!added) + { + // Add this element to the root if it has no parent + root.push(cntr); + } + } + + if (_addChildren) + { + for (var i = 0; i < this.children.length; i++) + { + this.children[i].appendToTree(_tree, true); + } + } + + return cntr; +} + /** egwActionManager Object **/ /** - * egwActionManager manages a list of actions, provides functions to add new - * actions or to update them via JSON. + * egwActionManager manages a list of actions - it overwrites the egwAction class + * and allows child actions to be added to it. */ -function egwActionManager(_handler) +function egwActionManager() { - //Preset the handler parameter to null - if (typeof _handler == "undefined") - _handler = null; + var action = new egwAction(null, false); - this.handler = _handler; - this.actions = []; -} + action.type = "actionManager"; + action.canHaveChildren = true; -egwActionManager.prototype.addAction = function(_type, _id, _caption, _iconUrl, - _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, _iconUrl, _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.getActionById(elem.id); - if (!action) - { - if (typeof elem.type == "undefined") - elem.type = "default"; - - var constructor = null; - - if (typeof _egwActionClasses[elem.type] != "undefined") - constructor = _egwActionClasses[elem.type].actionConstructor; - - if (typeof constructor == "function" && constructor) - action = new constructor(elem.id, this.handler); - else - throw "Given action type \"" + elem.type + "\" not registered."; - - this.actions.push(action); - } - - action.updateAction(elem); - } - } -} - -/** - * Returns the action inside the action manager which has the given id. - */ -egwActionManager.prototype.getActionById = function(_id) -{ - for (var i = 0; i < this.actions.length; i++) - { - if (this.actions[i].id == _id) - return this.actions[i]; - } - - return null; + return action; } diff --git a/phpgwapi/js/egw_action/egw_action_popup.js b/phpgwapi/js/egw_action/egw_action_popup.js index e6c2804485..e0a29e7ff0 100644 --- a/phpgwapi/js/egw_action/egw_action_popup.js +++ b/phpgwapi/js/egw_action/egw_action_popup.js @@ -21,6 +21,7 @@ function egwPopupAction(_id, _handler, _caption, _icon, _onExecute, _allowOnMult 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; @@ -163,33 +164,52 @@ function egwPopupActionImplementation() } /** - * Builds the context menu from the given action links + * Groups and sorts the given action tree layer */ - ai._buildMenu = function(_links, _selected) + ai._groupLayers = function(_layer, _links, _parentGroup) { - var menu = new egwMenu(); - - //Sort the links in ordered groups + // Seperate the multiple groups out of the layer var link_groups = {}; - for (k in _links) + + 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 = _links[k].actionObj.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; - for (var i = 0; i < link_groups[grp].length; i++) + var groupObj = { + "actionObj": actionObj, + "visible": visible, + "enabled": enabled, + "groups": [] + }; + + for (var j = 0; j < link_groups[grp].length; j++) { - var elem = link_groups[grp][i]; - if (elem.actionObj.order > _links[k].actionObj.order) + var elem = link_groups[grp][j].actionObj; + if (elem.order > actionObj.order) { inserted = true; - link_groups[grp].splice(i, 0, _links[k]); + link_groups[grp].splice(j, 0, groupObj); break; } } @@ -197,37 +217,65 @@ function egwPopupActionImplementation() // If the object hasn't been inserted, add it to the end of the list if (!inserted) { - link_groups[grp].push(_links[k]); + 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); } } - // Insert the link groups sorted into an array + // Transform the link_groups object into an sorted array var groups = []; + for (k in link_groups) + { groups.push({"grp": k, "links": link_groups[k]}); + } + groups.sort(function(a, b) { - return (a.grp > b.grp) ? 1 : ((a.grp < b.grp) ? -1 : 0); + var ia = parseInt(a.grp); + var ib = parseInt(b.grp); + return (ia > ib) ? 1 : ((ia < ib) ? -1 : 0); }); - for (var i = 0; i < groups.length; i++) + // Append the groups to the groups2 array + var groups2 = []; + for (k in groups) + { + groups2.push(groups[k].links); + } + + _parentGroup.groups = groups2; + } + + /** + * Build the menu layers + */ + ai._buildMenuLayer = function(_menu, _groups, _selected, _enabled) + { + for (var i = 0; i < _groups.length; i++) { // Add an seperator after each group if (i != 0) { - menu.addItem("", "-"); + _menu.addItem("", "-"); } // Go through the elements of each group - for (var j = 0; j < groups[i].links.length; j++) + for (var j = 0; j < _groups[i].length; j++) { - var link = groups[i].links[j]; + var link = _groups[i][j]; + if (link.visible) { - var item = menu.addItem(link.actionObj.id, link.actionObj.caption, + var item = _menu.addItem(link.actionObj.id, link.actionObj.caption, link.actionObj.iconUrl); item["default"] = link.actionObj["default"]; item.data = link.actionObj; - if (link.enabled) + if (link.enabled && _enabled) { item.set_onClick(function(elem) { elem.data.execute(_selected); @@ -237,9 +285,48 @@ function egwPopupActionImplementation() { item.set_enabled(false); } + + // Append the parent groups + if (link.groups) + { + this._buildMenuLayer(item, link.groups, _selected, link.enabled); + } } } } + } + + /** + * Builds the context menu from the given action links + */ + ai._buildMenu = function(_links, _selected) + { + // Build a tree containing all actions + var tree = {"root": []}; + + for (k in _links) + { + _links[k].actionObj.appendToTree(tree); + } + + 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(); + + // We needed the dummy object container in order to pass the array as + // reference - this is not needed anymore + groups = groups.groups; + + // Build the menu layers + this._buildMenuLayer(menu, groups, _selected, true); return menu; } diff --git a/phpgwapi/js/egw_action/test/test_action.html b/phpgwapi/js/egw_action/test/test_action.html index d89b0a9dae..5a051a2e69 100644 --- a/phpgwapi/js/egw_action/test/test_action.html +++ b/phpgwapi/js/egw_action/test/test_action.html @@ -175,7 +175,6 @@ "onExecute": alertClicked, "allowOnMultiple": false, "type": "popup", - "default": true }, { "id": "file_delete", @@ -219,6 +218,40 @@ "type": "popup", "group": 1, "order": 2 + }, + { + "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 + } + ] } ] ); @@ -231,14 +264,16 @@ {"actionId": "file_email", "enabled": true}, {"actionId": "file_compress_email", "enabled": true}, {"actionId": "file_compress", "enabled": true}, - {"actionId": "file_delete", "enabled": true} + {"actionId": "file_delete", "enabled": true}, + {"actionId": "send_to", "enabled": true} ]; var listboxFolderLinks = [ {"actionId": "folder_open", "enabled": true}, {"actionId": "file_compress_email", "enabled": true}, {"actionId": "file_compress", "enabled": true}, - {"actionId": "file_delete", "enabled": true} + {"actionId": "file_delete", "enabled": true}, + "send_to" ]; $('#lb1 tr:odd').addClass('odd');