Moved et2 core interfaces to own file; implemented Class.free which cares about calling 'destroy', removing all references the object may hold and rendering the object unusable after 'free' has been called; added 'getMem_freeMem_trace' which helps hunting down memory leaks with objects derriving from Class; added first implementation of the nextmatch widget - currently does nothing else but rendering the header and clicking on the nextmatch_sortheader labels

This commit is contained in:
Andreas Stöckel 2011-08-25 13:35:53 +00:00
parent e6a34adfe7
commit ec5b162bfa
24 changed files with 1432 additions and 86 deletions

View File

@ -13,32 +13,10 @@
"use strict";
/*egw:uses
et2_core_interfaces;
et2_core_widget;
*/
/**
* Interface for all widget classes, which are based on a DOM node.
*/
var et2_IDOMNode = new Interface({
/**
* Returns the DOM-Node of the current widget. The return value has to be
* a plain DOM node. If you want to return an jQuery object as you receive
* it with
*
* obj = $j(node);
*
* simply return obj[0];
*
* @param _sender The _sender parameter defines which widget is asking for
* the DOMNode. Depending on that, the widget may return different nodes.
* This is used in the grid. Normally the _sender parameter can be omitted
* in most implementations of the getDOMNode function.
* However, you should always define the _sender parameter when calling
* getDOMNode!
*/
getDOMNode: function(_sender) {}
});
/**
* Abstract widget class which can be inserted into the DOM. All widget classes
* deriving from this class have to care about implementing the "getDOMNode"
@ -110,7 +88,7 @@ var et2_DOMWidget = et2_widget.extend(et2_IDOMNode, {
if (this._surroundingsMgr)
{
this._surroundingsMgr.destroy();
this._surroundingsMgr.free();
this._surroundingsMgr = null;
}

View File

@ -594,3 +594,13 @@ function et2_hasChild(_nodes, _child)
return false;
}
/**
* Generates a localy unique id and returns it
*/
var _et2_uniqueId = 0;
function et2_uniqueId()
{
return _et2_uniqueId++;
}

View File

@ -72,7 +72,14 @@
*/
// Inspired by base2 and Prototype
(function(){
var initializing = false
var initializing = false;
/**
* Turn this to "true" to track creation and destruction of elements
*/
var getMem_freeMem_trace = false;
var tracedObjects = {};
// Check whether "function decompilation" works - fnTest is normally used to
// check whether a
@ -257,6 +264,18 @@
}
}
// Do some tracing of the getMem_freeMem_trace is activated
if (getMem_freeMem_trace)
{
this.__OBJ_UID = "obj_" + et2_uniqueId();
var className = this.className();
tracedObjects[this.__OBJ_UID] = {
"created": new Date().getTime(),
"class": className
}
et2_debug("log", "*" + this.__OBJ_UID + " (" + className + ")");
}
if (this.init)
{
this.init.apply(this, arguments);
@ -289,6 +308,61 @@
// Add the basic functions
/**
* Destructor function - it calls "destroy" if it has been defined and then
* deletes all keys of this element, so that any access to this element will
* eventually throw an exception, making it easier to hunt down memory leaks.
*/
Class.prototype.free = function() {
if (this.destroy)
{
this.destroy();
}
// Trace the freeing of the object
if (getMem_freeMem_trace)
{
delete(tracedObjects[this.__OBJ_UID]);
et2_debug("log", "-" + this.__OBJ_UID);
}
// Delete every object entry
for (var key in this)
{
delete(this[key]);
}
// Don't raise an exception when attempting to free an element multiple
// times.
this.free = function() {};
};
// Some debug functions for memory leak hunting
if (getMem_freeMem_trace)
{
/**
* Prints a list of all objects UIDs which have not been freed yet.
*/
Class.prototype.showTrace = function() {
console.log(tracedObjects);
},
/**
* VERY slow - for debugging only!
*/
Class.prototype.className = function() {
for (var key in window)
{
if (key.substr(0, 3) == "et2" && this.constructor == window[key])
{
return key;
}
}
return "?";
}
}
/**
* Returns the value of the given attribute. If the property does not
* exist, an error message is issued.
@ -310,7 +384,7 @@
{
et2_debug("error", this, "Attribute '" + _name + "' does not exist!");
}
}
};
/**
* The setAttribute function sets the attribute with the given name to
@ -345,7 +419,7 @@
{
et2_debug("warn", this, "Attribute '" + _name + "' does not exist!");
}
}
};
/**
* generateAttributeSet sanitizes the given associative array of attributes
@ -392,7 +466,7 @@
}
return _attrs;
}
};
/**
* The initAttributes function sets the attributes to their default
@ -408,7 +482,7 @@
this.setAttribute(key, _attrs[key], false);
}
}
}
};
/**
* The implements function can be used to check whether the object
@ -423,7 +497,7 @@
}
}
return true;
}
};
/**
* The instanceOf function can be used to check for both - classes and
@ -439,7 +513,7 @@
{
return this instanceof _obj;
}
}
};
}).call(window);

View File

@ -14,30 +14,10 @@
/*egw:uses
jquery.jquery;
et2_core_interfaces;
et2_core_valueWidget;
*/
/**
* Interface for all widgets which support returning a value
*/
var et2_IInput = new Interface({
/**
* getValue has to return the value of the input widget
*/
getValue: function() {},
/**
* Is dirty returns true if the value of the widget has changed since it
* was loaded.
*/
isDirty: function() {},
/**
* Causes the dirty flag to be reseted.
*/
resetDirty: function() {}
});
/**
* et2_inputWidget derrives from et2_simpleWidget and implements the IInput
* interface. When derriving from this class, call setDOMNode with an input

View File

@ -0,0 +1,74 @@
/**
* eGroupWare eTemplate2 - File which contains all interfaces
*
* @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
et2_core_inheritance;
*/
/**
* Interface for all widget classes, which are based on a DOM node.
*/
var et2_IDOMNode = new Interface({
/**
* Returns the DOM-Node of the current widget. The return value has to be
* a plain DOM node. If you want to return an jQuery object as you receive
* it with
*
* obj = $j(node);
*
* simply return obj[0];
*
* @param _sender The _sender parameter defines which widget is asking for
* the DOMNode. Depending on that, the widget may return different nodes.
* This is used in the grid. Normally the _sender parameter can be omitted
* in most implementations of the getDOMNode function.
* However, you should always define the _sender parameter when calling
* getDOMNode!
*/
getDOMNode: function(_sender) {}
});
/**
* Interface for all widgets which support returning a value
*/
var et2_IInput = new Interface({
/**
* getValue has to return the value of the input widget
*/
getValue: function() {},
/**
* Is dirty returns true if the value of the widget has changed since it
* was loaded.
*/
isDirty: function() {},
/**
* Causes the dirty flag to be reseted.
*/
resetDirty: function() {}
});
/**
* Interface for widgets which should be automatically resized
*/
var et2_IResizeable = new Interface({
/**
* Called whenever the window is resized
*/
resize: function() {}
});

View File

@ -0,0 +1,95 @@
/**
* eGroupWare eTemplate2 - Stylesheet class
*
* @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"
/**
* Contains the egwDynStyleSheet class which allows dynamic generation of stylesheet
* rules - updating a single stylesheet rule is way more efficient than updating
* the element style of many objects.
*/
var EGW_DYNAMIC_STYLESHEET = null;
/**
* Main egwDynStyleSheet class - all egwDynStyleSheets share the same stylesheet
* which is dynamically inserted into the head section of the DOM-Tree.
* This stylesheet is created with the first egwDynStyleSheet class.
*/
function et2_dynStyleSheet()
{
// Check whether the EGW_DYNAMIC_STYLESHEET has already be created
if (!EGW_DYNAMIC_STYLESHEET)
{
var style = document.createElement("style");
document.getElementsByTagName("head")[0].appendChild(style);
this.styleSheet = style.sheet ? style.sheet : style.styleSheet;
this.selectors = {};
this.selectorCount = 0;
EGW_DYNAMIC_STYLESHEET = this;
return this;
}
else
{
return EGW_DYNAMIC_STYLESHEET;
}
}
/**
* Creates/Updates the given stylesheet rule. Example call:
*
* styleSheet.updateRule("#container", "background-color: blue; font-family: sans;")
*
* @param string _selector is the css selector to which the given rule should apply
* @param string _rule is the rule which is bound to the selector.
*/
et2_dynStyleSheet.prototype.updateRule = function (_selector, _rule)
{
var ruleObj = {
"index": this.selectorCount
}
// Remove any existing rule first
if (typeof this.selectors[_selector] !== "undefined")
{
var ruleObj = this.selectors[_selector];
if (typeof this.styleSheet.removeRule !== "undefined")
{
this.styleSheet.removeRule(ruleObj.index);
}
else
{
this.styleSheet.deleteRule(ruleObj.index);
}
delete (this.selectors[_selector]);
}
else
{
this.selectorCount++;
}
// Add the rule to the stylesheet
if (typeof this.styleSheet.addRule !== "undefined")
{
this.styleSheet.addRule(_selector, _rule, ruleObj.index);
}
else
{
this.styleSheet.insertRule(_selector + "{" + _rule + "}", ruleObj.index);
}
this.selectors[_selector] = ruleObj;
}

View File

@ -220,7 +220,7 @@ var et2_widget = Class.extend({
// Call the destructor of all children
for (var i = this._children.length - 1; i >= 0; i--)
{
this._children[i].destroy();
this._children[i].free();
}
// Remove this element from the parent
@ -322,7 +322,13 @@ var et2_widget = Class.extend({
// Check whether the node is one of the supported widget classes.
if (this.isOfSupportedWidgetClass(_node))
{
_node.parent = this;
// Remove the node from its original parent
if (_node._parent)
{
_node._parent.removeChild(_node);
}
_node._parent = this;
this._children.splice(_idx, 0, _node);
}
else

View File

@ -0,0 +1,439 @@
/**
* eGroupWare eTemplate2 - Class which generates the outer container for the grid
*
* @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
* @version $Id$
*/
"use strict"
/*egw:uses
jquery.jquery;
et2_core_common;
et2_core_stylesheet;
*/
/**
* Base view class which is responsible for displaying a grid view element.
*/
var et2_dataview_gridContainer = Class.extend({
/**
* Constant which regulates the column padding.
*/
columnPadding: 2,
/**
* Some browser dependant variables which will be calculated on creation of
* the first gridContainer object.
*/
scrollbarWidth: false,
headerBorderWidth: false,
columnBorderWidth: false,
/**
* Constructor for the grid container
* @param object _parentNode is the DOM-Node into which the grid view will be inserted
*/
init: function(_parentNode) {
// Copy the parent node parameter
this.parentNode = $j(_parentNode);
// Initialize some variables
this.columnNodes = []; // Array with the header containers
this.columns = [];
this.columnMgr = null;
this.width = 0;
this.height = 0;
// 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: function() {
// Clear the columns
this._clearHeader();
// Detatch the outer element
this.table.remove();
},
/**
* Returns the column container node for the given column index
*
* @param _columnIdx the integer column index
*/
getHeaderContainerNode: function(_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: function(_columnData) {
// Free all column objects which have been created till this moment
this.headTr.empty();
// Create the column manager and pass the _columnData to it
this.columns = _columnData; //XXX
//this.columnMgr = new et2_dataview_columnsMgr(_columnData);
// Build the header row
this._buildHeader();
},
/**
* Resizes the grid
*/
resize: function(_w, _h) {
if (this.width != _w)
{
this.width = _w;
// Rebuild the column stylesheets
this._updateColumns();
}
if (this.height != _h)
{
// Set the height of the grid.
// TODO
}
},
/* --- PRIVATE FUNCTIONS --- */
/* --- Code for building the grid container DOM-Tree elements ---- */
/**
* Builds the base DOM-Tree elements
*/
_createElements: function() {
/*
Structure:
<table class="egwGridView_outer">
<thead>
<tr> [HEAD] </tr>
</thead>
<tbody>
<tr> [GRID CONTAINER] </tr>
</tbody>
</table>
*/
this.containerTr = $j(document.createElement("tr"));
this.headTr = $j(document.createElement("tr"));
this.thead = $j(document.createElement("thead"))
.append(this.headTr);
this.tbody = $j(document.createElement("tbody"))
.append(this.containerTr);
this.table = $j(document.createElement("table"))
.addClass("egwGridView_outer")
.append(this.thead, this.tbody)
.appendTo(this.parentNode);
},
/* --- Code for building the header row --- */
/**
* Clears the header row
*/
_clearHeader: function() {
// Destroy the column manager if it had been created
if (this.columnMgr)
{
this.columnMgr.free();
this.columnMgr = null;
}
// Reset the headerColumns array and empty the table row
this.columnNodes = [];
this.headTr.empty();
},
/**
* Builds the containers for the header row
*/
_buildHeader: function() {
for (var i = 0; i < this.columns.length; i++)
{
var col = this.columns[i];
// Create the column header and the container element
var cont = $j(document.createElement("div"))
.addClass("innerContainer")
.addClass(col.divClass);
var column = $j(document.createElement("th"))
.addClass(col.tdClass)
.append(cont)
.appendTo(this.headTr);
// Store both nodes in the columnNodes array
this.columnNodes.push({
"column": column,
"container": cont
});
}
this._buildSelectCol();
},
/**
* Builds the select cols column
*/
_buildSelectCol: function() {
// Build the "select columns" icon
this.selectColIcon = $j(document.createElement("span"))
.addClass("selectcols");
// Build the option column
this.selectCol = $j(document.createElement("th"))
.addClass("optcol")
.append(this.selectColIcon)
.appendTo(this.headTr);
this.selectCol.css("width", this.scrollbarWidth - this.selectCol.outerWidth()
+ this.selectCol.width() + 1);
},
/* --- Code for calculating the browser/css depending widths --- */
/**
* Reads the browser dependant variables
*/
_getDepVars: function() {
if (this.scrollbarWidth === false)
{
// Clone the table and attach it to the outer body tag
var clone = this.table.clone();
$j(window.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
*/
_getScrollbarWidth: function(_table) {
// 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 = $j(document.createElement("div"))
.css("height", "1000px");
var div_outer = $j(document.createElement("div"))
.css("height", "100px")
.css("width", "100px")
.css("overflow", "auto")
.append(div_inner);
var td = $j(document.createElement("td"))
.append(div_outer);
// Store the scrollbar width statically.
$j("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
*/
_getHeaderBorderWidth: function(_table) {
// Create a temporary th which is appended to the outer thead row
var cont = $j(document.createElement("div"))
.addClass("innerContainer");
var th = $j(document.createElement("th"))
.append(cont);
// Insert the th into the document tree
$j("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
*/
_getColumnBorderWidth : function(_table) {
// Create a temporary th which is appended to the outer thead row
var cont = $j(document.createElement("div"))
.addClass("innerContainer");
var td = $j(document.createElement("td"))
.append(cont);
// Insert the th into the document tree
$j("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;
}
});
/*
* Sets the column data which is retrieved by calling egwGridColumns.getColumnData.
* The columns will be updated.
*/
/*egwGridViewOuter.prototype.updateColumns = function(_columns)
{
// Copy the columns data
this.columns = _columns;
var first = true;
// Count the visible rows
var total_cnt = 0;
for (var i = 0; i < this.columns.length; i++)
{
if (this.columns[i].visible)
{
total_cnt++;
}
}
var vis_col = this.visibleColumnCount = 0;
var totalWidth = 0;
// Set the grid column styles
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++;
this.styleSheet.updateRule("." + col.tdClass,
"display: " + (col.visible ? "table-cell" : "none") + "; " +
((vis_col == total_cnt) ? "border-right-width: 0 " : "border-right-width: 1px ") +
"!important;");
this.styleSheet.updateRule(".egwGridView_outer ." + col.divClass,
"width: " + (col.width - this.headerBorderWidth) + "px;");
// Ugly browser dependant code - each browser seems to treat the
// right (collapsed) border of the row differently
addBorder = 0;
if ($j.browser.mozilla)
{
var maj = $j.browser.version.split(".")[0];
if (maj < 2) {
addBorder = 1; // Versions <= FF 3.6
}
}
if ($j.browser.webkit && !first)
{
addBorder = 1;
}
if (($j.browser.msie || $j.browser.opera) && first)
{
addBorder = -1;
}
// Make the last columns one pixel smaller, to prevent a horizontal
// scrollbar from showing up
if (vis_col == total_cnt)
{
addBorder += 1;
}
var width = (col.width - this.columnBorderWidth - addBorder);
this.styleSheet.updateRule(".egwGridView_grid ." + col.divClass,
"width: " + width + "px;");
totalWidth += col.width;
first = false;
}
else
{
this.styleSheet.updateRule("." + col.tdClass,
"display: " + (col.visible ? "table-cell" : "none") + ";");
}
}
// Add the full row and spacer class
this.styleSheet.updateRule(".egwGridView_grid ." + this.uniqueId + "_div_fullRow",
"width: " + (totalWidth - this.columnBorderWidth - 1) + "px; border-right-width: 0 !important;");
this.styleSheet.updateRule(".egwGridView_outer ." + this.uniqueId + "_spacer_fullRow",
"width: " + (totalWidth - 1) + "px; border-right-width: 0 !important;");
// Build the header if this hasn't been done yet
this.buildBaseHeader();
// Update the grid
this.grid.updateColumns(this.columns);
}
egwGridViewOuter.prototype.setHeight = function(_h)
{
this.grid.setScrollHeight(_h - this.outer_thead.outerHeight());
}
});*/

View File

@ -0,0 +1,351 @@
/**
* eGroupWare eTemplate2 - JS Nextmatch 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_widget;
et2_core_interfaces;
et2_core_DOMWidget;
et2_widget_template;
et2_widget_grid;
et2_widget_selectbox;
*/
/**
* Interface all special nextmatch header elements have to implement.
*/
var et2_INextmatchHeader = new Interface({
/**
* The 'setNextmatch' function is called by the parent nextmatch widget
* and tells the nextmatch header widgets which widget they should direct
* their 'sort', 'search' or 'filter' calls to.
*/
setNextmatch: function(_nextmatch) {}
});
var et2_INextmatchSortable = new Interface({
setSortmode: function(_mode) {}
});
/**
* Class which implements the "nextmatch" XET-Tag
*/
var et2_nextmatch = et2_DOMWidget.extend({
attributes: {
"template": {
"name": "Template",
"type": "string",
"description": "The id of the template which contains the grid layout."
}
},
legacyOptions: ["template"],
init: function() {
this._super.apply(this, arguments);
this.div = $j(document.createElement("div"))
.addClass("et2_nextmatch");
// Create the outer grid container
this.dataviewContainer = new et2_dataview_gridContainer(this.div);
this.columns = [];
this.activeFilters = {};
},
destroy: function() {
// Destroy the dataview objects
this.dataviewContainer.free();
this._super.apply(this, arguments);
},
/**
* Sorts the nextmatch widget by the given ID.
*
* @param _id is the id of the data entry which should be sorted.
* @param _asc if true, the elements are sorted ascending, otherwise
* descending. If not set, the sort direction will be determined
* automatically.
*/
sortBy: function(_id, _asc) {
// Create the "sort" entry in the active filters if it did not exist
// yet.
if (typeof this.activeFilters["sort"] == "undefined")
{
this.activeFilters["sort"] = {
"id": null,
"asc": true
};
}
// Determine the sort direction automatically if it is not set
if (typeof _asc == "undefined")
{
if (this.activeFilters["sort"].id == _id)
{
_asc = !this.activeFilters["sort"].asc;
}
}
// Update the entry in the activeFilters object
this.activeFilters["sort"] = {
"id": _id,
"asc": _asc
}
// Set the sortmode display
this.iterateOver(function(_widget) {
_widget.setSortmode((_widget.id == _id) ? (_asc ? "asc": "desc") : "none");
}, this, et2_INextmatchSortable);
et2_debug("info", "Sorting nextmatch by '" + _id + "' in direction '" +
(_asc ? "asc" : "desc") + "'");
},
resetSort: function() {
// Check whether the nextmatch widget is currently sorted
if (typeof this.activeFilters["sort"] != "undefined")
{
// Reset the sortmode
this.iterateOver(function(_widget) {
_widget.setSortmode("none");
}, this, et2_INextmatchSortable);
// If yes, delete the "sort" filter
delete(this.activeFilters["sort"]);
this.applyFilters();
}
},
applyFilters: function() {
et2_debug("info", "Changing nextmatch filters to ", this.activeFilters);
},
/**
* Generates the column name for the given column widget
*/
_genColumnName: function(_widget) {
var result = null;
_widget.iterateOver(function(_widget) {
if (!result)
{
result = _widget.options.label;
}
else
{
result += ", " + _widget.options.label;
}
}, this, et2_INextmatchHeader);
return result;
},
_parseHeaderRow: function(_row) {
// Go over the header row and create the column entries
this.columns = new Array(_row.length);
for (var x = 0; x < _row.length; x++)
{
this.columns[x] = {
"widget": _row[x].widget,
"name": this._genColumnName(_row[x].widget),
"disabled": false,
"canDisable": true,
"width": "auto"
}
// Append the widget to this container
this.addChild(_row[x].widget);
}
this.dataviewContainer.setColumns(this.columns);
},
_parseGrid: function(_grid) {
// Search the rows for a header-row - if one is found, parse it
for (var y = 0; y < _grid.rowData.length; y++)
{
if (_grid.rowData[y]["class"] == "th")
{
this._parseHeaderRow(_grid.cells[y]);
}
}
},
/**
* When the template attribute is set, the nextmatch widget tries to load
* that template and to fetch the grid which is inside of it. It then calls
* _parseGrid in order to get the information for the column headers etc.
*/
set_template: function(_value) {
if (!this.template)
{
// Load the template
var template = et2_createWidget("template", {"id": _value}, this);
if (!template.proxiedTemplate)
{
et2_debug("error", "Error while loading definition template for" +
"nextmatch widget.");
return;
}
// Fetch the grid element and parse it
var definitionGrid = template.proxiedTemplate.getChildren()[0];
if (definitionGrid && definitionGrid instanceof et2_grid)
{
this._parseGrid(definitionGrid);
}
else
{
et2_debug("error", "Nextmatch widget expects a grid to be the " +
"first child of the defined template.");
return;
}
// Free the template again
template.free();
// Call the "setNextmatch" function of all registered
// INextmatchHeader widgets.
this.iterateOver(function (_node) {
_node.setNextmatch(this);
}, this, et2_INextmatchHeader);
}
},
getDOMNode: function(_sender) {
if (_sender == this)
{
return this.div[0];
}
for (var i = 0; i < this.columns.length; i++)
{
if (_sender == this.columns[i].widget)
{
return this.dataviewContainer.getHeaderContainerNode(i);
}
}
return null;
}
});
et2_register_widget(et2_nextmatch, ["nextmatch"]);
/**
* Classes for the nextmatch sortheaders etc.
*/
var et2_nextmatch_header = et2_baseWidget.extend(et2_INextmatchHeader, {
attributes: {
"label": {
"name": "Caption",
"type": "string",
"description": "Caption for the nextmatch header",
"translate": true
}
},
init: function() {
this._super.apply(this, arguments);
this.labelNode = $j(document.createElement("span"));
this.nextmatch = null;
this.setDOMNode(this.labelNode[0]);
},
destroy: function() {
this._super.apply(this, arguments);
},
/**
* Set nextmatch is the function which has to be implemented for the
* et2_INextmatchHeader interface.
*/
setNextmatch: function(_nextmatch) {
this.nextmatch = _nextmatch;
},
set_label: function(_value) {
this.label = _value;
this.labelNode.text(_value);
}
});
et2_register_widget(et2_nextmatch_header, ['nextmatch-header',
'nextmatch-accountfilter', 'nextmatch-customfilter', 'nextmatch-customfields']);
var et2_nextmatch_sortheader = et2_nextmatch_header.extend(et2_INextmatchSortable, {
init: function() {
this._super.apply(this, arguments);
this.sortmode = "none";
this.labelNode.addClass("nextmatch_sortheader none");
},
click: function() {
if (this.nextmatch && this._super.apply(this, arguments))
{
this.nextmatch.sortBy(this.id);
return true;
}
return false;
},
/**
* Function which implements the et2_INextmatchSortable function.
*/
setSortmode: function(_mode) {
// Remove the last sortmode class and add the new one
this.labelNode.removeClass(this.sortmode)
.addClass(_mode);
this.sortmode = _mode;
}
});
et2_register_widget(et2_nextmatch_sortheader, ['nextmatch-sortheader']);
var et2_nextmatch_filterheader = et2_selectbox.extend(et2_INextmatchHeader, {
/**
* Set nextmatch is the function which has to be implemented for the
* et2_INextmatchHeader interface.
*/
setNextmatch: function(_nextmatch) {
this.nextmatch = _nextmatch;
}
});
et2_register_widget(et2_nextmatch_filterheader, ['nextmatch-filterheader']);

View File

@ -14,7 +14,7 @@
/*egw:uses
jquery.jquery;
et2_core_inputWidget;
et2_core_interfaces;
et2_core_baseWidget;
*/

View File

@ -39,17 +39,12 @@ var et2_grid = et2_DOMWidget.extend({
// 2D-Array which holds references to the DOM td tags
this.cells = [];
this.rowData = [];
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) {
@ -72,6 +67,7 @@ var et2_grid = et2_DOMWidget.extend({
"colData": _colData[x],
"rowData": _rowData[y],
"disabled": _colData[x].disabled || _rowData[y].disabled,
"class": _colData[x]["class"],
"colSpan": 1,
"autoColSpan": false,
"rowSpan": 1,
@ -369,7 +365,7 @@ var et2_grid = et2_DOMWidget.extend({
this._expandLastCells(cells);
// Create the table rows
this.createTableFromCells(cells);
this.createTableFromCells(cells, rowData);
}
else
{
@ -377,20 +373,26 @@ var et2_grid = et2_DOMWidget.extend({
}
},
createTableFromCells: function(_cells) {
createTableFromCells: function(_cells, _rowData) {
// 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;
this.rowData = _rowData;
// Create the table rows.
for (var y = 0; y < h; y++)
{
var row = _cells[y];
var tr = $j(document.createElement("tr")).appendTo(this.tbody);
var row_hidden = true;
var tr = $j(document.createElement("tr")).appendTo(this.tbody)
.addClass(this.rowData[y]["class"]);
if (this.rowData[y].disabled)
{
tr.hide();
}
// Create the cells. x is incremented by the colSpan value of the
// cell.
@ -402,16 +404,12 @@ var et2_grid = et2_DOMWidget.extend({
if (cell.td == null && cell.widget != null)
{
// Create the cell
var td = $j(document.createElement("td")).appendTo(tr);
var td = $j(document.createElement("td")).appendTo(tr)
.addClass(cell["class"]);
if (cell.disabled)
{
td.hide();
//td.css("border", "2px solid red");
}
else
{
row_hidden = false;
}
// Add the entry for the widget to the management array
@ -450,11 +448,6 @@ var et2_grid = et2_DOMWidget.extend({
x++;
}
}
if (row_hidden)
{
tr.hide();
}
}
},
@ -468,6 +461,16 @@ var et2_grid = et2_DOMWidget.extend({
// Remember all widgets which have already been instanciated
var instances = [];
// Copy the some data from the rowData array
var rowData = new Array(_obj.rowData.length);
for (var y = 0; y < _obj.rowData.length; y++)
{
rowData[y] = {
"disabled": _obj.rowData[y].disabled,
"class": _obj.rowData[y]["class"]
}
}
// Copy the cells array of the other grid and clone the widgets
// inside of it
var cells = new Array(_obj.cells.length);
@ -508,13 +511,14 @@ var et2_grid = et2_DOMWidget.extend({
"td": null,
"colSpan": srcCell.colSpan,
"rowSpan": srcCell.rowSpan,
"disabled": srcCell.disabled
"disabled": srcCell.disabled,
"class": srcCell["class"]
}
}
}
// Create the table
this.createTableFromCells(cells);
this.createTableFromCells(cells, rowData);
// Copy a reference to the content array manager
if (_obj._mgr)

View File

@ -29,6 +29,8 @@
et2_widget_tabs;
et2_widget_hrule;
et2_extension_nextmatch;
// Requirements for the etemplate2 object
et2_core_xml;
et2_core_arrayMgr;
@ -67,7 +69,7 @@ etemplate2.prototype.clear = function()
if (this.widgetContainer != null)
{
// $j(':input',this.DOMContainer).validator().data("validator").destroy();
this.widgetContainer.destroy();
this.widgetContainer.free();
this.widgetContainer = null;
}
}

View File

@ -0,0 +1,75 @@
<?xml version="1.0"?>
<overlay>
<template id="grid_defn">
<grid>
<columns>
<column width="2%"/>
<column/>
<column disabled="@no_customfields"/>
<column/>
<column width="120"/>
<column/>
<column/>
<column width="8%" disabled="@no_info_owner_info_responsible"/>
<column width="12%"/>
<column width="3%" disabled="@no_actions"/>
<column width="3%" disabled="@no_actions"/>
</columns>
<rows>
<row class="th">
<vbox options="0,0">
<nextmatch-filterheader id="info_type" no_lang="1" options="Type"/>
<nextmatch-filterheader align="center" id="info_status" options="Status"/>
<nextmatch-sortheader align="right" label="Completed" id="info_percent"/>
</vbox>
<grid width="100%" spacing="0" padding="0">
<columns>
<column/>
<column/>
</columns>
<rows>
<row>
<nextmatch-customfilter id="linked" onchange="1" options="link-entry"/>
<nextmatch-sortheader align="right" label="Priority" id="info_priority" options="DESC" class="noPrint"/>
</row>
<row>
<nextmatch-sortheader label="Subject" id="info_subject"/>
<nextmatch-sortheader align="right" label="Creation" id="info_id" options="DESC" class="noPrint"/>
</row>
<row>
<nextmatch-sortheader label="Description" id="info_des"/>
<nextmatch-sortheader align="right" label="last changed" id="info_datemodified" options="DESC" class="noPrint"/>
</row>
</rows>
</grid>
<nextmatch-customfields id="customfields"/>
<nextmatch-header label="Category" id="cat_id"/>
<vbox options="0,0">
<nextmatch-sortheader label="Startdate" id="info_startdate"/>
<nextmatch-sortheader label="Enddate" id="info_enddate"/>
<nextmatch-sortheader label="Date completed" id="info_datecompleted"/>
</vbox>
<vbox cols="1" rows="2">
<nextmatch-sortheader label="Times" id="info_used_time"/>
<nextmatch-sortheader label="planned" id="info_planned_time" class="planned"/>
</vbox>
<vbox cols="1" rows="2" options="0,0">
<nextmatch-sortheader label="Times" id="info_used_time"/>
<nextmatch-sortheader label="planned" id="info_planned_time" class="planned"/>
<nextmatch-sortheader label="Re-planned" id="info_replanned_time" class="replanned"/>
</vbox>
<vbox options="0,0">
<nextmatch-accountfilter statustext="Select to filter by owner" id="info_owner" options="Owner" class="user_filter"/>
<nextmatch-accountfilter statustext="Select to filter by responsible" id="info_responsible" options="Responsible,both" class="user_filter"/>
</vbox>
<nextmatch-sortheader label="last changed" id="info_datemodified" options="DESC"/>
<description value="Sub" class="noPrint"/>
<nextmatch-header label="Action" id="actions" class="noPrint"/>
</row>
</rows>
</grid>
</template>
<nextmatch options="grid_defn" />
</overlay>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 393 B

BIN
etemplate/js/test/gfx/down.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 194 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 202 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 431 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 408 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 215 B

BIN
etemplate/js/test/gfx/up.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 189 B

220
etemplate/js/test/grid.css Normal file
View File

@ -0,0 +1,220 @@
.egwGridView_grid {
table-layout: fixed;
border-spacing: 0;
border-collapse: collapse;
}
.egwGridView_outer div.innerContainer.queued {
background-image: url(gfx/ajax-loader.gif);
background-position: center;
background-repeat: no-repeat;
height: 19px;
}
.egwGridView_grid tr.focused td {
background-image: url(gfx/focused_hatching.png);
background-repeat: repeat;
}
.egwGridView_grid tr.selected td {
background-color: #b7c3ff;
}
.egwGridView_grid tr.draggedOver td {
background-color: #ffd09c !important;
}
/*.egwGridView_grid tr.selected.odd td {
background-color: #9dadff;
}*/
.egwGridView_scrollarea {
width: 100%;
overflow: auto;
}
.egwGridView_spacer {
background-image: url(gfx/non_loaded_bg.png);
background-position: top left;
}
.egwGridView_outer {
table-layout: fixed;
border-spacing: 0;
border-collapse: collapse;
padding: 0;
}
.egwGridView_outer td, .egwGridView_outer tr {
padding: 0;
margin: 0;
}
.egwGridView_grid td {
border-right: 1px solid silver;
border-bottom: 1px solid #e0e0e0;
padding: 2px 3px 2px 4px;
margin: 0;
}
.egwGridView_outer th div.innerContainer,
.egwGridView_grid td div.innerContainer {
margin: 0;
padding: 0;
display: block;
overflow: hidden;
}
.egwGridView_grid tr.fullRow {
font-style: italic;
}
.egwGridView_grid tr.row:hover {
background-color: #f0f0ff;
}
.egwGridView_grid tr {
padding: 2px 3px 2px 4px;
margin: 0;
}
.egwGridView_grid tr.hidden {
display: none;
}
/*.egwGridView_grid tr.odd {
background-color: #F1F1F1;
}*/
.egwGridView_grid span.indentation {
display: inline-block;
}
.egwGridView_grid span {
vertical-align: middle;
}
.egwGridView_grid img.icon {
vertical-align: middle;
margin: 2px 5px 2px 2px;
-moz-user-select: none;
-khtml-user-select: none;
user-select: none;
}
.egwGridView_grid span.arrow {
display: inline-block;
vertical-align: middle;
width: 8px;
height: 8px;
background-repeat: no-repeat;
margin-right: 2px;
-moz-user-select: none;
-khtml-user-select: none;
user-select: none;
}
.egwGridView_grid span.arrow.opened {
cursor: pointer;
background-image: url(gfx/arrows.png);
background-position: -8px 0;
}
.egwGridView_grid span.arrow.closed {
cursor: pointer;
background-image: url(gfx/arrows.png);
background-position: 0 0;
}
.egwGridView_grid span.arrow.loading {
cursor: pointer;
background-image: url(gfx/ajax-loader.gif);
background-position: 0 0;
}
.egwGridView_grid span.iconContainer {
display: inline-block;
padding: 0;
margin: 0;
text-align: center;
}
.egwGridView_grid span.caption {
cursor: default;
-moz-user-select: none;
-khtml-user-select: none;
user-select: none;
}
.egwGridView_outer thead th {
background-color: #E0E0E0;
font-weight: normal;
padding: 5px;
text-align: left;
border-left: 1px solid silver;
border-top: 1px solid silver;
border-right: 1px solid gray;
border-bottom: 1px solid gray;
background-image: url(gfx/header_overlay.png);
background-position: center;
background-repeat: repeat-x;
-webkit-user-select: none;
cursor: default;
}
.egwGridView_outer thead th:hover {
background-color: #F0F0F0;
}
.egwGridView_outer thead th.optcol {
padding: 0;
text-align: center;
}
.egwGridView_outer thead th.optcol:active {
background-color: #D0D0D0;
border-left: 1px solid gray;
border-top: 1px solid gray;
border-right: 1px solid silver;
border-bottom: 1px solid silver;
}
.selectcols {
display: inline-block;
width: 10px;
height: 9px;
margin: 0;
padding: 0;
vertical-align: middle;
background-image: url(gfx/selectcols.png);
background-position: center;
background-repeat: no-repeat;
}
.egwGridView_grid td.frame,
.egwGridView_outer td.frame,
.egwGridView_grid td.egwGridView_spacer {
padding: 0 !important;
border-right: 0 none silver !important;
border-bottom: 0 none silver !important;
}
.egwGridView_outer span.sort {
display: inline-block;
width: 7px;
height: 7px;
background-repeat: no-repeat;
background-position: center;
margin: 2px;
vertical-align: middle;
}
.egwGridView_outer span.sort.asc {
background-image: url(gfx/up.png);
}
.egwGridView_outer span.sort.desc {
background-image: url(gfx/down.png);
}

View File

@ -316,4 +316,35 @@ label input, label span, label div, label select, label textarea {
background-image:url(gfx/hint.png);
}
/**
* Nextmatch widget
*/
.et2_nextmatch {
background-color: silver;
}
.nextmatch_sortheader {
color: #003075;
cursor: pointer;
padding-right: 18px;
margin-right: 10px;
background-repeat: no-repeat;
background-position: right center;
}
.nextmatch_sortheader:hover {
text-decoration: underline;
}
.nextmatch_sortheader.asc {
font-weight: bold;
background-image: url(gfx/up.png);
}
.nextmatch_sortheader.desc {
font-weight: bold;
background-image: url(gfx/down.png);
}

View File

@ -5,6 +5,8 @@
<script src="jquery.js"></script>
<script src="../et2_core_xml.js"></script>
<script src="../et2_core_inheritance.js"></script>
<script src="../et2_core_interfaces.js"></script>
<script src="../et2_core_stylesheet.js"></script>
<script src="../et2_core_common.js"></script>
<script src="../et2_core_arrayMgr.js"></script>
<script src="../et2_core_widget.js"></script>
@ -29,6 +31,9 @@
<script src="../et2_widget_date.js"></script>
<script src="../et2_widget_tabs.js"></script>
<script src="../et2_extension_nextmatch.js"></script>
<script src="../et2_dataview_view_gridcontainer.js"></script>
<script src="../etemplate2.js"></script>
<script src="../lib/tooltip.js"></script>
@ -42,6 +47,7 @@
<script src="et2_test_expressions.json"></script>
<script src="et2_test_dates.json"></script>
<link rel="StyleSheet" type="text/css" href="./test.css" />
<link rel="StyleSheet" type="text/css" href="./grid.css" />
<style>
#linklist {
@ -69,6 +75,7 @@
<a href="#" onclick="open_xet('et2_test_expressions.xet', expression_test_data);">Expression test</a>
<a href="#" onclick="open_xet('et2_test_hbox.xet');">HBox test</a>
<a href="#" onclick="open_xet('et2_test_label.xet');">Label test</a>
<a href="#" onclick="open_xet('et2_test_nextmatch.xet');">Nextmatch test</a>
</div>
<div class="header">ETemplate2 container:</div>
<div id="container"></div>