Moved most code of the JS actionManager class to the action object itself, so that every action object might have child object if the action implementation allows that. Added support for sub-actions for actions representet as popup menus

This commit is contained in:
Andreas Stöckel 2011-03-23 14:05:39 +00:00
parent e33aa2978c
commit e0d40e649b
3 changed files with 391 additions and 103 deletions

View File

@ -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 = [];
}
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];
action.type = "actionManager";
action.canHaveChildren = true;
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;
}

View File

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

View File

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