UNPKG

@qooxdoo/framework

Version:

The JS Framework for Coders

1,716 lines (1,465 loc) 65 kB
/* ************************************************************************ 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(tableModel, custom) { super(); // // 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")) { this.__changeLocaleTableListenerId = 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); } // ARIA attrs this.getContentElement().setAttribute("role", "grid"); }, /* ***************************************************************************** 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" }, /** * Whether to reset the selection when the unpopulated table area is tapped. * The default is false which keeps the behaviour as before */ resetSelectionOnTapBelowRows: { check: "Boolean", init: false, apply: "_applyResetSelectionOnTapBelowRows" }, /** * If set then defines the minimum height of the focus indicator when editing */ minCellEditHeight: { check: "Integer", nullable: true, init: null, apply: "_applyMinCellEditHeight" }, /** The renderer to use for styling the rows. */ dataRowRenderer: { check: "qx.ui.table.IRowRenderer", init: null, nullable: true, event: "changeDataRowRenderer" }, /** * The action to take when a cell is being edited and the focus moves elsewhere. */ cellEditorBlurAction: { check: ["nothing", "save", "cancel"], init: "nothing" }, /** * 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() { 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(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(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(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(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(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(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(columnModel) { return new qx.ui.table.pane.Model(columnModel); } } }, /* ***************************************************************************** MEMBERS ***************************************************************************** */ members: { __focusedCol: null, __focusedRow: null, __scrollerParent: null, __selectionManager: null, __additionalStatusBarText: null, __lastRowCount: null, __lastColCount: null, __internalChange: null, __columnMenuButtons: null, __columnModel: null, __emptyTableModel: null, __hadVerticalScrollBar: null, __timer: null, // overridden _createChildControlImpl(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 || super._createChildControlImpl(id); }, // property modifier _applySelectionModel(value, old) { this.__selectionManager.setSelectionModel(value); if (old != null) { old.removeListener("changeSelection", this._onSelectionChanged, this); } value.addListener("changeSelection", this._onSelectionChanged, this); }, // property modifier _applyRowHeight(value, old) { var scrollerArr = this._getPaneScrollerArr(); for (var i = 0; i < scrollerArr.length; i++) { scrollerArr[i].updateVerScrollBarMaximum(); } }, // property modifier _applyHeaderCellsVisible(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(value, old) { var scrollerArr = this._getPaneScrollerArr(); for (var i = 0; i < scrollerArr.length; i++) { scrollerArr[i].getHeader().setHeight(value); } }, // property modifier _applyMinCellEditHeight(value) { var scrollerArr = this._getPaneScrollerArr(); for (var i = 0; i < scrollerArr.length; i++) { scrollerArr[i].setMinCellEditHeight(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() { if (!this.__emptyTableModel) { this.__emptyTableModel = new qx.ui.table.model.Simple(); this.__emptyTableModel.setColumns([]); this.__emptyTableModel.setData([]); } return this.__emptyTableModel; }, // property modifier _applyTableModel(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() { 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(value, old) { if (value) { this._showChildControl("statusbar"); } else { this._excludeChildControl("statusbar"); } if (value) { this._updateStatusBar(); } }, // property modifier _applyAdditionalStatusBarText(value, old) { this.__additionalStatusBarText = value; this._updateStatusBar(); }, // property modifier _applyColumnVisibilityButtonVisible(value, old) { if (value) { this._showChildControl("column-button"); } else { this._excludeChildControl("column-button"); } }, // property modifier _applyMetaColumnCounts(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(value, old) { var scrollerArr = this._getPaneScrollerArr(); for (var i = 0; i < scrollerArr.length; i++) { scrollerArr[i].setFocusCellOnPointerMove(value); } }, // property modifier _applyShowCellFocusIndicator(value, old) { var scrollerArr = this._getPaneScrollerArr(); for (var i = 0; i < scrollerArr.length; i++) { scrollerArr[i].setShowCellFocusIndicator(value); } }, // property modifier _applyContextMenuFromDataCellsOnly(value, old) { var scrollerArr = this._getPaneScrollerArr(); for (var i = 0; i < scrollerArr.length; i++) { scrollerArr[i].setContextMenuFromDataCellsOnly(value); } }, // property modifier _applyKeepFirstVisibleRowComplete(value, old) { var scrollerArr = this._getPaneScrollerArr(); for (var i = 0; i < scrollerArr.length; i++) { scrollerArr[i].onKeepFirstVisibleRowCompleteChanged(); } }, // property modifier _applyResetSelectionOnHeaderTap(value, old) { var scrollerArr = this._getPaneScrollerArr(); for (var i = 0; i < scrollerArr.length; i++) { scrollerArr[i].setResetSelectionOnHeaderTap(value); } }, // property modifier _applyResetSelectionOnTapBelowRows(value, old) { var scrollerArr = this._getPaneScrollerArr(); for (var i = 0; i < scrollerArr.length; i++) { scrollerArr[i].setResetSelectionOnTapBelowRows(value); } }, /** * Returns the selection manager. * * @return {qx.ui.table.selection.Manager} the selection manager. */ getSelectionManager() { return this.__selectionManager; }, /** * Returns an array containing all TablePaneScrollers in this table. * * @return {qx.ui.table.pane.Scroller[]} all TablePaneScrollers in this table. */ _getPaneScrollerArr() { 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(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(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(evt) { this.updateContent(); this._updateStatusBar(); }, // overridden _onChangeTheme() { super._onChangeTheme(); this.getDataRowRenderer().initThemeValues(); this.updateContent(); this._updateStatusBar(); }, /** * Event handler. Called when the selection has changed. * * @param evt {Map} the event. */ _onSelectionChanged(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(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(evt) { var data = evt.getData(); this._updateTableData( data.firstRow, data.lastRow, data.firstColumn, data.lastColumn, data.removeStart, data.removeCount ); }, // overridden _onContextMenuOpen(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( 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(); // ARIA attrs this.getContentElement().setAttribute("aria-rowcount", rowCount); } const colCount = this.getTableModel().getColumnCount(); if (colCount != this.__lastColCount) { this.__lastColCount = colCount; // ARIA attrs this.getContentElement().setAttribute("aria-colcount", colCount); } }, /** * Event handler. Called when a TablePaneScroller has been scrolled vertically. * * @param evt {Map} the event. */ _onScrollY(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(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(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(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(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(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(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(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(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); } // ARIA attrs const cellId = "qooxdoo-table-cell-" + this.toHashCode() + "-" + row + "-" + col; this.getContentElement().setAttribute("aria-activedescendant", cellId); } }, /** * Resets (clears) the current selection */ resetSelection() { this.getSelectionModel().resetSelection(); }, /** * Resets the focused cell. */ resetCellFocus() { 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() { return this.__focusedCol; }, /** * Returns the row of the currently focused cell. * * @return {Integer} the model index of the focused cell's column. */ getFocusedRow() { return this.__focusedRow; }, /** * Select whether the focused row is highlighted * * @param bHighlight {Boolean} * Flag indicating whether the focused row should be highlighted. * */ highlightFocusedRow(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(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(deltaX, deltaY) { var col = this.__focusedCol; var row = this.__focusedRow; // could also be undefined [BUG #4676]. In that case default to first cell focus if ( col === null || col === undefined || row === null || row === undefined ) { this.setFocusedCell(0, 0, true); 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.setFocused