Got selection working; only problem left with popup menus is, that entries don't get focused on rightclick

This commit is contained in:
Andreas Stöckel 2012-03-28 15:36:17 +00:00
parent b55250b81a
commit 0ae151e638
4 changed files with 275 additions and 94 deletions

View File

@ -16,7 +16,6 @@
et2_dataview_interfaces; et2_dataview_interfaces;
et2_dataview_controller_selection; et2_dataview_controller_selection;
et2_dataview_view_aoi;
et2_dataview_view_row; et2_dataview_view_row;
egw_action.egw_action; egw_action.egw_action;
@ -64,7 +63,6 @@ var et2_dataview_controller = Class.extend({
this._rowCallback = _rowCallback; this._rowCallback = _rowCallback;
this._linkCallback = _linkCallback; this._linkCallback = _linkCallback;
this._context = _context; this._context = _context;
this._actionObjectManager = _actionObjectManager;
// Initialize the "index map" which contains all currently displayed // Initialize the "index map" which contains all currently displayed
// containers hashed by the "index" // containers hashed by the "index"
@ -81,13 +79,14 @@ var et2_dataview_controller = Class.extend({
this._grid.setDataCallback(this._gridCallback, this); this._grid.setDataCallback(this._gridCallback, this);
// Create the selection manager // Create the selection manager
// this._selectionMgr = new et2_dataview_selectionManager(this._indexMap); this._selectionMgr = new et2_dataview_selectionManager(this._indexMap,
_actionObjectManager, this._selectionFetchRange, this);
}, },
destroy: function () { destroy: function () {
// Destroy the selection manager // Destroy the selection manager
// this._selectionMgr.free(); this._selectionMgr.free();
// Clear the selection timeout // Clear the selection timeout
this._clearTimer(); this._clearTimer();
@ -171,8 +170,7 @@ var et2_dataview_controller = Class.extend({
{ {
this._indexMap[_idx] = { this._indexMap[_idx] = {
"row": null, "row": null,
"uid": null, "uid": null
"ao": null
}; };
} }
@ -452,30 +450,23 @@ var et2_dataview_controller = Class.extend({
this.entry this.entry
); );
// If we have an object manager, create a new action object for this var links = null;
// row and a new row AOI
// Get the action links if the links callback is set
if (this.self._linkCallback) if (this.self._linkCallback)
{ {
// Call the link callback links = this.self._linkCallback.call(
var links = this.self._linkCallback.call( this.self._context,
this.self._context, _data,
_data, this.entry.idx,
this.entry.idx, this.entry.uid
this.entry.uid
); );
// Create the action object interface
var aoi = new et2_dataview_rowAOI(tr);
// Create the action object
var ao = this.entry.ao =
this.self._actionObjectManager.addObject(this.entry.uid, aoi);
ao.updateActionLinks(links);
// Hook the row into the selection manager
// this.self._selectionMgr.hook(ao, aoi, this.entry.uid);
} }
// Register the row in the selection manager
this.self._selectionMgr.registerRow(this.entry.uid, this.entry.idx,
tr, links);
// Invalidate the current row entry // Invalidate the current row entry
this.entry.row.invalidate(); this.entry.row.invalidate();
} }
@ -485,8 +476,13 @@ var et2_dataview_controller = Class.extend({
* *
*/ */
_destroyCallback: function (_row) { _destroyCallback: function (_row) {
// Remove the action object that corresponds to that row
this.entry.ao.remove(); // Unregister the row from the selection manager
if (this.entry.row)
{
var tr = this.entry.row.getDOMNode();
this.self._selectionMgr.unregisterRow(this.entry.uid, tr);
}
// There is no further row connected to the entry // There is no further row connected to the entry
this.entry.row = null; this.entry.row = null;
@ -621,6 +617,12 @@ var et2_dataview_controller = Class.extend({
entry.idx = newIdx; entry.idx = newIdx;
newMap[newIdx] = entry; newMap[newIdx] = entry;
} }
else
{
// Make sure the old entry gets invalidated
entry.idx = null;
entry.row = null;
}
} }
// Make the new index map the current index map // Make the new index map the current index map
@ -656,6 +658,16 @@ var et2_dataview_controller = Class.extend({
// Update the total element count in the grid // Update the total element count in the grid
this.self._grid.setTotalCount(_response.total); this.self._grid.setTotalCount(_response.total);
},
_selectionFetchRange: function (_range, _callback, _context) {
this._dataProvider.dataFetch(
{ "start": _range.top, "num_rows": _range.bottom - _range.top + 1,
"no_data": true },
function (_response) {
_callback.call(_context, _response.order);
}
);
} }
}); });

View File

@ -10,21 +10,28 @@
* @version $Id$ * @version $Id$
*/ */
/*egw:uses
et2_dataview_view_aoi;
*/
/** /**
* The selectioManager is internally used by the et2_dataview_controller class * The selectioManager is internally used by the et2_dataview_controller class
* to manage the row selection. * to manage the row selection.
*/ */
var et2_dataview_selectionManager = Class.extend({ var et2_dataview_selectionManager = Class.extend({
init: function (_indexMap) { init: function (_indexMap, _actionObjectManager, _queryRangeCallback,
// Copy the reference to the index map _context) {
// Copy the arguments
this._indexMap = _indexMap; this._indexMap = _indexMap;
this._actionObjectManager = _actionObjectManager;
this._queryRangeCallback = _queryRangeCallback;
this._context = _context;
// Internal map which contains all curently selected uids // Internal map which contains all curently selected uids and their
this._selectedUids = {}; // state
this._registeredRows = {};
// Controls whether the selection is currently inverted (e.g. after this._focusedEntry = null;
// selectAll)
this._invertSelection = false; this._invertSelection = false;
}, },
@ -32,86 +39,247 @@ var et2_dataview_selectionManager = Class.extend({
this._indexMap = _indexMap; this._indexMap = _indexMap;
}, },
/** registerRow: function (_uid, _idx, _tr, _links) {
* Resets the selection state of all selected elements.
*/ // Get the corresponding entry from the registered rows array
resetSelection: function () { var entry = this._getRegisteredRowsEntry(_uid);
// Iterate over the index map and reset the selection flag of all rows
for (var key in this._indexMap) // Create the AOI for the tr
if (!entry.tr)
{ {
if (this._indexMap[key].ao) // Create the AOI which is used internally in the selection manager
// this AOI is not connected to the AO, as the selection manager
// cares about selection etc.
entry.aoi = new et2_dataview_rowAOI(_tr);
entry.aoi.setStateChangeCallback(
function (_newState, _changedBit, _shiftState) {
if (_changedBit === EGW_AO_STATE_SELECTED)
{
// Call the select handler
this._handleSelect(
_uid,
entry,
egwBitIsSet(_shiftState, EGW_AO_SHIFT_STATE_BLOCK),
egwBitIsSet(_shiftState, EGW_AO_SHIFT_STATE_MULTI)
);
}
}, this);
// Create AOI
if (_links)
{ {
this._indexMap[key].ao.setSelected(false); var dummyAOI = new egwActionObjectInterface();
dummyAOI.getDOMNode = function () {return _tr};
// Create an action object for the tr and connect it to a dummy AOI
entry.ao = this._actionObjectManager.addObject(_uid, dummyAOI);
entry.ao.updateActionLinks(_links);
} }
} }
// Reset the internal representation and the inversion flag // Update the entry
this._selectedUids = {}; entry.idx = _idx;
entry.tr = _tr;
// Update the visible state of the _tr
this._updateEntryState(entry, entry.state);
},
unregisterRow: function (_uid, _tr) {
if (typeof this._registeredRows[_uid] !== "undefined"
&& this._registeredRows[_uid].tr === _tr)
{
this._registeredRows[_uid].tr = null;
this._registeredRows[_uid].aoi = null;
// Remove the action object from its container
if (this._registeredRows[_uid].ao)
{
this._registeredRows[_uid].ao.remove();
this._registeredRows[_uid].ao = null;
}
if (this._registeredRows[_uid].state === EGW_AO_STATE_NORMAL)
{
delete this._registeredRows[_uid];
}
}
},
resetSelection: function () {
this._invertSelection = false; this._invertSelection = false;
for (var key in this._registeredRows)
{
this.setSelected(key, false);
}
}, },
/** setSelected: function (_uid, _selected) {
* Marks the given uid as selected. var entry = this._getRegisteredRowsEntry(_uid);
*/ this._updateEntryState(entry,
uidAddSelection: function (_uid) { egwSetBit(entry.state, EGW_AO_STATE_SELECTED, _selected));
this._selectedUids[_uid] = true;
}, },
/** setFocused: function (_uid, _focused) {
* Removes the selection from the given uid. // Reset the state of the currently focused entry
*/ if (this._focusedEntry)
uidRemoveSelection: function (_uid) { {
delete this._selectedUids[_uid]; this._updateEntryState(this._focusedEntry,
egwSetBit(this._focusedEntry.state, EGW_AO_STATE_FOCUSED,
false));
this._focusedEntry = null;
}
// Mark the new given uid as focused
if (_focused)
{
var entry = this._focusedEntry = this._getRegisteredRowsEntry(_uid);
this._updateEntryState(entry,
egwSetBit(entry.state, EGW_AO_STATE_FOCUSED, true));
}
}, },
/** selectAll: function () {
* Returns whether the given uid is selected or not. // Reset the selection
*/ this.resetSelection();
uidIsSelected: function (_uid) {
return (!this._invertSelection) === // Set the "invert selection" flag
(this._selectedUids[_uid] ? true : false); this._invertSelection = true;
// Update the selection
for (var key in this._registeredRows)
{
var entry = this._registeredRows[key];
this._updateEntryState(entry, entry.state);
}
}, },
/**
* Hooks into the given action object / action object interface in order
* to handle selection.
*/
hook: function (_ao, _aoi, _uid) {
// Hook into the action object state change handler, as we need /** -- PRIVATE FUNCTIONS -- **/
// our own selection code
// Big TODO: Remove the old selection handling code from
// egwAction once it is no longer used outside et2 applications
_aoi.setStateChangeCallback(
function (_newState, _changedBit, _shiftState) {
var selected = egwBitIsSet(_newState, EGW_AO_STATE_SELECTED);
// Deselect all other objects inside this container, if the "MULTI" shift- _updateState: function (_uid, _state) {
// state is not set var entry = this._getRegisteredRowsEntry(_uid);
if (!egwBitIsSet(_shiftState, EGW_AO_SHIFT_STATE_MULTI))
{ this._updateEntryState(_entry, _state);
this.resetSelection();
return entry;
},
_updateEntryState: function (_entry, _state) {
// Update the state of the entry
_entry.state = _state;
if (this._invertSelection)
{
_state ^= EGW_AO_STATE_SELECTED;
}
// Update the state if it has changed
if ((_entry.aoi && _entry.aoi.getState() !== _state) || _entry.state != _state)
{
// Update the visual state
if (_entry.aoi)
{
_entry.aoi.setState(_state);
}
// Delete the element if state was set to "NORMAL" and there is
// no tr
if (_state === EGW_AO_STATE_NORMAL && !_entry.tr)
{
delete this._registeredRows[_entry.uid];
}
}
},
_getRegisteredRowsEntry: function (_uid) {
if (typeof this._registeredRows[_uid] === "undefined")
{
this._registeredRows[_uid] = {
"uid": _uid,
"idx": null,
"state": EGW_AO_STATE_NORMAL,
"tr": null,
"aoi": null,
"ao": null
};
}
return this._registeredRows[_uid];
},
_handleSelect: function (_uid, _entry, _shift, _ctrl) {
// If not "_ctrl" is set, reset the selection
if (!_ctrl)
{
this.resetSelection();
}
// Mark the element that was clicked as selected
var entry = this._getRegisteredRowsEntry(_uid);
this.setSelected(_uid,
!_ctrl || !egwBitIsSet(entry.state, EGW_AO_STATE_SELECTED));
// Focus the element if shift is not pressed
if (!_shift)
{
this.setFocused(_uid, true);
}
else if (this._focusedEntry)
{
this._selectRange(this._focusedEntry.idx, _entry.idx);
}
},
_selectRange: function (_start, _stop) {
// Contains ranges that are not currently in the index map and that have
// to be queried
var queryRanges = [];
// Iterate over the given range and select the elements in the range
// from _start to _stop
var naStart = false;
var s = Math.min(_start, _stop);
var e = Math.max(_stop, _start);
for (var i = s; i <= e; i++)
{
if (typeof this._indexMap[i] !== "undefined" &&
this._indexMap[i].uid)
{
// Add the range to the "queryRanges"
if (naStart !== false) {
queryRanges.push(et2_bounds(naStart, i - 1));
naStart = false;
} }
// Update the internal status of the uid // Select the element
if (selected) this.setSelected(this._indexMap[i].uid, true);
{ } else if (naStart === false) {
this.uidAddSelection(_uid); naStart = i;
} }
else }
{
this.uidRemoveSelection(_uid);
}
_ao.setSelected(selected); // Add the last range to the "queryRanges"
if (naStart !== false) {
queryRanges.push(et2_bounds(naStart, i - 1));
naStart = false;
}
return _newState; // Query all unknown ranges from the server
for (var i = 0; i < queryRanges.length; i++)
}, this); {
this._queryRangeCallback.call(this._context, queryRanges[i],
// Set the selection state of the ao function (_order) {
_ao.setSelected(this.uidIsSelected(_uid)); for (var j = 0; j < _order.length; j++)
{
this.setSelected(_order[j], true);
}
}, this);
}
} }
}); });

View File

@ -14,6 +14,7 @@
.egwGridView_grid tr.focused td { .egwGridView_grid tr.focused td {
background-image: url(gfx/focused_hatching.png); background-image: url(gfx/focused_hatching.png);
background-color: #ad98e8 !important;
background-repeat: repeat; background-repeat: repeat;
} }

View File

@ -153,7 +153,7 @@ egw.extend("data", egw.MODULE_APP_LOCAL, function (_app, _wnd) {
_filters, _filters,
_widgetId, _widgetId,
egw.dataKnownUIDs(_app), egw.dataKnownUIDs(_app),
lastModification _queriedRange["no_data"] ? 0xFFFFFFFFFFFF : lastModification
], ],
function(result) { function(result) {
parseServerResponse(result, _callback, _context); parseServerResponse(result, _callback, _context);