egroupware/etemplate/js/et2_grid.js
Andreas Stöckel 5af5594f60 Major update of the et2_widget internal structure. The following changes were made:
- All attributes of the widgets are now parsed from XML before the widget itself is created.
These attributes plus all default values are then added to an associative array. The associative array is passed as second parameter to the init function of et2_widget, but is also available as this.options *after* the constructor of the et2_widget baseclass has been called.
The et2_widget constructor also calls a function parseArrayMgrAttrs(_attrs) - in this function widget implementations can read the values from e.g. the content and validation_errors array and merge it into the given _attrs associative array.
After the complete internal widgettree is completely loaded and created the "loadingFinished" function gets called and invokes all given setter functions. After that it "glues" the DOM tree together. This should also (I didn't measure it) be a bit faster than before, when the DOM-Tree was created on the fly. 

Please have a look at the changes of the et2_textbox widget to see how this affects writing widgets.

Note: The "id" property is copied to the object scope on the top of the et2_widget constructor.

- When widgets are cloned the "options" array gets passed along to the newly created widget. This means that changes made on the widgets during runtime are not automatically copied to the clone - as this didn't happen anyhow it is not a really disadvantage.
On the other side there should be no difference between widgets directly inside the "overlay" xet tag and widgets which are inside instanciated templates.

- The selbox widget doesn't work anymore - it relied on the loadAttributes function which isn't available anymore. et2_selbox should use the parseArrayMgrAttrs function to access

- I've commented out some of the "validator"-code in etemplate2.js as it created some error messages when destroying the widget tree.
2011-08-19 16:00:44 +00:00

538 lines
12 KiB
JavaScript

/**
* eGroupWare eTemplate2 - JS Description object
*
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package etemplate
* @subpackage api
* @link http://www.egroupware.org
* @author Andreas Stöckel
* @copyright Stylite 2011
* @version $Id$
*/
"use strict";
/*egw:uses
jquery.jquery;
et2_DOMWidget;
et2_xml;
*/
/**
* Class which implements the "grid" XET-Tag
*/
var et2_grid = et2_DOMWidget.extend({
init: function(_parent) {
// Create the table body and the table
this.tbody = $j(document.createElement("tbody"));
this.table = $j(document.createElement("table"))
.addClass("et2_grid");
this.table.append(this.tbody);
// Call the parent constructor
this._super.apply(this, arguments);
// Counters for rows and columns
this.rowCount = 0;
this.columnCount = 0;
// 2D-Array which holds references to the DOM td tags
this.cells = [];
this.managementArray = [];
},
destroy: function() {
this._super.call(this, arguments);
// Delete all references to cells or widgets
this.cells = null;
this.managementArray = null;
this.table = null;
this.tbody = null;
},
_initCells: function(_colData, _rowData) {
// Copy the width and height
var w = _colData.length;
var h = _rowData.length;
// Create the 2D-Cells array
var cells = new Array(h);
for (var y = 0; y < h; y++)
{
cells[y] = new Array(w);
// Initialize the cell description objects
for (var x = 0; x < w; x++)
{
cells[y][x] = {
"td": null,
"widget": null,
"colData": _colData[x],
"rowData": _rowData[y],
"colSpan": 1,
"autoColSpan": false,
"rowSpan": 1,
"autoRowSpan": false,
"x": x,
"y": y
};
}
}
return cells;
},
_getColDataEntry: function() {
return {
"width": "auto",
"class": "",
"align": "",
"span": "1"
};
},
_getRowDataEntry: function() {
return {
"height": "auto",
"class": "",
"valign": "",
"span": "1"
};
},
_getCell: function(_cells, _x, _y) {
if ((0 <= _y) && (_y < _cells.length))
{
var row = _cells[_y];
if ((0 <= _x) && (_x < row.length))
{
return row[_x];
}
}
throw("Error while accessing grid cells, invalid element count or span value!");
},
_forceNumber: function(_val) {
if (isNaN(_val))
{
throw(_val + " is not a number!");
}
return parseInt(_val);
},
_fetchRowColData: function(columns, rows, colData, rowData) {
// Parse the columns tag
et2_filteredNodeIterator(columns, function(node, nodeName) {
var colDataEntry = this._getColDataEntry();
if (nodeName == "column")
{
colDataEntry["width"] = et2_readAttrWithDefault(node, "width", "auto");
colDataEntry["class"] = et2_readAttrWithDefault(node, "class", "");
colDataEntry["align"] = et2_readAttrWithDefault(node, "align", "");
colDataEntry["span"] = et2_readAttrWithDefault(node, "span", "1");
}
else
{
colDataEntry["span"] = "all";
}
colData.push(colDataEntry);
}, this);
// Parse the rows tag
et2_filteredNodeIterator(rows, function(node, nodeName) {
var rowDataEntry = this._getRowDataEntry();
if (nodeName == "row")
{
rowDataEntry["height"] = et2_readAttrWithDefault(node, "height", "auto");
rowDataEntry["class"] = et2_readAttrWithDefault(node, "class", "");
rowDataEntry["valign"] = et2_readAttrWithDefault(node, "valign", "");
rowDataEntry["span"] = et2_readAttrWithDefault(node, "span", "1");
}
else
{
rowDataEntry["span"] = "all";
}
rowData.push(rowDataEntry);
}, this);
},
_fillCells: function(cells, columns, rows) {
var h = cells.length;
var w = (h > 0) ? cells[0].length : 0;
// Read the elements inside the columns
var x = 0;
et2_filteredNodeIterator(columns, function(node, nodeName) {
function _readColNode(node, nodeName) {
if (y >= h)
{
et2_debug("warn", "Skipped grid cell in column, '" +
nodeName + "'");
return;
}
var cell = this._getCell(cells, x, y);
// Read the span value of the element
if (node.getAttribute("span"))
{
cell.rowSpan = node.getAttribute("span");
}
else
{
cell.rowSpan = cell.colData["span"];
cell.autoRowSpan = true;
}
if (cell.rowSpan == "all")
{
cell.rowSpan = cells.length;
}
var span = cell.rowSpan = this._forceNumber(cell.rowSpan);
// Create the widget
var widget = this.createElementFromNode(node, nodeName);
// Fill all cells the widget is spanning
for (var i = 0; i < span && y < cells.length; i++, y++)
{
this._getCell(cells, x, y).widget = widget;
}
};
// If the node is a column, create the widgets which belong into
// the column
var y = 0;
if (nodeName == "column")
{
et2_filteredNodeIterator(node, _readColNode, this);
}
else
{
_readColNode.call(this, node, nodeName);
}
x++;
}, this);
// Read the elements inside the rows
var y = 0;
et2_filteredNodeIterator(rows, function(node, nodeName) {
function _readRowNode(node, nodeName) {
if (x >= w)
{
et2_debug("warn", "Skipped grid cell in row, '" +
nodeName + "'");
return;
}
var cell = this._getCell(cells, x, y);
// Read the span value of the element
if (node.getAttribute("span"))
{
cell.colSpan = node.getAttribute("span");
}
else
{
cell.colSpan = cell.rowData["span"];
cell.autoColSpan = true;
}
if (cell.colSpan == "all")
{
cell.colSpan = cells[y].length;
}
var span = cell.colSpan = this._forceNumber(cell.colSpan);
// Create the element
var widget = this.createElementFromNode(node, nodeName);
// Fill all cells the widget is spanning
for (var i = 0; i < span && x < cells[y].length; i++, x++)
{
cell = this._getCell(cells, x, y);
if (cell.widget == null)
{
cell.widget = widget;
}
else
{
throw("Grid cell collision, two elements " +
"defined for cell (" + x + "," + y + ")!");
}
}
}
// If the node is a row, create the widgets which belong into
// the row
var x = 0;
if (nodeName == "row")
{
et2_filteredNodeIterator(node, _readRowNode, this);
}
else
{
_readRowNode.call(this, node, nodeName);
}
y++;
}, this);
},
_expandLastCells: function(_cells) {
var h = _cells.length;
var w = (h > 0) ? _cells[0].length : 0;
// Determine the last cell in each row and expand its span value if
// the span has not been explicitly set.
for (var y = 0; y < h; y++)
{
for (var x = w - 1; x >= 0; x--)
{
var cell = _cells[y][x];
if (cell.widget != null)
{
if (cell.autoColSpan)
{
cell.colSpan = w - x;
}
break;
}
}
}
// Determine the last cell in each column and expand its span value if
// the span has not been explicitly set.
for (var x = 0; x < w; x++)
{
for (var y = h - 1; y >= 0; y--)
{
var cell = _cells[y][x];
if (cell.widget != null)
{
if (cell.autoRowSpan)
{
cell.rowSpan = h - y;
}
break;
}
}
}
},
/**
* As the does not fit very well into the default widget structure, we're
* overwriting the loadFromXML function and doing a two-pass reading -
* in the first step the
*/
loadFromXML: function(_node) {
// Get the columns and rows tag
var rowsElems = et2_directChildrenByTagName(_node, "rows");
var columnsElems = et2_directChildrenByTagName(_node, "columns");
if (rowsElems.length == 1 && columnsElems.length == 1)
{
var columns = columnsElems[0];
var rows = rowsElems[0];
var colData = [];
var rowData = [];
// Fetch the column and row data
this._fetchRowColData(columns, rows, colData, rowData);
// Initialize the cells
var cells = this._initCells(colData, rowData);
// Create the widgets inside the cells and read the span values
this._fillCells(cells, columns, rows);
// Expand the span values of the last cells
this._expandLastCells(cells);
// Create the table rows
this.createTableFromCells(cells);
}
else
{
throw("Error while parsing grid, none or multiple rows or columns tags!");
}
},
createTableFromCells: function(_cells) {
// Set the rowCount and columnCount variables
var h = this.rowCount = _cells.length;
var w = this.columnCount = (h > 0) ? _cells[0].length : 0;
this.managementArray = [];
this.cells = _cells;
// Create the table rows.
for (var y = 0; y < h; y++)
{
var row = _cells[y];
var tr = $j(document.createElement("tr")).appendTo(this.tbody);
// Create the cells. x is incremented by the colSpan value of the
// cell.
for (var x = 0; x < w;)
{
// Fetch a cell from the cells
var cell = this._getCell(_cells, x, y);
if (cell.td == null && cell.widget != null)
{
// Create the cell
var td = $j(document.createElement("td")).appendTo(tr);
// Add the entry for the widget to the management array
this.managementArray.push({
"cell": td[0],
"widget": cell.widget
});
// Set the span values of the cell
var cs = (x == w - 1) ? w - x : Math.min(w - x, cell.colSpan);
var rs = (y == h - 1) ? h - y : Math.min(h - y, cell.rowSpan);
// Set the col and row span values
if (cs > 1) {
td.attr("colspan", cs);
}
if (rs > 1) {
td.attr("rowspan", rs);
}
// Assign the td to the cell
for (var sx = x; sx < x + cs; sx++)
{
for (var sy = y; sy < y + rs; sy++)
{
this._getCell(_cells, sx, sy).td = td;
}
}
x += cell.colSpan;
}
else
{
x++;
}
}
}
},
/**
* The grid needs its own assign function in order to fill the grid
* accordingly.
*/
assign: function(_obj) {
if (_obj instanceof et2_grid)
{
// Remember all widgets which have already been instanciated
var instances = [];
// Copy the cells array of the other grid and clone the widgets
// inside of it
var cells = new Array(_obj.cells.length);
for (var y = 0; y < _obj.cells.length; y++)
{
cells[y] = new Array(_obj.cells[y].length);
for (var x = 0; x < _obj.cells[y].length; x++)
{
var srcCell = _obj.cells[y][x];
var widget = null;
if (srcCell.widget)
{
// Search for the widget inside the instances array
for (var i = 0; i < instances.length; i++)
{
if (instances[i].srcWidget == srcCell.widget)
{
widget = instances[i].widget;
break;
}
}
if (widget == null)
{
widget = srcCell.widget.clone(this, srcCell.widget.type);
instances.push({
"widget": widget,
"srcWidget": srcCell.widget
});
}
}
cells[y][x] = {
"widget": widget,
"td": null,
"colSpan": srcCell.colSpan,
"rowSpan": srcCell.rowSpan
}
}
}
// Create the table
this.createTableFromCells(cells);
// Copy a reference to the content array manager
if (_obj._mgr)
{
this._mgr = _obj._mgr;
}
}
else
{
throw("Invalid assign to grid!");
}
},
getDOMNode: function(_sender) {
// If the parent class functions are asking for the DOM-Node, return the
// outer table.
if (_sender == this)
{
return this.table[0];
}
// Check whether the _sender object exists inside the management array
for (var i = 0; i < this.managementArray.length; i++)
{
if (this.managementArray[i].widget == _sender)
{
return this.managementArray[i].cell;
}
}
return null;
},
set_id: function(_value) {
this._super.apply(this, arguments);
// Check whether a namespace exists for this element
this.checkCreateNamespace();
}
});
et2_register_widget(et2_grid, ["grid"]);