@qooxdoo/framework
Version:
The JS Framework for Coders
1,821 lines (1,504 loc) • 64.2 kB
JavaScript
/* ************************************************************************
qooxdoo - the new era of web development
http://qooxdoo.org
Copyright:
2006 STZ-IDA, Germany, http://www.stz-ida.de
License:
MIT: https://opensource.org/licenses/MIT
See the LICENSE file in the project's top-level directory for details.
Authors:
* Til Schneider (til132)
* Fabian Jakobs (fjakobs)
* Jonathan Weiß (jonathan_rass)
************************************************************************ */
/**
* Table
*
* A detailed description can be found in the package description
* {@link qx.ui.table}.
*
* @childControl statusbar {qx.ui.basic.Label} label to show the status of the table
* @childControl column-button {qx.ui.table.columnmenu.Button} button to open the column menu
*/
qx.Class.define("qx.ui.table.Table",
{
extend : qx.ui.core.Widget,
include : qx.ui.core.MDragDropScrolling,
/*
*****************************************************************************
CONSTRUCTOR
*****************************************************************************
*/
/**
* @param tableModel {qx.ui.table.ITableModel ? null}
* The table model to read the data from.
*
* @param custom {Map ? null}
* A map provided to override the various supplemental classes allocated
* within this constructor. Each property must be a function which
* returns an object instance, as indicated by shown the defaults listed
* here:
*
* <dl>
* <dt>initiallyHiddenColumns</dt>
* <dd>
* {Array?}
* A list of column numbers that should be initially invisible. Any
* column not mentioned will be initially visible, and if no array
* is provided, all columns will be initially visible.
* </dd>
* <dt>selectionManager</dt>
* <dd><pre class='javascript'>
* function(obj)
* {
* return new qx.ui.table.selection.Manager(obj);
* }
* </pre></dd>
* <dt>selectionModel</dt>
* <dd><pre class='javascript'>
* function(obj)
* {
* return new qx.ui.table.selection.Model(obj);
* }
* </pre></dd>
* <dt>tableColumnModel</dt>
* <dd><pre class='javascript'>
* function(obj)
* {
* return new qx.ui.table.columnmodel.Basic(obj);
* }
* </pre></dd>
* <dt>tablePaneModel</dt>
* <dd><pre class='javascript'>
* function(obj)
* {
* return new qx.ui.table.pane.Model(obj);
* }
* </pre></dd>
* <dt>tablePane</dt>
* <dd><pre class='javascript'>
* function(obj)
* {
* return new qx.ui.table.pane.Pane(obj);
* }
* </pre></dd>
* <dt>tablePaneHeader</dt>
* <dd><pre class='javascript'>
* function(obj)
* {
* return new qx.ui.table.pane.Header(obj);
* }
* </pre></dd>
* <dt>tablePaneScroller</dt>
* <dd><pre class='javascript'>
* function(obj)
* {
* return new qx.ui.table.pane.Scroller(obj);
* }
* </pre></dd>
* <dt>tablePaneModel</dt>
* <dd><pre class='javascript'>
* function(obj)
* {
* return new qx.ui.table.pane.Model(obj);
* }
* </pre></dd>
* <dt>columnMenu</dt>
* <dd><pre class='javascript'>
* function()
* {
* return new qx.ui.table.columnmenu.Button();
* }
* </pre></dd>
* </dl>
*/
construct : function(tableModel, custom)
{
this.base(arguments);
//
// Use default objects if custom objects are not specified
//
if (!custom) {
custom = { };
}
if (custom.initiallyHiddenColumns) {
this.setInitiallyHiddenColumns(custom.initiallyHiddenColumns);
}
if (custom.selectionManager) {
this.setNewSelectionManager(custom.selectionManager);
}
if (custom.selectionModel) {
this.setNewSelectionModel(custom.selectionModel);
}
if (custom.tableColumnModel) {
this.setNewTableColumnModel(custom.tableColumnModel);
}
if (custom.tablePane) {
this.setNewTablePane(custom.tablePane);
}
if (custom.tablePaneHeader) {
this.setNewTablePaneHeader(custom.tablePaneHeader);
}
if (custom.tablePaneScroller) {
this.setNewTablePaneScroller(custom.tablePaneScroller);
}
if (custom.tablePaneModel) {
this.setNewTablePaneModel(custom.tablePaneModel);
}
if (custom.columnMenu) {
this.setNewColumnMenu(custom.columnMenu);
}
this._setLayout(new qx.ui.layout.VBox());
// Create the child widgets
this.__scrollerParent = new qx.ui.container.Composite(new qx.ui.layout.HBox());
this._add(this.__scrollerParent, {flex: 1});
// Allocate a default data row renderer
this.setDataRowRenderer(new qx.ui.table.rowrenderer.Default(this));
// Create the models
this.__selectionManager = this.getNewSelectionManager()(this);
this.setSelectionModel(this.getNewSelectionModel()(this));
this.setTableModel(tableModel || this.getEmptyTableModel());
// create the main meta column
this.setMetaColumnCounts([ -1 ]);
// Make focusable
this.setTabIndex(1);
this.addListener("keydown", this._onKeyDown);
this.addListener("focus", this._onFocusChanged);
this.addListener("blur", this._onFocusChanged);
// attach the resize listener to the last child of the layout. This
// ensures that all other children are laid out before
var spacer = new qx.ui.core.Widget().set({
height: 0
});
this._add(spacer);
spacer.addListener("resize", this._onResize, this);
this.__focusedCol = null;
this.__focusedRow = null;
// add an event listener which updates the table content on locale change
if (qx.core.Environment.get("qx.dynlocale")) {
qx.locale.Manager.getInstance().addListener("changeLocale", this._onChangeLocale, this);
}
this.initStatusBarVisible();
// If the table model has an init() method...
tableModel = this.getTableModel();
if (tableModel.init && typeof(tableModel.init) == "function")
{
// ... then call it now to allow the table model to affect table
// properties.
tableModel.init(this);
}
},
/*
*****************************************************************************
EVENTS
*****************************************************************************
*/
events :
{
/**
* Dispatched before adding the column list to the column visibility menu.
* The event data is a map with two properties: table and menu. Listeners
* may add additional items to the menu, which appear at the top of the
* menu.
*/
"columnVisibilityMenuCreateStart" : "qx.event.type.Data",
/**
* Dispatched after adding the column list to the column visibility menu.
* The event data is a map with two properties: table and menu. Listeners
* may add additional items to the menu, which appear at the bottom of the
* menu.
*/
"columnVisibilityMenuCreateEnd" : "qx.event.type.Data",
/**
* Dispatched when the width of the table has changed.
*/
"tableWidthChanged" : "qx.event.type.Event",
/**
* Dispatched when updating scrollbars discovers that a vertical scrollbar
* is needed when it previously was not, or vice versa. The data is a
* boolean indicating whether a vertical scrollbar is now being used.
*/
"verticalScrollBarChanged" : "qx.event.type.Data",
/**
* Dispatched when a data cell has been tapped.
*/
"cellTap" : "qx.ui.table.pane.CellEvent",
/**
* Dispatched when a data cell has been tapped.
*/
"cellDbltap" : "qx.ui.table.pane.CellEvent",
/**
* Dispatched when the context menu is needed in a data cell
*/
"cellContextmenu" : "qx.ui.table.pane.CellEvent",
/**
* Dispatched after a cell editor is flushed.
*
* The data is a map containing this properties:
* <ul>
* <li>row</li>
* <li>col</li>
* <li>value</li>
* <li>oldValue</li>
* </ul>
*/
"dataEdited" : "qx.event.type.Data"
},
/*
*****************************************************************************
STATICS
*****************************************************************************
*/
statics :
{
/** Events that must be redirected to the scrollers. */
__redirectEvents : { cellTap: 1, cellDbltap: 1, cellContextmenu: 1 }
},
/*
*****************************************************************************
PROPERTIES
*****************************************************************************
*/
properties :
{
appearance :
{
refine : true,
init : "table"
},
focusable :
{
refine : true,
init : true
},
minWidth :
{
refine : true,
init : 50
},
/**
* The list of columns that are initially hidden. This property is set by
* the constructor, from the value received in
* custom.initiallyHiddenColumns, and is only used when a column model is
* initialized. It can be of great benefit in tables with numerous columns
* where most are not initially visible. The process of creating the
* headers for all of the columns, only to have those columns discarded
* shortly thereafter when setColumnVisibility(false) is called, is a
* waste of (significant, in some browsers) time. Specifying the
* non-visible columns at constructor time can therefore avoid the initial
* creation of all of those superfluous widgets.
*/
initiallyHiddenColumns :
{
init : null
},
/**
* Whether the widget contains content which may be selected by the user.
*
* If the value set to <code>true</code> the native browser selection can
* be used for text selection. But it is normally useful for
* forms fields, longer texts/documents, editors, etc.
*
* Note: This has no effect on Table!
*/
selectable :
{
refine : true,
init : false
},
/** The selection model. */
selectionModel :
{
check : "qx.ui.table.selection.Model",
apply : "_applySelectionModel",
event : "changeSelectionModel"
},
/** The table model. */
tableModel :
{
check : "qx.ui.table.ITableModel",
apply : "_applyTableModel",
event : "changeTableModel"
},
/** The height of the table rows. */
rowHeight :
{
check : "Number",
init : 20,
apply : "_applyRowHeight",
event : "changeRowHeight",
themeable : true
},
/**
* Force line height to match row height. May be disabled if cell
* renderers being used wish to render multiple lines of data within a
* cell. (With the default setting, all but the first of multiple lines
* of data will not be visible.)
*/
forceLineHeight :
{
check : "Boolean",
init : true
},
/**
* Whether the header cells are visible. When setting this to false,
* you'll likely also want to set the {#columnVisibilityButtonVisible}
* property to false as well, to entirely remove the header row.
*/
headerCellsVisible :
{
check : "Boolean",
init : true,
apply : "_applyHeaderCellsVisible",
themeable : true
},
/** The height of the header cells. */
headerCellHeight :
{
check : "Integer",
init : 16,
apply : "_applyHeaderCellHeight",
event : "changeHeaderCellHeight",
nullable : true,
themeable : true
},
/** Whether to show the status bar */
statusBarVisible :
{
check : "Boolean",
init : true,
apply : "_applyStatusBarVisible"
},
/** The Statusbartext, set it, if you want some more Information */
additionalStatusBarText :
{
nullable : true,
init : null,
apply : "_applyAdditionalStatusBarText"
},
/** Whether to show the column visibility button */
columnVisibilityButtonVisible :
{
check : "Boolean",
init : true,
apply : "_applyColumnVisibilityButtonVisible",
themeable : true
},
/**
* @type {Integer[]} The number of columns per meta column. If the last array entry is -1,
* this meta column will get the remaining columns.
*/
metaColumnCounts :
{
check : "Object",
apply : "_applyMetaColumnCounts"
},
/**
* Whether the focus should moved when the pointer is moved over a cell. If false
* the focus is only moved on pointer taps.
*/
focusCellOnPointerMove :
{
check : "Boolean",
init : false,
apply : "_applyFocusCellOnPointerMove"
},
/**
* Whether row focus change by keyboard also modifies selection
*/
rowFocusChangeModifiesSelection :
{
check : "Boolean",
init : true
},
/**
* Whether the cell focus indicator should be shown
*/
showCellFocusIndicator :
{
check : "Boolean",
init : true,
apply : "_applyShowCellFocusIndicator"
},
/**
* By default, the "cellContextmenu" event is fired only when a data cell
* is right-clicked. It is not fired when a right-click occurs in the
* empty area of the table below the last data row. By turning on this
* property, "cellContextMenu" events will also be generated when a
* right-click occurs in that empty area. In such a case, row identifier
* in the event data will be null, so event handlers can check (row ===
* null) to handle this case.
*/
contextMenuFromDataCellsOnly :
{
check : "Boolean",
init : true,
apply : "_applyContextMenuFromDataCellsOnly"
},
/**
* Whether the table should keep the first visible row complete. If set to false,
* the first row may be rendered partial, depending on the vertical scroll value.
*/
keepFirstVisibleRowComplete :
{
check : "Boolean",
init : true,
apply : "_applyKeepFirstVisibleRowComplete"
},
/**
* Whether the table cells should be updated when only the selection or the
* focus changed. This slows down the table update but allows to react on a
* changed selection or a changed focus in a cell renderer.
*/
alwaysUpdateCells :
{
check : "Boolean",
init : false
},
/**
* Whether to reset the selection when a header cell is tapped. Since
* most data models do not have provisions to retain a selection after
* sorting, the default is to reset the selection in this case. Some data
* models, however, do have the capability to retain the selection, so
* when using those, this property should be set to false.
*/
resetSelectionOnHeaderTap :
{
check : "Boolean",
init : true,
apply : "_applyResetSelectionOnHeaderTap"
},
/** The renderer to use for styling the rows. */
dataRowRenderer :
{
check : "qx.ui.table.IRowRenderer",
init : null,
nullable : true,
event : "changeDataRowRenderer"
},
/**
* A function to call when before modal cell editor is opened.
*
* @signature function(cellEditor, cellInfo)
*
* @param cellEditor {qx.ui.window.Window}
* The modal window which has been created for this cell editor
*
* @param cellInfo {Map}
* Information about the cell for which this cell editor was created.
* It contains the following properties:
* col, row, xPos, value
*
*/
modalCellEditorPreOpenFunction :
{
check : "Function",
init : null,
nullable : true
},
/**
* By default, all Scrollers' (meta-columns') horizontal scrollbars are
* shown if any one is required. Allow not showing any that are not
* required.
*/
excludeScrollerScrollbarsIfNotNeeded :
{
check : "Boolean",
init : false,
nullable : false
},
/**
* A function to instantiate a new column menu button.
*/
newColumnMenu :
{
check : "Function",
init : function() {
return new qx.ui.table.columnmenu.Button();
}
},
/**
* A function to instantiate a selection manager. this allows subclasses of
* Table to subclass this internal class. To take effect, this property must
* be set before calling the Table constructor.
*/
newSelectionManager :
{
check : "Function",
init : function(obj) {
return new qx.ui.table.selection.Manager(obj);
}
},
/**
* A function to instantiate a selection model. this allows subclasses of
* Table to subclass this internal class. To take effect, this property must
* be set before calling the Table constructor.
*/
newSelectionModel :
{
check : "Function",
init : function(obj) {
return new qx.ui.table.selection.Model(obj);
}
},
/**
* A function to instantiate a table column model. This allows subclasses
* of Table to subclass this internal class. To take effect, this
* property must be set before calling the Table constructor.
*/
newTableColumnModel :
{
check : "Function",
init : function(table) {
return new qx.ui.table.columnmodel.Basic(table);
}
},
/**
* A function to instantiate a table pane. this allows subclasses of
* Table to subclass this internal class. To take effect, this property
* must be set before calling the Table constructor.
*/
newTablePane :
{
check : "Function",
init : function(obj) {
return new qx.ui.table.pane.Pane(obj);
}
},
/**
* A function to instantiate a table pane. this allows subclasses of
* Table to subclass this internal class. To take effect, this property
* must be set before calling the Table constructor.
*/
newTablePaneHeader :
{
check : "Function",
init : function(obj) {
return new qx.ui.table.pane.Header(obj);
}
},
/**
* A function to instantiate a table pane scroller. this allows
* subclasses of Table to subclass this internal class. To take effect,
* this property must be set before calling the Table constructor.
*/
newTablePaneScroller :
{
check : "Function",
init : function(obj) {
return new qx.ui.table.pane.Scroller(obj);
}
},
/**
* A function to instantiate a table pane model. this allows subclasses
* of Table to subclass this internal class. To take effect, this
* property must be set before calling the Table constructor.
*/
newTablePaneModel :
{
check : "Function",
init : function(columnModel) {
return new qx.ui.table.pane.Model(columnModel);
}
}
},
/*
*****************************************************************************
MEMBERS
*****************************************************************************
*/
members :
{
__focusedCol : null,
__focusedRow : null,
__scrollerParent : null,
__selectionManager : null,
__additionalStatusBarText : null,
__lastRowCount : null,
__internalChange : null,
__columnMenuButtons : null,
__columnModel : null,
__emptyTableModel : null,
__hadVerticalScrollBar : null,
__timer : null,
// overridden
_createChildControlImpl : function(id, hash)
{
var control;
switch(id)
{
case "statusbar":
control = new qx.ui.basic.Label();
control.set(
{
allowGrowX: true
});
this._add(control);
break;
case "column-button":
control = this.getNewColumnMenu()();
control.set({
focusable : false
});
// Create the initial menu too
var menu = control.factory("menu", { table : this });
// Add a listener to initialize the column menu when it becomes visible
menu.addListener(
"appear",
this._initColumnMenu,
this
);
break;
}
return control || this.base(arguments, id);
},
// property modifier
_applySelectionModel : function(value, old)
{
this.__selectionManager.setSelectionModel(value);
if (old != null) {
old.removeListener("changeSelection", this._onSelectionChanged, this);
}
value.addListener("changeSelection", this._onSelectionChanged, this);
},
// property modifier
_applyRowHeight : function(value, old)
{
var scrollerArr = this._getPaneScrollerArr();
for (var i=0; i<scrollerArr.length; i++) {
scrollerArr[i].updateVerScrollBarMaximum();
}
},
// property modifier
_applyHeaderCellsVisible : function(value, old)
{
var scrollerArr = this._getPaneScrollerArr();
for (var i=0; i<scrollerArr.length; i++) {
if (value) {
scrollerArr[i]._showChildControl("header");
} else {
scrollerArr[i]._excludeChildControl("header");
}
}
// also hide the column visibility button
if(this.getColumnVisibilityButtonVisible()) {
this._applyColumnVisibilityButtonVisible(value);
}
},
// property modifier
_applyHeaderCellHeight : function(value, old)
{
var scrollerArr = this._getPaneScrollerArr();
for (var i=0; i<scrollerArr.length; i++) {
scrollerArr[i].getHeader().setHeight(value);
}
},
/**
* Get an empty table model instance to use for this table. Use this table
* to configure the table with no table model.
*
* @return {qx.ui.table.ITableModel} The empty table model
*/
getEmptyTableModel : function()
{
if (!this.__emptyTableModel)
{
this.__emptyTableModel = new qx.ui.table.model.Simple();
this.__emptyTableModel.setColumns([]);
this.__emptyTableModel.setData([]);
}
return this.__emptyTableModel;
},
// property modifier
_applyTableModel : function(value, old)
{
this.getTableColumnModel().init(value.getColumnCount(), this);
if (old != null)
{
old.removeListener(
"metaDataChanged",
this._onTableModelMetaDataChanged, this
);
old.removeListener(
"dataChanged",
this._onTableModelDataChanged,
this);
}
value.addListener(
"metaDataChanged",
this._onTableModelMetaDataChanged, this
);
value.addListener(
"dataChanged",
this._onTableModelDataChanged,
this);
// Update the status bar
this._updateStatusBar();
this._updateTableData(
0, value.getRowCount(),
0, value.getColumnCount()
);
this._onTableModelMetaDataChanged();
// If the table model has an init() method, call it. We don't, however,
// call it if this is the initial setting of the table model, as the
// scrollers are not yet initialized. In that case, the init method is
// called explicitly by the Table constructor.
if (old && value.init && typeof(value.init) == "function")
{
value.init(this);
}
},
/**
* Get the The table column model.
*
* @return {qx.ui.table.columnmodel.Basic} The table's column model
*/
getTableColumnModel : function()
{
if (!this.__columnModel)
{
var columnModel = this.__columnModel = this.getNewTableColumnModel()(this);
columnModel.addListener("visibilityChanged", this._onColVisibilityChanged, this);
columnModel.addListener("widthChanged", this._onColWidthChanged, this);
columnModel.addListener("orderChanged", this._onColOrderChanged, this);
// Get the current table model
var tableModel = this.getTableModel();
columnModel.init(tableModel.getColumnCount(), this);
// Reset the table column model in each table pane model
var scrollerArr = this._getPaneScrollerArr();
for (var i=0; i<scrollerArr.length; i++)
{
var paneScroller = scrollerArr[i];
var paneModel = paneScroller.getTablePaneModel();
paneModel.setTableColumnModel(columnModel);
}
}
return this.__columnModel;
},
// property modifier
_applyStatusBarVisible : function(value, old)
{
if (value) {
this._showChildControl("statusbar");
} else {
this._excludeChildControl("statusbar");
}
if (value) {
this._updateStatusBar();
}
},
// property modifier
_applyAdditionalStatusBarText : function(value, old)
{
this.__additionalStatusBarText = value;
this._updateStatusBar();
},
// property modifier
_applyColumnVisibilityButtonVisible : function(value, old)
{
if (value) {
this._showChildControl("column-button");
} else {
this._excludeChildControl("column-button");
}
},
// property modifier
_applyMetaColumnCounts : function(value, old)
{
var metaColumnCounts = value;
var scrollerArr = this._getPaneScrollerArr();
var handlers = { };
if (value > old)
{
// Save event listeners on the redirected events so we can re-apply
// them to new scrollers.
var manager = qx.event.Registration.getManager(scrollerArr[0]);
for (var evName in qx.ui.table.Table.__redirectEvents)
{
handlers[evName] = { };
handlers[evName].capture = manager.getListeners(scrollerArr[0],
evName,
true);
handlers[evName].bubble = manager.getListeners(scrollerArr[0],
evName,
false);
}
}
// Remove the panes not needed any more
this._cleanUpMetaColumns(metaColumnCounts.length);
// Update the old panes
var leftX = 0;
for (var i=0; i<scrollerArr.length; i++)
{
var paneScroller = scrollerArr[i];
var paneModel = paneScroller.getTablePaneModel();
paneModel.setFirstColumnX(leftX);
paneModel.setMaxColumnCount(metaColumnCounts[i]);
leftX += metaColumnCounts[i];
}
// Add the new panes
if (metaColumnCounts.length > scrollerArr.length)
{
var columnModel = this.getTableColumnModel();
for (var i=scrollerArr.length; i<metaColumnCounts.length; i++)
{
var paneModel = this.getNewTablePaneModel()(columnModel);
paneModel.setFirstColumnX(leftX);
paneModel.setMaxColumnCount(metaColumnCounts[i]);
leftX += metaColumnCounts[i];
var paneScroller = this.getNewTablePaneScroller()(this);
paneScroller.setTablePaneModel(paneModel);
// Register event listener for vertical scrolling
paneScroller.addListener("changeScrollY", this._onScrollY, this);
// Apply redirected events to this new scroller
for (evName in qx.ui.table.Table.__redirectEvents)
{
// On first setting of meta columns (constructing phase), there
// are no handlers to deal with yet.
if (! handlers[evName])
{
break;
}
if (handlers[evName].capture &&
handlers[evName].capture.length > 0)
{
var capture = handlers[evName].capture;
for (var j = 0; j < capture.length; j++)
{
// Determine what context to use. If the context does not
// exist, we assume that the context is this table. If it
// does exist and it equals the first pane scroller (from
// which we retrieved the listeners) then set the context
// to be this new pane scroller. Otherwise leave the context
// as it was set.
var context = capture[j].context;
if (! context)
{
context = this;
}
else if (context == scrollerArr[0])
{
context = paneScroller;
}
paneScroller.addListener(
evName,
capture[j].handler,
context,
true);
}
}
if (handlers[evName].bubble &&
handlers[evName].bubble.length > 0)
{
var bubble = handlers[evName].bubble;
for (var j = 0; j < bubble.length; j++)
{
// Determine what context to use. If the context does not
// exist, we assume that the context is this table. If it
// does exist and it equals the first pane scroller (from
// which we retrieved the listeners) then set the context
// to be this new pane scroller. Otherwise leave the context
// as it was set.
var context = bubble[j].context;
if (! context)
{
context = this;
}
else if (context == scrollerArr[0])
{
context = paneScroller;
}
paneScroller.addListener(
evName,
bubble[j].handler,
context,
false);
}
}
}
// last meta column is flexible
var flex = (i == metaColumnCounts.length - 1) ? 1 : 0;
this.__scrollerParent.add(paneScroller, {flex: flex});
scrollerArr = this._getPaneScrollerArr();
}
}
// Update all meta columns
for (var i=0; i<scrollerArr.length; i++)
{
var paneScroller = scrollerArr[i];
var isLast = (i == (scrollerArr.length - 1));
// Set the right header height
paneScroller.getHeader().setHeight(this.getHeaderCellHeight());
// Put the column visibility button in the top right corner of the last meta column
paneScroller.setTopRightWidget(isLast ? this.getChildControl("column-button") : null);
}
if (!this.isColumnVisibilityButtonVisible()) {
this._excludeChildControl("column-button");
}
this._updateScrollerWidths();
this._updateScrollBarVisibility();
},
// property modifier
_applyFocusCellOnPointerMove : function(value, old)
{
var scrollerArr = this._getPaneScrollerArr();
for (var i=0; i<scrollerArr.length; i++) {
scrollerArr[i].setFocusCellOnPointerMove(value);
}
},
// property modifier
_applyShowCellFocusIndicator : function(value, old)
{
var scrollerArr = this._getPaneScrollerArr();
for (var i=0; i<scrollerArr.length; i++) {
scrollerArr[i].setShowCellFocusIndicator(value);
}
},
// property modifier
_applyContextMenuFromDataCellsOnly : function(value, old)
{
var scrollerArr = this._getPaneScrollerArr();
for (var i=0; i<scrollerArr.length; i++) {
scrollerArr[i].setContextMenuFromDataCellsOnly(value);
}
},
// property modifier
_applyKeepFirstVisibleRowComplete : function(value, old)
{
var scrollerArr = this._getPaneScrollerArr();
for (var i=0; i<scrollerArr.length; i++) {
scrollerArr[i].onKeepFirstVisibleRowCompleteChanged();
}
},
// property modifier
_applyResetSelectionOnHeaderTap : function(value, old)
{
var scrollerArr = this._getPaneScrollerArr();
for (var i=0; i<scrollerArr.length; i++) {
scrollerArr[i].setResetSelectionOnHeaderTap(value);
}
},
/**
* Returns the selection manager.
*
* @return {qx.ui.table.selection.Manager} the selection manager.
*/
getSelectionManager : function() {
return this.__selectionManager;
},
/**
* Returns an array containing all TablePaneScrollers in this table.
*
* @return {qx.ui.table.pane.Scroller[]} all TablePaneScrollers in this table.
*/
_getPaneScrollerArr : function() {
return this.__scrollerParent.getChildren();
},
/**
* Returns a TablePaneScroller of this table.
*
* @param metaColumn {Integer} the meta column to get the TablePaneScroller for.
* @return {qx.ui.table.pane.Scroller} the qx.ui.table.pane.Scroller.
*/
getPaneScroller : function(metaColumn) {
return this._getPaneScrollerArr()[metaColumn];
},
/**
* Cleans up the meta columns.
*
* @param fromMetaColumn {Integer} the first meta column to clean up. All following
* meta columns will be cleaned up, too. All previous meta columns will
* stay unchanged. If 0 all meta columns will be cleaned up.
*/
_cleanUpMetaColumns : function(fromMetaColumn)
{
var scrollerArr = this._getPaneScrollerArr();
if (scrollerArr != null)
{
for (var i=scrollerArr.length-1; i>=fromMetaColumn; i--)
{
scrollerArr[i].destroy();
}
}
},
/**
* Event handler. Called when the locale has changed.
*
* @param evt {Event} the event.
*/
_onChangeLocale : function(evt)
{
this.updateContent();
this._updateStatusBar();
},
// overridden
_onChangeTheme : function() {
this.base(arguments);
this.getDataRowRenderer().initThemeValues();
this.updateContent();
this._updateStatusBar();
},
/**
* Event handler. Called when the selection has changed.
*
* @param evt {Map} the event.
*/
_onSelectionChanged : function(evt)
{
var scrollerArr = this._getPaneScrollerArr();
for (var i=0; i<scrollerArr.length; i++) {
scrollerArr[i].onSelectionChanged();
}
this._updateStatusBar();
},
/**
* Event handler. Called when the table model meta data has changed.
*
* @param evt {Map} the event.
*/
_onTableModelMetaDataChanged : function(evt)
{
var scrollerArr = this._getPaneScrollerArr();
for (var i=0; i<scrollerArr.length; i++) {
scrollerArr[i].onTableModelMetaDataChanged();
}
this._updateStatusBar();
},
/**
* Event handler. Called when the table model data has changed.
*
* @param evt {Map} the event.
*/
_onTableModelDataChanged : function(evt)
{
var data = evt.getData();
this._updateTableData(
data.firstRow, data.lastRow,
data.firstColumn, data.lastColumn,
data.removeStart, data.removeCount
);
},
// overridden
_onContextMenuOpen : function(e)
{
// This is Widget's context menu handler which typically retrieves
// and displays the menu as soon as it receives a "contextmenu" event.
// We want to allow the cellContextmenu handler to create the menu,
// so we'll override this method with a null one, and do the menu
// placement and display handling in our _onContextMenu method.
},
/**
* To update the table if the table model has changed and remove selection.
*
* @param firstRow {Integer} The index of the first row that has changed.
* @param lastRow {Integer} The index of the last row that has changed.
* @param firstColumn {Integer} The model index of the first column that has changed.
* @param lastColumn {Integer} The model index of the last column that has changed.
* @param removeStart {Integer ? null} The first index of the interval (including), to remove selection.
* @param removeCount {Integer ? null} The count of the interval, to remove selection.
*/
_updateTableData : function(firstRow, lastRow, firstColumn, lastColumn, removeStart, removeCount)
{
var scrollerArr = this._getPaneScrollerArr();
// update selection if rows were removed
if (removeCount) {
this.getSelectionModel().removeSelectionInterval(removeStart, removeStart + removeCount - 1, true);
// remove focus if the focused row has been removed
if (this.__focusedRow >= removeStart && this.__focusedRow < (removeStart + removeCount)) {
this.setFocusedCell();
}
}
for (var i=0; i<scrollerArr.length; i++)
{
scrollerArr[i].onTableModelDataChanged(
firstRow, lastRow,
firstColumn, lastColumn
);
}
var rowCount = this.getTableModel().getRowCount();
if (rowCount != this.__lastRowCount)
{
this.__lastRowCount = rowCount;
this._updateScrollBarVisibility();
this._updateStatusBar();
}
},
/**
* Event handler. Called when a TablePaneScroller has been scrolled vertically.
*
* @param evt {Map} the event.
*/
_onScrollY : function(evt)
{
if (!this.__internalChange)
{
this.__internalChange = true;
// Set the same scroll position to all meta columns
var scrollerArr = this._getPaneScrollerArr();
for (var i=0; i<scrollerArr.length; i++) {
scrollerArr[i].setScrollY(evt.getData());
}
this.__internalChange = false;
}
},
/**
* Event handler. Called when a key was pressed.
*
* @param evt {qx.event.type.KeySequence} the event.
* @deprecated {6.0} please use _onKeyDown instead!
*/
_onKeyPress : function(evt)
{
qx.log.Logger.deprecatedMethodWarning(this._onKeyPress, "The method '_onKeyPress()' is deprecated. Please use '_onKeyDown()' instead.");
qx.log.Logger.deprecateMethodOverriding(this, qx.ui.table.Table, "_onKeyPress", "The method '_onKeyPress()' is deprecated. Please use '_onKeyDown()' instead.");
this._onKeyDown(evt);
},
/**
* Event handler. Called when on key down event
*
* @param evt {qx.event.type.KeySequence} the event.
*/
_onKeyDown : function(evt)
{
if (!this.getEnabled()) {
return;
}
// No editing mode
var oldFocusedRow = this.__focusedRow;
var consumed = false;
// Handle keys that are independent from the modifiers
var identifier = evt.getKeyIdentifier();
if (this.isEditing())
{
// Editing mode
if (evt.getModifiers() == 0)
{
switch(identifier)
{
case "Enter":
this.stopEditing();
var oldFocusedRow = this.__focusedRow;
this.moveFocusedCell(0, 1);
if (this.__focusedRow != oldFocusedRow) {
consumed = this.startEditing();
}
break;
case "Escape":
this.cancelEditing();
this.focus();
break;
default:
consumed = false;
break;
}
}
}
else
{
consumed = true;
// No editing mode
if (evt.isCtrlPressed())
{
// Handle keys that depend on modifiers
switch(identifier)
{
case "A": // Ctrl + A
var rowCount = this.getTableModel().getRowCount();
if (rowCount > 0) {
this.getSelectionModel().setSelectionInterval(0, rowCount - 1);
}
break;
default:
consumed = false;
break;
}
}
else
{
// Handle keys that are independent from the modifiers
switch(identifier)
{
case "Space":
this.__selectionManager.handleSelectKeyDown(this.__focusedRow, evt);
break;
case "F2":
case "Enter":
this.startEditing();
consumed = true;
break;
case "Home":
this.setFocusedCell(this.__focusedCol, 0, true);
break;
case "End":
var rowCount = this.getTableModel().getRowCount();
this.setFocusedCell(this.__focusedCol, rowCount - 1, true);
break;
case "Left":
this.moveFocusedCell(-1, 0);
break;
case "Right":
this.moveFocusedCell(1, 0);
break;
case "Up":
this.moveFocusedCell(0, -1);
break;
case "Down":
this.moveFocusedCell(0, 1);
break;
case "PageUp":
case "PageDown":
var scroller = this.getPaneScroller(0);
var pane = scroller.getTablePane();
var rowHeight = this.getRowHeight();
var direction = (identifier == "PageUp") ? -1 : 1;
rowCount = pane.getVisibleRowCount() - 1;
scroller.setScrollY(scroller.getScrollY() + direction * rowCount * rowHeight);
this.moveFocusedCell(0, direction * rowCount);
break;
default:
consumed = false;
}
}
}
if (oldFocusedRow != this.__focusedRow &&
this.getRowFocusChangeModifiesSelection())
{
// The focus moved -> Let the selection manager handle this event
this.__selectionManager.handleMoveKeyDown(this.__focusedRow, evt);
}
if (consumed)
{
evt.preventDefault();
evt.stopPropagation();
}
},
/**
* Event handler. Called when the table gets the focus.
*
* @param evt {Map} the event.
*/
_onFocusChanged : function(evt)
{
var scrollerArr = this._getPaneScrollerArr();
for (var i=0; i<scrollerArr.length; i++) {
scrollerArr[i].onFocusChanged();
}
},
/**
* Event handler. Called when the visibility of a column has changed.
*
* @param evt {Map} the event.
*/
_onColVisibilityChanged : function(evt)
{
var scrollerArr = this._getPaneScrollerArr();
for (var i=0; i<scrollerArr.length; i++) {
scrollerArr[i].onColVisibilityChanged();
}
var data = evt.getData();
if (this.__columnMenuButtons != null && data.col != null &&
data.visible != null) {
this.__columnMenuButtons[data.col].setColumnVisible(data.visible);
}
this._updateScrollerWidths();
this._updateScrollBarVisibility();
},
/**
* Event handler. Called when the width of a column has changed.
*
* @param evt {Map} the event.
*/
_onColWidthChanged : function(evt)
{
var scrollerArr = this._getPaneScrollerArr();
for (var i=0; i<scrollerArr.length; i++)
{
var data = evt.getData();
scrollerArr[i].setColumnWidth(data.col, data.newWidth);
}
this._updateScrollerWidths();
this._updateScrollBarVisibility();
},
/**
* Event handler. Called when the column order has changed.
*
* @param evt {Map} the event.
*/
_onColOrderChanged : function(evt)
{
var scrollerArr = this._getPaneScrollerArr();
for (var i=0; i<scrollerArr.length; i++) {
scrollerArr[i].onColOrderChanged();
}
// A column may have been moved between meta columns
this._updateScrollerWidths();
this._updateScrollBarVisibility();
},
/**
* Gets the TablePaneScroller at a certain x position in the page. If there is
* no TablePaneScroller at this position, null is returned.
*
* @param pageX {Integer} the position in the page to check (in pixels).
* @return {qx.ui.table.pane.Scroller} the TablePaneScroller or null.
*/
getTablePaneScrollerAtPageX : function(pageX)
{
var metaCol = this._getMetaColumnAtPageX(pageX);
return (metaCol != -1) ? this.getPaneScroller(metaCol) : null;
},
/**
* Sets the currently focused cell. A value of <code>null</code> hides the
* focus cell.
*
* @param col {Integer?null} the model index of the focused cell's column.
* @param row {Integer?null} the model index of the focused cell's row.
* @param scrollVisible {Boolean ? false} whether to scroll the new focused cell
* visible.
*/
setFocusedCell : function(col, row, scrollVisible)
{
if (!this.isEditing() && (col != this.__focusedCol || row != this.__focusedRow))
{
if (col === null) {
col = 0;
}
this.__focusedCol = col;
this.__focusedRow = row;
var scrollerArr = this._getPaneScrollerArr();
for (var i=0; i<scrollerArr.length; i++) {
scrollerArr[i].setFocusedCell(col, row);
}
if (col != null && scrollVisible) {
this.scrollCellVisible(col, row);
}
}
},
/**
* Resets (clears) the current selection
*/
resetSelection : function() {
this.getSelectionModel().resetSelection();
},
/**
* Resets the focused cell.
*/
resetCellFocus : function() {
this.setFocusedCell(null, null, false);
},
/**
* Returns the column of the currently focused cell.
*
* @return {Integer} the model index of the focused cell's column.
*/
getFocusedColumn : function() {
return this.__focusedCol;
},
/**
* Returns the row of the currently focused cell.
*
* @return {Integer} the model index of the focused cell's column.
*/
getFocusedRow : function() {
return this.__focusedRow;
},
/**
* Select whether the focused row is highlighted
*
* @param bHighlight {Boolean}
* Flag indicating whether the focused row should be highlighted.
*
*/
highlightFocusedRow : function(bHighlight)
{
this.getDataRowRenderer().setHighlightFocusRow(bHighlight);
},
/**
* Remove the highlighting of the current focus row.
*
* This is used to temporarily remove the highlighting of the currently
* focused row, and is expected to be used most typically by adding a
* listener on the "pointerout" event, so that the focus highlighting is
* suspended when the pointer leaves the table:
*
* table.addListener("pointerout", table.clearFocusedRowHighlight);
*
* @param evt {qx.event.type.Pointer} Incoming pointer event
*/
clearFocusedRowHighlight : function(evt)
{
if(evt)
{
var relatedTarget = evt.getRelatedTarget();
if (
relatedTarget instanceof qx.ui.table.pane.Pane ||
relatedTarget instanceof qx.ui.table.pane.FocusIndicator
) {
return ;
}
}
// Remove focus from any cell that has it
this.resetCellFocus();
// Now, for each pane scroller...
var scrollerArr = this._getPaneScrollerArr();
for (var i=0; i<scrollerArr.length; i++)
{
// ... repaint without focus.
scrollerArr[i].onFocusChanged();
}
},
/**
* Moves the focus.
*
* @param deltaX {Integer} The delta by which the focus should be moved on the x axis.
* @param deltaY {Integer} The delta by which the focus should be moved on the y axis.
*/
moveFocusedCell : function(deltaX, deltaY)
{
var col = this.__focusedCol;
var row = this.__focusedRow;
// could also be undefined [BUG #4676]
if (col == null || row == null) {
return;
}
if (deltaX != 0)
{
var columnModel = this.getTableColumnModel();
var x = columnModel.getVisibleX(col);
var colCount = columnModel.getVisibleColumnCount();
x = qx.lang.Number.limit(x + deltaX, 0, colCount - 1);
col = columnModel.getVisibleColumnAtX(x);
}
if (deltaY != 0)
{
var tableModel = this.getTableModel();
row = qx.lang.Number.limit(row + deltaY, 0, tableModel.getRowCount() - 1);
}
this.setFocusedCell(col, row, true);
},
/**
* Scrolls a cell visible.
*
* @param col {Integer} the model index of the column the cell belongs to.
* @param row {Integer} the model index of the row the cell belongs to.
*/
scrollCellVisible : function(col, row)
{
// get the dom element
var elem = this.getContentElement().getDomElement();
// if the dom element is not available, the table hasn't been rendered
if (!elem) {
// postpone the scroll until the table has appeared
this.addListenerOnce("appear", function() {
this.scrollCellVisible(col, row);
}, this);
}
var columnModel = this.getTableColumnModel();
var x = columnModel.getVisibleX(col);
var metaColumn = this._getMetaColumnAtColumnX(x);
if (metaColumn != -1) {
this.getPaneScroller(metaColumn).scrollCellVisible(col, row);
}
},
/**
* Returns whether currently a cell is editing.
*