egroupware_official/api/js/etemplate/et2_dataview.ts

673 lines
16 KiB
TypeScript

/**
* EGroupware eTemplate2 - dataview code
*
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package etemplate
* @subpackage dataview
* @link http://www.egroupware.org
* @author Andreas Stöckel
* @copyright Stylite 2011-2012
* @version $Id$
*
/*egw:uses
/vendor/bower-asset/jquery/dist/jquery.js;
et2_core_common;
et2_dataview_model_columns;
et2_dataview_view_grid;
et2_dataview_view_rowProvider;
et2_dataview_view_resizeable;
*/
import {et2_dataview_columns} from './et2_dataview_model_columns';
import {et2_dataview_view_resizable} from "./et2_dataview_view_resizeable";
import {et2_dataview_grid} from "./et2_dataview_view_grid";
import {et2_dataview_rowProvider} from "./et2_dataview_view_rowProvider"
/**
* The et2_dataview class is the main class for displaying a dataview. The
* dataview class manages the creation of the outer html nodes (like the table,
* header, etc.) and contains the root container: an instance of
* et2_dataview_view_grid, which can be accessed using the "grid" property of
* this object.
*
* @augments Class
*/
export class et2_dataview
{
/**
* Constant which regulates the column padding.
*/
columnPadding: number;
/**
* Some browser dependant variables which will be calculated on creation of
* the first gridContainer object.
*/
scrollbarWidth: number;
headerBorderWidth: number;
columnBorderWidth: number;
private width: number;
private height: number;
private uniqueId: string;
/**
* Hooks to allow parent to keep up to date if things change
*/
onUpdateColumns: Function;
selectColumnsClick: Function;
private parentNode: JQuery;
egw: any;
private columnNodes: any[];
private columns: any[];
private columnMgr: et2_dataview_columns;
private rowProvider: et2_dataview_rowProvider;
private grid: et2_dataview_grid;
// DOM stuff
private selectColIcon: JQuery;
private headTr: any;
private containerTr: JQuery;
private selectCol: JQuery;
private thead: JQuery;
private tbody: JQuery;
private table: JQuery;
private visibleColumnCount: number;
/**
* Constructor for the grid container
*
* @param {DOMElement} _parentNode is the DOM-Node into which the grid view will be inserted
* @param {egw} _egw
* @memberOf et2_dataview
*/
constructor(_parentNode, _egw) {
// Copy the arguments
this.parentNode = jQuery(_parentNode);
this.egw = _egw;
// Initialize some variables
this.columnNodes = []; // Array with the header containers
this.columns = [];
this.columnMgr = null;
this.rowProvider = null;
this.width = 0;
this.height = 0;
this.uniqueId = "gridCont_" + this.egw.uid();
// Build the base nodes
this._createElements();
// Read the browser dependant variables
this._getDepVars();
}
/**
* Destroys the object, removes all dom nodes and clears all references.
*/
destroy()
{
// Clear the columns
this._clearHeader();
// Free the grid
if (this.grid)
{
this.grid.destroy();
}
// Free the row provider
if (this.rowProvider)
{
this.rowProvider.destroy();
}
// Detatch the outer element
this.table.remove();
}
/**
* Clears all data rows and reloads them
*/
clear()
{
if (this.grid)
{
this.grid.clear();
}
}
/**
* Returns the column container node for the given column index
*
* @param _columnIdx the integer column index
*/
getHeaderContainerNode(_columnIdx)
{
if (typeof this.columnNodes[_columnIdx] != "undefined")
{
return this.columnNodes[_columnIdx].container[0];
}
return null;
}
/**
* Sets the column descriptors and creates the column header according to it.
* The inner grid will be emptied if it has already been built.
*/
setColumns(_columnData)
{
// Free all column objects which have been created till this moment
this._clearHeader();
// Copy the given column data
this.columnMgr = new et2_dataview_columns(_columnData);
// Create the stylesheets
this.updateColumns();
// Build the header row
this._buildHeader();
// Build the grid
this._buildGrid();
}
/**
* Resizes the grid
*/
resize(_w: number, _h: number)
{
// Not fully initialized yet...
if (!this.columnMgr) return;
if (this.width != _w)
{
this.width = _w;
// Take grid border width into account
_w -= (this.table.outerWidth(true) - this.table.innerWidth());
// Take grid header border's width into account. eg. category colors may add extra pixel into width
_w = _w - (this.thead.find('tr').outerWidth() - this.thead.find('tr').innerWidth());
// Rebuild the column stylesheets
this.columnMgr.setTotalWidth(_w - this.scrollbarWidth);
this._updateColumns();
}
if (this.height != _h)
{
this.height = _h;
// Set the height of the grid.
if (this.grid)
{
this.grid.setScrollHeight(this.height -
this.headTr.outerHeight(true));
}
}
}
/**
* Returns the column manager object. You can use it to set the visibility
* of columns etc. Call "updateHeader" if you did any changes.
*/
getColumnMgr() {
return this.columnMgr;
}
/**
* Recalculates the stylesheets which determine the column visibility and
* width.
*
* @param setDefault boolean Allow admins to save current settings as default for all users
*/
updateColumns(setDefault : boolean = false)
{
if (this.columnMgr)
{
this._updateColumns();
}
// Ability to notify parent / someone else
if (this.onUpdateColumns)
{
this.onUpdateColumns(setDefault);
}
}
/* --- PRIVATE FUNCTIONS --- */
/* --- Code for building the grid container DOM-Tree elements ---- */
/**
* Builds the base DOM-Tree elements
*/
private _createElements()
{
/*
Structure:
<table class="egwGridView_outer">
<thead>
<tr> [HEAD] </tr>
</thead>
<tbody>
<tr> [GRID CONTAINER] </tr>
</tbody>
</table>
*/
this.containerTr = jQuery(document.createElement("tr"));
this.headTr = jQuery(document.createElement("tr"));
this.thead = jQuery(document.createElement("thead"))
.append(this.headTr);
this.tbody = jQuery(document.createElement("tbody"))
.append(this.containerTr);
this.table = jQuery(document.createElement("table"))
.addClass("egwGridView_outer")
.append(this.thead, this.tbody)
.appendTo(this.parentNode);
}
/* --- Code for building the header row --- */
/**
* Clears the header row
*/
private _clearHeader ()
{
if (this.columnMgr)
{
this.columnMgr.destroy();
this.columnMgr = null;
}
// Remove dynamic CSS,
for (var i = 0; i < this.columns.length; i++)
{
if(this.columns[i].tdClass)
{
this.egw.css('.'+this.columns[i].tdClass);
}
if(this.columns[i].divClass)
{
this.egw.css('.'+this.columns[i].divClass);
this.egw.css(".egwGridView_outer ." + this.columns[i].divClass);
this.egw.css(".egwGridView_grid ." + this.columns[i].divClass);
}
}
this.egw.css(".egwGridView_grid ." + this.uniqueId + "_div_fullRow");
this.egw.css(".egwGridView_outer ." + this.uniqueId + "_td_fullRow");
this.egw.css(".egwGridView_outer ." + this.uniqueId + "_spacer_fullRow");
// Reset the headerColumns array and empty the table row
this.columnNodes = [];
this.columns = [];
this.headTr.empty();
}
/**
* Sets the column data which is retrieved by calling egwGridColumns.getColumnData.
* The columns will be updated.
*/
private _updateColumns()
{
// Copy the columns data
this.columns = this.columnMgr.getColumnData();
// Count the visible rows
var total_cnt = 0;
for (var i = 0; i < this.columns.length; i++)
{
if (this.columns[i].visible)
{
total_cnt++;
}
}
// Set the grid column styles
var first = true;
var vis_col = this.visibleColumnCount = 0;
var totalWidth = 0;
for (var i = 0; i < this.columns.length; i++)
{
var col = this.columns[i];
col.tdClass = this.uniqueId + "_td_" + col.id;
col.divClass = this.uniqueId + "_div_" + col.id;
if (col.visible)
{
vis_col++;
this.visibleColumnCount++;
// Update the visibility of the column
this.egw.css("." + col.tdClass,
"display: table-cell; " +
"!important;");
// Ugly browser dependant code - each browser seems to treat the
// right (collapsed) border of the row differently
var subBorder = 0;
var subHBorder = 0;
/*
if (jQuery.browser.mozilla)
{
var maj = jQuery.browser.version.split(".")[0];
if (maj < 2) {
subBorder = 1; // Versions <= FF 3.6
}
}
if (jQuery.browser.webkit)
{
if (!first)
{
subBorder = 1;
}
subHBorder = 1;
}
if ((jQuery.browser.msie || jQuery.browser.opera) && first)
{
subBorder = -1;
}
*/
// Make the last columns one pixel smaller, to prevent a horizontal
// scrollbar from showing up
if (vis_col == total_cnt)
{
subBorder += 1;
}
// Write the width of the header columns
var headerWidth = Math.max(0, (col.width - this.headerBorderWidth - subHBorder));
this.egw.css(".egwGridView_outer ." + col.divClass,
"width: " + headerWidth + "px;");
// Write the width of the body-columns
var columnWidth = Math.max(0, (col.width - this.columnBorderWidth - subBorder));
this.egw.css(".egwGridView_grid ." + col.divClass,
"width: " + columnWidth + "px;");
totalWidth += col.width;
first = false;
}
else
{
this.egw.css("." + col.tdClass, "display: none;");
}
}
// Add the full row and spacer class
this.egw.css(".egwGridView_grid ." + this.uniqueId + "_div_fullRow",
"width: " + (totalWidth - this.columnBorderWidth - 2) + "px; border-right-width: 0 !important;");
this.egw.css(".egwGridView_outer ." + this.uniqueId + "_td_fullRow",
"border-right-width: 0 !important;");
this.egw.css(".egwGridView_outer ." + this.uniqueId + "_spacer_fullRow",
"width: " + (totalWidth - 1) + "px; border-right-width: 0 !important;");
}
/**
* Builds the containers for the header row
*/
private _buildHeader()
{
var self = this;
var handler = function(event) {
};
for (var i = 0; i < this.columns.length; i++)
{
var col = this.columns[i];
// Create the column header and the container element
var cont = jQuery(document.createElement("div"))
.addClass("innerContainer")
.addClass(col.divClass);
var column = jQuery(document.createElement("th"))
.addClass(col.tdClass)
.attr("align", "left")
.append(cont)
.appendTo(this.headTr);
if(this.columnMgr && this.columnMgr.getColumnById(i))
{
column.addClass(this.columnMgr.getColumnById(i).fixedWidth ? 'fixedWidth' : 'relativeWidth');
if(this.columnMgr.getColumnById(i).visibility === et2_dataview_column.ET2_COL_VISIBILITY_ALWAYS_NOSELECT)
{
column.addClass('noResize');
}
}
// make column resizable
var enc_column = self.columnMgr.getColumnById(col.id);
if(enc_column.visibility !== et2_dataview_column.ET2_COL_VISIBILITY_ALWAYS_NOSELECT)
{
et2_dataview_view_resizable.makeResizeable(column, function(_w) {
// User wants the column to stay where they put it, even for relative
// width columns, so set it explicitly first and adjust other relative
// columns to match.
if(this.relativeWidth)
{
// Set to selected width
this.set_width(_w + "px");
self.columnMgr.updated();
// Just triggers recalculation
self.columnMgr.getColumnWidth(0);
// Set relative widths to match
var relative = self.columnMgr.totalWidth - self.columnMgr.totalFixed + _w;
this.set_width(_w / relative);
for(var i = 0; i < self.columnMgr.columnCount(); i++)
{
var col = self.columnMgr.getColumnById('col_'+i);
if(!col || col == this || col.fixedWidth) continue;
col.set_width(self.columnMgr.getColumnWidth(i) / relative);
}
// Triggers column change callback, which saves
self.updateColumns();
}
else
{
this.set_width(this.relativeWidth ? (_w / self.columnMgr.totalWidth) : _w + "px");
self.columnMgr.updated();
self.updateColumns();
}
}, enc_column);
}
// Store both nodes in the columnNodes array
this.columnNodes.push({
"column": column,
"container": cont
});
}
this._buildSelectCol();
}
/**
* Builds the select cols column
*/
private _buildSelectCol()
{
// Build the "select columns" icon
this.selectColIcon = jQuery(document.createElement("span"))
.addClass("selectcols")
.css('display', 'inline-block'); // otherwise jQuery('span.selectcols',this.dataview.headTr).show() set it to "inline" causing it to not show up because 0 height
// Build the option column
this.selectCol = jQuery(document.createElement("th"))
.addClass("optcol")
.append(this.selectColIcon)
// Toggle display of option popup
.click(this, function(e) {if(e.data.selectColumnsClick) e.data.selectColumnsClick(e);})
.appendTo(this.headTr);
this.selectCol.css("width", this.scrollbarWidth - this.selectCol.outerWidth()
+ this.selectCol.width() + 1);
}
/**
* Builds the inner grid class
*/
private _buildGrid()
{
// Create the collection of column ids
var colIds = [];
for (var i = 0; i < this.columns.length; i++)
{
if(this.columns[i].visible)
{
colIds[i] = this.columns[i].id;
}
}
// Create the row provider
if(this.rowProvider)
{
this.rowProvider.destroy();
}
this.rowProvider = new et2_dataview_rowProvider(this.uniqueId, colIds);
// Create the grid class and pass "19" as the starting average row height
this.grid = new et2_dataview_grid(null, null, this.egw, this.rowProvider, 19);
// Insert the grid into the DOM-Tree
var tr = jQuery(this.grid.getFirstNode());
this.containerTr.replaceWith(tr);
this.containerTr = tr;
}
/* --- Code for calculating the browser/css depending widths --- */
/**
* Reads the browser dependant variables
*/
private _getDepVars()
{
if (typeof this.scrollbarWidth === 'undefined')
{
// Clone the table and attach it to the outer body tag
var clone = this.table.clone();
jQuery(egw.top.document.getElementsByTagName("body")[0])
.append(clone);
// Read the scrollbar width
this.scrollbarWidth = this.constructor.prototype.scrollbarWidth =
this._getScrollbarWidth(clone);
// Read the header border width
this.headerBorderWidth = this.constructor.prototype.headerBorderWidth =
this._getHeaderBorderWidth(clone);
// Read the column border width
this.columnBorderWidth = this.constructor.prototype.columnBorderWidth =
this._getColumnBorderWidth(clone);
// Remove the cloned DOM-Node again from the outer body
clone.remove();
}
}
/**
* Reads the scrollbar width
*/
private _getScrollbarWidth(_table: JQuery)
{
// Create a temporary td and two divs, which are inserted into the
// DOM-Tree. The outer div has a fixed size and "overflow" set to auto.
// When the second div is inserted, it will be forced to display a scrollbar.
var div_inner = jQuery(document.createElement("div"))
.css("height", "1000px");
var div_outer = jQuery(document.createElement("div"))
.css("height", "100px")
.css("width", "100px")
.css("overflow", "auto")
.append(div_inner);
var td = jQuery(document.createElement("td"))
.append(div_outer);
// Store the scrollbar width statically.
jQuery("tbody tr", _table).append(td);
var width = Math.max(10, div_outer.outerWidth() - div_inner.outerWidth());
// Remove the elements again
div_outer.remove();
return width;
}
/**
* Calculates the total width of the header column border
*/
private _getHeaderBorderWidth(_table: JQuery)
{
// Create a temporary th which is appended to the outer thead row
var cont = jQuery(document.createElement("div"))
.addClass("innerContainer");
var th = jQuery(document.createElement("th"))
.append(cont);
// Insert the th into the document tree
jQuery("thead tr", _table).append(th);
// Calculate the total border width
var width = th.outerWidth(true) - cont.width();
// Remove the appended element again
th.remove();
return width;
}
/**
* Calculates the total width of the column border
*/
private _getColumnBorderWidth(_table: JQuery)
{
// Create a temporary th which is appended to the outer thead row
var cont = jQuery(document.createElement("div"))
.addClass("innerContainer");
var td = jQuery(document.createElement("td"))
.append(cont);
// Insert the th into the document tree
jQuery("tbody tr", _table).append(td);
// Calculate the total border width
_table.addClass("egwGridView_grid");
var width = td.outerWidth(true) - cont.width();
// Remove the appended element again
td.remove();
return width;
}
}