Improved grid, now correctly renders the timesheet.edit template, grid cells are now automatically expanded, grid objects can now be cloned and used in templates, added dummy implementation for button and textbox, added JS 'use strict' to be notified about JS programming mistakes in FF 4+.

This commit is contained in:
Andreas Stöckel 2011-08-07 13:43:46 +00:00
parent b4530a6d3e
commit db990deffd
11 changed files with 653 additions and 30 deletions

View File

@ -0,0 +1,49 @@
/**
* eGroupWare eTemplate2 - JS Button 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;
*/
/**
* Class which implements the "button" XET-Tag
*/
var et2_button = et2_DOMWidget.extend({
init: function(_parent) {
this.btn = $j(document.createElement("button"))
.addClass("et2_button");
this._super.apply(this, arguments);
this.label = "";
},
set_label: function(_value) {
if (_value != this.value)
{
this.label = _value;
this.btn.text(_value);
}
},
getDOMNode: function() {
return this.btn[0];
}
});
et2_register_widget(et2_button, ["button"]);

View File

@ -10,6 +10,8 @@
* @version $Id$
*/
"use strict";
/*egw:uses
jquery.jquery;
et2_widget;
@ -18,10 +20,11 @@
/**
* Class which implements the "description" XET-Tag
*/
et2_description = et2_DOMWidget.extend({
var et2_description = et2_DOMWidget.extend({
init: function(_parent) {
this.span = $j(document.createElement("span"));
this.span = $j(document.createElement("span"))
.addClass("et2_label");
this._super.apply(this, arguments);
this.value = "";

View File

@ -7,9 +7,11 @@
* @link http://www.egroupware.org
* @author Andreas Stöckel
* @copyright Stylite 2011
* @version $Id: et2_description.js 36016 2011-08-05 14:53:54Z igel457 $
* @version $Id$
*/
"use strict";
/*egw:uses
jquery.jquery;
et2_widget;
@ -19,12 +21,13 @@
/**
* Class which implements the "grid" XET-Tag
*/
et2_grid = et2_DOMWidget.extend({
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"));
this.table = $j(document.createElement("table"))
.addClass("et2_grid");
this.table.append(this.tbody);
// Call the parent constructor
@ -65,7 +68,9 @@ et2_grid = et2_DOMWidget.extend({
"colData": _colData[x],
"rowData": _rowData[y],
"colSpan": 1,
"autoColSpan": false,
"rowSpan": 1,
"autoRowSpan": false,
"x": x,
"y": y
};
@ -158,11 +163,22 @@ et2_grid = et2_DOMWidget.extend({
},
_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
@ -173,6 +189,7 @@ et2_grid = et2_DOMWidget.extend({
else
{
cell.rowSpan = cell.colData["span"];
cell.autoRowSpan = true;
}
if (cell.rowSpan == "all")
@ -212,6 +229,13 @@ et2_grid = et2_DOMWidget.extend({
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
@ -222,6 +246,7 @@ et2_grid = et2_DOMWidget.extend({
else
{
cell.colSpan = cell.rowData["span"];
cell.autoColSpan = true;
}
if (cell.colSpan == "all")
@ -266,6 +291,49 @@ et2_grid = et2_DOMWidget.extend({
}, 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 -
@ -273,8 +341,8 @@ et2_grid = et2_DOMWidget.extend({
*/
loadFromXML: function(_node) {
// Get the columns and rows tag
var rowsElems = _node.getElementsByTagName("rows");
var columnsElems = _node.getElementsByTagName("columns");
var rowsElems = et2_directChildrenByTagName(_node, "rows");
var columnsElems = et2_directChildrenByTagName(_node, "columns");
if (rowsElems.length == 1 && columnsElems.length == 1)
{
@ -292,6 +360,9 @@ et2_grid = et2_DOMWidget.extend({
// 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);
}
@ -337,8 +408,8 @@ et2_grid = et2_DOMWidget.extend({
cell.widget.onSetParent();
// Set the span values of the cell
var cs = Math.min(w - x, cell.colSpan);
var rs = Math.min(h - y, cell.rowSpan);
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) {
@ -368,6 +439,45 @@ et2_grid = et2_DOMWidget.extend({
}
},
/**
* The grid needs its own assign function in order to fill the grid
* accordingly.
*/
assign: function(_obj) {
if (_obj instanceof et2_grid)
{
// 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];
cells[y][x] = {
"widget": (srcCell.widget ?
srcCell.widget.clone(this, srcCell.widget.type) :
null),
"td": null,
"colSpan": srcCell.colSpan,
"rowSpan": srcCell.rowSpan
}
}
}
// Create the table
this.createTableFromCells(cells);
}
else
{
throw("Invalid assign to grid!");
}
},
getDOMNode: function(_sender) {
// If the parent class functions are asking for the DOM-Node, return the
// outer table.

View File

@ -10,6 +10,8 @@
* @version $Id$
*/
"use strict";
/**
* Usage of the JS inheritance system
* ----------------------------------
@ -23,11 +25,11 @@
*
* An interface has to be created in the following way:
*
* IBreathingObject = new Interface({
* var IBreathingObject = new Interface({
* breath: function() {}
* });
*
* Human = Class.extend(IBreathingObject, {
* var Human = Class.extend(IBreathingObject, {
* walk: function() {
* console.log("Walking");
* },
@ -44,7 +46,7 @@
*
* will return true. Lets create a specific class of "Human":
*
* ChuckNorris = Human.extend({
* var ChuckNorris = Human.extend({
* breath: function() {
* console.log("Chuck Norris does not breath, he holds air hostage.");
* },
@ -142,13 +144,7 @@
}
};
// The base Class implementation (does nothing)
this.Class = function(){};
// Create a new Class that inherits from this class. The first parameter
// is an array which defines a set of interfaces the object has to
// implement. An interface is simply an object with named functions.
Class.extend = function(interfaces, prop) {
function classExtend(interfaces, prop) {
if (typeof prop == "undefined")
{
@ -235,9 +231,17 @@
Class.prototype.constructor = Class;
// And make this class extendable
Class.extend = arguments.callee;
Class.extend = classExtend;
return Class;
};
})();
// The base Class implementation (does nothing)
this.Class = function(){};
// Create a new Class that inherits from this class. The first parameter
// is an array which defines a set of interfaces the object has to
// implement. An interface is simply an object with named functions.
Class.extend = classExtend;
}).call(window);

View File

@ -10,6 +10,8 @@
* @version $Id$
*/
"use strict";
/*egw:uses
et2_widget;
*/
@ -22,7 +24,7 @@
*
* TODO: Check whether this widget behaves as it should.
*/
et2_template = et2_DOMWidget.extend({
var et2_template = et2_DOMWidget.extend({
/**
* Initializes this template widget as a simple container.

View File

@ -0,0 +1,49 @@
/**
* eGroupWare eTemplate2 - JS Textbox 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;
*/
/**
* Class which implements the "textbox" XET-Tag
*/
var et2_textbox = et2_DOMWidget.extend({
init: function(_parent) {
this.input = $j(document.createElement("input"))
.addClass("et2_input");
this._super.apply(this, arguments);
this.label = "";
},
set_value: function(_value) {
if (_value != this.value)
{
this.label = _value;
this.input.attr("value", _value);
}
},
getDOMNode: function() {
return this.input[0];
}
});
et2_register_widget(et2_textbox, ["textbox"]);

View File

@ -10,6 +10,8 @@
* @version $Id$
*/
"use strict";
/*egw:uses
jquery.jquery;
et2_xml;
@ -49,7 +51,7 @@ function et2_register_widget(_constructor, _types)
/**
* The et2 widget base class.
*/
et2_widget = Class.extend({
var et2_widget = Class.extend({
/**
* The init function is the constructor of the widget. When deriving new
@ -388,7 +390,7 @@ et2_widget = Class.extend({
/**
* Interface for all widget classes, which are based on a DOM node.
*/
et2_IDOMNode = new Interface({
var et2_IDOMNode = new Interface({
/**
* Returns the DOM-Node of the current widget.
*
@ -407,7 +409,7 @@ et2_IDOMNode = new Interface({
* deriving from this class have to care about implementing the "getDOMNode"
* function which has to return the DOM-Node.
*/
et2_DOMWidget = et2_widget.extend(et2_IDOMNode, {
var et2_DOMWidget = et2_widget.extend(et2_IDOMNode, {
/**
* When the DOMWidget is initialized, it grabs the DOM-Node of the parent
@ -481,14 +483,14 @@ et2_DOMWidget = et2_widget.extend(et2_IDOMNode, {
{
node.setAttribute("id", _value);
}
},
}
});
/**
* Container object for not-yet supported widgets
*/
et2_placeholder = et2_DOMWidget.extend({
var et2_placeholder = et2_DOMWidget.extend({
init: function() {
// Create the placeholder div
@ -532,7 +534,7 @@ et2_placeholder = et2_DOMWidget.extend({
/**
* Common container object
*/
et2_container = et2_DOMWidget.extend({
var et2_container = et2_DOMWidget.extend({
init: function() {
this.div = document.createElement("div");
@ -550,7 +552,7 @@ et2_container = et2_DOMWidget.extend({
* Interface for all widgets which support returning a value
*/
et2_IValue = new Interface({
var et2_IValue = new Interface({
getValue: function() {}
});

View File

@ -10,6 +10,8 @@
* @version $Id$
*/
"use strict";
/**
* Loads the given URL asynchronously from the server. When the file is loaded,
* the given callback function is called, where "this" is set to the given
@ -82,6 +84,23 @@ function et2_loadXMLFromURL(_url, _callback, _context)
}
}
function et2_directChildrenByTagName(_node, _tagName)
{
// Normalize the tag name
_tagName = _tagName.toLowerCase();
var result = [];
for (var i = 0; i < _node.childNodes.length; i++)
{
if (_tagName == _node.childNodes[i].nodeName.toLowerCase())
{
result.push(_node.childNodes[i]);
}
}
return result;
}
function et2_filteredNodeIterator(_node, _callback, _context)
{
for (var i = 0; i < _node.childNodes.length; i++)

View File

@ -40,5 +40,169 @@
</rows>
</grid>
<label value="Horizontal span test:" />
<grid>
<columns>
<column/>
<column/>
<column/>
<column/>
</columns>
<rows>
<row>
<label value="1" span="2"/>
<label value="3" span="2"/>
</row>
<row>
<label value="1"/>
<label value="2" span="3"/>
</row>
<row>
<label value="1"/>
<label value="2"/>
<label value="3"/>
<label value="4"/>
</row>
<row>
<label value="1"/>
<label value="2" span="1"/>
</row>
<row>
<label value="1"/>
<label value="2"/>
</row>
<row>
<label value="1"/>
<label value="2" span="all"/>
</row>
</rows>
</grid>
<label value="Vertical span test:" />
<grid>
<columns>
<column>
<label span="3" value="1"/>
<label span="1" value="4"/>
<label span="1" value="5"/>
</column>
<column>
<label span="1" value="1"/>
<label span="2" value="2"/>
<label span="2" value="4"/>
</column>
<column>
<label span="1" value="1"/>
<label value="2"/>
</column>
<column>
<label span="1" value="1"/>
<label span="all" value="2"/>
</column>
<column>
<label span="1" value="1"/>
<label span="1" value="2"/>
</column>
</columns>
<rows>
<row/>
<row/>
<row/>
<row/>
<row/>
</rows>
</grid>
<template id="embedded_grid">
<label value="Grid embedded in template:"/>
<grid>
<columns>
<column>
<label value="Northwest"/>
<label value="Northeast"/>
</column>
<label value="Equator"/>
<column>
<label value="Southwest"/>
<label value="Southeast"/>
</column>
</columns>
<rows>
<row/>
<row/>
</rows>
</grid>
</template>
<template id="embedded_grid" />
<label value="MDN XUL Grid Tutorial Example 1:"/>
<grid>
<columns>
<column/>
<column/>
</columns>
<rows>
<row>
<button label="Rabbit"/>
<button label="Elephant"/>
</row>
<row>
<button label="Koala"/>
<button label="Gorilla"/>
</row>
</rows>
</grid>
<label value="MDN XUL Grid Tutorial Example 2:"/>
<grid>
<columns>
<column/>
<column/>
</columns>
<rows>
<row>
<label value="Document Title:"/>
<textbox id="doctitle"/>
</row>
<row>
<label value="Path:"/>
<hbox>
<textbox id="docpath"/>
<button label="Browse..."/>
</hbox>
</row>
</rows>
</grid>
<label value="Grid in grid"/>
<grid spacing="1" padding="1">
<columns>
<column/>
<column/>
</columns>
<rows>
<row>
<label value="There is another grid to my right:"/>
<grid>
<columns>
<column/>
</columns>
<rows>
<row>
<label value="Lonely label in a grid." />
</row>
</rows>
</grid>
</row>
</rows>
</grid>
</overlay>

View File

@ -0,0 +1,210 @@
<?xml version="1.0"?>
<!-- $Id: edit.xet 35783 2011-07-20 15:23:36Z leithoff $ -->
<overlay>
<template id="timesheet.edit.general" template="" lang="" group="0" version="1.7.002">
<grid width="100%" height="165">
<columns>
<column width="95"/>
<column width="120"/>
<column width="80" disabled="@ts_viewtype"/>
<column disabled="@ts_viewtype"/>
</columns>
<rows>
<row class="row" disabled="@ts_viewtype">
<description value="Title" options=",,,ts_title"/>
<textbox blur="@ts_title_blur" id="ts_title" size="65" maxlength="80" span="all" class="fullWidth"/>
<description/>
<description/>
</row>
<row class="row" disabled="!@ts_viewtype">
<description value="comment"/>
<textbox multiline="true" id="ts_description" rows="5" cols="50" span="all" class="fullWidth"/>
<description/>
<description/>
</row>
<row class="row">
<description value="Category" options=",,,cat_id"/>
<menulist span="all">
<menupopup type="select-cat" id="cat_id" options="None"/>
</menulist>
</row>
<row class="row">
<description value="Date" options=",,,ts_start"/>
<date id="ts_start" needed="1" options=",8"/>
<description value="Starttime"/>
<date-timeonly id="start_time" options="H:i"/>
</row>
<row class="row">
<description value="Duration" options=",,,ts_duration"/>
<date-duration id="ts_duration" options=",hm"/>
<description value="or endtime" class="noWrap"/>
<date-timeonly id="end_time" options="H:i"/>
</row>
<row class="row" disabled="@ts_viewtype">
<description value="Quantity" options=",,,ts_quantity"/>
<textbox type="float" blur="@ts_quantity_blur" statustext="empty if identical to duration" id="ts_quantity" precision="3" span="all"/>
</row>
<row class="row" disabled="@no_ts_status">
<description value="Status"/>
<menulist span="all">
<menupopup statustext="select a status of the timesheet" id="ts_status" options="please select"/>
</menulist>
</row>
</rows>
</grid>
</template>
<template id="timesheet.edit.notes" template="" lang="" group="0" version="1.5.001">
<grid width="100%" height="165">
<columns>
<column/>
</columns>
<rows>
<row class="row" valign="top">
<textbox multiline="true" id="ts_description" rows="11" cols="70" class="fullWidth"/>
</row>
</rows>
</grid>
</template>
<template id="timesheet.edit.links" template="" lang="" group="0" version="0.1.001">
<grid width="100%" height="165" overflow="auto">
<columns>
<column width="100"/>
<column/>
</columns>
<rows>
<row class="th" disabled="@view">
<description value="Create new links" span="all"/>
</row>
<row class="row" disabled="@status_only">
<link-to id="link_to" span="all"/>
</row>
<row class="th">
<description value="Existing links" span="all"/>
</row>
<row class="row_off" valign="top">
<link-list id="link_to" span="all"/>
</row>
</rows>
</grid>
</template>
<template id="timesheet.edit.customfields" template="" lang="" group="0" version="1.5.001">
<grid width="100%" height="165" overflow="auto">
<columns>
<column/>
</columns>
<rows>
<row>
<customfields/>
</row>
</rows>
</grid>
</template>
<template id="timesheet.edit.history" template="" lang="" group="0" version="1.7.001">
<grid width="100%" height="165" overflow="auto">
<columns>
<column/>
</columns>
<rows>
<row valign="top">
<historylog id="history"/>
</row>
</rows>
</grid>
</template>
<template id="timesheet.edit" template="" lang="" group="0" version="1.9.001">
<grid width="100%">
<columns>
<column width="100"/>
<column/>
</columns>
<rows>
<row disabled="!@msg">
<description align="center" id="msg" no_lang="1" span="all" class="redItalic"/>
<description/>
</row>
<row class="th" height="28">
<description value="User" options=",,,ts_owner"/>
<menulist span="all">
<menupopup id="ts_owner" no_lang="1"/>
</menulist>
</row>
<row class="row">
<description value="Project" options=",,,ts_project"/>
<grid spacing="1" padding="1">
<columns>
<column/>
</columns>
<rows>
<row disabled="@pm_integration=none">
<projectmanager-select statustext="Select a project" id="pm_id" onchange="1" options="None" span="all" class="fullWidth"/>
</row>
<row disabled="@pm_integration=full">
<textbox blur="@ts_project_blur" id="ts_project" size="65" maxlength="80" class="fullWidth"/>
</row>
</rows>
</grid>
</row>
<row class="row" disabled="@ts_viewtype">
<description value="Unitprice" options=",,ts_unitprice"/>
<grid>
<columns>
<column disabled="@pm_integration=none"/>
<column/>
</columns>
<rows>
<row>
<projectmanager-pricelist id="pl_id" onchange="this.form['exec[ts_unitprice]'].value=this.options[this.selectedIndex].text.lastIndexOf('(') &lt; 0 ? '' : this.options[this.selectedIndex].text.slice(this.options[this.selectedIndex].text.lastIndexOf('(')+1,-1);" options="None"/>
<textbox type="float" id="ts_unitprice" span="all"/>
</row>
</rows>
</grid>
</row>
<row>
<tabbox id="tabs" span="all">
<tabs>
<tab label="General" statustext=""/>
<tab label="Notes" statustext=""/>
<tab label="Links" statustext=""/>
<tab label="Custom Fields" statustext=""/>
<tab label="History" statustext=""/>
</tabs>
<tabpanels>
<template id="timesheet.edit.general"/>
<template id="timesheet.edit.notes"/>
<template id="timesheet.edit.links"/>
<template id="timesheet.edit.customfields"/>
<template id="timesheet.edit.history"/>
</tabpanels>
</tabbox>
</row>
<row disabled="!@ts_modified">
<description value="Last modified"/>
<hbox>
<date-time id="ts_modified" readonly="true"/>
<menulist>
<menupopup type="select-account" label="by" id="ts_modifier" readonly="true"/>
</menulist>
</hbox>
</row>
<row>
<hbox span="2">
<hbox>
<button statustext="Edit this entry" label="Edit" id="button[edit]"/>
<button statustext="Saves this entry and add a new one" label="Save &amp; New" id="button[save_new]"/>
<button statustext="Saves the changes made" label="Save" id="button[save]"/>
<button statustext="Applies the changes made" label="Apply" id="button[apply]"/>
<button statustext="closes the window without saving the changes" label="Cancel" id="button[cancel]" onclick="window.close();"/>
<html id="js"/>
</hbox>
<button align="right" statustext="Delete this entry" label="Delete" id="button[delete]" onclick="return confirm('Delete this entry');"/>
</hbox>
</row>
</rows>
</grid>
<styles>
.fullWidth select { widht: 100%; }
.fullWidth input { widht: 100%; }
.fullWidth textarea { widht: 100%; }
</styles>
</template>
</overlay>

View File

@ -10,6 +10,8 @@
<script src="../et2_template.js"></script>
<script src="../et2_description.js"></script>
<script src="../et2_grid.js"></script>
<script src="../et2_button.js"></script>
<script src="../et2_textbox.js"></script>
<style type="text/css">
body {
font-family: Lucida Grande, sans-serif;
@ -64,13 +66,22 @@
color: #3030A0;
margin: 2px 0 2px 0;
}
.et2_grid td {
border: 1px dashed silver;
}
.et2_label {
color: #101050;
font-size: 10pt;
}
</style>
</head>
<body>
<h1>EGroupware ETemplate2 Test</h1>
<div class="header">Choose one of the following tests:</div>
<div id="linklist">
<a href="#" onclick="open_xet('../../../timesheet/templates/default/edit.xet');">Timesheet edit dialog</a>
<a href="#" onclick="open_xet('et2_test_timesheet_edit.xet');">Timesheet edit dialog</a>
<a href="#" onclick="open_xet('et2_test_template.xet');">Template proxy test</a>
<a href="#" onclick="open_xet('et2_test_grid.xet');">Grid test</a>
</div>