UNPKG

@qooxdoo/framework

Version:

The JS Framework for Coders

787 lines (672 loc) 24.7 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) ************************************************************************ */ /** * A model that contains all meta data about columns, such as width, renderer, * visibility and order. * * @see qx.ui.table.ITableModel */ qx.Class.define("qx.ui.table.columnmodel.Basic", { extend: qx.core.Object, construct() { super(); this.__overallColumnArr = []; this.__visibleColumnArr = []; }, /* ***************************************************************************** EVENTS ***************************************************************************** */ events: { /** * Fired when the width of a column has changed. The data property of the event is * a map having the following attributes: * <ul> * <li>col: The model index of the column the width of which has changed.</li> * <li>newWidth: The new width of the column in pixels.</li> * <li>oldWidth: The old width of the column in pixels.</li> * </ul> */ widthChanged: "qx.event.type.Data", /** * Fired when the visibility of a column has changed. This event is equal to * "visibilityChanged", but is fired right before. */ visibilityChangedPre: "qx.event.type.Data", /** * Fired when the visibility of a column has changed. The data property of the * event is a map having the following attributes: * <ul> * <li>col: The model index of the column the visibility of which has changed.</li> * <li>visible: Whether the column is now visible.</li> * </ul> */ visibilityChanged: "qx.event.type.Data", /** * Fired when the column order has changed. The data property of the * event is a map having the following attributes: * <ul> * <li>col: The model index of the column that was moved.</li> * <li>fromOverXPos: The old overall x position of the column.</li> * <li>toOverXPos: The new overall x position of the column.</li> * </ul> */ orderChanged: "qx.event.type.Data", /** * Fired when the cell renderer of a column has changed. * The data property of the event is a map having the following attributes: * <ul> * <li>col: The model index of the column that was moved.</li> * </ul> */ headerCellRendererChanged: "qx.event.type.Data" }, /* ***************************************************************************** STATICS ***************************************************************************** */ statics: { /** @type {Integer} the default width of a column in pixels. */ DEFAULT_WIDTH: 100, /** @type {qx.ui.table.headerrenderer.Default} the default header cell renderer. */ DEFAULT_HEADER_RENDERER: qx.ui.table.headerrenderer.Default, /** @type {qx.ui.table.cellrenderer.Default} the default data cell renderer. */ DEFAULT_DATA_RENDERER: qx.ui.table.cellrenderer.Default, /** @type {qx.ui.table.celleditor.TextField} the default editor factory. */ DEFAULT_EDITOR_FACTORY: qx.ui.table.celleditor.TextField }, /* ***************************************************************************** MEMBERS ***************************************************************************** */ members: { __internalChange: null, __colToXPosMap: null, __visibleColumnArr: null, __overallColumnArr: null, __columnDataArr: null, __headerRenderer: null, __dataRenderer: null, __editorFactory: null, /** * Initializes the column model. * * @param colCount {Integer} * The number of columns the model should have. * * @param table {qx.ui.table.Table} * The table to which this column model is attached. */ init(colCount, table) { if (qx.core.Environment.get("qx.debug")) { this.assertInteger(colCount, "Invalid argument 'colCount'."); } this.__columnDataArr = []; var width = qx.ui.table.columnmodel.Basic.DEFAULT_WIDTH; var headerRenderer = this.__headerRenderer || (this.__headerRenderer = new qx.ui.table.columnmodel.Basic.DEFAULT_HEADER_RENDERER()); var dataRenderer = this.__dataRenderer || (this.__dataRenderer = new qx.ui.table.columnmodel.Basic.DEFAULT_DATA_RENDERER()); var editorFactory = this.__editorFactory || (this.__editorFactory = new qx.ui.table.columnmodel.Basic.DEFAULT_EDITOR_FACTORY()); this.__overallColumnArr = []; this.__visibleColumnArr = []; // Get the initially hidden column array, if one was provided. Older // subclasses may not provide the 'table' argument, so we treat them // traditionally with no initially hidden columns. var initiallyHiddenColumns; // Was a table provided to us? if (table) { // Yup. Get its list of initially hidden columns, if the user provided // such a list. initiallyHiddenColumns = table.getInitiallyHiddenColumns(); } // If no table was specified, or if the user didn't provide a list of // initially hidden columns, use an empty list. initiallyHiddenColumns = initiallyHiddenColumns || []; for (var col = 0; col < colCount; col++) { this.__columnDataArr[col] = { width: width, headerRenderer: headerRenderer, dataRenderer: dataRenderer, editorFactory: editorFactory }; this.__overallColumnArr[col] = col; this.__visibleColumnArr[col] = col; } this.__colToXPosMap = null; // If any columns are initially hidden, hide them now. Make it an // internal change so that events are not generated. this.__internalChange = true; for (var hidden = 0; hidden < initiallyHiddenColumns.length; hidden++) { this.setColumnVisible(initiallyHiddenColumns[hidden], false); } this.__internalChange = false; for (col = 0; col < colCount; col++) { var data = { col: col, visible: this.isColumnVisible(col) }; this.fireDataEvent("visibilityChangedPre", data); this.fireDataEvent("visibilityChanged", data); } }, /** * Return the array of visible columns * * @return {Array} List of all visible columns */ getVisibleColumns() { return this.__visibleColumnArr != null ? this.__visibleColumnArr : []; }, /** * Sets the width of a column. * * @param col {Integer} * The model index of the column. * * @param width {Integer} * The new width the column should get in pixels. * * @param isPointerAction {Boolean} * <i>true</i> if the column width is being changed as a result of a * pointer drag in the header; false or undefined otherwise. * */ setColumnWidth(col, width, isPointerAction) { if (qx.core.Environment.get("qx.debug")) { this.assertInteger(col, "Invalid argument 'col'."); this.assertInteger(width, "Invalid argument 'width'."); this.assertNotUndefined( this.__columnDataArr[col], "Column not found in table model" ); } var oldWidth = this.__columnDataArr[col].width; if (oldWidth != width) { this.__columnDataArr[col].width = width; var data = { col: col, newWidth: width, oldWidth: oldWidth, isPointerAction: isPointerAction || false }; this.fireDataEvent("widthChanged", data); } }, /** * Returns the width of a column. * * @param col {Integer} the model index of the column. * @return {Integer} the width of the column in pixels. */ getColumnWidth(col) { if (qx.core.Environment.get("qx.debug")) { this.assertInteger(col, "Invalid argument 'col'."); this.assertNotUndefined( this.__columnDataArr[col], "Column not found in table model" ); } return this.__columnDataArr[col].width; }, /** * Sets the header renderer of a column. Use setHeaderCellRenderers * instead of this method if you want to set the header renderer of many * columns. * * @param col {Integer} the model index of the column. * @param renderer {qx.ui.table.IHeaderRenderer} the new header renderer the column * should get. */ setHeaderCellRenderer(col, renderer) { if (qx.core.Environment.get("qx.debug")) { this.assertInteger(col, "Invalid argument 'col'."); this.assertInterface( renderer, qx.ui.table.IHeaderRenderer, "Invalid argument 'renderer'." ); this.assertNotUndefined( this.__columnDataArr[col], "Column not found in table model" ); } var oldRenderer = this.__columnDataArr[col].headerRenderer; if (oldRenderer !== this.__headerRenderer) { oldRenderer.dispose(); } this.__columnDataArr[col].headerRenderer = renderer; if (!this.__internalChange) { this.fireDataEvent("headerCellRendererChanged", { col: col }); } }, /** * Sets the header renderer of one or more columns. Use this method, in * favor of setHeaderCellRenderer, if you want to set the header renderer * of many columns. This method fires the "headerCellRendererChanged" * event only once, after setting all renderers, whereas * setHeaderCellRenderer fires it for each changed renderer which can be * slow with many columns. * * @param renderers {Map} * Map, where the keys are column numbers and values are the renderers, * implementing qx.ui.table.IHeaderRenderer, of the the new header * renderers for that column */ setHeaderCellRenderers(renderers) { var col; // Prevent firing "headerCellRendererChanged" for each column. Instead, // we'll fire it once at the end. this.__internalChange = true; // For each listed column... for (col in renderers) { // ... set that column's renderer this.setHeaderCellRenderer(+col, renderers[col]); } // Turn off the internal-change flag so operation returns to normal this.__internalChange = false; // Now we can fire the event once. The data indicates which columns // changed. Internally to qooxdoo, nothing cares about the event data. this.fireDataEvent("headerCellRendererChanged", { cols: Object.keys(renderers) }); }, /** * Returns the header renderer of a column. * * @param col {Integer} the model index of the column. * @return {qx.ui.table.IHeaderRenderer} the header renderer of the column. */ getHeaderCellRenderer(col) { if (qx.core.Environment.get("qx.debug")) { this.assertInteger(col, "Invalid argument 'col'."); this.assertNotUndefined( this.__columnDataArr[col], "Column not found in table model" ); } return this.__columnDataArr[col].headerRenderer; }, /** * Sets the data renderer of a column. * * @param col {Integer} the model index of the column. * @param renderer {qx.ui.table.ICellRenderer} the new data renderer * the column should get. * @return {qx.ui.table.ICellRenderer?null} If an old renderer was set and * it was not the default renderer, the old renderer is returned for * pooling or disposing. */ setDataCellRenderer(col, renderer) { if (qx.core.Environment.get("qx.debug")) { this.assertInteger(col, "Invalid argument 'col'."); this.assertInterface( renderer, qx.ui.table.ICellRenderer, "Invalid argument 'renderer'." ); this.assertNotUndefined( this.__columnDataArr[col], "Column not found in table model" ); } var oldRenderer = this.__columnDataArr[col].dataRenderer; this.__columnDataArr[col].dataRenderer = renderer; if (oldRenderer !== this.__dataRenderer) { return oldRenderer; } return null; }, /** * Returns the data renderer of a column. * * @param col {Integer} the model index of the column. * @return {qx.ui.table.ICellRenderer} the data renderer of the column. */ getDataCellRenderer(col) { if (qx.core.Environment.get("qx.debug")) { this.assertInteger(col, "Invalid argument 'col'."); this.assertNotUndefined( this.__columnDataArr[col], "Column not found in table model" ); } return this.__columnDataArr[col].dataRenderer; }, /** * Sets the cell editor factory of a column. * * @param col {Integer} the model index of the column. * @param factory {qx.ui.table.ICellEditorFactory} the new cell editor factory the column should get. */ setCellEditorFactory(col, factory) { if (qx.core.Environment.get("qx.debug")) { this.assertInteger(col, "Invalid argument 'col'."); this.assertInterface( factory, qx.ui.table.ICellEditorFactory, "Invalid argument 'factory'." ); this.assertNotUndefined( this.__columnDataArr[col], "Column not found in table model" ); } var oldFactory = this.__columnDataArr[col].editorFactory; if (oldFactory === factory) { return; } if (oldFactory !== this.__editorFactory) { oldFactory.dispose(); } this.__columnDataArr[col].editorFactory = factory; }, /** * Returns the cell editor factory of a column. * * @param col {Integer} the model index of the column. * @return {qx.ui.table.ICellEditorFactory} the cell editor factory of the column. */ getCellEditorFactory(col) { if (qx.core.Environment.get("qx.debug")) { this.assertInteger(col, "Invalid argument 'col'."); this.assertNotUndefined( this.__columnDataArr[col], "Column not found in table model" ); } return this.__columnDataArr[col].editorFactory; }, /** * Returns the map that translates model indexes to x positions. * * The returned map contains for a model index (int) a map having two * properties: overX (the overall x position of the column, int) and * visX (the visible x position of the column, int). visX is missing for * hidden columns. * * @return {Map} the "column to x position" map. */ _getColToXPosMap() { if (this.__colToXPosMap == null) { this.__colToXPosMap = {}; for (var overX = 0; overX < this.__overallColumnArr.length; overX++) { var col = this.__overallColumnArr[overX]; this.__colToXPosMap[col] = { overX: overX }; } for (var visX = 0; visX < this.__visibleColumnArr.length; visX++) { var col = this.__visibleColumnArr[visX]; this.__colToXPosMap[col].visX = visX; } } return this.__colToXPosMap; }, /** * Returns the number of visible columns. * * @return {Integer} the number of visible columns. */ getVisibleColumnCount() { return this.__visibleColumnArr != null ? this.__visibleColumnArr.length : 0; }, /** * Returns the model index of a column at a certain visible x position. * * @param visXPos {Integer} the visible x position of the column. * @return {Integer} the model index of the column. */ getVisibleColumnAtX(visXPos) { if (qx.core.Environment.get("qx.debug")) { this.assertInteger(visXPos, "Invalid argument 'visXPos'."); } return this.__visibleColumnArr[visXPos]; }, /** * Returns the visible x position of a column. * * @param col {Integer} the model index of the column. * @return {Integer} the visible x position of the column. */ getVisibleX(col) { if (qx.core.Environment.get("qx.debug")) { this.assertInteger(col, "Invalid argument 'col'."); } return this._getColToXPosMap()[col].visX; }, /** * Returns the overall number of columns (including hidden columns). * * @return {Integer} the overall number of columns. */ getOverallColumnCount() { return this.__overallColumnArr.length; }, /** * Returns the model index of a column at a certain overall x position. * * @param overXPos {Integer} the overall x position of the column. * @return {Integer} the model index of the column. */ getOverallColumnAtX(overXPos) { if (qx.core.Environment.get("qx.debug")) { this.assertInteger(overXPos, "Invalid argument 'overXPos'."); } return this.__overallColumnArr[overXPos]; }, /** * Returns the overall x position of a column. * * @param col {Integer} the model index of the column. * @return {Integer} the overall x position of the column. */ getOverallX(col) { if (qx.core.Environment.get("qx.debug")) { this.assertInteger(col, "Invalid argument 'col'."); } return this._getColToXPosMap()[col].overX; }, /** * Returns whether a certain column is visible. * * @param col {Integer} the model index of the column. * @return {Boolean} whether the column is visible. */ isColumnVisible(col) { if (qx.core.Environment.get("qx.debug")) { this.assertInteger(col, "Invalid argument 'col'."); } return this._getColToXPosMap()[col].visX != null; }, /** * Sets whether a certain column is visible. * * @param col {Integer} the model index of the column. * @param visible {Boolean} whether the column should be visible. */ setColumnVisible(col, visible) { if (qx.core.Environment.get("qx.debug")) { this.assertInteger(col, "Invalid argument 'col'."); this.assertBoolean(visible, "Invalid argument 'visible'."); } if (visible != this.isColumnVisible(col)) { if (visible) { var colToXPosMap = this._getColToXPosMap(); var overX = colToXPosMap[col].overX; if (overX == null) { throw new Error( "Showing column failed: " + col + ". The column is not added to this TablePaneModel." ); } // get the visX of the next visible column after the column to show var nextVisX; for (var x = overX + 1; x < this.__overallColumnArr.length; x++) { var currCol = this.__overallColumnArr[x]; var currVisX = colToXPosMap[currCol].visX; if (currVisX != null) { nextVisX = currVisX; break; } } // If there comes no visible column any more, then show the column // at the end if (nextVisX == null) { nextVisX = this.__visibleColumnArr.length; } // Add the column to the visible columns this.__visibleColumnArr.splice(nextVisX, 0, col); } else { var visX = this.getVisibleX(col); this.__visibleColumnArr.splice(visX, 1); } // Invalidate the __colToXPosMap this.__colToXPosMap = null; // Inform the listeners if (!this.__internalChange) { var data = { col: col, visible: visible }; this.fireDataEvent("visibilityChangedPre", data); this.fireDataEvent("visibilityChanged", data); } } }, /** * Moves a column. * * @param fromOverXPos {Integer} the overall x position of the column to move. * @param toOverXPos {Integer} the overall x position of where the column should be * moved to. */ moveColumn(fromOverXPos, toOverXPos) { if (qx.core.Environment.get("qx.debug")) { this.assertInteger(fromOverXPos, "Invalid argument 'fromOverXPos'."); this.assertInteger(toOverXPos, "Invalid argument 'toOverXPos'."); } this.__internalChange = true; var col = this.__overallColumnArr[fromOverXPos]; var visible = this.isColumnVisible(col); if (visible) { this.setColumnVisible(col, false); } this.__overallColumnArr.splice(fromOverXPos, 1); this.__overallColumnArr.splice(toOverXPos, 0, col); // Invalidate the __colToXPosMap this.__colToXPosMap = null; if (visible) { this.setColumnVisible(col, true); } this.__internalChange = false; // Inform the listeners var data = { col: col, fromOverXPos: fromOverXPos, toOverXPos: toOverXPos }; this.fireDataEvent("orderChanged", data); }, /** * Reorders all columns to new overall positions. Will fire one "orderChanged" event * without data afterwards * * @param newPositions {Integer[]} Array mapping the index of a column in table model to its wanted overall * position on screen (both zero based). If the table models holds * col0, col1, col2 and col3 and you give [1,3,2,0], the new column order * will be col1, col3, col2, col0 */ setColumnsOrder(newPositions) { if (qx.core.Environment.get("qx.debug")) { this.assertArray(newPositions, "Invalid argument 'newPositions'."); } if (newPositions.length == this.__overallColumnArr.length) { this.__internalChange = true; // Go through each column an switch visible ones to invisible. Reason is unknown, // this just mimicks the behaviour of moveColumn. Possibly useful because setting // a column visible later updates a map with its screen coords. var isVisible = new Array(newPositions.length); for ( var colIdx = 0; colIdx < this.__overallColumnArr.length; colIdx++ ) { var visible = this.isColumnVisible(colIdx); isVisible[colIdx] = visible; //Remember, as this relies on this.__colToXPosMap which is cleared below if (visible) { this.setColumnVisible(colIdx, false); } } // Store new position values this.__overallColumnArr = qx.lang.Array.clone(newPositions); // Invalidate the __colToXPosMap this.__colToXPosMap = null; // Go through each column an switch invisible ones back to visible for ( var colIdx = 0; colIdx < this.__overallColumnArr.length; colIdx++ ) { if (isVisible[colIdx]) { this.setColumnVisible(colIdx, true); } } this.__internalChange = false; // Inform the listeners. Do not add data as all known listeners in qooxdoo // only take this event to mean "total repaint necesscary". Fabian will look // after deprecating the data part of the orderChanged - event this.fireDataEvent("orderChanged"); } else { throw new Error( "setColumnsOrder: Invalid number of column positions given, expected " + this.__overallColumnArr.length + ", got " + newPositions.length ); } } }, /* ***************************************************************************** DESTRUCTOR ***************************************************************************** */ destruct() { for (var i = 0; i < this.__columnDataArr.length; i++) { this.__columnDataArr[i].headerRenderer.dispose(); this.__columnDataArr[i].dataRenderer.dispose(); this.__columnDataArr[i].editorFactory.dispose(); } this.__overallColumnArr = this.__visibleColumnArr = this.__columnDataArr = this.__colToXPosMap = null; this._disposeObjects( "__headerRenderer", "__dataRenderer", "__editorFactory" ); } });