mirror of
https://github.com/EGroupware/egroupware.git
synced 2024-11-25 17:33:49 +01:00
671 lines
16 KiB
TypeScript
671 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 https://www.egroupware.org
|
|
* @author Andreas Stöckel
|
|
* @copyright EGroupware GmbH 2011-2021
|
|
*/
|
|
|
|
/*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_column, 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"
|
|
import {egw} from "../jsapi/egw_global";
|
|
|
|
/**
|
|
* 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;
|
|
rowProvider: et2_dataview_rowProvider;
|
|
|
|
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;
|
|
}
|
|
}
|