- Added working egw_grid component including test and documentation, codebase will be used to replace the nextmatchWidget in etemplate2

- Improved egw_action.js:
	- Bugfixing regaring handling of egwActionObjects organized in trees (hasn't been tested before)
	- Improved egwActionObjectInterface interface and functionality: AOIs can now mark themselves as invisible/visible and request the action objects to reconnect the action implementations
	- Action objects do now automatically register the action implementations
	- Vastly improved speed when working with huge amounts (> 500) of objects organized in trees (as done in the grid test)
	- Improved egwActionObject functionality: Added new functions regarding selecting groups of objects
This commit is contained in:
Andreas Stöckel 2011-03-02 21:18:20 +00:00
parent daea81489f
commit ae7987264e
14 changed files with 1316 additions and 115 deletions

View File

@ -340,6 +340,7 @@ egwActionLink.prototype.set_actionId = function(_value)
var EGW_AO_STATE_NORMAL = 0x00;
var EGW_AO_STATE_SELECTED = 0x01;
var EGW_AO_STATE_FOCUSED = 0x02;
var EGW_AO_STATE_VISIBLE = 0x04; //< Can only be set by the AOI, means that the object is attached to the DOM-Tree and visible
var EGW_AO_EVENT_DRAG_OVER_ENTER = 0x00;
var EGW_AO_EVENT_DRAG_OVER_LEAVE = 0x01;
@ -386,14 +387,22 @@ function egwActionObject(_id, _parent, _iface, _manager, _flags)
this.manager = _manager;
this.flags = _flags;
this.registeredImpls = [];
// Two variables which help fast travelling through the object tree, when
// searching for the selected/focused object.
this.selectedChildren = [];
this.focusedChild = null;
this.iface = _iface;
this.iface.setStateChangeCallback(this._ifaceCallback, this)
this.iface.setStateChangeCallback(this._ifaceCallback, this);
this.iface.setReconnectActionsCallback(this._reconnectCallback, this);
}
/**
* Returns the object from the tree with the given ID
*/
//TODO: Add "ByID"-Suffix to all other of those functions.
//TODO: Add "ById"-Suffix to all other of those functions.
//TODO: Add search function to egw_action_commons.js
egwActionObject.prototype.getObjectById = function(_id)
{
@ -531,24 +540,35 @@ egwActionObject.prototype.getContainerRoot = function()
}
/**
* Returns all selected objects which are in the current container.
* Returns all selected objects which are in the current subtree.
*
* @param function _test is a function, which gets an object and checks whether
* it will be added to the list.
* @param array _list is internally used to fetch all selected elements, please
* omit this parameter when calling the function.
*/
egwActionObject.prototype.getSelectedObjects = function(_test)
egwActionObject.prototype.getSelectedObjects = function(_test, _list)
{
if (typeof _test == "undefined")
_test = null;
var result = [];
var list = this.getContainerRoot().flatList();
for (var i = 0; i < list.length; i++)
if (typeof _list == "undefined")
{
if (list[i].getSelected() && (!_test || _test(list[i])))
_list = {"elements": []}
}
if ((!_test || _test(this)) && this.getSelected())
_list.elements.push(this);
if (this.selectedChildren)
{
for (var i = 0; i < this.selectedChildren.length; i++)
{
result.push(list[i]);
this.selectedChildren[i].getSelectedObjects(_test, _list)
}
}
return result;
return _list.elements;
}
/**
@ -556,7 +576,7 @@ egwActionObject.prototype.getSelectedObjects = function(_test)
*/
egwActionObject.prototype.getAllSelected = function()
{
if (this.getSelected())
if (this.children.length == this.selectedChildren.length)
{
for (var i = 0; i < this.children.length; i++)
{
@ -583,11 +603,7 @@ egwActionObject.prototype.toggleAllSelected = function(_select)
_select = !this.getAllSelected();
}
this.setSelected(_select);
for (var i = 0; i < this.children.length; i++)
{
this.children[i].toggleAllSelected(_select);
}
this.setAllSelected(_select);
}
/**
@ -620,6 +636,7 @@ egwActionObject.prototype.flatList = function(_obj)
* and this one. The operation returns an empty list, if a container object is
* found on the way.
*/
//TODO: Remove flatList here!
egwActionObject.prototype.traversePath = function(_to)
{
var contRoot = this.getContainerRoot();
@ -667,23 +684,8 @@ egwActionObject.prototype.getIndex = function()
*/
egwActionObject.prototype.getFocusedObject = function()
{
//Search for the focused object in the children
for (var i = 0; i < this.children.length; i++)
{
var obj = this.children[i].getFocusedObject()
if (obj)
{
return obj;
}
}
//One of the child objects hasn't been focused, probably this object is
if (!egwBitIsSet(this.flags, EGW_AO_FLAG_IS_CONTAINER) && this.getFocused())
{
return this;
}
return null;
var cr = this.getContainerRoot();
return cr ? cr.focusedChild : null;
}
/**
@ -695,12 +697,33 @@ egwActionObject.prototype.getFocusedObject = function()
* @param int _shiftState is the status of extra keys being pressed during the
* selection process.
*/
egwActionObject.prototype._ifaceCallback = function(_newState, _shiftState)
egwActionObject.prototype._ifaceCallback = function(_newState, _changedBit, _shiftState)
{
if (typeof _shiftState == "undefined")
_shiftState = EGW_AO_SHIFT_STATE_NONE;
var selected = egwBitIsSet(_newState, EGW_AO_STATE_SELECTED);
var visible = egwBitIsSet(_newState, EGW_AO_STATE_VISIBLE);
// Check whether the visibility of the object changed
if (_changedBit == EGW_AO_STATE_VISIBLE && visible != this.getVisible())
{
// Deselect the object
if (!visible)
{
this.setSelected(false);
this.setFocused(false);
return EGW_AO_STATE_NORMAL;
}
else
{
// Auto-register the actions attached to this object
this.registerActions();
}
}
// Remove the focus from all children on the same level
if (this.parent)
if (this.parent && visible && _changedBit == EGW_AO_STATE_SELECTED)
{
var selected = egwBitIsSet(_newState, EGW_AO_STATE_SELECTED);
var objs = [];
@ -714,14 +737,7 @@ egwActionObject.prototype._ifaceCallback = function(_newState, _shiftState)
// state is not set
if (!egwBitIsSet(_shiftState, EGW_AO_SHIFT_STATE_MULTI))
{
var lst = this.getContainerRoot().flatList();
for (var i = 0; i < lst.length; i++)
{
if (lst[i] != this)
{
lst[i].setSelected(false);
}
}
var lst = this.getContainerRoot().setAllSelected(false);
}
// If the LIST state is active, get all objects inbetween this one and the focused one
@ -745,8 +761,13 @@ egwActionObject.prototype._ifaceCallback = function(_newState, _shiftState)
if (objs.length == 0 || !egwBitIsSet(_shiftState, EGW_AO_SHIFT_STATE_BLOCK))
{
this.setFocused(true);
_newState = egwSetBit(EGW_AO_STATE_FOCUSED, _newState, true);
}
this.setSelected(selected);
}
return _newState;
}
/**
@ -765,6 +786,15 @@ egwActionObject.prototype.getFocused = function()
return egwBitIsSet(this.getState(), EGW_AO_STATE_FOCUSED);
}
/**
* Returns whether the object currently is visible - visible means, that the
* AOI has a dom node and is visible.
*/
egwActionObject.prototype.getVisible = function()
{
return egwBitIsSet(this.getState(), EGW_AO_STATE_VISIBLE);
}
/**
* Returns the complete state of the object.
*/
@ -779,55 +809,28 @@ egwActionObject.prototype.getState = function()
* be de-focused.
*
* @param boolean _focused - whether to remove or set the focus. Defaults to true
* @param object _recPrev is internally used to prevent infinit recursion. Do not touch.
*/
egwActionObject.prototype.setFocused = function(_focused, _recPrev)
egwActionObject.prototype.setFocused = function(_focused)
{
if (typeof _focused == "undefined")
_focused = true;
//TODO: When deleting and moving objects is implemented, don't forget to update
// the selection and the focused element!!
var state = this.iface.getState();
if (typeof _recPrev == "undefined")
_recPrev = false;
//Check whether the focused state has changed
if (_focused != this.getFocused())
if (egwBitIsSet(state, EGW_AO_STATE_FOCUSED) != _focused)
{
//Reset the focus of the formerly focused element
if (!_recPrev)
// Un-focus the currently focused object
var currentlyFocused = this.getFocusedObject();
if (currentlyFocused && currentlyFocused != this)
{
var focused = this.getRootObject().getFocusedObject();
if (focused)
{
focused.setFocused(false, this);
}
currentlyFocused.setFocused(false);
}
if (!_focused)
this.iface.setState(egwSetBit(state, EGW_AO_STATE_FOCUSED, _focused));
if (this.parent)
{
//If the object is not focused, reset the focus state of all children
for (var i = 0; i < this.children.length; i++)
{
if (this.children[i] != _recPrev)
{
this.children[i].setFocused(false, _recPrev);
}
}
this.parent.updateFocusedChild(this, _focused);
}
else
{
//Otherwise set the focused state of the parent to true
if (this.parent)
{
this.parent.setFocused(true, _recPrev);
}
}
//No perform the actual change in the interface state.
this.iface.setState(egwSetBit(this.iface.getState(), EGW_AO_STATE_FOCUSED,
_focused));
}
}
@ -837,8 +840,103 @@ egwActionObject.prototype.setFocused = function(_focused, _recPrev)
*/
egwActionObject.prototype.setSelected = function(_selected)
{
this.iface.setState(egwSetBit(this.iface.getState(), EGW_AO_STATE_SELECTED,
_selected));
var state = this.iface.getState();
if ((egwBitIsSet(state, EGW_AO_STATE_SELECTED) != _selected) &&
egwBitIsSet(state, EGW_AO_STATE_VISIBLE))
{
this.iface.setState(egwSetBit(state, EGW_AO_STATE_SELECTED, _selected));
if (this.parent)
{
this.parent.updateSelectedChildren(this, _selected || this.selectedChildren.length > 0);
}
}
}
/**
* Sets the selected state of all elements, including children
*/
egwActionObject.prototype.setAllSelected = function(_selected, _informParent)
{
if (typeof _informParent == "undefined")
_informParent = true;
var state = this.iface.getState();
// Update this element
if (egwBitIsSet(state, EGW_AO_STATE_SELECTED) != _selected)
{
this.iface.setState(egwSetBit(state, EGW_AO_STATE_SELECTED, _selected));
if (_informParent && this.parent)
{
this.parent.updateSelectedChildren(this, _selected);
}
}
// Update the children if the should be selected or if they should be
// deselected and there are selected children.
if (_selected || this.selectedChildren.length > 0)
{
for (var i = 0; i < this.children.length; i++)
{
this.children[i].setAllSelected(_selected, false);
}
}
// Copy the selected children list
this.selectedChildren = _selected ? this.children : [];
}
/**
* Updates the selectedChildren array each actionObject has in order to determine
* all selected children in a very fast manner.
* TODO: Has also to be updated, if an child is added/removed!
*/
egwActionObject.prototype.updateSelectedChildren = function(_child, _selected)
{
var id = this.selectedChildren.indexOf(_child);
var wasEmpty = this.selectedChildren.length == 0;
// Add or remove the given child from the selectedChildren list
if (_selected && id == -1)
{
this.selectedChildren.push(_child);
}
else if (!_selected && id != -1)
{
this.selectedChildren.splice(id, 1);
}
// If the emptieness of the selectedChildren array has changed, update the
// parent selected children array.
if (wasEmpty != this.selectedChildren.length == 0 && this.parent)
{
this.parent.updateSelectedChildren(this, wasEmpty);
}
}
/**
* Updates the focusedChild up to the container boundary.
*/
egwActionObject.prototype.updateFocusedChild = function(_child, _focused)
{
if (_focused)
{
this.focusedChild = _child;
}
else
{
if (this.focusedChild = _child)
{
this.focusedChild = null;
}
}
if (this.parent && !egwBitIsSet(this.flags, EGW_AO_FLAG_IS_CONTAINER))
{
this.parent.updateFocusedChild(_child, _focused);
}
}
/**
@ -893,6 +991,20 @@ egwActionObject.prototype.updateActionLinks = function(_actionLinks, _recursive,
this.children[i].updateActionLinks(_actionLinks, true, _doCreate);
}
}
if (this.getVisible())
{
this.registerActions();
}
}
/**
* Reconnects the actions.
*/
egwActionObject.prototype._reconnectCallback = function()
{
this.registeredImpls = [];
this.registerActions;
}
/**
@ -911,9 +1023,15 @@ egwActionObject.prototype.registerActions = function()
{
var impl = _egwActionClasses[group].implementation();
// Register a handler for that action with the interface of that object,
// the callback and this object as context for the callback
impl.registerAction(this.iface, this.executeActionImplementation, this);
if (this.registeredImpls.indexOf(impl) == -1)
{
// Register a handler for that action with the interface of that object,
// the callback and this object as context for the callback
if (impl.registerAction(this.iface, this.executeActionImplementation, this))
{
this.registeredImpls.push(impl);
}
}
}
}
}
@ -964,7 +1082,7 @@ egwActionObject.prototype.executeActionImplementation = function(_implContext, _
*/
egwActionObject.prototype.forceSelection = function()
{
var selected = this.getSelectedObjects();
var selected = this.getContainerRoot().getSelectedObjects();
// Check whether this object is in the list
var thisInList = selected.indexOf(this) != -1;
@ -972,11 +1090,11 @@ egwActionObject.prototype.forceSelection = function()
// If not, select it
if (!thisInList)
{
this.getContainerRoot().setAllSelected(false);
this.setSelected(true);
this._ifaceCallback(egwSetBit(this.getState(), EGW_AO_STATE_SELECTED, true),
EGW_AO_SHIFT_STATE_NONE);
selected = [this];
}
this.setFocused(true);
}
/**
@ -994,7 +1112,7 @@ egwActionObject.prototype.forceSelection = function()
egwActionObject.prototype.getSelectedLinks = function(_actionType)
{
// Get all objects in this container which are currently selected
var selected = this.getSelectedObjects();
var selected = this.getContainerRoot().getSelectedObjects();
var actionLinks = {};
var testedSelected = [];
@ -1138,9 +1256,12 @@ function egwActionObjectInterface()
// or not.
this.doSetState = function(_state, _outerCall) {};
this.doTriggerEvent = function(_event) {};
this._state = EGW_AO_STATE_NORMAL || EGW_AO_STATE_VISIBLE;
this._state = EGW_AO_STATE_NORMAL;
this.stateChangeCallback = null;
this.stateChangeContext = null;
this.reconnectActionsCallback = null;
this.reconnectActionsContext = null;
}
/**
@ -1153,23 +1274,50 @@ egwActionObjectInterface.prototype.setStateChangeCallback = function(_callback,
this.stateChangeContext = _context;
}
/**
* 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.
*/
egwActionObjectInterface.prototype.setReconnectActionsCallback = function(_callback, _context)
{
this.reconnectActionsCallback = _callback;
this.reconnectActionsContext = _context;
}
/**
* Will be called by the aoi if the actions have to be re-registered due to a
* DOM-Node exchange.
*/
egwActionObjectInterface.prototype.reconnectActions = function()
{
if (this.reconnectActionsCallback)
{
this.reconnectActionsCallback.call(this.reconnectActionsContext);
}
}
/**
* 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 boolean _selected Whether the object is selected or not.
* @param int _shiftState special keys which change how the state change will
* be treated.
* @param int _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
*/
egwActionObjectInterface.prototype._selectChange = function(_selected, _shiftState)
egwActionObjectInterface.prototype.updateState = function(_stateBit, _set, _shiftState)
{
//Set the EGW_AO_STATE_SELECTED bit accordingly and call the callback
this._state = egwSetBit(this._state, EGW_AO_STATE_SELECTED, _selected);
// Calculate the new state
var newState = egwSetBit(this._state, _stateBit, _set);
// Call the stateChangeCallback if the state really changed
if (this.stateChangeCallback)
{
this.stateChangeCallback.call(this.stateChangeContext,
this._state, _shiftState);
this._state = this.stateChangeCallback.call(this.stateChangeContext, newState,
_stateBit, _shiftState);
}
else
{
this._state = newState;
}
}
@ -1196,7 +1344,7 @@ egwActionObjectInterface.prototype.setState = function(_state)
if (_state != this._state)
{
this._state = _state;
this.doSetState(_state, true);
this.doSetState(_state);
}
}
@ -1210,7 +1358,6 @@ egwActionObjectInterface.prototype.getState = function()
return this._state;
}
/** egwActionObjectManager Object **/
/**

View File

@ -83,3 +83,30 @@ if (typeof Array.prototype.indexOf == "undefined")
};
}
/**
* Isolates the shift state from an event object
*/
function egwGetShiftState(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;
}
function egwPreventSelect(e)
{
if (egwGetShiftState(e) > EGW_AO_SHIFT_STATE_NONE)
{
this.onselectstart = function() {
return false;
}
return false;
}
}
function egwResetPreventSelect(elem)
{
}

View File

@ -88,7 +88,10 @@ function egwPopupActionImplementation()
}
return false;
}
return true;
}
return false;
}
ai.doUnregisterAction = function(_aoi)

View File

@ -0,0 +1,586 @@
/**
* 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 classes which are able to display an dynamic data view which
*/
/*
uses
egw_action,
egw_action_common,
egw_menu,
jquery;
*/
/**
* Main class for the grid view. The grid view is a combination of a classic
* list view with multiple columns and the tree view.
*
* @param object _parentNode is the DOM-Node the grid-view should be inserted into
* @param array _columns is an array of all colloumns the grid should be able to
* display.
* TODO: A column should be an object and become much more mighty (sorting,
* visibility, etc.)
*/
function egwGrid(_parentNode, _columns)
{
this.parentNode = _parentNode;
this.columns = _columns;
this.updateElems = [];
this.children = [];
this.inUpdate = false;
this.tbody = null;
// Append the grid base elements to the parent node
$(this.parentNode).append(this._buildBase());
}
/**
* Adds a new item to the grid and returns it
*
* @param string _id is an unique identifier of the new grid item. It can be searched
* lateron with the "getItemById" function.
* @param string _caption is the caption of the new element
* @param string _icon is the URL to the icon image
* @param object _columns is an object which can contain string entries with html
* for every column_id available in the grid.
* @returns object the newly created egwGridItem
*/
egwGrid.prototype.addItem = function(_id, _caption, _icon, _columns)
{
var item = new egwGridItem(this, null, _id, _caption, _icon, _columns);
this.children.push(item);
this.update(item);
return item;
}
/**
* Updates the given element - if the element is visible, it is added to the
* DOM-Tree if it hasn't been attached to it yet. If the object is already in
* the DOM-Tree, it will be rebuilt, which means, that the DOM-Data of its row
* will be replaced by a new one.
* If multiple updates are in progress, it is wise to group those by using
* the beginUpdate and endUpdate functions, as this saves some redundancy
* like re-colorizing the rows if a new one has been added.
*
* @param object _elem is the egwGridItem object, which will be updated
*/
egwGrid.prototype.update = function(_elem)
{
if (_elem.isVisible())
{
if (!this.inUpdate)
{
if (this._updateElement(_elem))
{
this._colorizeRows();
}
}
else
{
if (this.updateElems.indexOf(_elem) == -1)
{
this.updateElems.push(_elem);
}
}
}
}
/**
* Starts a group of updates: After beginUpdate has been called, the update function
* will collect the update wishes and execute them as soon as endUpdate is called.
* This brings a major performance improvement if lots of elements are added.
*/
egwGrid.prototype.beginUpdate = function()
{
this.inUpdate = true;
}
/**
* Ends the update grouping and actually executes the updates.
*/
egwGrid.prototype.endUpdate = function()
{
// Call the update function for all elements which wanted to be updated
// since the last beginUpdate call.
var added = false;
this.inUpdate = false;
for (var i = 0; i < this.updateElems.length; i++)
{
added = this._updateElement(this.updateElems[i]) || added;
}
this.updateElems = [];
// If elements have been (visibly) added to the tree, call the colorize rows
// function.
if (added)
{
this._colorizeRows();
}
}
/**
* Builds the base DOM-Structure of the grid and returns the outer object.
*/
egwGrid.prototype._buildBase = function()
{
var table = $(document.createElement("table"));
table.addClass("grid");
this.tbody = $(document.createElement("tbody"));
table.append(this.tbody);
this.tbody.append(this._buildHeader());
return table;
}
/**
* Builds the header row and returns it
*/
egwGrid.prototype._buildHeader = function()
{
var row = document.createElement("tr");
for (var i = 0; i < this.columns.length; i++)
{
var column = $(document.createElement("th"));
if (i == 0)
{
column.addClass("front");
}
column.html(this.columns[i].caption);
if (typeof this.columns[i].width != "undefined")
{
column.css("width", this.columns[i].width);
}
$(row).append(column);
}
return row;
}
/**
* Gives odd rows the additional "odd" CSS-class which creates the "zebra" structure.
*/
egwGrid.prototype._colorizeRows = function()
{
this.tbody.children().removeClass("odd");
$("tr:not(.hidden):odd", this.tbody).addClass("odd");
}
/**
* Internal function which actually performs the update of the given element as
* it is described in the update function.
*/
egwGrid.prototype._updateElement = function(_elem)
{
// If the element has to be inserted into the dom tree first, search for the
// proper position:
var insertAfter = null;
var oldRow = null;
if (_elem.isVisible())
{
if (!_elem.domData)
{
var parentChildren = _elem.parent ? _elem.parent.children : this.children;
var index = _elem.index();
// Fetch the node after which this one should be inserted
if (index > 0)
{
insertAfter = parentChildren[index - 1].lastInsertedChild().domData.row;
}
else
{
insertAfter = _elem.parent ? _elem.parent.domData.row :
this.tbody.children().get(0);
}
}
// Check whether the element already has a row attached to it
var row = _elem.buildRow(_elem.domData ? _elem.domData.row : null);
// Insert the row after the fetched element
if (insertAfter)
{
$(row).insertAfter(insertAfter);
return _elem.isVisible();
}
}
return false;
}
/** egwGridItem Class **/
/**
* The egwGridItem represents a single row inside an egwGrid. Each egwGridItem
* contains an egwActionObjectInterface-Object (can be recieved by calling getAOI()),
* which is used to interconnect with the egw_action framework.
*
* Don't create new egwGridItems yourself, use the addItem functions supplied by
* egwGrid and egwGridItem.
*
* @param object _grid is the parent egwGrid object
* @param object _parent is the parent egwGridItem. Null if no parent exists.
* @param string _caption is the caption of the grid item
* @param string _icon is the url to the icon image
* @param object _columns is an object which can contain string entries with html
* for every column_id available in the grid.
* TODO: Remove _canHaveChildren and replace with something better
*/
function egwGridItem(_grid, _parent, _id, _caption, _icon, _columns, _canHaveChildren)
{
if (typeof _canHaveChildren == "undefined")
{
_canHaveChildren = true;
}
// Setup the egwActionObjectInterface
this._setupAOI();
this.clickCallback = null;
this.grid = _grid;
this.parent = _parent;
this.id = _id;
this.caption = _caption;
this.icon = _icon;
this.columns = _columns;
if (_canHaveChildren)
{
this.children = [];
}
else
{
this.children = false;
}
this.opened = false;
this.domData = null;
}
/**
* Creates AOI instance of the item and overwrites all necessary functions.
*/
egwGridItem.prototype._setupAOI = function()
{
this.aoi = new egwActionObjectInterface;
// The default state of an aoi is EGW_AO_STATE_NORMAL || EGW_AO_STATE_VISIBLE -
// egwGridItems are not necessarily visible by default
this.aoi._state = EGW_AO_STATE_NORMAL;
this.aoi.gridItem = this;
this.aoi.doSetState = gridAOIDoSetState;
this.aoi.getDOMNode = gridAOIGetDOMNode;
}
function gridAOIDoSetState(_state, _shiftState)
{
if (this.gridItem.domData)
{
$(this.gridItem.domData.row).toggleClass("selected", egwBitIsSet(_state,
EGW_AO_STATE_SELECTED));
$(this.gridItem.domData.row).toggleClass("focused", egwBitIsSet(_state,
EGW_AO_STATE_FOCUSED));
}
}
function gridAOIGetDOMNode()
{
return this.gridItem.domData ? this.gridItem.domData.row : null;
}
/**
* Returns the actionObjectInterface object of this grid item.
*/
egwGridItem.prototype.getAOI = function()
{
return this.aoi;
}
/**
* Returns whether the grid item is actually visible - in this case this means,
* whether the element is a child of an item which is currently not opened.
*/
egwGridItem.prototype.isVisible = function()
{
return (this.parent ? this.parent.opened && this.parent.isVisible() : true);
}
/**
* Returns the depth of this entry inside the item tree.
*/
egwGridItem.prototype.getDepth = function()
{
return (this.parent ? this.parent.getDepth() + 1 : 0);
}
/**
* Returns the last child which is inserted into the DOM-Tree. Used by the update
* function to determine where new rows should be inserted into the dom tree.
*/
egwGridItem.prototype.lastInsertedChild = function()
{
for (var i = (this.children.length - 1); i >= 0; i--)
{
if (this.children[i].domData)
{
return this.children[i].lastInsertedChild();
}
}
return this;
}
/**
* Adds a new child item to this item. Parameters are equivalent to those of
* egwGrid.addItem.
*/
egwGridItem.prototype.addItem = function(_id, _caption, _icon, _columns)
{
//If the element was not designed to have children, update it in the grid
if (!this.children)
{
this.children = [];
this.grid.update(this);
}
var item = new egwGridItem(this.grid, this, _id, _caption, _icon, _columns);
this.children.push(item);
this.grid.update(item);
return item;
}
/**
* Returns the position of this element inside it's parent children list.
*/
egwGridItem.prototype.index = function()
{
if (this.parent)
{
return this.parent.children.indexOf(this);
}
else
{
return this.grid.children.indexOf(this);
}
}
/**
* Internal function which updates the visibility of this item and its children
* if the open state of the element is changed.
*/
egwGridItem.prototype._updateVisibility = function(_visible)
{
// Set the visibility of this object - if it is at all inserted in the dom-
// tree.
if (this.domData)
{
$(this.domData.row).toggleClass("hidden", !_visible);
// Deselect this row, if it is no longer visible
this.aoi.updateState(EGW_AO_STATE_VISIBLE, _visible);
// Update the visibility of all children
for (var i = 0; i < this.children.length; i++)
{
this.children[i]._updateVisibility(_visible && this.opened);
}
}
}
/**
* Toggles the visibility of the child elements.
*
* @param boolean _open specifies whether this item is opened or closed.
*/
egwGridItem.prototype.setOpened = function(_open)
{
var self = this;
function doSetOpen()
{
// Set the arrow direction
if (_open)
{
self.domData.arrow.addClass("opened");
self.domData.arrow.removeClass("closed");
}
else
{
self.domData.arrow.addClass("closed");
self.domData.arrow.removeClass("opened");
}
// Update all DOM rows
if (_open)
{
self.grid.beginUpdate();
for (var i = 0; i < self.children.length; i++)
{
var child = self.children[i];
if (!child.domData)
{
self.grid.update(child);
}
}
self.grid.endUpdate();
}
// And make them (in)visible
self._updateVisibility(true);
self.grid._colorizeRows();
}
if (this.opened != _open && this.children !== false)
{
this.opened = _open;
if (this.children.length == 0 && _open)
{
// alert("JSON Callback")
}
else
{
doSetOpen();
}
}
}
/**
* Builds the actual DOM-representation of the item and attaches all events to the
* DOM-Nodes.
*
* @param object _row If an existing DOM-Item should be updated, it can be passed
* here and the updated objects will be inserted inside of the row object. Defaults
* to null.
*/
egwGridItem.prototype.buildRow = function(_row)
{
// Build the container row
var row = null;
if (typeof _row == "undefined" || !_row)
{
row = document.createElement("tr");
}
else
{
row = _row;
$(row).empty();
}
$(row).toggleClass("hidden", !this.isVisible())
// Build the indentation object
var indentation = $(document.createElement("span"));
indentation.addClass("indentation");
indentation.css("width", (this.getDepth() * 12) + "px");
// Build the arrow element
var arrow = $(document.createElement("span"));
arrow.addClass("arrow");
if (this.children !== false)
{
arrow.addClass(this.opened ? "opened" : "closed");
arrow.click(this, function(e) {
e.data.setOpened(!e.data.opened);
return false;
});
}
// Build the icon element
icon = $(document.createElement("img"));
if (this.icon)
{
icon.attr("src", this.icon);
}
icon.addClass("icon");
// Build the caption
var caption = $(document.createElement("span"));
caption.text(this.caption);
caption.addClass("caption");
// Build the td surrounding those elements
var column_caption = $(document.createElement("td"));
column_caption.append(indentation, arrow, icon, caption);
column_caption.mousedown(egwPreventSelect);
column_caption.click(this, function(e) {
egwResetPreventSelect(this);
this.onselectstart = null;
e.data._columnClick(egwGetShiftState(e), 0);
});
// Append the column to the row
$(row).append(column_caption);
for (var i = 1; i < this.grid.columns.length; i++) // Skips the front column
{
var content = "";
var gridcol = this.grid.columns[i];
if (typeof this.columns[gridcol.id] != "undefined")
{
content = this.columns[gridcol.id];
}
else
{
if (typeof gridcol["default"] != "undefined")
{
content = gridcol["default"];
}
}
// Create a column and append it to the row
var column = $(document.createElement("td"));
column.html(content);
column.mousedown(egwPreventSelect);
column.click({"item": this, "col": gridcol.id}, function(e) {
egwPreventSelect(this);
e.data.item._columnClick(egwGetShiftState(e), e.data.col);
});
$(row).append(column);
}
this.domData = {
"row": row,
"arrow": arrow,
"icon": icon,
"caption": caption
}
// The item is now visible
this.aoi.updateState(EGW_AO_STATE_VISIBLE, true);
return row;
}
egwGridItem.prototype._columnClick = function(_shiftState, _column)
{
var state = this.aoi.getState();
var isSelected = egwBitIsSet(state, EGW_AO_STATE_SELECTED);
this.aoi.updateState(EGW_AO_STATE_SELECTED,
!egwBitIsSet(_shiftState, EGW_AO_SHIFT_STATE_MULTI) || !isSelected,
_shiftState);
}

View File

@ -0,0 +1,94 @@
body {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
body, td, th {
font-family: Verdana,Arial,Helvetica,sans-serif;
font-size: 11px;
}
.grid {
width: 100%;
border-spacing: 0px;
}
.grid tr.hidden {
display: none;
}
.grid td, .grid th {
border: 1px solid white;
}
.grid tr.focused td {
border: 1px dotted black;
}
.grid tr.selected td {
background-image: url(imgs/select_overlay.png);
background-position: center;
background-repeat: repeat-x;
}
.grid span.arrow {
display: inline-block;
vertical-align: middle;
width: 8px;
height: 8px;
background-repeat: no-repeat;
margin-right: 2px;
-moz-user-select: none;
-khtml-user-select: none;
user-select: none;
}
.grid span.arrow.opened {
cursor: pointer;
background-image: url(imgs/arrows.png);
background-position: -8px 0;
}
.grid span.arrow.closed {
cursor: pointer;
background-image: url(imgs/arrows.png);
background-position: 0 0;
}
.grid tr.odd {
background-color: #F1F1F1;
}
.grid th {
background-color: #E0E0E0;
font-weight: normal;
padding: 5px;
text-align: left;
}
.grid td {
padding: 0 5px 0 5px;
vertical-align: middle;
}
.grid th.front {
font-weight: bold;
}
.grid span.caption {
vertical-align: middle;
}
.grid span.indentation {
display: inline-block;
}
.grid img.icon {
vertical-align: middle;
margin: 2px 5px 2px 2px;
-moz-user-select: none;
-khtml-user-select: none;
user-select: none;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 240 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 239 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 393 B

View File

@ -0,0 +1,74 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="16"
height="8"
id="svg2"
version="1.1"
inkscape:version="0.48.0 r9654"
sodipodi:docname="Neues Dokument 1">
<defs
id="defs4" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="1.06"
inkscape:cx="-123.46433"
inkscape:cy="113.30747"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="true"
inkscape:window-width="1600"
inkscape:window-height="823"
inkscape:window-x="0"
inkscape:window-y="24"
inkscape:window-maximized="1">
<inkscape:grid
type="xygrid"
id="grid2985"
empspacing="5"
visible="true"
enabled="true"
snapvisiblegridlinesonly="true" />
</sodipodi:namedview>
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Ebene 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-1044.3622)">
<path
style="fill:#5b5b5b;fill-opacity:1;stroke:#3a3a3a;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 0.75446427,1045.3622 6.00000033,3 -6.00000033,3 z"
id="path2987"
inkscape:connector-curvature="0" />
<path
style="fill:#5b5b5b;fill-opacity:1;stroke:#3a3a3a;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 15,1045.1392 -2.999999,6 -2.999997,-6 z"
id="path2987-7"
inkscape:connector-curvature="0" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 695 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 377 B

View File

@ -0,0 +1,103 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="64"
height="64"
id="svg2"
version="1.1"
inkscape:version="0.48.0 r9654"
sodipodi:docname="select_overlay.svg"
inkscape:export-filename="/home/andreas/source/egroupware/egw_grid/select_overlay.png"
inkscape:export-xdpi="90"
inkscape:export-ydpi="90">
<defs
id="defs4">
<linearGradient
id="linearGradient3763">
<stop
style="stop-color:#1d60ba;stop-opacity:0.58749998;"
offset="0"
id="stop3765" />
<stop
id="stop3771"
offset="0.5"
style="stop-color:#d9e6fa;stop-opacity:0.61666667;" />
<stop
style="stop-color:#5292e4;stop-opacity:0.55000001;"
offset="1"
id="stop3767" />
</linearGradient>
<linearGradient
id="linearGradient3755">
<stop
style="stop-color:#000000;stop-opacity:1;"
offset="0"
id="stop3757" />
<stop
style="stop-color:#000000;stop-opacity:0"
offset="1"
id="stop3759" />
</linearGradient>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient3763"
id="linearGradient3769"
x1="32.324883"
y1="988.21747"
x2="32.324883"
y2="1053.0642"
gradientUnits="userSpaceOnUse" />
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:zoom="1.979899"
inkscape:cx="74.885539"
inkscape:cy="30.467687"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:window-width="1600"
inkscape:window-height="823"
inkscape:window-x="0"
inkscape:window-y="24"
inkscape:window-maximized="1" />
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Ebene 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-988.36218)">
<rect
style="fill:url(#linearGradient3769);fill-opacity:1;fill-rule:nonzero;stroke:none"
id="rect2985"
width="64"
height="64"
x="0"
y="988.36218" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

@ -106,9 +106,9 @@
var state = getShiftState(e);
// "Normal" Listbox behaviour
aoi.doSetState(egwSetBit(aoi.getState(), EGW_AO_STATE_SELECTED,
!egwBitIsSet(state, EGW_AO_SHIFT_STATE_MULTI) || !selected),
false, 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,
@ -117,22 +117,16 @@
});
$(aoi.checkBox).change(function() {
aoi.doSetState(egwSetBit(aoi.getState(), EGW_AO_STATE_SELECTED,
this.checked), false, EGW_AO_SHIFT_STATE_MULTI);
aoi.updateState(EGW_AO_STATE_SELECTED, this.checked, EGW_AO_SHIFT_STATE_MULTI);
});
aoi.doSetState = function(_state, _outerCall, _shiftState) {
aoi.doSetState = function(_state) {
var selected = egwBitIsSet(_state, EGW_AO_STATE_SELECTED);
this.checkBox.checked = selected;
$(this.node).toggleClass('focused',
egwBitIsSet(_state, EGW_AO_STATE_FOCUSED));
$(this.node).toggleClass('selected',
selected);
if (! _outerCall)
{
this._selectChange(egwBitIsSet(_state,
EGW_AO_STATE_SELECTED), _shiftState);
}
}
return aoi;
@ -258,8 +252,6 @@
obj.updateActionLinks(listboxFileLinks);
else
obj.updateActionLinks(listboxFolderLinks);
obj.registerActions();
});
$("#selectAll").click(function() {

View File

@ -0,0 +1,175 @@
<html>
<head>
<title>Grid Test</title>
<!-- Basic action stuff -->
<script src="../egw_action.js"></script>
<script src="../egw_action_common.js"></script>
<!-- Grid stuff -->
<script src="../egw_grid.js"></script>
<script src="js/jquery.js"></script>
<link rel="stylesheet" href="grid.css"/>
<!-- 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_menu.js"></script>
<script src="../egw_menu_dhtmlx.js"></script>
</head>
<body>
<div id="container">
</div>
<script>
var grid = null;
var actionManager = null;
var objectManager = null;
function alertClicked(_action, _senders)
{
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 + "'");
}
$(document).ready(function() {
actionManager = new egwActionManager();
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",
"default": true
},
{
"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
}
]
);
objectManager = new egwActionObjectManager("", actionManager);
grid = new egwGrid(document.getElementById("container"),
[
{
"caption": "Name",
"width": "50%"
},
{
"id": "size",
"caption": "Size"
},
{
"id": "rights",
"caption": "<a href=\"http://www.google.de/\">UNIX Filerights</a>",
"default": "---------"
},
{
"id": "mime",
"caption": "File-Type/MIME"
}
]
);
var listboxFolderLinks = [
{"actionId": "folder_open", "enabled": true},
{"actionId": "file_compress_email", "enabled": true},
{"actionId": "file_compress", "enabled": true},
{"actionId": "file_delete", "enabled": true}
];
var info =
{
"size": "16KiB",
"mime": "Directory"
}
grid.beginUpdate();
function recurse_add(item, obj, depth)
{
for (var i = 1; i <= 6; i++)
{
var id = "file" + i;
var it = item.addItem(id, "Test" + i, "imgs/mime16_directory.png", info);
var _obj = obj.addObject(id, it.getAOI());
_obj.updateActionLinks(listboxFolderLinks);
if (depth < 4)
recurse_add(it, _obj, depth + 1);
}
}
recurse_add(grid, objectManager, 0);
grid.endUpdate();
});
</script>
</body>
</html>