@qooxdoo/framework
Version:
The JS Framework for Coders
787 lines (672 loc) • 24.7 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)
************************************************************************ */
/**
* 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"
);
}
});