@qooxdoo/framework
Version:
The JS Framework for Coders
367 lines (302 loc) • 9.38 kB
JavaScript
/* ************************************************************************
qooxdoo - the new era of web development
http://qooxdoo.org
Copyright:
2004-2009 1&1 Internet AG, Germany, http://www.1und1.de
License:
MIT: https://opensource.org/licenses/MIT
See the LICENSE file in the project's top-level directory for details.
Authors:
* Fabian Jakobs (fjakobs)
* Jonathan Weiß (jonathan_rass)
************************************************************************ */
/**
* EXPERIMENTAL!
*
* The WidgetCell layer renders each cell with a qooxdoo widget. The concrete
* widget instance for each cell is provided by a cell provider.
*/
qx.Class.define("qx.ui.virtual.layer.WidgetCell",
{
extend : qx.ui.virtual.layer.Abstract,
include : [
qx.ui.core.MChildrenHandling
],
/**
* @param widgetCellProvider {qx.ui.virtual.core.IWidgetCellProvider} This
* class manages the life cycle of the cell widgets.
*/
construct : function(widgetCellProvider)
{
this.base(arguments);
this.setZIndex(12);
if (qx.core.Environment.get("qx.debug")) {
this.assertInterface(
widgetCellProvider,
qx.ui.virtual.core.IWidgetCellProvider
);
}
this._cellProvider = widgetCellProvider;
this.__spacerPool = [];
},
/*
*****************************************************************************
PROPERTIES
*****************************************************************************
*/
properties :
{
// overridden
anonymous :
{
refine: true,
init: false
}
},
events :
{
/**
* Is fired when the {@link #_fullUpdate} or the
* {@link #_updateLayerWindow} is finished.
*/
updated : "qx.event.type.Event"
},
/*
*****************************************************************************
MEMBERS
*****************************************************************************
*/
members :
{
__spacerPool : null,
/**
* Returns the widget used to render the given cell. May return null if the
* cell isn’t rendered currently rendered.
*
* @param row {Integer} The cell's row index
* @param column {Integer} The cell's column index
* @return {qx.ui.core.LayoutItem|null} the widget used to render the given
* cell or <code>null</code>
*/
getRenderedCellWidget : function(row, column)
{
if (this._getChildren().length === 0) {
return null;
}
var columnCount = this.getColumnSizes().length;
var rowCount = this.getRowSizes().length;
var firstRow = this.getFirstRow();
var firstColumn = this.getFirstColumn();
if (
row < firstRow ||
row >= firstRow + rowCount ||
column < firstColumn ||
column >= firstColumn + columnCount
) {
return null;
}
var childIndex = (column - firstColumn) + (row - firstRow) * columnCount;
var widget = this._getChildren()[childIndex];
if (!widget || widget.getUserData("cell.empty")) {
return null;
} else {
return widget;
}
},
/**
* Get the spacer widget, for empty cells
*
* @return {qx.ui.core.Spacer} The spacer widget.
*/
_getSpacer : function()
{
var spacer = this.__spacerPool.pop();
if (!spacer)
{
spacer = new qx.ui.core.Spacer();
spacer.setUserData("cell.empty", 1);
}
return spacer;
},
/**
* Activates one of the still not empty items.
* @param elementToPool {qx.ui.core.Widget} The widget which gets pooled.
*/
_activateNotEmptyChild : function(elementToPool)
{
// get the current active element
var active = qx.ui.core.FocusHandler.getInstance().getActiveWidget();
// if the element to pool is active or one of its children
if (active == elementToPool || qx.ui.core.Widget.contains(elementToPool, active)) {
// search for a new child to activate
var children = this._getChildren();
for (var i = children.length - 1; i >= 0; i--) {
if (!children[i].getUserData("cell.empty")) {
children[i].activate();
break;
}
};
}
},
// overridden
_fullUpdate : function(firstRow, firstColumn, rowSizes, columnSizes)
{
var cellProvider = this._cellProvider;
var children = this._getChildren().concat();
for (var i=0; i<children.length; i++)
{
var child = children[i];
if (child.getUserData("cell.empty")) {
this.__spacerPool.push(child);
} else {
this._activateNotEmptyChild(child);
cellProvider.poolCellWidget(child);
}
}
var top = 0;
var left = 0;
var visibleItems = [];
for (var y=0; y<rowSizes.length; y++)
{
for (var x=0; x<columnSizes.length; x++)
{
var row = firstRow + y;
var column = firstColumn + x;
var item = cellProvider.getCellWidget(row, column) || this._getSpacer();
visibleItems.push(item);
item.setUserBounds(left, top, columnSizes[x], rowSizes[y]);
item.setUserData("cell.row", row);
item.setUserData("cell.column", column);
this._add(item);
left += columnSizes[x];
}
top += rowSizes[y];
left = 0;
}
children.forEach(function(child){
if (visibleItems.indexOf(child) === -1) {
this._remove(child);
}
}.bind(this));
this.fireEvent("updated");
},
_updateLayerWindow : function(
firstRow, firstColumn,
rowSizes, columnSizes
)
{
// compute overlap of old and new window
//
// +---+
// | ##--+
// | ## |
// +--## |
// +---+
//
if (qx.core.Environment.get("qx.debug"))
{
this.assertPositiveInteger(firstRow);
this.assertPositiveInteger(firstColumn);
this.assertArray(rowSizes);
this.assertArray(columnSizes);
}
var lastRow = firstRow + rowSizes.length - 1;
var lastColumn = firstColumn + columnSizes.length - 1;
var overlap = {
firstRow: Math.max(firstRow, this.getFirstRow()),
lastRow: Math.min(lastRow, this._lastRow),
firstColumn: Math.max(firstColumn, this.getFirstColumn()),
lastColumn: Math.min(lastColumn, this._lastColumn)
};
this._lastColumn = lastColumn;
this._lastRow = lastRow;
if (
overlap.firstRow > overlap.lastRow ||
overlap.firstColumn > overlap.lastColumn
) {
return this._fullUpdate(
firstRow, firstColumn,
rowSizes, columnSizes
);
}
// collect the widgets to move
var children = this._getChildren();
var lineLength = this.getColumnSizes().length;
var widgetsToMove = [];
var widgetsToMoveIndexes = {};
for (var row=firstRow; row<=lastRow; row++)
{
widgetsToMove[row] = [];
for (var column=firstColumn; column<=lastColumn; column++)
{
if (
row >= overlap.firstRow &&
row <= overlap.lastRow &&
column >= overlap.firstColumn &&
column <= overlap.lastColumn
)
{
var x = column - this.getFirstColumn();
var y = row - this.getFirstRow();
var index = y*lineLength + x;
widgetsToMove[row][column] = children[index];
widgetsToMoveIndexes[index] = true;
}
}
}
var cellProvider = this._cellProvider;
// pool widgets
var children = this._getChildren().concat();
for (var i=0; i<children.length; i++)
{
if (!widgetsToMoveIndexes[i])
{
var child = children[i];
if (child.getUserData("cell.empty")) {
this.__spacerPool.push(child);
} else {
this._activateNotEmptyChild(child);
cellProvider.poolCellWidget(child);
}
}
}
var top = 0;
var left = 0;
var visibleItems = [];
for (var y=0; y<rowSizes.length; y++)
{
for (var x=0; x<columnSizes.length; x++)
{
var row = firstRow + y;
var column = firstColumn + x;
var item =
widgetsToMove[row][column] ||
cellProvider.getCellWidget(row, column) ||
this._getSpacer();
visibleItems.push(item);
item.setUserBounds(left, top, columnSizes[x], rowSizes[y]);
item.setUserData("cell.row", row);
item.setUserData("cell.column", column);
this._add(item);
left += columnSizes[x];
}
top += rowSizes[y];
left = 0;
}
children.forEach(function(child){
if (visibleItems.indexOf(child) === -1) {
this._remove(child);
}
}.bind(this));
this.fireEvent("updated");
}
},
destruct : function()
{
var children = this._getChildren();
for (var i=0; i<children.length; i++) {
children[i].dispose();
}
this._cellProvider = this.__spacerPool = null;
}
});