- 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
@ -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)
|
||||
{
|
||||
result.push(list[i]);
|
||||
for (var i = 0; i < this.selectedChildren.length; 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,56 +809,29 @@ 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)
|
||||
{
|
||||
//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);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//Otherwise set the focused state of the parent to true
|
||||
this.iface.setState(egwSetBit(state, EGW_AO_STATE_FOCUSED, _focused));
|
||||
if (this.parent)
|
||||
{
|
||||
this.parent.setFocused(true, _recPrev);
|
||||
this.parent.updateFocusedChild(this, _focused);
|
||||
}
|
||||
}
|
||||
|
||||
//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();
|
||||
|
||||
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
|
||||
impl.registerAction(this.iface, this.executeActionImplementation, this);
|
||||
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 **/
|
||||
|
||||
/**
|
||||
|
@ -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)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -88,7 +88,10 @@ function egwPopupActionImplementation()
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
ai.doUnregisterAction = function(_aoi)
|
||||
|
586
phpgwapi/js/egw_action/egw_grid.js
Normal 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);
|
||||
}
|
||||
|
94
phpgwapi/js/egw_action/test/grid.css
Normal 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;
|
||||
}
|
BIN
phpgwapi/js/egw_action/test/imgs/arrow_down.png
Executable file
After Width: | Height: | Size: 240 B |
BIN
phpgwapi/js/egw_action/test/imgs/arrow_left.png
Executable file
After Width: | Height: | Size: 239 B |
BIN
phpgwapi/js/egw_action/test/imgs/arrows.png
Normal file
After Width: | Height: | Size: 393 B |
74
phpgwapi/js/egw_action/test/imgs/arrows.svg
Normal 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 |
BIN
phpgwapi/js/egw_action/test/imgs/mime16_directory.png
Normal file
After Width: | Height: | Size: 695 B |
BIN
phpgwapi/js/egw_action/test/imgs/select_overlay.png
Normal file
After Width: | Height: | Size: 377 B |
103
phpgwapi/js/egw_action/test/imgs/select_overlay.svg
Normal 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 |
@ -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() {
|
||||
|
175
phpgwapi/js/egw_action/test/test_grid.html
Normal 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>
|