Revert "Bring web-component work into master branch"

To many things are not working :(
- addressbook, infolog, even timesheet index lacks at least r/o textbox and problably other widgets
- smallPART (with many extra widgets) is completly unusable
- *box widget seems not to skip empty boxes
--> we need more testing and progress, before we can merge that

This reverts commit 9cee681b94.
This commit is contained in:
Ralf Becker 2021-09-03 11:28:22 +02:00
parent a566599b7f
commit 582793d52b
33 changed files with 3223 additions and 6733 deletions

View File

@ -1,123 +0,0 @@
<?php
/**
* API: loading for web-components modified eTemplate from server
*
* Usage: /egroupware/api/etemplate.php/<app>/templates/default/<name>.xet
*
* @link https://www.egroupware.org
* @author Ralf Becker <rb@egroupware-org>
* @package api
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
*/
use EGroupware\Api;
// add et2- prefix to following widgets/tags
const ADD_ET2_PREFIX_REGEXP = '#<((/?)([vh]?box|textbox|textarea|button))(/?|\s[^>]*)>#m';
// switch evtl. set output-compression off, as we cant calculate a Content-Length header with transparent compression
ini_set('zlib.output_compression', 0);
$GLOBALS['egw_info'] = array(
'flags' => array(
'currentapp' => 'api',
'noheader' => true,
// miss-use session creation callback to send the template, in case we have no session
'autocreate_session_callback' => 'send_template',
'nocachecontrol' => true,
)
);
$start = microtime(true);
include '../header.inc.php';
send_template();
function send_template()
{
$header_include = microtime(true);
// release session, as we don't need it and it blocks parallel requests
$GLOBALS['egw']->session->commit_session();
header('Content-Type: application/xml; charset=UTF-8');
//$path = EGW_SERVER_ROOT.$_SERVER['PATH_INFO'];
// check for customized template in VFS
list(, $app, , $template, $name) = explode('/', $_SERVER['PATH_INFO']);
$path = Api\Etemplate::rel2path(Api\Etemplate::relPath($app . '.' . basename($name, '.xet'), $template));
if(empty($path) || !file_exists($path) || !is_readable($path))
{
http_response_code(404);
exit;
}
/* disable caching for now, as you need to delete the cache, once you change ADD_ET2_PREFIX_REGEXP
$cache = $GLOBALS['egw_info']['server']['temp_dir'].'/egw_cache/eT2-Cache-'.$GLOBALS['egw_info']['server']['install_id'].$_SERVER['PATH_INFO'];
if (file_exists($cache) && filemtime($cache) > filemtime($path) &&
($str = file_get_contents($cache)) !== false)
{
$cache_read = microtime(true);
}
else*/
if(($str = file_get_contents($path)) !== false)
{
// fix <menulist...><menupopup type="select-*"/></menulist> --> <select type="select-*" .../>
$str = preg_replace('#<menulist([^>]*)>[\r\n\s]*<menupopup([^>]+>)[\r\n\s]*</menulist>#', '<select$1$2', $str);
// fix <textbox multiline="true" .../> --> <textarea .../> (et2-prefix and self-closing is handled below)
$str = preg_replace('#<textbox(.*?)\smultiline="true"(.*?)/>#u', '<textarea$1$2/>', $str);
$str = preg_replace_callback(ADD_ET2_PREFIX_REGEXP, static function (array $matches)
{
return '<' . $matches[2] . 'et2-' . $matches[3] .
// web-components must not be self-closing (no "<et2-button .../>", but "<et2-button ...></et2-button>")
(substr($matches[4], -1) === '/' ? substr($matches[4], 0, -1) . '></et2-' . $matches[3] : $matches[4]) . '>';
}, $str);
$processing = microtime(true);
if(isset($cache) && (file_exists($cache_dir = dirname($cache)) || mkdir($cache_dir, 0755, true)))
{
file_put_contents($cache, $str);
}
}
// stop here for not existing file path-traversal for both file and cache here
if(empty($str) || strpos($path, '..') !== false)
{
http_response_code(404);
exit;
}
// headers to allow caching, egw_framework specifies etag on url to force reload, even with Expires header
Api\Session::cache_control(86400); // cache for one day
$etag = '"' . md5($str) . '"';
Header('ETag: ' . $etag);
// if servers send a If-None-Match header, response with 304 Not Modified, if etag matches
if(isset($_SERVER['HTTP_IF_NONE_MATCH']) && $_SERVER['HTTP_IF_NONE_MATCH'] === $etag)
{
header("HTTP/1.1 304 Not Modified");
exit;
}
// we run our own gzip compression, to set a correct Content-Length of the encoded content
if(function_exists('gzencode') && in_array('gzip', explode(',', $_SERVER['HTTP_ACCEPT_ENCODING']), true))
{
$gzip_start = microtime(true);
$str = gzencode($str);
header('Content-Encoding: gzip');
$gziping = microtime(true) - $gzip_start;
}
header('X-Timing: header-include=' . number_format($header_include - $GLOBALS['start'], 3) .
(empty($processing) ? ', cache-read=' . number_format($cache_read - $header_include, 3) :
', processing=' . number_format($processing - $header_include, 3)) .
(!empty($gziping) ? ', gziping=' . number_format($gziping, 3) : '') .
', total=' . number_format(microtime(true) - $GLOBALS['start'], 3)
);
// Content-Length header is important, otherwise browsers dont cache!
Header('Content-Length: ' . bytes($str));
echo $str;
exit; // stop further processing eg. redirect to login
}

View File

@ -1,97 +0,0 @@
/**
* EGroupware eTemplate2 - Box widget
*
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package etemplate
* @subpackage api
* @link https://www.egroupware.org
* @author Nathan Gray
*/
import {css, html, LitElement} from "@lion/core";
import {Et2Widget} from "../Et2Widget/Et2Widget";
export class Et2Box extends Et2Widget(LitElement)
{
static get styles()
{
return [
css`
:host {
display: block;
width: 100%;
}
:host > div {
display: flex;
flex-wrap: nowrap;
justify-content: space-between;
align-items: stretch;
}
/* CSS for child elements */
::slotted(*) {
margin: 0px 2px;
flex: 1 0 auto;
}
::slotted([align="left"]) {
margin-right: auto;
}
::slotted([align="right"]) {
margin-left: auto;
}
`,
];
}
render()
{
return html`
<div class="et2_box" ${this.id ? html`id="${this.id}"` : ''}>
<slot></slot>
</div> `;
}
set_label(new_label)
{
// Boxes don't have labels
}
_createNamespace() : boolean
{
return true;
}
}
customElements.define("et2-box", Et2Box);
export class Et2HBox extends Et2Box
{
static get styles()
{
return [
...super.styles,
css`
:host > div {
flex-direction: row;
}`
];
}
}
customElements.define("et2-hbox", Et2HBox);
export class Et2VBox extends Et2Box
{
static get styles()
{
return [
...super.styles,
css`
:host > div {
flex-direction: column;
}`
];
}
}
customElements.define("et2-vbox", Et2VBox);

View File

@ -1,32 +0,0 @@
/**
* Test file for Etemplate webComponent base widget Et2Box
*/
import {assert, fixture} from '@open-wc/testing';
import {Et2Box} from "../Et2Box";
import {html} from "lit-element";
describe("Box widget", () =>
{
// Reference to component under test
let element : Et2Box;
// Setup run before each test
beforeEach(async() =>
{
// Create an element to test with, and wait until it's ready
element = await fixture<Et2Box>(html`
<et2-box></et2-box>
`);
});
it('is defined', () =>
{
assert.instanceOf(element, Et2Box);
});
it('has no label', () =>
{
element.set_label("Nope");
assert.isEmpty(element.shadowRoot.querySelectorAll('.et2_label'));
})
});

View File

@ -1,183 +0,0 @@
/**
* EGroupware eTemplate2 - Button widget
*
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package etemplate
* @subpackage api
* @link https://www.egroupware.org
* @author Nathan Gray
*/
import {css, html} from "@lion/core";
import {LionButton} from "@lion/button";
import {SlotMixin} from "@lion/core";
import {Et2InputWidget} from "../Et2InputWidget/Et2InputWidget";
export class Et2Button extends Et2InputWidget(SlotMixin(LionButton))
{
protected _created_icon_node : HTMLImageElement;
protected clicked : boolean = false;
private _image : string;
static get styles()
{
return [
...super.styles,
css`
:host {
padding: 1px 8px;
/* These should probably come from somewhere else */
border-radius: 3px;
background-color: #e6e6e6;
max-width: 125px;
}
:host([readonly]) {
display: none;
}
/* Set size for icon */
::slotted([slot="icon"]) {
width: 20px;
padding-right: 3px;
}`,
];
}
static get properties()
{
return {
...super.properties,
image: {type: String}
}
}
get slots()
{
return {
...super.slots,
icon: () =>
{
return document.createElement("img");
}
}
}
constructor()
{
super();
// Property default values
this._image = '';
// Do not add icon here, no children can be added in constructor
// Define a default click handler
// If a different one gets set via attribute, it will be used instead
this.onclick = (typeof this.onclick === "function") ? this.onclick : () =>
{
return this.getInstanceManager().submit();
};
}
connectedCallback()
{
super.connectedCallback();
//this.classList.add("et2_button")
}
set image(new_image : string)
{
let oldValue = this._image;
if(new_image.indexOf("http") >= 0)
{
this._image = new_image
}
else
{
this._image = this.egw().image(new_image, 'etemplate');
}
this.requestUpdate("image", oldValue);
}
_handleClick(event : MouseEvent) : boolean
{
// ignore click on readonly button
if(this.disabled || this.readonly)
{
return false;
}
this.clicked = true;
// Cancel buttons don't trigger the close confirmation prompt
if(this.classList.contains("et2_button_cancel"))
{
this.getInstanceManager()?.skip_close_prompt();
}
if(!super._handleClick(event))
{
this.clicked = false;
return false;
}
this.clicked = false;
this.getInstanceManager()?.skip_close_prompt(false);
return true;
}
render()
{
if(this.readonly)
{
return '';
}
this._iconNode.src = this._image;
return html`
<div class="button-content et2_button" id="${this._buttonId}">
<slot name="icon"></slot>
<slot>${this._label}</slot>
</div> `;
}
get _iconNode() : HTMLImageElement
{
return <HTMLImageElement>(Array.from(this.children)).find(
el => (<HTMLElement>el).slot === "icon",
);
}
/**
* Implementation of the et2_IInput interface
*/
/**
* Always return false as a button is never dirty
*/
isDirty()
{
return false;
}
resetDirty()
{
}
getValue()
{
if(this.clicked)
{
return true;
}
// If "null" is returned, the result is not added to the submitted
// array.
return null;
}
}
// @ts-ignore TypeScript is not recognizing that Et2Button is a LitElement
customElements.define("et2-button", Et2Button);

View File

@ -1,69 +0,0 @@
/**
* Test file for Etemplate webComponent base widget Et2Box
*/
import {assert, fixture} from '@open-wc/testing';
import {Et2Button} from "../Et2Button";
import type {Et2Widget} from "../../Et2Widget/Et2Widget";
import {html} from "lit-element";
import * as sinon from 'sinon';
describe("Button widget", () =>
{
// Reference to component under test
let element : Et2Button;
// Setup run before each test
beforeEach(async() =>
{
// Create an element to test with, and wait until it's ready
element = await fixture<Et2Button>(html`
<et2-button label="I'm a button"></et2-button>
`);
// Stub egw()
sinon.stub(element, "egw").returns({
tooltipUnbind: () => {},
// Image always give check mark. Use data URL to avoid having to serve an actual image
image: i => "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAxNS4wLjAsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4NCjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+DQo8c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkViZW5lXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4Ig0KCSB3aWR0aD0iMzJweCIgaGVpZ2h0PSIzMnB4IiB2aWV3Qm94PSIwIDAgMzIgMzIiIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgMCAwIDMyIDMyIiB4bWw6c3BhY2U9InByZXNlcnZlIj4NCjxwYXRoIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBmaWxsPSIjNjk2OTY5IiBkPSJNNi45NDMsMjguNDUzDQoJYzAuOTA2LDAuNzY1LDIuMDk3LDEuMTI3LDMuMjg2LDEuMTA5YzAuNDMsMC4wMTQsMC44NTItMC4wNjgsMS4yNjUtMC4yMDdjMC42NzktMC4xOCwxLjMyOC0wLjQ1LDEuODY2LTAuOTAyTDI5LjQwMywxNC45DQoJYzEuNzcyLTEuNDk4LDEuNzcyLTMuOTI1LDAtNS40MjJjLTEuNzcyLTEuNDk3LTQuNjQ2LTEuNDk3LTYuNDE4LDBMMTAuMTE5LDIwLjM0OWwtMi4zODktMi40MjRjLTEuNDQtMS40NTctMy43NzItMS40NTctNS4yMTIsMA0KCWMtMS40MzgsMS40Ni0xLjQzOCwzLjgyNSwwLDUuMjgxQzIuNTE4LDIzLjIwNiw1LjQ3NCwyNi45NDcsNi45NDMsMjguNDUzeiIvPg0KPC9zdmc+DQo="
});
});
// Make sure it works
it('is defined', () =>
{
assert.instanceOf(element, Et2Button);
});
it('has a label', () =>
{
element.set_label("Label set");
assert.equal(element.textContent, "Label set");
})
it("click happens", () =>
{
// Setup
let clickSpy = sinon.spy();
element.onclick = clickSpy;
// Click
element.dispatchEvent(new MouseEvent("click"));
// Check for once & only once
assert(clickSpy.calledOnce, "Click only once");
})
it("gets an icon", async() =>
{
element.image = "check";
// Wait for the render to finish
await element.updateComplete;
let image = element.querySelectorAll("img");
assert.equal(image.length, 1);
assert.equal(image[0].src, element.egw().image("check"));
})
});

View File

@ -1,40 +0,0 @@
/**
* EGroupware eTemplate2 - Colorpicker widget (WebComponent)
*
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package etemplate
* @subpackage api
* @link https://www.egroupware.org
* @author Hadi Nategh
*/
import {html} from "@lion/core";
import {LionInput} from "@lion/input";
import {Et2Widget} from "../Et2Widget/Et2Widget";
import {Et2InputWidget} from "../Et2InputWidget/Et2InputWidget";
export class Et2Colorpicker extends Et2InputWidget(Et2Widget(LionInput))
{
constructor()
{
super();
}
connectedCallback()
{
super.connectedCallback();
}
render()
{
return html`
<div class="et2_colorpicker" id="${this.id}">
<input class="et2_colorpicker" type="color" />
</div>
`;
}
}
customElements.define('et2-colorpicker', Et2Colorpicker);

View File

@ -1,187 +0,0 @@
/**
* EGroupware eTemplate2 - Date widget (WebComponent)
*
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package etemplate
* @subpackage api
* @link https://www.egroupware.org
* @author Nathan Gray
*/
import {css, html} from "@lion/core";
import {LionInputDatepicker} from "@lion/input-datepicker";
import {Unparseable} from "@lion/form-core";
import {Et2InputWidget} from "../Et2InputWidget/Et2InputWidget";
/**
* To parse a date into the right format
*
* @param {string} dateString
* @returns {Date | undefined}
*/
export function parseDate(dateString)
{
// First try the server format
try
{
let date = new Date(dateString);
if(date instanceof Date)
{
return date;
}
}
catch(e)
{
// Nope, that didn't parse directly
}
let formatString = <string>(egw.preference("dateformat") || 'Y-m-d');
formatString = formatString.replaceAll(/-\/\./ig, '-');
let parsedString = "";
switch(formatString)
{
case 'd-m-Y':
case 'd/m/Y':
parsedString = `${dateString.slice(6, 10)}/${dateString.slice(
3,
5,
)}/${dateString.slice(0, 2)}`;
break;
case 'm-d-Y':
parsedString = `${dateString.slice(6, 10)}/${dateString.slice(
0,
2,
)}/${dateString.slice(3, 5)}`;
break;
case 'Y-m-d':
case 'Y/m/d':
parsedString = `${dateString.slice(0, 4)}/${dateString.slice(
5,
7,
)}/${dateString.slice(8, 10)}`;
break;
case 'd-M-Y':
parsedString = `${dateString.slice(6, 10)}/${dateString.slice(
3,
5,
)}/${dateString.slice(0, 2)}`;
default:
parsedString = '0000/00/00';
}
const [year, month, day] = parsedString.split('/').map(Number);
const parsedDate = new Date(Date.UTC(year, month - 1, day));
// Check if parsedDate is not `Invalid Date` or that the date has changed (e.g. the not existing 31.02.2020)
if (
year > 0 &&
month > 0 &&
day > 0 &&
parsedDate.getDate() === day &&
parsedDate.getMonth() === month - 1
)
{
return parsedDate;
}
return undefined;
}
/**
* Format dates according to user preference
*
* @param {Date} date
* @param {import('@lion/localize/types/LocalizeMixinTypes').FormatDateOptions} [options] Intl options are available
* set 'dateFormat': "Y-m-d" to specify a particular format
* @returns {string}
*/
export function formatDate(date: Date, options): string
{
if (!date || !(date instanceof Date))
{
return "";
}
let _value = '';
// Add timezone offset back in, or formatDate will lose those hours
let formatDate = new Date(date.valueOf() + date.getTimezoneOffset() * 60 * 1000);
let dateformat = options.dateFormat || <string>egw.preference("dateformat") || 'Y-m-d';
var replace_map = {
d: (date.getUTCDate() < 10 ? "0" : "") + date.getUTCDate(),
m: (date.getUTCMonth() < 9 ? "0" : "") + (date.getUTCMonth() + 1),
Y: "" + date.getUTCFullYear()
}
var re = new RegExp(Object.keys(replace_map).join("|"), "gi");
_value = dateformat.replace(re, function (matched)
{
return replace_map[matched];
});
return _value;
}
export class Et2Date extends Et2InputWidget(LionInputDatepicker)
{
static get styles()
{
return [
...super.styles,
css`
/* Custom CSS */
`,
];
}
static get properties()
{
return {
...super.properties
}
}
constructor()
{
super();
this.parser = parseDate;
this.formatter = formatDate;
}
connectedCallback()
{
super.connectedCallback();
}
/**
* @param {Date} modelValue
*/
// eslint-disable-next-line class-methods-use-this
serializer(modelValue : Date)
{
// isValidDate() is hidden inside LionInputDate, and not exported
// @ts-ignore Can't call isNan(Date), but we're just checking
if(!(modelValue instanceof Date) || isNaN(modelValue))
{
return '';
}
// modelValue is localized, so we take the timezone offset in milliseconds and subtract it
// before converting it to ISO string.
const offset = modelValue.getTimezoneOffset() * 60000;
return new Date(modelValue.getTime() - offset).toJSON().replace(/\.\d{3}Z$/, 'Z');
}
getValue()
{
// The supplied value was not understandable, return null
if(this.modelValue instanceof Unparseable)
{
return null;
}
// It isn't always the case that we want the serializer value, but for Et2Date we do
return this.serializer(this.modelValue);
}
}
// @ts-ignore TypeScript is not recognizing that Et2Date is a LitElement
customElements.define("et2-date", Et2Date);

View File

@ -1,146 +0,0 @@
import {et2_IInput, et2_IInputNode} from "../et2_core_interfaces";
import {Et2Widget} from "../Et2Widget/Et2Widget";
import {dedupeMixin} from "@lion/core";
/**
* This mixin will allow any LitElement to become an Et2InputWidget
*
* Usage:
* export class Et2Button extends Et2InputWidget(LitWidget)) {...}
*/
/**
* Need to define the interface first, to get around TypeScript issues with protected/public
* This must match the public API for Et2InputWidgetClass
* @see https://lit.dev/docs/composition/mixins/#typing-the-subclass
*/
export declare class Et2InputWidgetInterface
{
readOnly : boolean;
protected value : string | number | Object;
public set_value(any) : void;
public get_value() : any;
public getValue() : any;
public isDirty() : boolean;
public resetDirty() : void;
public isValid(messages : string[]) : boolean;
}
const Et2InputWidgetMixin = (superclass) =>
{
class Et2InputWidgetClass extends Et2Widget(superclass) implements et2_IInput, et2_IInputNode
{
protected value : string | number | Object;
protected _oldValue : string | number | Object;
/** WebComponent **/
static get styles()
{
return [
...super.styles
];
}
static get properties()
{
return {
...super.properties,
// readOnly is what the property is in Lion, readonly is the attribute
readOnly: {
type: Boolean,
attribute: 'readonly',
reflect: true,
},
};
}
constructor(...args : any[])
{
super(...args);
}
set_value(new_value)
{
this.value = new_value;
}
get_value()
{
return this.getValue();
}
getValue()
{
return typeof this.serializedValue !== "undefined" ? this.serializedValue : this.modalValue;
}
isDirty()
{
let value = this.getValue();
if(typeof value !== typeof this._oldValue)
{
return true;
}
if(this._oldValue === value)
{
return false;
}
switch(typeof this._oldValue)
{
case "object":
if(Array.isArray(this._oldValue) &&
this._oldValue.length !== value.length
)
{
return true;
}
for(let key in this._oldValue)
{
if(this._oldValue[key] !== value[key])
{
return true;
}
}
return false;
default:
return this._oldValue != value;
}
}
resetDirty()
{
this._oldValue = this.getValue();
}
isValid(messages)
{
var ok = true;
// Check for required
if(this.options && this.options.needed && !this.options.readonly && !this.disabled &&
(this.getValue() == null || this.getValue().valueOf() == ''))
{
messages.push(this.egw().lang('Field must not be empty !!!'));
ok = false;
}
return ok;
}
getInputNode()
{
// From LionInput
return this._inputNode;
}
}
return Et2InputWidgetClass;
}
export const Et2InputWidget = dedupeMixin(Et2InputWidgetMixin);

View File

@ -1,39 +0,0 @@
/**
* Common base for easily running some standard tests on all input widgets
*
* This file should not get run on its own, extend it
*
* TODO: Not sure exactly how to make this happen yet. Maybe:
* https://github.com/mochajs/mocha/wiki/Shared-Behaviours
* <code>
* shared:
* exports.shouldBehaveLikeAUser = function() {
* it('should have .name.first', function() {
* this.user.name.first.should.equal('tobi');
* })
*
* it('should have .name.last', function() {
* this.user.name.last.should.equal('holowaychuk');
* })
*
* describe('.fullname()', function() {
* it('should return the full name', function() {
* this.user.fullname().should.equal('tobi holowaychuk');
* })
* })
* };
* test.js:
*
* var User = require('./user').User
* , Admin = require('./user').Admin
* , shared = require('./shared');
*
* describe('User', function() {
* beforeEach(function() {
* this.user = new User('tobi', 'holowaychuk');
* })
*
* shared.shouldBehaveLikeAUser();
* })
* </code>
*/

View File

@ -1,83 +0,0 @@
/**
* EGroupware eTemplate2 - Textbox widget (WebComponent)
*
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package etemplate
* @subpackage api
* @link https://www.egroupware.org
* @author Nathan Gray
*/
import {css, html} from "@lion/core";
import {LionTextarea} from "@lion/textarea";
import {Et2InputWidget} from "../Et2InputWidget/Et2InputWidget";
import {Et2Widget} from "../Et2Widget/Et2Widget";
export class Et2Textarea extends Et2InputWidget(LionTextarea)
{
static get styles()
{
return [
...super.styles,
css`
:host {
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
}
`,
];
}
static get properties()
{
return {
...super.properties,
/**
* Specify the width of the text area.
* If not set, it will expand to fill the space available.
*/
width: {type: String, reflect: true},
/**
* Specify the height of the text area.
* If not set, it will expand to fill the space available.
*/
height: {type: String, reflect: true},
onkeypress: Function,
}
}
constructor()
{
super();
}
connectedCallback()
{
super.connectedCallback();
}
set width(value)
{
if(this._inputNode)
{
this._inputNode.style.width = value;
}
this.resizeTextarea();
}
set height(value)
{
if(this._inputNode)
{
this._inputNode.style.height = value;
}
this.resizeTextarea();
}
}
// @ts-ignore TypeScript is not recognizing that Et2Textarea is a LitElement
customElements.define("et2-textarea", Et2Textarea);

View File

@ -1,49 +0,0 @@
/**
* EGroupware eTemplate2 - Textbox widget (WebComponent)
*
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package etemplate
* @subpackage api
* @link https://www.egroupware.org
* @author Nathan Gray
*/
import {css, html} from "@lion/core";
import {LionInput} from "@lion/input";
import {Et2InputWidget} from "../Et2InputWidget/Et2InputWidget";
export class Et2Textbox extends Et2InputWidget(LionInput)
{
static get styles()
{
return [
...super.styles,
css`
/* Custom CSS */
`,
];
}
static get properties()
{
return {
...super.properties,
onkeypress: Function,
}
}
constructor(...args : any[])
{
super(...args);
}
connectedCallback()
{
super.connectedCallback();
}
}
// @ts-ignore TypeScript is not recognizing that Et2Textbox is a LitElement
customElements.define("et2-textbox", Et2Textbox);

View File

@ -1,32 +0,0 @@
/**
* Test file for Etemplate webComponent Textbox
*/
import {assert, fixture} from '@open-wc/testing';
import {Et2Textbox} from "../Et2Textbox";
import {html} from "lit-element";
describe("Textbox widget", () =>
{
// Reference to component under test
let element : Et2Textbox;
// Setup run before each test
beforeEach(async() =>
{
// Create an element to test with, and wait until it's ready
element = await fixture<Et2Textbox>(html`
<et2-textbox></et2-textbox>
`);
});
it('is defined', () =>
{
assert.instanceOf(element, Et2Textbox);
});
it('has a label', () =>
{
element.set_label("Yay label");
assert.isEmpty(element.shadowRoot.querySelectorAll('.et2_label'));
})
});

View File

@ -1,34 +0,0 @@
/**
* Testing Et2Textbox input / values
*/
import {assert, fixture} from "@open-wc/testing";
import {html} from "lit-html";
import {Et2Textbox} from "../Et2Textbox";
describe("Textbox input / values", () =>
{
// Reference to component under test
let element : Et2Textbox;
// Setup run before each test
beforeEach(async() =>
{
// Create an element to test with, and wait until it's ready
element = await fixture<Et2Textbox>(html`
<et2-textbox></et2-textbox>
`);
});
it("Takes a value", () =>
{
/*
Complains about set_value() being missing?
let test_value = "test value";
debugger;
element.set_value(test_value);
assert(document.querySelector('input').should.have.text(test_value))
*/
})
});

View File

@ -1,910 +0,0 @@
import {et2_IDOMNode, et2_implements_registry} from "../et2_core_interfaces";
import {et2_arrayMgr} from "../et2_core_arrayMgr";
import {et2_attribute_registry, et2_registry, et2_widget} from "../et2_core_widget";
import type {etemplate2} from "../etemplate2";
import {et2_compileLegacyJS} from "../et2_core_legacyJSFunctions";
import {et2_cloneObject, et2_csvSplit} from "../et2_core_common";
// @ts-ignore
import type {IegwAppLocal} from "../../jsapi/egw_global";
import {ClassWithAttributes, ClassWithInterfaces} from "../et2_core_inheritance";
import {dedupeMixin} from "@lion/core";
import type {et2_container} from "../et2_core_baseWidget";
/**
* This mixin will allow any LitElement to become an Et2Widget
*
* Usage:
* @example
* export class Et2Loading extends Et2Widget(BXLoading) { ... }
* @example
* export class Et2Button extends Et2InputWidget(Et2Widget(BXButton)) { ... }
*
* @see Mixin explanation https://lit.dev/docs/composition/mixins/
*/
function applyMixins(derivedCtor : any, baseCtors : any[])
{
baseCtors.forEach(baseCtor =>
{
Object.getOwnPropertyNames(baseCtor.prototype).forEach(name =>
{
if(name !== 'constructor')
{
derivedCtor.prototype[name] = baseCtor.prototype[name];
}
});
});
}
const Et2WidgetMixin = (superClass) =>
{
class Et2WidgetClass extends superClass implements et2_IDOMNode
{
/** et2_widget compatability **/
protected _mgrs : et2_arrayMgr[] = [];
protected _parent : Et2WidgetClass | et2_widget | null = null;
private _inst : etemplate2 | null = null;
private supportedWidgetClasses = [];
/**
* Not actually required by et2_widget, but needed to keep track of non-webComponent children
*/
private _legacy_children : et2_widget[] = [];
/**
* Properties - default values, and actually creating them as fields
*/
protected _label : string = "";
private statustext : string = "";
protected disabled : Boolean = false;
/** WebComponent **/
static get properties()
{
return {
...super.properties,
/**
* Defines whether this widget is visible.
* Not to be confused with an input widget's HTML attribute 'disabled'.",
*/
disabled: {
type: Boolean,
reflect: true
},
/**
* Tooltip which is shown for this element on hover
*/
statustext: {type: String},
// Defined in parent hierarchy
//label: {type: String},
onclick: {
type: Function
},
/*** Style type attributes ***/
/**
* Used by Et2Box to determine alignment.
* Allowed values are left, right
*/
align: {
type: String,
reflect: true
},
/**
* Allow styles to be set on widgets.
* Any valid CSS will work. Mostly for testing, maybe we won't use this?
* That kind of style should normally go in the app.css
*/
style: {
type: String,
reflect: true
}
};
}
/**
* Widget Mixin constructor
*
* Note the ...args parameter and super() call
*
* @param args
*/
constructor(...args : any[])
{
super(...args);
this.addEventListener("click", this._handleClick.bind(this));
}
connectedCallback()
{
super.connectedCallback();
this.set_label(this._label);
if(this.statustext)
{
this.egw().tooltipBind(this, this.statustext);
}
}
disconnectedCallback()
{
this.egw()?.tooltipUnbind(this);
this.removeEventListener("click", this._handleClick.bind(this));
}
/**
* NOT the setter, since we cannot add to the DOM before connectedCallback()
*
* TODO: This is not best practice. Should just set property, DOM modification should be done in render
* https://lit-element.polymer-project.org/guide/templates#design-a-performant-template
*
* @param value
*/
set_label(value : string)
{
let oldValue = this._label;
// Remove old
let oldLabels = this.getElementsByClassName("et2_label");
while(oldLabels[0])
{
this.removeChild(oldLabels[0]);
}
this._label = value;
if(value)
{
let label = document.createElement("span");
label.classList.add("et2_label");
label.textContent = this._label;
// We should have a slot in the template for the label
//label.slot="label";
this.appendChild(label);
this.requestUpdate('label', oldValue);
}
}
set label(value : string)
{
let oldValue = this.label;
this._label = value;
this.requestUpdate('label', oldValue);
}
/**
* Event handlers
*/
/**
* Click handler calling custom handler set via onclick attribute to this.onclick
*
* @param _ev
* @returns
*/
_handleClick(_ev : MouseEvent) : boolean
{
if (typeof this.onclick == 'function')
{
// Make sure function gets a reference to the widget, splice it in as 2. argument if not
var args = Array.prototype.slice.call(arguments);
if(args.indexOf(this) == -1) args.splice(1, 0, this);
return this.onclick.apply(this, args);
}
return true;
}
/** et2_widget compatability **/
destroy()
{
// Not really needed, use the disconnectedCallback() and let the browser handle it
}
isInTree() : boolean
{
// TODO: Probably should watch the state or something
return true;
}
/**
* Loads the widget tree from an XML node
*
* @param _node xml node
*/
loadFromXML(_node)
{
// Load the child nodes.
for(var i = 0; i < _node.childNodes.length; i++)
{
var node = _node.childNodes[i];
var widgetType = node.nodeName.toLowerCase();
if(widgetType == "#comment")
{
continue;
}
if(widgetType == "#text")
{
if(node.data.replace(/^\s+|\s+$/g, ''))
{
this.innerText = node.data;
}
continue;
}
// Create the new element
this.createElementFromNode(node);
}
}
/**
* Create a et2_widget from an XML node.
*
* First the type and attributes are read from the node. Then the readonly & modifications
* arrays are checked for changes specific to the loaded data. Then the appropriate
* constructor is called. After the constructor returns, the widget has a chance to
* further initialize itself from the XML node when the widget's loadFromXML() method
* is called with the node.
*
* @param _node XML node to read
* @param _name XML node name
*
* @return et2_widget
*/
createElementFromNode(_node, _name?)
{
var attributes = {};
// Parse the "readonly" and "type" flag for this element here, as they
// determine which constructor is used
let _nodeName = attributes["type"] = _node.getAttribute("type") ?
_node.getAttribute("type") : _node.nodeName.toLowerCase();
const readonly = attributes["readonly"] = this.getArrayMgr("readonlys") ?
(<any>this.getArrayMgr("readonlys")).isReadOnly(
_node.getAttribute("id"), _node.getAttribute("readonly"),
typeof this.readonly !== "undefined" ? this.readonly : false) : false;
// Check to see if modifications change type
var modifications = this.getArrayMgr("modifications");
if(modifications && _node.getAttribute("id"))
{
let entry : any = modifications.getEntry(_node.getAttribute("id"));
if(entry == null)
{
// Try again, but skip the fancy stuff
// TODO: Figure out why the getEntry() call doesn't always work
entry = modifications.data[_node.getAttribute("id")];
if(entry)
{
this.egw().debug("warn", "getEntry(" + _node.getAttribute("id") + ") failed, but the data is there.", modifications, entry);
}
else
{
// Try the root, in case a namespace got missed
entry = modifications.getRoot().getEntry(_node.getAttribute("id"));
}
}
if(entry && entry.type && typeof entry.type === 'string')
{
_nodeName = attributes["type"] = entry.type;
}
entry = null;
}
// if _nodeName / type-attribute contains something to expand (eg. type="@${row}[type]"),
// we need to expand it now as it defines the constructor and by that attributes parsed via parseXMLAttrs!
if(_nodeName.charAt(0) == '@' || _nodeName.indexOf('$') >= 0)
{
_nodeName = attributes["type"] = this.getArrayMgr('content').expandName(_nodeName);
}
let widget = null;
if(undefined == window.customElements.get(_nodeName))
{
// Get the constructor - if the widget is readonly, use the special "_ro"
// constructor if it is available
var constructor = et2_registry[typeof et2_registry[_nodeName] == "undefined" ? 'placeholder' : _nodeName];
if(readonly === true && typeof et2_registry[_nodeName + "_ro"] != "undefined")
{
constructor = et2_registry[_nodeName + "_ro"];
}
// Parse the attributes from the given XML attributes object
this.parseXMLAttrs(_node.attributes, attributes, constructor.prototype);
// Do an sanity check for the attributes
ClassWithAttributes.generateAttributeSet(et2_attribute_registry[constructor.name], attributes);
// Creates the new widget, passes this widget as an instance and
// passes the widgetType. Then it goes on loading the XML for it.
widget = new constructor(this, attributes);
// Load the widget itself from XML
widget.loadFromXML(_node);
}
else
{
if(readonly === true && typeof window.customElements.get(_nodeName + "_ro") != "undefined")
{
_nodeName += "_ro";
}
widget = loadWebComponent(_nodeName, _node, this);
if(this.addChild)
{
// webcomponent going into old et2_widget
this.addChild(widget);
}
}
return widget;
}
/**
* The parseXMLAttrs function takes an XML DOM attributes object
* and adds the given attributes to the _target associative array. This
* function also parses the legacyOptions.
*
* @param _attrsObj is the XML DOM attributes object
* @param {object} _target is the object to which the attributes should be written.
* @param {et2_widget} _proto prototype with attributes and legacyOptions attribute
*/
parseXMLAttrs(_attrsObj, _target, _proto)
{
// Check whether the attributes object is really existing, if not abort
if(typeof _attrsObj == "undefined")
{
return;
}
// Iterate over the given attributes and parse them
var mgr = this.getArrayMgr("content");
for(var i = 0; i < _attrsObj.length; i++)
{
var attrName = _attrsObj[i].name;
var attrValue = _attrsObj[i].value;
// Special handling for the legacy options
if(attrName == "options" && _proto.constructor.legacyOptions && _proto.constructor.legacyOptions.length > 0)
{
let legacy = _proto.constructor.legacyOptions || [];
let attrs = et2_attribute_registry[Object.getPrototypeOf(_proto).constructor.name] || {};
// Check for modifications on legacy options here. Normal modifications
// are handled in widget constructor, but it's too late for legacy options then
if(_target.id && this.getArrayMgr("modifications").getEntry(_target.id))
{
var mod : any = this.getArrayMgr("modifications").getEntry(_target.id);
if(typeof mod.options != "undefined") attrValue = _attrsObj[i].value = mod.options;
}
// expand legacyOptions with content
if(attrValue.charAt(0) == '@' || attrValue.indexOf('$') != -1)
{
attrValue = mgr.expandName(attrValue);
}
// Parse the legacy options (as a string, other types not allowed)
var splitted = et2_csvSplit(attrValue + "");
for(var j = 0; j < splitted.length && j < legacy.length; j++)
{
// Blank = not set, unless there's more legacy options provided after
if(splitted[j].trim().length === 0 && legacy.length >= splitted.length) continue;
// Check to make sure we don't overwrite a current option with a legacy option
if(typeof _target[legacy[j]] === "undefined")
{
attrValue = splitted[j];
/**
If more legacy options than expected, stuff them all in the last legacy option
Some legacy options take a comma separated list.
*/
if(j == legacy.length - 1 && splitted.length > legacy.length)
{
attrValue = splitted.slice(j);
}
var attr = et2_attribute_registry[_proto.constructor.name][legacy[j]] || {};
// If the attribute is marked as boolean, parse the
// expression as bool expression.
if(attr.type == "boolean")
{
attrValue = mgr.parseBoolExpression(attrValue);
}
else if(typeof attrValue != "object")
{
attrValue = mgr.expandName(attrValue);
}
_target[legacy[j]] = attrValue;
}
}
}
else if(attrName == "readonly" && typeof _target[attrName] != "undefined")
{
// do NOT overwrite already evaluated readonly attribute
}
else
{
let attrs = et2_attribute_registry[_proto.constructor.name] || {};
if(mgr != null && typeof attrs[attrName] != "undefined")
{
var attr = attrs[attrName];
// If the attribute is marked as boolean, parse the
// expression as bool expression.
if(attr.type == "boolean")
{
attrValue = mgr.parseBoolExpression(attrValue);
}
else
{
attrValue = mgr.expandName(attrValue);
}
}
// Set the attribute
_target[attrName] = attrValue;
}
}
}
iterateOver(_callback : Function, _context, _type)
{
if(et2_implements_registry[_type] && et2_implements_registry[_type](this))
{
_callback.call(_context, this);
}
// Webcomponent children
for(let child of Array.from(this.children))
{
if(typeof child.iterateOver == "function")
{
child.iterateOver(_callback, _context, _type);
}
}
// Legacy children
for(let i = 0; i < this._legacy_children.length; i++)
{
this._legacy_children[i].iterateOver(_callback, _context, _type);
}
}
/**
* Needed for legacy compatability.
*
* @param {Promise[]} promises List of promises from widgets that are not done. Pass an empty array, it will be filled if needed.
*/
loadingFinished(promises)
{
/**
* This is needed mostly as a bridge between non-WebComponent widgets and
* connectedCallback(). It's not really needed if the whole tree is WebComponent.
* WebComponents can be added as children immediately after creation, and they handle the
* rest themselves with their normal lifecycle (especially connectedCallback(), which is kind
* of the equivalent of doLoadingFinished()
*/
if(this.getParent() instanceof et2_widget && this.getParent().getDOMNode(this))
{
this.getParent().getDOMNode(this).append(this);
}
for(let i = 0; i < this._legacy_children.length; i++)
{
let child = this._legacy_children[i];
let child_node = typeof child.getDOMNode !== "undefined" ? child.getDOMNode(child) : null;
if(child_node && child_node !== this)
{
this.append(child_node);
}
child.loadingFinished(promises);
}
}
getWidgetById(_id)
{
if(this.id == _id)
{
return this;
}
}
setParent(new_parent : Et2WidgetClass | et2_widget)
{
this._parent = new_parent;
if(this.id)
{
// Create a namespace for this object
if(this._createNamespace())
{
this.checkCreateNamespace();
}
}
}
getParent() : HTMLElement | et2_widget
{
let parentNode = this.parentNode;
// If parent is an old et2_widget, use it
if(this._parent)
{
return this._parent;
}
return <HTMLElement>parentNode;
}
addChild(child : et2_widget | Et2WidgetClass)
{
if(child instanceof et2_widget)
{
child._parent = this;
// During legacy widget creation, the child's DOM node won't be available yet.
this._legacy_children.push(child);
let child_node = null;
try
{
child_node = typeof child.getDOMNode !== "undefined" ? child.getDOMNode(child) : null;
}
catch(e)
{
// Child did not give up its DOM node nicely but errored instead
}
if(child_node && child_node !== this)
{
this.append(child_node);
}
}
else
{
this.append(child);
}
}
/**
* Get [legacy] children
* Use <obj>.children to get web component children
* @returns {et2_widget[]}
*/
getChildren()
{
return this._legacy_children;
}
getType() : string
{
return this.nodeName;
}
getDOMNode() : HTMLElement
{
return this;
}
/**
* Creates a copy of this widget.
*
* @param {et2_widget} _parent parent to set for clone, default null
*/
clone(_parent?) : Et2WidgetClass
{
// Default _parent to null
if(typeof _parent == "undefined")
{
_parent = null;
}
// Create the copy
var copy = <Et2WidgetClass>this.cloneNode(true);
// Create a clone of all child widgets of the given object
for(var i = 0; i < copy.getChildren().length; i++)
{
copy.addChild(copy.getChildren()[i].clone(this));
}
// Copy a reference to the content array manager
copy.setArrayMgrs(this.getArrayMgrs());
return copy;
}
/**
* Sets the array manager for the given part
*
* @param {string} _part which array mgr to set
* @param {object} _mgr
*/
setArrayMgr(_part : string, _mgr : et2_arrayMgr)
{
this._mgrs[_part] = _mgr;
}
/**
* Returns the array manager object for the given part
*
* @param {string} managed_array_type name of array mgr to return
*/
getArrayMgr(managed_array_type : string) : et2_arrayMgr | null
{
if(this._mgrs && typeof this._mgrs[managed_array_type] != "undefined")
{
return this._mgrs[managed_array_type];
}
else if(this.getParent())
{
return this.getParent().getArrayMgr(managed_array_type);
}
return null;
}
/**
* Sets all array manager objects - this function can be used to set the
* root array managers of the container object.
*
* @param {object} _mgrs
*/
setArrayMgrs(_mgrs)
{
this._mgrs = <et2_arrayMgr[]>et2_cloneObject(_mgrs);
}
/**
* Returns an associative array containing the top-most array managers.
*
* @param _mgrs is used internally and should not be supplied.
*/
getArrayMgrs(_mgrs? : object)
{
if(typeof _mgrs == "undefined")
{
_mgrs = {};
}
// Add all managers of this object to the result, if they have not already
// been set in the result
for(var key in this._mgrs)
{
if(typeof _mgrs[key] == "undefined")
{
_mgrs[key] = this._mgrs[key];
}
}
// Recursively applies this function to the parent widget
if(this._parent)
{
this._parent.getArrayMgrs(_mgrs);
}
return _mgrs;
}
/**
* Checks whether a namespace exists for this element in the content array.
* If yes, an own perspective of the content array is created. If not, the
* parent content manager is used.
*
* Constructor attributes are passed in case a child needs to make decisions
*/
checkCreateNamespace()
{
// Get the content manager
var mgrs = this.getArrayMgrs();
for(var key in mgrs)
{
var mgr = mgrs[key];
// Get the original content manager if we have already created a
// perspective for this node
if(typeof this._mgrs[key] != "undefined" && mgr.perspectiveData.owner == this)
{
mgr = mgr.parentMgr;
}
// Check whether the manager has a namespace for the id of this object
var entry = mgr.getEntry(this.id);
if(typeof entry === 'object' && entry !== null || this.id)
{
// The content manager has an own node for this object, so
// create an own perspective.
this._mgrs[key] = mgr.openPerspective(this, this.id);
}
else
{
// The current content manager does not have an own namespace for
// this element, so use the content manager of the parent.
delete (this._mgrs[key]);
}
}
}
/**
* Returns the instance manager
*
* @return {etemplate2}
*/
getInstanceManager()
{
if(this._inst != null)
{
return this._inst;
}
else if(this.getParent())
{
return this.getParent().getInstanceManager();
}
return null;
}
/**
* Returns the base widget
* Usually this is the same as getInstanceManager().widgetContainer
*/
getRoot() : et2_container
{
if(this.getParent() != null)
{
return this.getParent().getRoot();
}
else
{
return <et2_container><unknown>this;
}
}
/**
* Returns the path into the data array. By default, array manager takes care of
* this, but some extensions need to override this
*/
getPath()
{
var path = this.getArrayMgr("content").getPath();
// Prevent namespaced widgets with value from going an extra layer deep
if(this.id && this._createNamespace() && path[path.length - 1] == this.id) path.pop();
return path;
}
_createNamespace() : boolean
{
return false;
}
egw() : IegwAppLocal
{
if(this.getParent() != null && !(this.getParent() instanceof HTMLElement))
{
return (<et2_widget>this.getParent()).egw();
}
// Get the window this object belongs to
var wnd = null;
// @ts-ignore Technically this doesn't have implements(), but it's mixed in
if(this.implements(et2_IDOMNode))
{
var node = (<et2_IDOMNode><unknown>this).getDOMNode();
if(node && node.ownerDocument)
{
wnd = node.ownerDocument.parentNode || node.ownerDocument.defaultView;
}
}
// If we're the root object, return the phpgwapi API instance
return typeof egw === "function" ? egw('phpgwapi', wnd) : null;
}
};
// Add some more stuff in
applyMixins(Et2WidgetClass, [ClassWithInterfaces]);
return Et2WidgetClass;
}
export const Et2Widget = dedupeMixin(Et2WidgetMixin);
/**
* Load a Web Component
* @param _nodeName
* @param _template_node
*/
export function loadWebComponent(_nodeName : string, _template_node, parent : Et2Widget | et2_widget) : HTMLElement
{
// @ts-ignore
let widget = <Et2Widget>document.createElement(_nodeName);
widget.textContent = _template_node.textContent;
const widget_class = window.customElements.get(_nodeName);
if(!widget_class)
{
throw Error("Unknown or unregistered WebComponent '" + _nodeName + "', could not find class");
}
widget.setParent(parent);
var mgr = widget.getArrayMgr("content");
// Set read-only. Doesn't really matter if it's a ro widget, but otherwise it needs set
widget.readonly = parent.getArrayMgr("readonlys") ?
(<any>parent.getArrayMgr("readonlys")).isReadOnly(
_template_node.getAttribute("id"), _template_node.getAttribute("readonly"),
typeof parent.readonly !== "undefined" ? parent.readonly : false) : false;
// Apply any set attributes - widget will do its own coercion
_template_node.getAttributeNames().forEach(attribute =>
{
let attrValue = _template_node.getAttribute(attribute);
// If there is not attribute set, ignore it. Widget sets its own default.
if(typeof attrValue === "undefined") return;
const property = widget_class.getPropertyOptions(attribute);
switch(property.type)
{
case Boolean:
// If the attribute is marked as boolean, parse the
// expression as bool expression.
attrValue = mgr.parseBoolExpression(attrValue);
break;
case Function:
// We parse it into a function here so we can pass in the widget as context.
// Leaving it to the LitElement conversion loses the widget as context
if(typeof attrValue !== "function")
{
attrValue = et2_compileLegacyJS(attrValue, widget, widget);
}
break;
default:
attrValue = mgr.expandName(attrValue);
break;
}
// Set as attribute or property, as appropriate
if(widget.getAttributeNames().indexOf(attribute) >= 0)
{
// Set as attribute (reflected in DOM)
widget.setAttribute(attribute, attrValue);
}
else
{
// Set as property, not attribute
widget[attribute] = attrValue;
}
});
if(widget_class.getPropertyOptions("value") && widget.set_value)
{
if(mgr != null)
{
let val = mgr.getEntry(widget.id, false, true);
if(val !== null)
{
widget.set_value(val);
}
}
}
// Children need to be loaded
widget.loadFromXML(_template_node);
return widget;
}

View File

@ -0,0 +1,21 @@
/**
* EGroupware eTemplate2 - Button widget
*
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package etemplate
* @subpackage api
* @link https://www.egroupware.org
* @author Nathan Gray
*/
/* Commented out while we work on rollup
import {LitElement,html} from "https://cdn.skypack.dev/lit-element";
import {SlButton} from "https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@2.0.0-beta.44/dist/shoelace.js";
export class Et2Button extends SlButton
{
size='small';
}
customElements.define("et2-button",Et2Button);
*/

View File

@ -12,266 +12,294 @@
et2_core_common;
*/
import {egw, IegwAppLocal} from "../jsapi/egw_global";
import {et2_checkType, et2_cloneObject, et2_no_init, et2_validateAttrib} from "./et2_core_common";
import {et2_IDOMNode, et2_IInput, et2_IInputNode, et2_implements_registry} from "./et2_core_interfaces";
import {egw} from "../jsapi/egw_global";
import {et2_checkType, et2_no_init, et2_validateAttrib} from "./et2_core_common";
import {et2_implements_registry} from "./et2_core_interfaces";
// Needed for mixin
export function mix (superclass)
{
return new MixinBuilder(superclass);
}
export class MixinBuilder {
constructor(superclass) {
this.superclass = superclass;
}
with(...mixins) {
return mixins.reduce(this.applyMixins, this.superclass);
}
applyMixins(derivedConstructor: any, baseConstructor: any) {
Object.getOwnPropertyNames(baseConstructor.prototype)
.forEach(name => {
Object.defineProperty(derivedConstructor.prototype,
name,
Object.
getOwnPropertyDescriptor(
baseConstructor.prototype,
name
)
);
});
}
copyProperties(target, source) {
for (let key of Reflect.ownKeys(source)) {
if (key !== "constructor" && key !== "prototype" && key !== "name") {
let desc = Object.getOwnPropertyDescriptor(source, key);
Object.defineProperty(target, key, desc);
}
}
}
}
// This one from Typescript docs
export function applyMixins(derivedCtor: any, constructors: any[]) {
constructors.forEach((baseCtor) => {
Object.getOwnPropertyNames(baseCtor.prototype).forEach((name) => {
Object.defineProperty(
derivedCtor.prototype,
name,
Object.getOwnPropertyDescriptor(baseCtor.prototype, name) ||
Object.create(null)
);
});
});
}
/*
Experiments in using mixins to combine et2_widget & LitElement
Note that this "works", in that it mixes the code properly.
It does not work in that the resulting class does not work with et2's inheritance & class checking stuff
// This one to make TypeScript happy?
interface et2_textbox extends et2_textbox, LitElement {}
// This one to make the inheritance magic happen
applyMixins(et2_textbox, [et2_textbox,LitElement]);
// Make it a real WebComponent
customElements.define("et2-textbox",et2_textbox);
*/
export class ClassWithInterfaces
{
/**
* The implements function can be used to check whether the object
* implements the given interface.
*
* As TypeScript can not (yet) check if an objects implements an interface on runtime,
* we currently implements with each interface a function called 'implements_'+interfacename
* to be able to check here.
*
* @param _iface name of interface to check
*/
implements(_iface_name: string)
/**
* The implements function can be used to check whether the object
* implements the given interface.
*
* As TypeScript can not (yet) check if an objects implements an interface on runtime,
* we currently implements with each interface a function called 'implements_'+interfacename
* to be able to check here.
*
* @param _iface name of interface to check
*/
implements (_iface_name : string)
{
if (typeof et2_implements_registry[_iface_name] === 'function' &&
et2_implements_registry[_iface_name](this))
{
if (typeof et2_implements_registry[_iface_name] === 'function' &&
et2_implements_registry[_iface_name](this))
{
return true
}
return false;
return true
}
return false;
}
/**
* Check if object is an instance of a class or implements an interface (specified by the interfaces name)
*
* @param _class_or_interfacename class(-name) or string with name of interface
*/
instanceOf(_class_or_interfacename: any): boolean
/**
* Check if object is an instance of a class or implements an interface (specified by the interfaces name)
*
* @param _class_or_interfacename class(-name) or string with name of interface
*/
instanceOf(_class_or_interfacename: any) : boolean
{
if (typeof _class_or_interfacename === 'string')
{
if (typeof _class_or_interfacename === 'string')
{
return this.implements(_class_or_interfacename);
}
return this instanceof _class_or_interfacename;
return this.implements(_class_or_interfacename);
}
return this instanceof _class_or_interfacename;
}
}
export class ClassWithAttributes extends ClassWithInterfaces
{
/**
* Object to collect the attributes we operate on
*/
attributes: object;
/**
* Object to collect the attributes we operate on
*/
attributes: object;
/**
* Returns the value of the given attribute. If the property does not
* exist, an error message is issued.
*
* @param {string} _name
* @return {*}
*/
getAttribute(_name)
{
if (typeof this.attributes[_name] != "undefined" &&
!this.attributes[_name].ignore)
{
if (typeof this["get_" + _name] == "function")
{
return this["get_" + _name]();
}
else
{
return this[_name];
}
/**
* Returns the value of the given attribute. If the property does not
* exist, an error message is issued.
*
* @param {string} _name
* @return {*}
*/
getAttribute(_name)
{
if (typeof this.attributes[_name] != "undefined" &&
!this.attributes[_name].ignore) {
if (typeof this["get_" + _name] == "function") {
return this["get_" + _name]();
} else {
return this[_name];
}
} else {
egw.debug("error", this, "Attribute '" + _name + "' does not exist!");
}
}
/**
* The setAttribute function sets the attribute with the given name to
* the given value. _override defines, whether this[_name] will be set,
* if this key already exists. _override defaults to true. A warning
* is issued if the attribute does not exist.
*
* @param {string} _name
* @param {*} _value
* @param {boolean} _override
*/
setAttribute(_name, _value, _override)
{
if (typeof this.attributes[_name] != "undefined") {
if (!this.attributes[_name].ignore) {
if (typeof _override == "undefined") {
_override = true;
}
else
{
egw.debug("error", this, "Attribute '" + _name + "' does not exist!");
var val = et2_checkType(_value, this.attributes[_name].type,
_name, this);
if (typeof this["set_" + _name] == "function") {
this["set_" + _name](val);
} else if (_override || typeof this[_name] == "undefined") {
this[_name] = val;
}
}
} else {
egw.debug("warn", this, "Attribute '" + _name + "' does not exist!");
}
}
/**
* generateAttributeSet sanitizes the given associative array of attributes
* (by passing each entry to "et2_checkType" and checking for existance of
* the attribute) and adds the default values to the associative array.
*
* @param {object} _attrs is the associative array containing the attributes.
*/
static generateAttributeSet(widget, _attrs)
{
// Sanity check and validation
for (var key in _attrs) {
if (typeof widget[key] != "undefined") {
if (!widget[key].ignore) {
_attrs[key] = et2_checkType(_attrs[key], widget[key].type,
key, this);
}
} else {
// Key does not exist - delete it and issue a warning
delete (_attrs[key]);
egw.debug("warn", this, "Attribute '" + key +
"' does not exist in " + _attrs.type + "!");
}
}
/**
* The setAttribute function sets the attribute with the given name to
* the given value. _override defines, whether this[_name] will be set,
* if this key already exists. _override defaults to true. A warning
* is issued if the attribute does not exist.
*
* @param {string} _name
* @param {*} _value
* @param {boolean} _override
*/
setAttribute(_name, _value, _override)
{
if (typeof this.attributes[_name] != "undefined")
{
if (!this.attributes[_name].ignore)
{
if (typeof _override == "undefined")
{
_override = true;
}
var val = et2_checkType(_value, this.attributes[_name].type,
_name, this);
if (typeof this["set_" + _name] == "function")
{
this["set_" + _name](val);
}
else if (_override || typeof this[_name] == "undefined")
{
this[_name] = val;
}
}
}
else
{
egw.debug("warn", this, "Attribute '" + _name + "' does not exist!");
// Include default values or already set values for this attribute
for (var key in widget) {
if (typeof _attrs[key] == "undefined") {
var _default = widget[key]["default"];
if (_default == et2_no_init) {
_default = undefined;
}
_attrs[key] = _default;
}
}
/**
* generateAttributeSet sanitizes the given associative array of attributes
* (by passing each entry to "et2_checkType" and checking for existance of
* the attribute) and adds the default values to the associative array.
*
* @param {object} _attrs is the associative array containing the attributes.
*/
static generateAttributeSet(widget, _attrs)
return _attrs;
}
/**
* The initAttributes function sets the attributes to their default
* values. The attributes are not overwritten, which means, that the
* default is only set, if either a setter exists or this[propName] does
* not exist yet.
*
* @param {object} _attrs is the associative array containing the attributes.
*/
initAttributes(_attrs)
{
for (var key in _attrs) {
if (typeof this.attributes[key] != "undefined" && !this.attributes[key].ignore && !(_attrs[key] == undefined)) {
this.setAttribute(key, _attrs[key], false);
}
}
}
static buildAttributes(class_prototype: object)
{
let class_tree = [];
let attributes = {};
let n = 0;
do {
n++;
class_tree.push(class_prototype);
class_prototype = Object.getPrototypeOf(class_prototype);
} while (class_prototype !== ClassWithAttributes && n < 50);
for (let i = class_tree.length - 1; i >= 0; i--) {
attributes = ClassWithAttributes.extendAttributes(attributes, class_tree[i]._attributes);
}
return attributes;
}
/**
* Extend current _attributes with the one from the parent class
*
* This gives inheritance from the parent plus the ability to override in the current class.
*
* @param _attributes
* @param _parent
*/
static extendAttributes(_parent: object, _attributes: object): object
{
function _copyMerge(_new, _old)
{
// Sanity check and validation
for (var key in _attrs)
{
if (typeof widget[key] != "undefined")
{
if (!widget[key].ignore)
{
_attrs[key] = et2_checkType(_attrs[key], widget[key].type,
key, this);
}
}
else
{
// Key does not exist - delete it and issue a warning
delete (_attrs[key]);
egw.debug("warn", this, "Attribute '" + key +
"' does not exist in " + _attrs.type + "!");
}
var result = {};
// Copy the new object
if (typeof _new != "undefined") {
for (var key in _new) {
result[key] = _new[key];
}
}
// Include default values or already set values for this attribute
for (var key in widget)
{
if (typeof _attrs[key] == "undefined")
{
var _default = widget[key]["default"];
if (_default == et2_no_init)
{
_default = undefined;
}
_attrs[key] = _default;
}
// Merge the old object
for (var key in _old) {
if (typeof result[key] == "undefined") {
result[key] = _old[key];
}
}
return _attrs;
return result;
}
/**
* The initAttributes function sets the attributes to their default
* values. The attributes are not overwritten, which means, that the
* default is only set, if either a setter exists or this[propName] does
* not exist yet.
*
* @param {object} _attrs is the associative array containing the attributes.
*/
initAttributes(_attrs)
{
for (var key in _attrs)
{
if (typeof this.attributes[key] != "undefined" && !this.attributes[key].ignore && !(_attrs[key] == undefined))
{
this.setAttribute(key, _attrs[key], false);
}
}
var attributes = {};
// Copy the old attributes
for (var key in _attributes) {
attributes[key] = _copyMerge({}, _attributes[key]);
}
static buildAttributes(class_prototype: object)
{
let class_tree = [];
let attributes = {};
let n = 0;
do
{
n++;
class_tree.push(class_prototype);
class_prototype = Object.getPrototypeOf(class_prototype);
}
while (class_prototype !== ClassWithAttributes && n < 50);
// Add the old attributes to the new ones. If the attributes already
// exist, they are merged.
for (var key in _parent) {
var _old = _parent[key];
for (let i = class_tree.length - 1; i >= 0; i--)
{
attributes = ClassWithAttributes.extendAttributes(attributes, class_tree[i]._attributes);
}
return attributes;
attributes[key] = _copyMerge(attributes[key], _old);
}
/**
* Extend current _attributes with the one from the parent class
*
* This gives inheritance from the parent plus the ability to override in the current class.
*
* @param _attributes
* @param _parent
*/
static extendAttributes(_parent: object, _attributes: object): object
{
function _copyMerge(_new, _old)
{
var result = {};
// Copy the new object
if (typeof _new != "undefined")
{
for (var key in _new)
{
result[key] = _new[key];
}
}
// Merge the old object
for (var key in _old)
{
if (typeof result[key] == "undefined")
{
result[key] = _old[key];
}
}
return result;
}
var attributes = {};
// Copy the old attributes
for (var key in _attributes)
{
attributes[key] = _copyMerge({}, _attributes[key]);
}
// Add the old attributes to the new ones. If the attributes already
// exist, they are merged.
for (var key in _parent)
{
var _old = _parent[key];
attributes[key] = _copyMerge(attributes[key], _old);
}
// Validate the attributes
for (var key in attributes)
{
et2_validateAttrib(key, attributes[key]);
}
return attributes;
// Validate the attributes
for (var key in attributes) {
et2_validateAttrib(key, attributes[key]);
}
return attributes;
}
}

View File

@ -15,19 +15,17 @@
*/
import {et2_no_init} from "./et2_core_common";
import {ClassWithAttributes} from "./et2_core_inheritance";
import {et2_widget, WidgetConfig} from "./et2_core_widget";
import {et2_valueWidget} from './et2_core_valueWidget'
import {et2_IInput, et2_IInputNode, et2_ISubmitListener} from "./et2_core_interfaces";
import { ClassWithAttributes } from "./et2_core_inheritance";
import { et2_widget, WidgetConfig } from "./et2_core_widget";
import { et2_valueWidget } from './et2_core_valueWidget'
import {et2_IInput, et2_ISubmitListener} from "./et2_core_interfaces";
import {et2_compileLegacyJS} from "./et2_core_legacyJSFunctions";
// fixing circular dependencies by only importing the type (not in compiled .js)
import type {et2_tabbox} from "./et2_widget_tabs";
export interface et2_input
{
getInputNode(): HTMLInputElement | HTMLElement;
export interface et2_input {
getInputNode() : HTMLInputElement|HTMLElement;
}
/**
* et2_inputWidget derrives from et2_simpleWidget and implements the IInput
* interface. When derriving from this class, call setDOMNode with an input
@ -35,9 +33,9 @@ export interface et2_input
*/
export class et2_inputWidget extends et2_valueWidget implements et2_IInput, et2_ISubmitListener, et2_input
{
static readonly _attributes: any = {
static readonly _attributes : any = {
"needed": {
"name": "Required",
"name": "Required",
"default": false,
"type": "boolean",
"description": "If required, the user must enter a value before the form can be submitted"
@ -80,7 +78,7 @@ export class et2_inputWidget extends et2_valueWidget implements et2_IInput, et2_
/**
* Constructor
*/
constructor(_parent, _attrs?: WidgetConfig, _child?: object)
constructor(_parent, _attrs? : WidgetConfig, _child? : object)
{
// Call the inherited constructor
super(_parent, _attrs, ClassWithAttributes.extendAttributes(et2_inputWidget._attributes, _child || {}));
@ -107,7 +105,7 @@ export class et2_inputWidget extends et2_valueWidget implements et2_IInput, et2_
/**
* Make sure dirty flag is properly set
*/
doLoadingFinished(): boolean | JQueryPromise<unknown>
doLoadingFinished() : boolean | JQueryPromise<unknown>
{
let result = super.doLoadingFinished();
@ -143,12 +141,10 @@ export class et2_inputWidget extends et2_valueWidget implements et2_IInput, et2_
{
jQuery(node)
.off('.et2_inputWidget')
.bind("change.et2_inputWidget", this, function (e)
{
.bind("change.et2_inputWidget", this, function(e) {
e.data.change.call(e.data, this);
})
.bind("focus.et2_inputWidget", this, function (e)
{
.bind("focus.et2_inputWidget", this, function(e) {
e.data.focus.call(e.data, this);
});
}
@ -177,16 +173,14 @@ export class et2_inputWidget extends et2_valueWidget implements et2_IInput, et2_
if (valid && this.onchange)
{
if (typeof this.onchange == 'function')
if(typeof this.onchange == 'function')
{
// Make sure function gets a reference to the widget
var args = Array.prototype.slice.call(arguments);
if (args.indexOf(this) == -1) args.push(this);
if(args.indexOf(this) == -1) args.push(this);
return this.onchange.apply(this, args);
}
else
{
} else {
return (et2_compileLegacyJS(this.options.onchange, this, _node))();
}
}
@ -195,11 +189,11 @@ export class et2_inputWidget extends et2_valueWidget implements et2_IInput, et2_
focus(_node)
{
if (typeof this.options.onfocus == 'function')
if(typeof this.options.onfocus == 'function')
{
// Make sure function gets a reference to the widget
var args = Array.prototype.slice.call(arguments);
if (args.indexOf(this) == -1) args.push(this);
if(args.indexOf(this) == -1) args.push(this);
return this.options.onfocus.apply(this, args);
}
@ -212,13 +206,13 @@ export class et2_inputWidget extends et2_valueWidget implements et2_IInput, et2_
*
* @param {string} _value value to set
*/
set_value(_value: any | null)
set_value(_value : any | null)
{
var node = this.getInputNode();
if (node)
{
jQuery(node).val(_value);
if (this.isAttached() && this._oldValue !== et2_no_init && this._oldValue !== _value)
if(this.isAttached() && this._oldValue !== et2_no_init && this._oldValue !== _value)
{
jQuery(node).change();
}
@ -229,7 +223,7 @@ export class et2_inputWidget extends et2_valueWidget implements et2_IInput, et2_
set_id(_value)
{
this.id = _value;
this.dom_id = _value && this.getInstanceManager() ? this.getInstanceManager().uniqueId + '_' + this.id : _value;
this.dom_id = _value && this.getInstanceManager() ? this.getInstanceManager().uniqueId+'_'+this.id : _value;
// Set the id of the _input_ node (in contrast to the default
// implementation, which sets the base node)
@ -255,12 +249,9 @@ export class et2_inputWidget extends et2_valueWidget implements et2_IInput, et2_
var node = this.getInputNode();
if (node)
{
if (_value && !this.options.readonly)
{
if(_value && !this.options.readonly) {
jQuery(node).attr("required", "required");
}
else
{
} else {
node.removeAttribute("required");
}
}
@ -282,8 +273,8 @@ export class et2_inputWidget extends et2_valueWidget implements et2_IInput, et2_
jQuery(node).addClass("invalid");
// If on a tab, switch to that tab so user can see it
let widget: et2_widget = this;
while (widget.getParent() && widget.getType() != 'tabbox')
let widget : et2_widget = this;
while(widget.getParent() && widget.getType() != 'tabbox')
{
widget = widget.getParent();
}
@ -328,26 +319,26 @@ export class et2_inputWidget extends et2_valueWidget implements et2_IInput, et2_
isDirty()
{
let value = this.getValue();
if (typeof value !== typeof this._oldValue)
if(typeof value !== typeof this._oldValue)
{
return true;
}
if (this._oldValue === value)
if(this._oldValue === value)
{
return false;
}
switch (typeof this._oldValue)
switch(typeof this._oldValue)
{
case "object":
if (typeof this._oldValue.length !== "undefined" &&
if(typeof this._oldValue.length !== "undefined" &&
this._oldValue.length !== value.length
)
{
return true;
}
for (let key in this._oldValue)
for(let key in this._oldValue)
{
if (this._oldValue[key] !== value[key]) return true;
if(this._oldValue[key] !== value[key]) return true;
}
return false;
default:
@ -391,3 +382,4 @@ export class et2_inputWidget extends et2_valueWidget implements et2_IInput, et2_
return valid;
}
}

View File

@ -22,10 +22,10 @@ import {egw, IegwAppLocal} from "../jsapi/egw_global";
import {et2_cloneObject, et2_csvSplit} from "./et2_core_common";
import {et2_compileLegacyJS} from "./et2_core_legacyJSFunctions";
import {et2_IDOMNode, et2_IInputNode} from "./et2_core_interfaces";
import {loadWebComponent} from "./Et2Widget/Et2Widget";
// fixing circular dependencies by only importing type
import type {et2_container} from "./et2_core_baseWidget";
import type {et2_inputWidget} from "./et2_core_inputWidget";
import type {et2_inputWidget, et2_input} from "./et2_core_inputWidget";
import {decorateLanguageService} from "ts-lit-plugin/lib/decorate-language-service";
/**
* The registry contains all XML tag names and the corresponding widget
@ -77,7 +77,7 @@ export function et2_register_widget(_constructor, _types)
* is not passed, it will default to null. Then you have to attach the element
* to a parent using the addChild or insertChild method.
*/
export function et2_createWidget(_name: string, _attrs: object, _parent?: any): et2_widget
export function et2_createWidget(_name : string, _attrs : object, _parent? : any) : et2_widget
{
"use strict";
@ -119,12 +119,10 @@ export function et2_createWidget(_name: string, _attrs: object, _parent?: any):
// make et2_createWidget publicly available as we need to call it from stylite/js/gantt.js (maybe others)
if (typeof window.et2_createWidget === 'undefined') window['et2_createWidget'] = et2_createWidget;
export interface WidgetConfig
{
export interface WidgetConfig {
type?: string;
readonly?: boolean;
width?: number;
[propName: string]: any;
}
@ -193,7 +191,7 @@ export class et2_widget extends ClassWithAttributes
private _type: string;
id: string;
supportedWidgetClasses: any[];
supportedWidgetClasses : any[];
options: WidgetConfig;
readonly: boolean;
@ -210,24 +208,21 @@ export class et2_widget extends ClassWithAttributes
* @param _attrs is an associative array of attributes.
* @param _child attributes object from the child
*/
constructor(_parent?, _attrs?: WidgetConfig, _child?: object)
constructor(_parent?, _attrs? : WidgetConfig, _child? : object)
{
super(); // because we in the top of the widget hierarchy
this.attributes = ClassWithAttributes.extendAttributes(et2_widget._attributes, _child || {});
// Check whether all attributes are available
if (typeof _parent == "undefined")
{
if (typeof _parent == "undefined") {
_parent = null;
}
if (typeof _attrs == "undefined")
{
if (typeof _attrs == "undefined") {
_attrs = {};
}
if (_attrs.attributes)
{
if (_attrs.attributes) {
jQuery.extend(_attrs, _attrs.attributes);
}
// Initialize all important parameters
@ -238,26 +233,22 @@ export class et2_widget extends ClassWithAttributes
this.id = _attrs["id"];
// Add this widget to the given parent widget
if (_parent != null)
{
if (_parent != null) {
_parent.addChild(this);
}
// The supported widget classes array defines a whitelist for all widget
// classes or interfaces child widgets have to support.
this.supportedWidgetClasses = [et2_widget, HTMLElement];
this.supportedWidgetClasses = [et2_widget];
if (_attrs["id"])
{
if (_attrs["id"]) {
// Create a namespace for this object
if (this._createNamespace())
{
if (this._createNamespace()) {
this.checkCreateNamespace(_attrs);
}
}
if (this.id)
{
if (this.id) {
//this.id = this.id.replace(/\[/g,'&#x5B;').replace(/]/g,'&#x5D;');
}
@ -280,33 +271,29 @@ export class et2_widget extends ClassWithAttributes
destroy()
{
// Call the destructor of all children
for (var i = this._children.length - 1; i >= 0; i--)
{
for (var i = this._children.length - 1; i >= 0; i--) {
this._children[i].destroy();
}
// Remove this element from the parent, if it exists
if (typeof this._parent != "undefined" && this._parent !== null)
{
if (typeof this._parent != "undefined" && this._parent !== null) {
this._parent.removeChild(this);
}
// Free the array managers if they belong to this widget
for (var key in this._mgrs)
{
if (this._mgrs[key] && this._mgrs[key].owner == this)
{
for (var key in this._mgrs) {
if (this._mgrs[key] && this._mgrs[key].owner == this) {
this._mgrs[key].destroy();
}
}
}
getType(): string
getType() : string
{
return this._type;
}
setType(_type: string)
setType(_type : string)
{
this._type = _type;
}
@ -321,8 +308,7 @@ export class et2_widget extends ClassWithAttributes
clone(_parent)
{
// Default _parent to null
if (typeof _parent == "undefined")
{
if (typeof _parent == "undefined") {
_parent = null;
}
@ -337,14 +323,12 @@ export class et2_widget extends ClassWithAttributes
assign(_obj)
{
if (typeof _obj._children == "undefined")
{
if (typeof _obj._children == "undefined") {
this.egw().debug("log", "Foo!");
}
// Create a clone of all child elements of the given object
for (var i = 0; i < _obj._children.length; i++)
{
for (var i = 0; i < _obj._children.length; i++) {
_obj._children[i].clone(this);
}
@ -357,7 +341,7 @@ export class et2_widget extends ClassWithAttributes
/**
* Returns the parent widget of this widget
*/
getParent(): et2_widget | null
getParent() : et2_widget | null
{
return this._parent;
}
@ -367,7 +351,7 @@ export class et2_widget extends ClassWithAttributes
/**
* Returns the list of children of this widget.
*/
getChildren(): et2_widget[]
getChildren() : et2_widget[]
{
return this._children;
}
@ -375,14 +359,11 @@ export class et2_widget extends ClassWithAttributes
/**
* Returns the base widget
*/
getRoot(): et2_container
getRoot() : et2_container
{
if (this._parent != null)
{
if (this._parent != null) {
return this._parent.getRoot();
}
else
{
} else {
return <et2_container><unknown>this;
}
}
@ -393,7 +374,7 @@ export class et2_widget extends ClassWithAttributes
* @param _node is the node which should be added. It has to be an instance
* of et2_widget
*/
addChild(_node: et2_widget)
addChild(_node : et2_widget)
{
this.insertChild(_node, this._children.length);
}
@ -405,22 +386,18 @@ export class et2_widget extends ClassWithAttributes
* of et2_widget
* @param _idx is the position at which the element should be added.
*/
insertChild(_node: et2_widget, _idx: number)
insertChild(_node : et2_widget, _idx: number)
{
// Check whether the node is one of the supported widget classes.
if (this.isOfSupportedWidgetClass(_node))
{
if (this.isOfSupportedWidgetClass(_node)) {
// Remove the node from its original parent
if (_node._parent)
{
if (_node._parent) {
_node._parent.removeChild(_node);
}
_node._parent = this;
this._children.splice(_idx, 0, _node);
}
else
{
} else {
this.egw().debug("error", "Widget " + _node._type + " is not supported by this widget class", this);
// throw("Widget is not supported by this widget class!");
}
@ -436,8 +413,7 @@ export class et2_widget extends ClassWithAttributes
// Retrieve the child from the child list
var idx = this._children.indexOf(_node);
if (idx >= 0)
{
if (idx >= 0) {
// This element is no longer parent of the child
_node._parent = null;
@ -450,29 +426,24 @@ export class et2_widget extends ClassWithAttributes
*
* @param _id is the id you're searching for
*/
getWidgetById(_id): et2_widget | null
getWidgetById(_id) : et2_widget | null
{
if (this.id == _id)
{
if (this.id == _id) {
return this;
}
if (!this._children) return null;
for (var i = 0; i < this._children.length; i++)
{
for (var i = 0; i < this._children.length; i++) {
var elem = this._children[i].getWidgetById(_id);
if (elem != null)
{
if (elem != null) {
return elem;
}
}
if (this.id && _id.indexOf('[') > -1 && this._children.length)
{
if (this.id && _id.indexOf('[') > -1 && this._children.length) {
var ids = (new et2_arrayMgr()).explodeKey(_id);
var widget: et2_widget = this;
for (var i = 0; i < ids.length && widget !== null; i++)
{
var widget : et2_widget = this;
for (var i = 0; i < ids.length && widget !== null; i++) {
widget = widget.getWidgetById(ids[i]);
}
return widget;
@ -491,18 +462,15 @@ export class et2_widget extends ClassWithAttributes
*/
iterateOver(_callback, _context, _type?)
{
if (typeof _type == "undefined")
{
if (typeof _type == "undefined") {
_type = et2_widget;
}
if (this.isInTree() && this.instanceOf(_type))
{
if (this.isInTree() && this.instanceOf(_type)) {
_callback.call(_context, this);
}
for (var i = 0; i < this._children.length; i++)
{
for (var i = 0; i < this._children.length; i++) {
this._children[i].iterateOver(_callback, _context, _type);
}
}
@ -518,15 +486,13 @@ export class et2_widget extends ClassWithAttributes
* return this._super(inTree);
* when calling this function the _vis parameter does not have to be supplied.
*/
isInTree(_sender?, _vis?: boolean)
isInTree(_sender?, _vis? : boolean)
{
if (typeof _vis == "undefined")
{
if (typeof _vis == "undefined") {
_vis = true;
}
if (this._parent)
{
if (this._parent) {
return _vis && this._parent.isInTree(this);
}
@ -535,10 +501,8 @@ export class et2_widget extends ClassWithAttributes
isOfSupportedWidgetClass(_obj)
{
for (var i = 0; i < this.supportedWidgetClasses.length; i++)
{
if (_obj instanceof this.supportedWidgetClasses[i])
{
for (var i = 0; i < this.supportedWidgetClasses.length; i++) {
if (_obj instanceof this.supportedWidgetClasses[i]) {
return true;
}
}
@ -557,55 +521,47 @@ export class et2_widget extends ClassWithAttributes
parseXMLAttrs(_attrsObj, _target, _proto)
{
// Check whether the attributes object is really existing, if not abort
if (typeof _attrsObj == "undefined")
{
if (typeof _attrsObj == "undefined") {
return;
}
// Iterate over the given attributes and parse them
var mgr = this.getArrayMgr("content");
for (var i = 0; i < _attrsObj.length; i++)
{
for (var i = 0; i < _attrsObj.length; i++) {
var attrName = _attrsObj[i].name;
var attrValue = _attrsObj[i].value;
// Special handling for the legacy options
if (attrName == "options" && _proto.constructor.legacyOptions && _proto.constructor.legacyOptions.length > 0)
{
if (attrName == "options" && _proto.constructor.legacyOptions && _proto.constructor.legacyOptions.length > 0) {
let legacy = _proto.constructor.legacyOptions || [];
let attrs = et2_attribute_registry[Object.getPrototypeOf(_proto).constructor.name] || {};
// Check for modifications on legacy options here. Normal modifications
// are handled in widget constructor, but it's too late for legacy options then
if (_target.id && this.getArrayMgr("modifications").getEntry(_target.id))
{
var mod: any = this.getArrayMgr("modifications").getEntry(_target.id);
if (_target.id && this.getArrayMgr("modifications").getEntry(_target.id)) {
var mod : any = this.getArrayMgr("modifications").getEntry(_target.id);
if (typeof mod.options != "undefined") attrValue = _attrsObj[i].value = mod.options;
}
// expand legacyOptions with content
if (attrValue.charAt(0) == '@' || attrValue.indexOf('$') != -1)
{
if (attrValue.charAt(0) == '@' || attrValue.indexOf('$') != -1) {
attrValue = mgr.expandName(attrValue);
}
// Parse the legacy options (as a string, other types not allowed)
var splitted = et2_csvSplit(attrValue + "");
for (var j = 0; j < splitted.length && j < legacy.length; j++)
{
for (var j = 0; j < splitted.length && j < legacy.length; j++) {
// Blank = not set, unless there's more legacy options provided after
if (splitted[j].trim().length === 0 && legacy.length >= splitted.length) continue;
// Check to make sure we don't overwrite a current option with a legacy option
if (typeof _target[legacy[j]] === "undefined")
{
if (typeof _target[legacy[j]] === "undefined") {
attrValue = splitted[j];
/**
If more legacy options than expected, stuff them all in the last legacy option
Some legacy options take a comma separated list.
*/
if (j == legacy.length - 1 && splitted.length > legacy.length)
{
if (j == legacy.length - 1 && splitted.length > legacy.length) {
attrValue = splitted.slice(j);
}
@ -613,37 +569,26 @@ export class et2_widget extends ClassWithAttributes
// If the attribute is marked as boolean, parse the
// expression as bool expression.
if (attr.type == "boolean")
{
if (attr.type == "boolean") {
attrValue = mgr.parseBoolExpression(attrValue);
}
else if (typeof attrValue != "object")
{
} else if (typeof attrValue != "object") {
attrValue = mgr.expandName(attrValue);
}
_target[legacy[j]] = attrValue;
}
}
}
else if (attrName == "readonly" && typeof _target[attrName] != "undefined")
{
} else if (attrName == "readonly" && typeof _target[attrName] != "undefined") {
// do NOT overwrite already evaluated readonly attribute
}
else
{
} else {
let attrs = et2_attribute_registry[_proto.constructor.name] || {};
if (mgr != null && typeof attrs[attrName] != "undefined")
{
if (mgr != null && typeof attrs[attrName] != "undefined") {
var attr = attrs[attrName];
// If the attribute is marked as boolean, parse the
// expression as bool expression.
if (attr.type == "boolean")
{
if (attr.type == "boolean") {
attrValue = mgr.parseBoolExpression(attrValue);
}
else
{
} else {
attrValue = mgr.expandName(attrValue);
}
}
@ -664,26 +609,20 @@ export class et2_widget extends ClassWithAttributes
{
// Apply the content of the modifications array
if (this.id)
{
if (typeof this.id != "string")
{
if (this.id) {
if (typeof this.id != "string") {
console.log(this.id);
}
if (this.getArrayMgr("modifications"))
{
if (this.getArrayMgr("modifications")) {
var data = this.getArrayMgr("modifications").getEntry(this.id);
// Check for already inside namespace
if (this._createNamespace() && this.getArrayMgr("modifications").perspectiveData.owner == this)
{
if (this._createNamespace() && this.getArrayMgr("modifications").perspectiveData.owner == this) {
data = this.getArrayMgr("modifications").data;
}
if (typeof data === 'object')
{
for (var key in data)
{
if (typeof data === 'object') {
for (var key in data) {
_attrs[key] = data[key];
}
}
@ -691,19 +630,16 @@ export class et2_widget extends ClassWithAttributes
}
// Translate the attributes
for (var key in _attrs)
{
if (_attrs[key] && typeof this.attributes[key] != "undefined")
{
for (var key in _attrs) {
if (_attrs[key] && typeof this.attributes[key] != "undefined") {
if (this.attributes[key].translate === true ||
(this.attributes[key].translate === "!no_lang" && !_attrs["no_lang"]))
{
(this.attributes[key].translate === "!no_lang" && !_attrs["no_lang"])) {
let value = _attrs[key];
// allow statustext to contain multiple translated sub-strings eg: {Firstname}.{Lastname}
if (value.indexOf('{') !== -1)
{
const egw = this.egw();
_attrs[key] = value.replace(/{([^}]+)}/g, function (str, p1)
_attrs[key] = value.replace(/{([^}]+)}/g, function(str,p1)
{
return egw.lang(p1);
});
@ -738,34 +674,28 @@ export class et2_widget extends ClassWithAttributes
// Parse the "readonly" and "type" flag for this element here, as they
// determine which constructor is used
var _nodeName = attributes["type"] = _node.getAttribute("type") ?
_node.getAttribute("type") : _node.nodeName.toLowerCase();
_node.getAttribute("type") : _node.nodeName.toLowerCase();
var readonly = attributes["readonly"] = this.getArrayMgr("readonlys") ?
(<any>this.getArrayMgr("readonlys")).isReadOnly(
_node.getAttribute("id"), _node.getAttribute("readonly"),
typeof this.readonly !== 'undefined' ? this.readonly : this.options.readonly) : false;
(<any>this.getArrayMgr("readonlys")).isReadOnly(
_node.getAttribute("id"), _node.getAttribute("readonly"),
typeof this.readonly !== 'undefined' ? this.readonly : this.options.readonly) : false;
// Check to see if modifications change type
var modifications = this.getArrayMgr("modifications");
if (modifications && _node.getAttribute("id"))
{
var entry: any = modifications.getEntry(_node.getAttribute("id"));
if (entry == null)
{
if (modifications && _node.getAttribute("id")) {
var entry : any = modifications.getEntry(_node.getAttribute("id"));
if (entry == null) {
// Try again, but skip the fancy stuff
// TODO: Figure out why the getEntry() call doesn't always work
var entry = modifications.data[_node.getAttribute("id")];
if (entry)
{
if (entry) {
this.egw().debug("warn", "getEntry(" + _node.getAttribute("id") + ") failed, but the data is there.", modifications, entry);
}
else
{
} else {
// Try the root, in case a namespace got missed
entry = modifications.getRoot().getEntry(_node.getAttribute("id"));
}
}
if (entry && entry.type && typeof entry.type === 'string')
{
if (entry && entry.type && typeof entry.type === 'string') {
_nodeName = attributes["type"] = entry.type;
}
entry = null;
@ -773,27 +703,25 @@ export class et2_widget extends ClassWithAttributes
// if _nodeName / type-attribute contains something to expand (eg. type="@${row}[type]"),
// we need to expand it now as it defines the constructor and by that attributes parsed via parseXMLAttrs!
if (_nodeName.charAt(0) == '@' || _nodeName.indexOf('$') >= 0)
{
if (_nodeName.charAt(0) == '@' || _nodeName.indexOf('$') >= 0) {
_nodeName = attributes["type"] = this.getArrayMgr('content').expandName(_nodeName);
}
// Get the constructor - if the widget is readonly, use the special "_ro"
// constructor if it is available
var constructor = et2_registry[typeof et2_registry[_nodeName] == "undefined" ? 'placeholder' : _nodeName];
if (readonly === true && typeof et2_registry[_nodeName + "_ro"] != "undefined")
{
if (readonly === true && typeof et2_registry[_nodeName + "_ro"] != "undefined") {
constructor = et2_registry[_nodeName + "_ro"];
}
if (undefined == window.customElements.get(_nodeName))
// Parse the attributes from the given XML attributes object
this.parseXMLAttrs(_node.attributes, attributes, constructor.prototype);
// Do an sanity check for the attributes
ClassWithAttributes.generateAttributeSet(et2_attribute_registry[constructor.name], attributes);
if(undefined == window.customElements.get(_nodeName))
{
// Parse the attributes from the given XML attributes object
this.parseXMLAttrs(_node.attributes, attributes, constructor.prototype);
// Do an sanity check for the attributes
ClassWithAttributes.generateAttributeSet(et2_attribute_registry[constructor.name], attributes);
// Creates the new widget, passes this widget as an instance and
// passes the widgetType. Then it goes on loading the XML for it.
var widget = new constructor(this, attributes);
@ -803,9 +731,9 @@ export class et2_widget extends ClassWithAttributes
}
else
{
widget = loadWebComponent(_nodeName, _node, this);
widget = this.loadWebComponent(_nodeName, _node, attributes);
if (this.addChild)
if(this.addChild)
{
// webcomponent going into old et2_widget
this.addChild(widget);
@ -814,6 +742,22 @@ export class et2_widget extends ClassWithAttributes
return widget;
}
/**
* Load a Web Component
* @param _nodeName
* @param _node
*/
loadWebComponent(_nodeName : string, _node, attributes : Object) : HTMLElement
{
let widget = document.createElement(_nodeName);
widget.textContent = _node.textContent;
// Apply any set attributes
_node.getAttributeNames().forEach(attribute => widget.setAttribute(attribute, attributes[attribute]));
return widget;
}
/**
* Loads the widget tree from an XML node
*
@ -822,20 +766,16 @@ export class et2_widget extends ClassWithAttributes
loadFromXML(_node)
{
// Load the child nodes.
for (var i = 0; i < _node.childNodes.length; i++)
{
for (var i = 0; i < _node.childNodes.length; i++) {
var node = _node.childNodes[i];
var widgetType = node.nodeName.toLowerCase();
if (widgetType == "#comment")
{
if (widgetType == "#comment") {
continue;
}
if (widgetType == "#text")
{
if (node.data.replace(/^\s+|\s+$/g, ''))
{
if (widgetType == "#text") {
if (node.data.replace(/^\s+|\s+$/g, '')) {
this.loadContent(node.data);
}
continue;
@ -883,39 +823,29 @@ export class et2_widget extends ClassWithAttributes
// Make sure promises is defined to avoid errors.
// We'll warn (below) if programmer should have passed it.
if (typeof promises == "undefined")
{
if (typeof promises == "undefined") {
promises = [];
var warn_if_deferred = true;
}
var loadChildren = function ()
{
var loadChildren = function () {
// Descend recursively into the tree
for (var i = 0; i < this._children.length; i++)
{
try
{
for (var i = 0; i < this._children.length; i++) {
try {
this._children[i].loadingFinished(promises);
}
catch (e)
{
} catch (e) {
egw.debug("error", "There was an error with a widget:\nError:%o\nProblem widget:%o", e.valueOf(), this._children[i], e.stack);
}
}
};
var result = this.doLoadingFinished();
if (typeof result == "boolean" && result)
{
if (typeof result == "boolean" && result) {
// Simple widget finishes nicely
loadChildren.apply(this, arguments);
}
else if (typeof result == "object" && result.done)
{
} else if (typeof result == "object" && result.done) {
// Warn if list was not provided
if (warn_if_deferred)
{
if (warn_if_deferred) {
// Might not be a problem, but if you need the widget to be really loaded, it could be
egw.debug("warn", "Loading was deferred for widget %o, but creator is not checking. Pass a list to loadingFinished().", this);
}
@ -938,17 +868,14 @@ export class et2_widget extends ClassWithAttributes
*/
initAttributes(_attrs)
{
for (var key in _attrs)
{
if (typeof this.attributes[key] != "undefined" && !this.attributes[key].ignore && !(_attrs[key] == undefined))
{
for (var key in _attrs) {
if (typeof this.attributes[key] != "undefined" && !this.attributes[key].ignore && !(_attrs[key] == undefined)) {
var val = _attrs[key];
// compile string values of attribute type "js" to functions
if (this.attributes[key].type == 'js' && typeof _attrs[key] == 'string')
{
if (this.attributes[key].type == 'js' && typeof _attrs[key] == 'string') {
val = et2_compileLegacyJS(val, this,
this.implements(et2_IInputNode) ? (<et2_inputWidget><unknown>this).getInputNode() :
(this.implements(et2_IDOMNode) ? (<et2_IDOMNode><unknown>this).getDOMNode() : null));
(this.implements(et2_IDOMNode) ? (<et2_IDOMNode><unknown>this).getDOMNode() : null));
}
this.setAttribute(key, val, false);
}
@ -964,7 +891,7 @@ export class et2_widget extends ClassWithAttributes
*
* @see {@link http://api.jquery.com/deferred.promise/|jQuery Promise}
*/
doLoadingFinished(): JQueryPromise<any> | boolean
doLoadingFinished() : JQueryPromise<any> | boolean
{
return true;
}
@ -976,23 +903,19 @@ export class et2_widget extends ClassWithAttributes
* to this widget tree. The api instance can be set in the "container"
* widget using the setApiInstance function.
*/
egw(): IegwAppLocal
egw() : IegwAppLocal
{
// The _egw property is not set
if (typeof this._egw === 'undefined')
{
if (this._parent != null)
{
if (typeof this._egw === 'undefined') {
if (this._parent != null) {
return this._parent.egw();
}
// Get the window this object belongs to
var wnd = null;
if (this.implements(et2_IDOMNode))
{
if (this.implements(et2_IDOMNode)) {
var node = (<et2_IDOMNode><unknown>this).getDOMNode();
if (node && node.ownerDocument)
{
if (node && node.ownerDocument) {
wnd = node.ownerDocument.parentNode || node.ownerDocument.defaultView;
}
}
@ -1010,7 +933,7 @@ export class et2_widget extends ClassWithAttributes
*
* @param {IegwAppLocal} _egw egw object to set
*/
setApiInstance(_egw: IegwAppLocal)
setApiInstance(_egw : IegwAppLocal)
{
this._egw = _egw;
}
@ -1033,26 +956,22 @@ export class et2_widget extends ClassWithAttributes
*
* @param _mgrs is used internally and should not be supplied.
*/
getArrayMgrs(_mgrs?: object)
getArrayMgrs(_mgrs? : object)
{
if (typeof _mgrs == "undefined")
{
if (typeof _mgrs == "undefined") {
_mgrs = {};
}
// Add all managers of this object to the result, if they have not already
// been set in the result
for (var key in this._mgrs)
{
if (typeof _mgrs[key] == "undefined")
{
for (var key in this._mgrs) {
if (typeof _mgrs[key] == "undefined") {
_mgrs[key] = this._mgrs[key];
}
}
// Recursively applies this function to the parent widget
if (this._parent)
{
if (this._parent) {
this._parent.getArrayMgrs(_mgrs);
}
@ -1065,7 +984,7 @@ export class et2_widget extends ClassWithAttributes
* @param {string} _part which array mgr to set
* @param {object} _mgr
*/
setArrayMgr(_part: string, _mgr: et2_arrayMgr)
setArrayMgr(_part : string, _mgr : et2_arrayMgr)
{
this._mgrs[_part] = _mgr;
}
@ -1075,14 +994,11 @@ export class et2_widget extends ClassWithAttributes
*
* @param {string} managed_array_type name of array mgr to return
*/
getArrayMgr(managed_array_type: string): et2_arrayMgr | null
getArrayMgr(managed_array_type : string) : et2_arrayMgr | null
{
if (this._mgrs && typeof this._mgrs[managed_array_type] != "undefined")
{
if (this._mgrs && typeof this._mgrs[managed_array_type] != "undefined") {
return this._mgrs[managed_array_type];
}
else if (this._parent)
{
} else if (this._parent) {
return this._parent.getArrayMgr(managed_array_type);
}
@ -1096,32 +1012,27 @@ export class et2_widget extends ClassWithAttributes
*
* Constructor attributes are passed in case a child needs to make decisions
*/
checkCreateNamespace(_attrs?: any)
checkCreateNamespace(_attrs? : any)
{
// Get the content manager
var mgrs = this.getArrayMgrs();
for (var key in mgrs)
{
for (var key in mgrs) {
var mgr = mgrs[key];
// Get the original content manager if we have already created a
// perspective for this node
if (typeof this._mgrs[key] != "undefined" && mgr.perspectiveData.owner == this)
{
if (typeof this._mgrs[key] != "undefined" && mgr.perspectiveData.owner == this) {
mgr = mgr.parentMgr;
}
// Check whether the manager has a namespace for the id of this object
var entry = mgr.getEntry(this.id);
if (typeof entry === 'object' && entry !== null || this.id)
{
if (typeof entry === 'object' && entry !== null || this.id) {
// The content manager has an own node for this object, so
// create an own perspective.
this._mgrs[key] = mgr.openPerspective(this, this.id);
}
else
{
} else {
// The current content manager does not have an own namespace for
// this element, so use the content manager of the parent.
delete (this._mgrs[key]);
@ -1137,7 +1048,7 @@ export class et2_widget extends ClassWithAttributes
*
* @private
*/
protected _createNamespace(): boolean
protected _createNamespace() : boolean
{
return false;
}
@ -1166,12 +1077,9 @@ export class et2_widget extends ClassWithAttributes
*/
getInstanceManager()
{
if (this._inst != null)
{
if (this._inst != null) {
return this._inst;
}
else if (this._parent)
{
} else if (this._parent) {
return this._parent.getInstanceManager();
}

View File

@ -20,7 +20,7 @@ import {et2_register_widget, WidgetConfig} from "./et2_core_widget";
import {et2_baseWidget} from './et2_core_baseWidget'
import {et2_inputWidget} from "./et2_core_inputWidget";
import {expose} from "./expose";
import {et2_IDetachedDOM, et2_IExposable, et2_IInputNode} from "./et2_core_interfaces";
import {et2_IDetachedDOM, et2_IExposable} from "./et2_core_interfaces";
import {egw} from "../jsapi/egw_global";
/**
@ -168,10 +168,10 @@ export class et2_description extends expose(class et2_description extends et2_ba
(for_widget = this.getRoot().getWidgetById(this.options.for))
) && for_widget && for_widget.id)
{
if(for_widget.dom_id || for_widget.getDOMNode().id)
if(for_widget.dom_id)
{
for_id = for_widget.dom_id || for_widget.getDOMNode().id;
if(for_widget.instanceOf(et2_IInputNode) && for_widget.getInputNode() && for_id !== for_widget.getInputNode().id)
for_id = for_widget.dom_id;
if(for_widget.instanceOf(et2_inputWidget) && for_widget.getInputNode() && for_widget.dom_id !== for_widget.getInputNode().id)
{
for_id = for_widget.getInputNode().id;
}

View File

@ -96,14 +96,14 @@ export class et2_grid extends et2_DOMWidget implements et2_IDetachedDOM, et2_IAl
description: "Defines sortable start callback function"
}
};
private table : JQuery;
private thead : JQuery;
private tfoot : JQuery;
private tbody : JQuery;
private table: JQuery;
private thead: JQuery;
private tfoot: JQuery;
private tbody: JQuery;
// Counters for rows and columns
private rowCount : number = 0;
private columnCount : number = 0;
private rowCount: number = 0;
private columnCount: number = 0;
// 2D-Array which holds references to the DOM td tags
private cells = [];
@ -116,10 +116,9 @@ export class et2_grid extends et2_DOMWidget implements et2_IDetachedDOM, et2_IAl
// Wrapper div for height & overflow, if needed
private wrapper = null;
private lastRowNode : null;
private lastRowNode: null;
private sortablejs : Sortable = null;
/**
* Constructor
*
@ -140,20 +139,19 @@ export class et2_grid extends et2_DOMWidget implements et2_IDetachedDOM, et2_IAl
.appendTo(this.table);
}
_initCells(_colData : ColumnEntry[], _rowData : RowEntry[])
{
_initCells(_colData : ColumnEntry[], _rowData : RowEntry[]) {
// Copy the width and height
const w = _colData.length;
const h = _rowData.length;
// Create the 2D-Cells array
const cells = new Array(h);
for(let y = 0; y < h; y++)
for (let y = 0; y < h; y++)
{
cells[y] = new Array(w);
// Initialize the cell description objects
for(let x = 0; x < w; x++)
for (let x = 0; x < w; x++)
{
// Some columns (nm) we do not parse into a boolean
const col_disabled = _colData[x].disabled;
@ -180,7 +178,7 @@ export class et2_grid extends et2_DOMWidget implements et2_IDetachedDOM, et2_IAl
_getColDataEntry() : ColumnEntry
{
return {
return {
width: "auto",
class: "",
align: "",
@ -202,10 +200,10 @@ export class et2_grid extends et2_DOMWidget implements et2_IDetachedDOM, et2_IAl
_getCell(_cells, _x, _y)
{
if((0 <= _y) && (_y < _cells.length))
if ((0 <= _y) && (_y < _cells.length))
{
const row = _cells[_y];
if((0 <= _x) && (_x < row.length))
if ((0 <= _x) && (_x < row.length))
{
return row[_x];
}
@ -216,7 +214,7 @@ export class et2_grid extends et2_DOMWidget implements et2_IDetachedDOM, et2_IAl
_forceNumber(_val)
{
if(isNaN(_val))
if (isNaN(_val))
{
throw(_val + " is not a number!");
}
@ -228,7 +226,7 @@ export class et2_grid extends et2_DOMWidget implements et2_IDetachedDOM, et2_IAl
{
// Some things cannot be done inside a nextmatch - nm will do the expansion later
var nm = false;
let widget : et2_widget = this;
let widget: et2_widget = this;
while(!nm && widget != this.getRoot())
{
nm = (widget.getType() == 'nextmatch');
@ -236,15 +234,14 @@ export class et2_grid extends et2_DOMWidget implements et2_IDetachedDOM, et2_IAl
}
// Parse the columns tag
et2_filteredNodeIterator(columns, function(node, nodeName)
{
et2_filteredNodeIterator(columns, function(node, nodeName) {
const colDataEntry = this._getColDataEntry();
// This cannot be done inside a nm, it will expand it later
colDataEntry["disabled"] = nm ?
et2_readAttrWithDefault(node, "disabled", "") :
this.getArrayMgr("content")
.parseBoolExpression(et2_readAttrWithDefault(node, "disabled", ""));
if(nodeName == "column")
et2_readAttrWithDefault(node, "disabled", "") :
this.getArrayMgr("content")
.parseBoolExpression(et2_readAttrWithDefault(node, "disabled", ""));
if (nodeName == "column")
{
colDataEntry["width"] = et2_readAttrWithDefault(node, "width", "auto");
colDataEntry["class"] = et2_readAttrWithDefault(node, "class", "");
@ -269,12 +266,11 @@ export class et2_grid extends et2_DOMWidget implements et2_IDetachedDOM, et2_IAl
}, this);
// Parse the rows tag
et2_filteredNodeIterator(rows, function(node, nodeName)
{
et2_filteredNodeIterator(rows, function(node, nodeName) {
const rowDataEntry = this._getRowDataEntry();
rowDataEntry["disabled"] = this.getArrayMgr("content")
.parseBoolExpression(et2_readAttrWithDefault(node, "disabled", ""));
if(nodeName == "row")
.parseBoolExpression(et2_readAttrWithDefault(node, "disabled", ""));
if (nodeName == "row")
{
// Remember this row for auto-repeat - it'll eventually be the last one
this.lastRowNode = node;
@ -304,8 +300,8 @@ export class et2_grid extends et2_DOMWidget implements et2_IDetachedDOM, et2_IAl
if(this.getArrayMgr("content"))
{
const content = this.getArrayMgr("content");
var rowDataEntry = rowData[rowData.length - 1];
rowIndex = rowData.length - 1;
var rowDataEntry = rowData[rowData.length-1];
rowIndex = rowData.length-1;
// Find out if we have any content rows, and how many
let cont = true;
while(cont)
@ -317,7 +313,7 @@ export class et2_grid extends et2_DOMWidget implements et2_IDetachedDOM, et2_IAl
rowIndex++;
}
else if(this.lastRowNode != null)
else if (this.lastRowNode != null)
{
// Have to look through actual widgets to support const[$row]
// style names - should be avoided so we can remove this extra check
@ -332,49 +328,41 @@ export class et2_grid extends et2_DOMWidget implements et2_IDetachedDOM, et2_IAl
// Not in a nextmatch, so we can expand with abandon
const currentPerspective = jQuery.extend({}, content.perspectiveData);
const check = function(node, nodeName)
{
if(nodeName == 'box' || nodeName == 'hbox' || nodeName == 'vbox')
{
const check = function (node, nodeName) {
if (nodeName == 'box' || nodeName == 'hbox' || nodeName == 'vbox') {
return et2_filteredNodeIterator(node, check, this);
}
content.perspectiveData.row = rowIndex;
for(let attr in node.attributes)
{
for (let attr in node.attributes) {
const value = et2_readAttrWithDefault(node, node.attributes[attr].name, "");
// Don't include first char, those should be handled by normal means
// and it would break nextmatch
if(value.indexOf('@') > 0 || value.indexOf('$') > 0)
{
if (value.indexOf('@') > 0 || value.indexOf('$') > 0) {
// Ok, we found something. How many? Check for values.
let ident = content.expandName(value);
// expandName() handles index into content (@), but we have to look up
// regular values
if(value[0] != '@')
{
if (value[0] != '@') {
// Returns null if there isn't an actual value
ident = content.getEntry(ident, false, true);
}
while(ident != null && rowIndex < 1000)
{
while (ident != null && rowIndex < 1000) {
rowData[rowIndex] = jQuery.extend({}, rowDataEntry);
content.perspectiveData.row = ++rowIndex;
ident = content.expandName(value);
if(value[0] != '@')
{
if (value[0] != '@') {
// Returns null if there isn't an actual value
ident = content.getEntry(ident, false, true);
}
}
if(rowIndex >= 1000)
{
if (rowIndex >= 1000) {
egw.debug("error", "Problem in autorepeat fallback: too many rows for '%s'. Use a nextmatch, or start debugging.", value);
}
return;
}
}
};
et2_filteredNodeIterator(this.lastRowNode, check, this);
et2_filteredNodeIterator(this.lastRowNode, check,this);
cont = false;
content.perspectiveData = currentPerspective;
}
@ -393,8 +381,7 @@ export class et2_grid extends et2_DOMWidget implements et2_IDetachedDOM, et2_IAl
}
}
_fillCells(cells, columns : object[], rows : object[])
{
_fillCells(cells, columns : object[], rows : object[]) {
const h = cells.length;
const w = (h > 0) ? cells[0].length : 0;
const currentPerspective = jQuery.extend({}, this.getArrayMgr("content").perspectiveData);
@ -402,12 +389,10 @@ export class et2_grid extends et2_DOMWidget implements et2_IDetachedDOM, et2_IAl
// Read the elements inside the columns
let x = 0;
et2_filteredNodeIterator(columns, function(node, nodeName)
{
et2_filteredNodeIterator(columns, function(node, nodeName) {
function _readColNode(node, nodeName)
{
if(y >= h)
function _readColNode(node, nodeName) {
if (y >= h)
{
this.egw().debug("warn", "Skipped grid cell in column, '" +
nodeName + "'");
@ -417,7 +402,7 @@ export class et2_grid extends et2_DOMWidget implements et2_IDetachedDOM, et2_IAl
const cell = this._getCell(cells, x, y);
// Read the span value of the element
if(node.getAttribute("span"))
if (node.getAttribute("span"))
{
cell.rowSpan = node.getAttribute("span");
}
@ -427,7 +412,7 @@ export class et2_grid extends et2_DOMWidget implements et2_IDetachedDOM, et2_IAl
cell.autoRowSpan = true;
}
if(cell.rowSpan == "all")
if (cell.rowSpan == "all")
{
cell.rowSpan = cells.length;
}
@ -438,7 +423,7 @@ export class et2_grid extends et2_DOMWidget implements et2_IDetachedDOM, et2_IAl
const widget = this.createElementFromNode(node, nodeName);
// Fill all cells the widget is spanning
for(let i = 0; i < span && y < cells.length; i++, y++)
for (let i = 0; i < span && y < cells.length; i++, y++)
{
this._getCell(cells, x, y).widget = widget;
}
@ -447,7 +432,7 @@ export class et2_grid extends et2_DOMWidget implements et2_IDetachedDOM, et2_IAl
// If the node is a column, create the widgets which belong into
// the column
var y = 0;
if(nodeName == "column")
if (nodeName == "column")
{
et2_filteredNodeIterator(node, _readColNode, this);
}
@ -470,12 +455,10 @@ export class et2_grid extends et2_DOMWidget implements et2_IDetachedDOM, et2_IAl
nm = (widget.getType() == 'nextmatch');
widget = widget.getParent();
}
et2_filteredNodeIterator(rows, function(node, nodeName)
{
et2_filteredNodeIterator(rows, function(node, nodeName) {
readRowNode = function _readRowNode(node, nodeName)
{
if(x >= w)
readRowNode = function _readRowNode(node, nodeName) {
if (x >= w)
{
if(nodeName != "description")
{
@ -490,7 +473,7 @@ export class et2_grid extends et2_DOMWidget implements et2_IDetachedDOM, et2_IAl
let cell = this._getCell(cells, x, y);
// Read the span value of the element
if(node.getAttribute("span"))
if (node.getAttribute("span"))
{
cell.colSpan = node.getAttribute("span");
}
@ -500,7 +483,7 @@ export class et2_grid extends et2_DOMWidget implements et2_IDetachedDOM, et2_IAl
cell.autoColSpan = true;
}
if(cell.colSpan == "all")
if (cell.colSpan == "all")
{
cell.colSpan = cells[y].length;
}
@ -508,13 +491,13 @@ export class et2_grid extends et2_DOMWidget implements et2_IDetachedDOM, et2_IAl
const span = cell.colSpan = this._forceNumber(cell.colSpan);
// Read the align value of the element
if(node.getAttribute("align"))
if (node.getAttribute("align"))
{
cell.align = node.getAttribute("align");
}
// store id of nextmatch-*headers, so it is available for disabled widgets, which get not instanciated
if(nodeName.substr(0, 10) == 'nextmatch-')
if (nodeName.substr(0, 10) == 'nextmatch-')
{
cell.nm_id = node.getAttribute('id');
}
@ -557,10 +540,10 @@ export class et2_grid extends et2_DOMWidget implements et2_IDetachedDOM, et2_IAl
}
// Fill all cells the widget is spanning
for(let i = 0; i < span && x < cells[y].length; i++, x++)
for (let i = 0; i < span && x < cells[y].length; i++, x++)
{
cell = this._getCell(cells, x, y);
if(cell.widget == null)
if (cell.widget == null)
{
cell.widget = widget;
}
@ -579,7 +562,7 @@ export class et2_grid extends et2_DOMWidget implements et2_IDetachedDOM, et2_IAl
{
return;
}
if(nodeName == "row")
if (nodeName == "row")
{
// Adjust for the row
for(var name in this.getArrayMgrs())
@ -587,7 +570,7 @@ export class et2_grid extends et2_DOMWidget implements et2_IDetachedDOM, et2_IAl
//this.getArrayMgr(name).perspectiveData.row = y;
}
let cell = this._getCell(cells, x, y);
let cell = this._getCell(cells, x,y);
if(cell.rowData.id)
{
this.getArrayMgr("content").expandName(cell.rowData.id);
@ -612,8 +595,7 @@ export class et2_grid extends et2_DOMWidget implements et2_IDetachedDOM, et2_IAl
}, this);
// Extra content rows
for(y; y < h; y++)
{
for(y; y < h; y++) {
x = 0;
et2_filteredNodeIterator(this.lastRowNode, readRowNode, this);
@ -632,15 +614,15 @@ export class et2_grid extends et2_DOMWidget implements et2_IDetachedDOM, et2_IAl
// 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 y = 0; y < h; y++)
{
for(var x = w - 1; x >= 0; x--)
for (var x = w - 1; x >= 0; x--)
{
var cell = _cells[y][x];
if(cell.widget != null)
if (cell.widget != null)
{
if(cell.autoColSpan)
if (cell.autoColSpan)
{
cell.colSpan = w - x;
}
@ -651,15 +633,15 @@ export class et2_grid extends et2_DOMWidget implements et2_IDetachedDOM, et2_IAl
// 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 x = 0; x < w; x++)
{
for(var y = h - 1; y >= 0; y--)
for (var y = h - 1; y >= 0; y--)
{
var cell = _cells[y][x];
if(cell.widget != null)
if (cell.widget != null)
{
if(cell.autoRowSpan)
if (cell.autoRowSpan)
{
cell.rowSpan = h - y;
}
@ -673,7 +655,6 @@ export class et2_grid extends et2_DOMWidget implements et2_IDetachedDOM, et2_IAl
{
return true;
}
/**
* As the does not fit very well into the default widget structure, we're
* overwriting the loadFromXML function and doing a two-pass reading -
@ -681,8 +662,7 @@ export class et2_grid extends et2_DOMWidget implements et2_IDetachedDOM, et2_IAl
*
* @param {object} _node xml node to process
*/
loadFromXML(_node)
{
loadFromXML(_node ) {
// Keep the node for later changing / reloading
this.template_node = _node;
@ -690,12 +670,12 @@ export class et2_grid extends et2_DOMWidget implements et2_IDetachedDOM, et2_IAl
const rowsElems = et2_directChildrenByTagName(_node, "rows");
const columnsElems = et2_directChildrenByTagName(_node, "columns");
if(rowsElems.length == 1 && columnsElems.length == 1)
if (rowsElems.length == 1 && columnsElems.length == 1)
{
const columns = columnsElems[0];
const rows = rowsElems[0];
const colData : ColumnEntry[] = [];
const rowData : RowEntry[] = [];
const colData: ColumnEntry[] = [];
const rowData: RowEntry[] = [];
// Fetch the column and row data
this._fetchRowColData(columns, rows, colData, rowData);
@ -718,7 +698,7 @@ export class et2_grid extends et2_DOMWidget implements et2_IDetachedDOM, et2_IAl
}
}
createTableFromCells(_cells, _colData : any[], _rowData : any[])
createTableFromCells(_cells, _colData: any[], _rowData: any[])
{
this.managementArray = [];
this.cells = _cells;
@ -730,19 +710,19 @@ export class et2_grid extends et2_DOMWidget implements et2_IDetachedDOM, et2_IAl
const w = this.columnCount = (h > 0) ? _cells[0].length : 0;
// Create the table rows.
for(let y = 0; y < h; y++)
for (let y = 0; y < h; y++)
{
let parent = this.tbody;
switch(this.rowData[y]["part"])
{
case 'header':
if(!this.tbody.children().length && !this.tfoot.children().length)
if (!this.tbody.children().length && !this.tfoot.children().length)
{
parent = this.thead;
}
break;
case 'footer':
if(!this.tbody.children().length)
if (!this.tbody.children().length)
{
parent = this.tfoot;
}
@ -751,17 +731,17 @@ export class et2_grid extends et2_DOMWidget implements et2_IDetachedDOM, et2_IAl
const tr = jQuery(document.createElement("tr")).appendTo(parent)
.addClass(this.rowData[y]["class"]);
if(this.rowData[y].disabled)
if (this.rowData[y].disabled)
{
tr.hide();
}
if(this.rowData[y].height != "auto")
if (this.rowData[y].height != "auto")
{
tr.height(this.rowData[y].height);
}
if(this.rowData[y].valign)
if (this.rowData[y].valign)
{
tr.attr("valign", this.rowData[y].valign);
}
@ -772,39 +752,31 @@ export class et2_grid extends et2_DOMWidget implements et2_IDetachedDOM, et2_IAl
}
// Create the cells. x is incremented by the colSpan value of the
// cell.
for(let x = 0; x < w;)
for (let x = 0; x < w;)
{
// Fetch a cell from the cells
const cell = this._getCell(_cells, x, y);
if(cell.td == null && cell.widget != null)
if (cell.td == null && cell.widget != null)
{
// Create the cell
const td = jQuery(document.createElement("td")).appendTo(tr)
.addClass(cell["class"]);
if(cell.disabled)
if (cell.disabled)
{
td.hide();
// Need to do different things with webComponents
if(typeof cell.widget.options !== "undefined")
{
cell.widget.options.disabled = cell.disabled;
}
else if(cell.widget.nodeName)
{
cell.widget.disabled = cell.disabled;
}
cell.widget.options.disabled = cell.disabled;
}
if(cell.width != "auto")
if (cell.width != "auto")
{
td.width(cell.width);
}
if(cell.align)
if (cell.align)
{
td.attr("align", cell.align);
td.attr("align",cell.align);
}
// Add the entry for the widget to the management array
@ -819,20 +791,18 @@ export class et2_grid extends et2_DOMWidget implements et2_IDetachedDOM, et2_IAl
const rs = (y == h - 1) ? h - y : Math.min(h - y, cell.rowSpan);
// Set the col and row span values
if(cs > 1)
{
if (cs > 1) {
td.attr("colspan", cs);
}
if(rs > 1)
{
if (rs > 1) {
td.attr("rowspan", rs);
}
// Assign the td to the cell
for(let sx = x; sx < x + cs; sx++)
for (let sx = x; sx < x + cs; sx++)
{
for(let sy = y; sy < y + rs; sy++)
for (let sy = y; sy < y + rs; sy++)
{
this._getCell(_cells, sx, sy).td = td;
}
@ -852,15 +822,15 @@ export class et2_grid extends et2_DOMWidget implements et2_IDetachedDOM, et2_IAl
{
// If the parent class functions are asking for the DOM-Node, return the
// outer table.
if(_sender == this || typeof _sender == 'undefined')
if (_sender == this || typeof _sender == 'undefined')
{
return this.wrapper != null ? this.wrapper[0] : this.table[0];
}
// Check whether the _sender object exists inside the management array
for(let i = 0; i < this.managementArray.length; i++)
for (let i = 0; i < this.managementArray.length; i++)
{
if(this.managementArray[i].widget == _sender)
if (this.managementArray[i].widget == _sender)
{
return this.managementArray[i].cell;
}
@ -869,22 +839,22 @@ export class et2_grid extends et2_DOMWidget implements et2_IDetachedDOM, et2_IAl
return null;
}
isInTree(_sender? : et2_widget) : boolean
isInTree(_sender?:et2_widget) : boolean
{
let vis = true;
if(typeof _sender != "undefined" && _sender != this)
if (typeof _sender != "undefined" && _sender != this)
{
vis = false;
// Check whether the _sender object exists inside the management array
for(let i = 0; i < this.managementArray.length; i++)
for (let i = 0; i < this.managementArray.length; i++)
{
if(this.managementArray[i].widget == _sender)
if (this.managementArray[i].widget == _sender)
{
vis = !(typeof this.managementArray[i].disabled === 'boolean' ?
this.managementArray[i].disabled :
this.getArrayMgr("content").parseBoolExpression(this.managementArray[i].disabled)
this.managementArray[i].disabled :
this.getArrayMgr("content").parseBoolExpression(this.managementArray[i].disabled)
);
break;
}
@ -903,7 +873,7 @@ export class et2_grid extends et2_DOMWidget implements et2_IDetachedDOM, et2_IAl
*
* @param {string} _value Overflow value, must be a valid CSS overflow value, default 'visible'
*/
set_overflow(_value : string)
set_overflow(_value: string)
{
let wrapper = this.wrapper || this.table.parent('[id$="_grid_wrapper"]');
@ -911,7 +881,7 @@ export class et2_grid extends et2_DOMWidget implements et2_IDetachedDOM, et2_IAl
if(wrapper.length == 0 && _value && _value !== 'visible')
{
this.wrapper = wrapper = this.table.wrap('<div id="' + this.id + '_grid_wrapper"></div>').parent();
this.wrapper = wrapper = this.table.wrap('<div id="'+this.id+'_grid_wrapper"></div>').parent();
if(this.height)
{
wrapper.css('height', this.height);
@ -919,8 +889,7 @@ export class et2_grid extends et2_DOMWidget implements et2_IDetachedDOM, et2_IAl
}
wrapper.css('overflow', _value);
if(wrapper.length && (!_value || _value === 'visible'))
{
if(wrapper.length && (!_value || _value === 'visible')) {
this.table.unwrap();
}
}
@ -937,7 +906,7 @@ export class et2_grid extends et2_DOMWidget implements et2_IDetachedDOM, et2_IAl
* @param {Object} [_value.sel_options] New select options
* @param {Object} [_value.readonlys] New read-only values
*/
set_value(_value : { content? : object, sel_options? : object, readonlys? : object })
set_value(_value : {content? : object, sel_options? : object, readonlys? : object})
{
// Destroy children, empty grid
for(let i = 0; i < this.managementArray.length; i++)
@ -974,7 +943,7 @@ export class et2_grid extends et2_DOMWidget implements et2_IDetachedDOM, et2_IAl
*
* @param {boolean|function} sortable Callback or false to disable
*/
set_sortable(sortable : boolean | Function)
set_sortable(sortable: boolean | Function)
{
const self = this;
let tbody = this.getDOMNode().getElementsByTagName('tbody')[0];
@ -985,37 +954,32 @@ export class et2_grid extends et2_DOMWidget implements et2_IDetachedDOM, et2_IAl
return;
}
for(let i = 0; i < tbody.children.length; i++)
for (let i =0; i < tbody.children.length; i++)
{
if(!tbody.children[i].classList.contains('th') && !tbody.children[i].id)
if (!tbody.children[i].classList.contains('th') && !tbody.children[i].id)
{
tbody.children[i].setAttribute('id', i.toString());
}
}
this.sortablejs = new Sortable(tbody, {
this.sortablejs = new Sortable(tbody,{
group: this.options.sortable_connectWith,
draggable: "tr:not(.th)",
filter: this.options.sortable_cancel,
ghostClass: this.options.sortable_placeholder,
dataIdAttr: 'id',
onAdd: function(event)
{
if(typeof self.options.sortable_recieveCallback == 'function')
{
onAdd:function (event) {
if (typeof self.options.sortable_recieveCallback == 'function') {
self.options.sortable_recieveCallback.call(self, event, this, self.id);
}
},
onStart: function(event, ui)
{
if(typeof self.options.sortable_startCallback == 'function')
{
onStart: function (event, ui) {
if (typeof self.options.sortable_startCallback == 'function') {
self.options.sortable_startCallback.call(self, event, this, self.id);
}
},
onSort: function(event)
{
self.egw().json(sortable, [
onSort: function (event) {
self.egw().json(sortable,[
self.getInstanceManager().etemplate_exec_id,
self.sortablejs.toArray(),
self.id],
@ -1034,14 +998,13 @@ export class et2_grid extends et2_DOMWidget implements et2_IDetachedDOM, et2_IAl
*/
_link_actions(actions : object[])
{
// Get the top level element for the tree
// Get the top level element for the tree
// get appObjectManager for the actual app, it might not always be the current app(e.g. running app content under admin tab)
// @ts-ignore
let objectManager = egw_getAppObjectManager(true, this.getInstanceManager().app);
objectManager = objectManager.getObjectById(this.getInstanceManager().uniqueId, 2) || objectManager;
objectManager = objectManager.getObjectById(this.getInstanceManager().uniqueId,2) || objectManager;
let widget_object = objectManager.getObjectById(this.id);
if(widget_object == null)
{
if (widget_object == null) {
// Add a new container to the object manager which will hold the widget
// objects
widget_object = objectManager.insertObject(false, new egwActionObject(
@ -1061,7 +1024,7 @@ export class et2_grid extends et2_DOMWidget implements et2_IDetachedDOM, et2_IAl
let i = 0, r = 0;
for(; i < this.rowData.length; i++)
{
if(this.rowData[i].part != 'body') continue;
if (this.rowData[i].part != 'body') continue;
const content = this.getArrayMgr('content').getEntry(i);
if(content)
{
@ -1110,33 +1073,33 @@ export class et2_grid extends et2_DOMWidget implements et2_IDetachedDOM, et2_IAl
_getColumnName()
{
const ids = [];
for(let r = 0; r < this.cells.length; ++r)
for(let r=0; r < this.cells.length; ++r)
{
const cols = this.cells[r];
for(let c = 0; c < cols.length; ++c)
for(let c=0; c < cols.length; ++c)
{
if(cols[c].nm_id) ids.push(cols[c].nm_id);
if (cols[c].nm_id) ids.push(cols[c].nm_id);
}
}
return ids.join('_');
}
resize(_height)
resize (_height)
{
if(typeof this.options != 'undefined' && _height
&& typeof this.options.resize_ratio != 'undefined' && this.options.resize_ratio)
if (typeof this.options != 'undefined' && _height
&& typeof this.options.resize_ratio != 'undefined' && this.options.resize_ratio)
{
// apply the ratio
_height = (this.options.resize_ratio != '') ? _height * this.options.resize_ratio : _height;
if(_height != 0)
_height = (this.options.resize_ratio != '')? _height * this.options.resize_ratio: _height;
if (_height != 0)
{
if(this.wrapper)
if (this.wrapper)
{
this.wrapper.height(this.wrapper.height() + _height);
}
else
{
this.table.height(this.table.height() + _height);
this.table.height(this.table.height() + _height );
}
}
@ -1156,32 +1119,28 @@ export class et2_grid extends et2_DOMWidget implements et2_IDetachedDOM, et2_IAl
*/
getRow(_sender : et2_widget) : et2_widget
{
if(!_sender || !this.cells) return;
if (!_sender || !this.cells) return;
for(let r = 0; r < this.cells.length; ++r)
for(let r=0; r < this.cells.length; ++r)
{
const row = this.cells[r];
for(var c = 0; c < row.length; ++c)
for(var c=0; c < row.length; ++c)
{
if(!row[c].widget) continue;
if (!row[c].widget) continue;
let found = row[c].widget === _sender;
if(!found) row[c].widget.iterateOver(function(_widget)
{
if(_widget === _sender) found = true;
});
if(found)
if (!found) row[c].widget.iterateOver(function(_widget) {if (_widget === _sender) found = true;});
if (found)
{
// return a fake row object allowing to iterate over it's children
const row_obj : et2_widget = new et2_widget(this, {});
for(var c = 0; c < row.length; ++c)
const row_obj: et2_widget = new et2_widget(this, {});
for(var c=0; c < row.length; ++c)
{
if(row[c].widget) row_obj.addChild(row[c].widget);
if (row[c].widget) row_obj.addChild(row[c].widget);
}
row_obj.isInTree = jQuery.proxy(this.isInTree, this);
// we must not free the children!
row_obj.destroy = function()
{
row_obj.destroy = function(){
// @ts-ignore
delete row_obj._children;
};
@ -1194,30 +1153,28 @@ export class et2_grid extends et2_DOMWidget implements et2_IDetachedDOM, et2_IAl
/**
* Needed for the align interface, but we're not doing anything with it...
*/
get_align() : string
{
get_align(): string {
return "";
}
}
et2_register_widget(et2_grid, ["grid"]);
interface ColumnEntry
{
id? : string,
width : string | number, // 'auto'
class : string, // "",
align : string, // "",
span : string | number // "1",
disabled : boolean // false
width: string | number, // 'auto'
class: string, // "",
align: string, // "",
span: string | number // "1",
disabled: boolean // false
}
interface RowEntry
{
id? : string
height : string | number, // "auto",
class : string, // "",
valign : string, // "top",
span : string | number, // "1",
disabled : boolean // false
height: string | number, // "auto",
class: string, // "",
valign: string, // "top",
span: string | number, // "1",
disabled: boolean // false
}

View File

@ -23,12 +23,7 @@ import {et2_nextmatch, et2_nextmatch_header_bar} from "./et2_extension_nextmatch
import {et2_tabbox} from "./et2_widget_tabs";
import '../jsapi/egw_json.js';
import {egwIsMobile} from "../egw_action/egw_action_common.js";
import './Et2Box/Et2Box';
import './Et2Button/Et2Button';
import './Et2Date/Et2Date';
import './Et2Textarea/Et2Textarea';
import './Et2Textbox/Et2Textbox';
import './Et2Colorpicker/Et2Colorpicker';
//import './et2-button';
/* Include all widget classes here, we only care about them registering, not importing anything*/
import './et2_widget_vfs'; // Vfs must be first (before et2_widget_file) due to import cycle
import './et2_widget_template';
@ -117,7 +112,7 @@ export class etemplate2
private app_obj: EgwApp;
app: string;
constructor(_container: HTMLElement, _menuaction?: string, _uniqueId?: string)
constructor(_container : HTMLElement, _menuaction? : string, _uniqueId?: string)
{
if (typeof _menuaction == "undefined")
{
@ -169,15 +164,13 @@ export class etemplate2
*
* @param {jQuery.event} e
*/
public resize(e)
{
public resize(e) {
const event = e;
const self = this;
let excess_height: number | boolean = false;
// Check if the framework has an specific excess height calculation
if (typeof window.framework != 'undefined' && typeof window.framework.get_wExcessHeight != 'undefined')
{
if (typeof window.framework != 'undefined' && typeof window.framework.get_wExcessHeight != 'undefined') {
excess_height = window.framework.get_wExcessHeight(window);
}
@ -206,7 +199,7 @@ export class etemplate2
// If we're visible, call the "resize" event of all functions which implement the
// "IResizeable" interface
if (jQuery(self.DOMContainer).is(":visible"))
if(jQuery(self.DOMContainer).is(":visible"))
{
self._widgetContainer.iterateOver(function (_widget)
{
@ -233,7 +226,7 @@ export class etemplate2
* @param _keep_app_object keep app object
* @param _keep_session keep server-side et2 session eg. for vfs-select
*/
public clear(_keep_app_object?: boolean, _keep_session?: boolean)
public clear(_keep_app_object?:boolean, _keep_session?: boolean)
{
jQuery(this._DOMContainer).trigger('clear');
@ -273,11 +266,11 @@ export class etemplate2
}
// If using a private app object, remove all of them
if (!_keep_app_object && this.app_obj !== window.app)
if(!_keep_app_object && this.app_obj !== window.app)
{
for (const app_name in this.app_obj)
for(const app_name in this.app_obj)
{
if (this.app_obj[app_name] instanceof EgwApp)
if(this.app_obj[app_name] instanceof EgwApp)
{
this.app_obj[app_name].destroy();
}
@ -316,7 +309,7 @@ export class etemplate2
// Create all neccessary _data entries
const neededEntries = ["content", "sel_options", "readonlys", "modifications",
"validation_errors"];
"validation_errors"];
for (let i = 0; i < neededEntries.length; i++)
{
if (typeof _data[neededEntries[i]] == "undefined" || !_data[neededEntries[i]])
@ -359,7 +352,7 @@ export class etemplate2
bind_unload()
{
// Prompt user to save for dirty popups
if (window !== egw_topWindow() && !this.close_prompt)
if(window !== egw_topWindow() && !this.close_prompt)
{
this.close_prompt = this._close_changed_prompt.bind(this);
window.addEventListener("beforeunload", this.close_prompt);
@ -370,22 +363,22 @@ export class etemplate2
{
// need to use async === "keepalive" to run via beforeunload
egw.json("EGroupware\\Api\\Etemplate::ajax_destroy_session",
[this._etemplate_exec_id], null, null, "keepalive").sendRequest();
[this._etemplate_exec_id], null, null, "keepalive").sendRequest();
}, this);
window.addEventListener("beforeunload", this.destroy_session);
}
}
private _close_changed_prompt(e: BeforeUnloadEvent)
private _close_changed_prompt(e : BeforeUnloadEvent)
{
if (this._skip_close_prompt || !this.isDirty())
if(this._skip_close_prompt || !this.isDirty())
{
return;
}
// Cancel the event
e.preventDefault(); // If you prevent default behavior in Mozilla Firefox prompt will always be shown
e.preventDefault(); // If you prevent default behavior in Mozilla Firefox prompt will always be shown
// Chrome requires returnValue to be set
e.returnValue = '';
@ -513,15 +506,12 @@ export class etemplate2
// require necessary translations from server AND the app.js file, if not already loaded
let promisses = [window.egw_ready]; // to wait for legacy-loaded JS
if (Array.isArray(_data.langRequire))
{
if (Array.isArray(_data.langRequire)) {
promisses.push(egw(currentapp, window).langRequire(window, _data.langRequire));
}
return Promise.all(promisses).catch((err) =>
{
console.log("et2.load(): error loading lang-files and app.js: " + err.message);
}).then(() =>
{
return Promise.all(promisses).catch((err) => {
console.log("et2.load(): error loading lang-files and app.js: "+err.message);
}).then(() => {
this.clear();
// Initialize application js
@ -573,8 +563,7 @@ export class etemplate2
const _load = function ()
{
egw.debug("log", "Loading template...");
if (egw.debug_level() >= 4 && console.timeStamp)
{
if (egw.debug_level() >= 4 && console.timeStamp) {
console.timeStamp("Begin rendering template");
}
@ -638,11 +627,11 @@ export class etemplate2
// Date fields open the calendar popup on focus
.not('.et2_date')
.filter(function ()
{
// Skip inputs that are out of tab ordering
const $this = jQuery(this);
return !$this.attr('tabindex') || parseInt($this.attr('tabIndex')) >= 0;
}).first();
{
// Skip inputs that are out of tab ordering
const $this = jQuery(this);
return !$this.attr('tabindex') || parseInt($this.attr('tabIndex')) >= 0;
}).first();
// mobile device, focus only if the field is empty (usually means new entry)
// should focus always for non-mobile one
@ -680,19 +669,17 @@ export class etemplate2
// Profiling
if (egw.debug_level() >= 4)
{
if (console.timeEnd)
{
if (console.timeEnd) {
console.timeEnd(_name);
}
if (console.profileEnd)
{
if (console.profileEnd) {
console.profileEnd(_name);
}
const end_time = (new Date).getTime();
let gen_time_div = jQuery('#divGenTime_' + appname);
if (!gen_time_div.length) gen_time_div = jQuery('.pageGenTime');
gen_time_div.find('.et2RenderTime').remove();
gen_time_div.append('<span class="et2RenderTime">' + egw.lang('eT2 rendering took %1s', '' + ((end_time - start_time) / 1000)) + '</span>');
gen_time_div.append('<span class="et2RenderTime">' + egw.lang('eT2 rendering took %1s', ''+((end_time - start_time) / 1000)) + '</span>');
}
}, this));
};
@ -831,8 +818,7 @@ export class etemplate2
indexes = [indexes.shift(), indexes.join('[')];
indexes[1] = indexes[1].substring(0, indexes[1].length - 1);
const children = indexes[1].split('][');
if (children.length)
{
if (children.length) {
indexes = jQuery.merge([indexes[0]], children);
}
}
@ -884,7 +870,7 @@ export class etemplate2
{
if (_widget.submit(values) === false)
{
if (!invalid && !_widget.isValid())
if(!invalid && !_widget.isValid())
{
invalid = _widget;
}
@ -965,7 +951,7 @@ export class etemplate2
this.unbind_unload();
const form = jQuery("<form id='form' action='" + egw().webserverUrl +
"/index.php?menuaction=" + this._widgetContainer.egw().getAppName() + ".EGroupware\\Api\\Etemplate.process_exec&ajax=true' method='POST'>");
"/index.php?menuaction=" + this._widgetContainer.egw().getAppName() + ".EGroupware\\Api\\Etemplate.process_exec&ajax=true' method='POST'>");
const etemplate_id = jQuery(document.createElement("input"))
.attr("name", 'etemplate_exec_id')
@ -1000,8 +986,7 @@ export class etemplate2
_root.iterateOver(function (_widget)
{
// The widget must have an id to be included in the values array
if (_widget.id === undefined || _widget.id === "")
{
if (_widget.id === undefined || _widget.id === "") {
return;
}
@ -1016,8 +1001,7 @@ export class etemplate2
indexes = [indexes.shift(), indexes.join('[')];
indexes[1] = indexes[1].substring(0, indexes[1].length - 1);
const children = indexes[1].split('][');
if (children.length)
{
if (children.length) {
indexes = jQuery.merge([indexes[0]], children);
}
path = path.concat(indexes);
@ -1140,7 +1124,7 @@ export class etemplate2
* @param {(string|null)} _type type of change. One of 'update','edit', 'delete', 'add' or null
* @return {boolean} true if nextmatch found and refreshed, false if not
*/
static app_refresh(_msg, _app, _id, _type)
static app_refresh (_msg, _app, _id, _type)
{
let refresh_done = false;
let app = _app.split('-');
@ -1254,7 +1238,7 @@ export class etemplate2
* @param {string} id DOM ID of the container node
* @returns {etemplate2|null}
*/
public static getById(id)
public static getById (id)
{
for (let name in etemplate2._byTemplate)
{
@ -1419,48 +1403,38 @@ export class etemplate2
* @returns {Boolean} Handled by this plugin
* @throws Invalid parameters if the required res.data parameters are missing
*/
public handle_assign(type, res, req)
{
public handle_assign(type, res, req) {
//type, req; // unused, but required by plugin signature
//Check whether all needed parameters have been passed and call the alertHandler function
if ((typeof res.data.id != 'undefined') &&
(typeof res.data.key != 'undefined') &&
(typeof res.data.value != 'undefined')
)
{
) {
if (typeof res.data.etemplate_exec_id == 'undefined' ||
res.data.etemplate_exec_id != this._etemplate_exec_id)
{
res.data.etemplate_exec_id != this._etemplate_exec_id) {
// Not for this etemplate, but not an error
return false;
}
if (res.data.key == 'etemplate_exec_id')
{
if (res.data.key == 'etemplate_exec_id') {
this._etemplate_exec_id = res.data.value;
return true;
}
if (this._widgetContainer == null)
{
if (this._widgetContainer == null) {
// Right etemplate, but it's already been cleared.
egw.debug('warn', "Tried to call assign on an un-loaded etemplate", res.data);
return false;
}
const widget = this._widgetContainer.getWidgetById(res.data.id);
if (widget)
{
if (typeof widget['set_' + res.data.key] != 'function')
{
if (widget) {
if (typeof widget['set_' + res.data.key] != 'function') {
egw.debug('warn', "Cannot set %s attribute %s via JSON assign, no set_%s()", res.data.id, res.data.key, res.data.key);
return false;
}
try
{
try {
widget['set_' + res.data.key].call(widget, res.data.value);
return true;
}
catch (e)
{
} catch (e) {
egw.debug("error", "When assigning %s on %s via AJAX, \n" + (e.message || e + ""), res.data.key, res.data.id, widget);
}
}

View File

@ -161,11 +161,11 @@ egw.extend('tooltip', egw.MODULE_WND_LOCAL, function(_app, _wnd)
* It is important to remove all tooltips from all elements which are
* no longer needed, in order to prevent memory leaks.
*
* @param _elem is the element to which the tooltip should get bound.
* @param _elem is the element to which the tooltip should get bound. It
* has to be a jQuery node.
* @param _html is the html code which should be shown as tooltip.
*/
tooltipBind: function(_elem, _html, _isHtml) {
_elem = jQuery(_elem);
if (_html != '')
{
_elem.bind('mouseenter.tooltip', function(e) {
@ -222,7 +222,6 @@ egw.extend('tooltip', egw.MODULE_WND_LOCAL, function(_app, _wnd)
* removed. _elem has to be a jQuery node.
*/
tooltipUnbind: function(_elem) {
_elem = jQuery(_elem);
if (current_elem == _elem)
{
hide();

View File

@ -76,4 +76,4 @@ class Button extends Etemplate\Widget
}
}
}
Etemplate\Widget::registerWidget(__NAMESPACE__.'\\Button', array('et2-button','button','buttononly'));
Etemplate\Widget::registerWidget(__NAMESPACE__.'\\Button', array('button','buttononly'));

View File

@ -29,7 +29,7 @@ use EGroupware\Api;
* &8 = dont show time for readonly and type date-time if time is 0:00,
* &16 = prefix r/o display with dow
* &32 = prefix r/o display with week-number
* &64 = prefix r/o display with weeknumber and dow
* &64 = prefix r/o display with weeknumber and dow
* &128 = no icon to trigger popup, click into input trigers it, also removing the separators to save space
*
* @todo validation of date-duration
@ -60,7 +60,7 @@ class Date extends Transformer
* @param string $cname
* @param array $expand values for keys 'c', 'row', 'c_', 'row_', 'cont'
*/
public function beforeSendToClient($cname, array $expand = null)
public function beforeSendToClient($cname, array $expand=null)
{
if($this->type == 'date-houronly')
{
@ -87,20 +87,14 @@ class Date extends Transformer
* @param array $expand
* @param array $data Row data
*/
public function set_row_value($cname, array $expand, array &$data)
public function set_row_value($cname, Array $expand, Array &$data)
{
if($this->type == 'date-duration')
{
return;
}
if($this->type == 'date-duration') return;
$form_name = self::form_name($cname, $this->id, $expand);
$value =& $this->get_array($data, $form_name, true);
if(true)
{
$value = $this->format_date($value);
}
if (true) $value = $this->format_date($value);
}
/**
@ -110,17 +104,14 @@ class Date extends Transformer
*/
public function format_date($value)
{
if(!$value)
{
return $value;
} // otherwise we will get current date or 1970-01-01 instead of an empty value
if (!$value) return $value; // otherwise we will get current date or 1970-01-01 instead of an empty value
// for DateTime objects (regular PHP and Api\DateTime ones), set user timezone
if($value instanceof \DateTime)
if ($value instanceof \DateTime)
{
$date = Api\DateTime::server2user($value);
}
elseif($this->attrs['data_format'] && $this->attrs['data_format'] !== 'object')
elseif ($this->attrs['data_format'] && $this->attrs['data_format'] !== 'object')
{
$date = Api\DateTime::createFromFormat($this->attrs['data_format'], $value, Api\DateTime::$user_timezone);
}
@ -151,65 +142,58 @@ class Date extends Transformer
* @param string $cname current namespace
* @param array $expand values for keys 'c', 'row', 'c_', 'row_', 'cont'
* @param array $content
* @param array &$validated =array() validated content
* @param array &$validated=array() validated content
* @return boolean true if no validation error, false otherwise
*/
public function validate($cname, array $expand, array $content, &$validated = array())
public function validate($cname, array $expand, array $content, &$validated=array())
{
$form_name = self::form_name($cname, $this->id, $expand);
if(!$this->is_readonly($cname, $form_name) && $this->type != 'date-since') // date-since is always readonly
if (!$this->is_readonly($cname, $form_name) && $this->type != 'date-since') // date-since is always readonly
{
$value = self::get_array($content, $form_name);
$valid =& self::get_array($validated, $form_name, true);
if($value && $this->type !== 'date-duration')
if ($value && $this->type !== 'date-duration')
{
try
{
if(substr($value, -1) === 'Z')
{
$value = substr($value, 0, -1);
}
if (substr($value, -1) === 'Z') $value = substr($value, 0, -1);
$date = new Api\DateTime($value);
}
catch (\Exception $e)
catch(\Exception $e)
{
unset($e);
$date = null;
$value = '';
// this is not really a user error, but one of the clientside engine
self::set_validation_error($form_name, lang("'%1' is not a valid date !!!", $value) . ' ' . $this->data_format);
self::set_validation_error($form_name,lang("'%1' is not a valid date !!!", $value).' '.$this->data_format);
}
}
if((string)$value === '' && $this->attrs['needed'])
if ((string)$value === '' && $this->attrs['needed'])
{
self::set_validation_error($form_name, lang('Field must not be empty !!!'));
self::set_validation_error($form_name,lang('Field must not be empty !!!'));
}
elseif(is_null($value))
elseif (is_null($value))
{
$valid = null;
}
elseif($this->type == 'date-duration')
elseif ($this->type == 'date-duration')
{
$valid = (string)$value === '' ? '' : (int)$value;
}
if(!empty($this->attrs['min']) && !empty($value))
if (!empty($this->attrs['min']) && !empty($value))
{
if(is_numeric($this->attrs['min']))
{
$min = new Api\DateTime(strtotime($this->attrs['min'] . 'days'));
$min = new Api\DateTime(strtotime( $this->attrs['min'] . 'days'));
}
elseif(preg_match('/[+-][[:digit:]]+[ymwd]/', $this->attrs['min']))
elseif (preg_match('/[+-][[:digit:]]+[ymwd]/',$this->attrs['min']))
{
// Relative date with periods
$min = new Api\DateTime(strtotime(str_replace(array('y', 'm', 'w', 'd'), array('years', 'months',
'weeks',
'days'), $this->attrs['min'])
)
);
$min = new Api\DateTime(strtotime(str_replace(array('y','m','w','d'), array('years','months','weeks','days'), $this->attrs['min'])));
}
else
{
@ -217,28 +201,23 @@ class Date extends Transformer
}
if($date < $min)
{
self::set_validation_error($form_name, lang(
self::set_validation_error($form_name,lang(
"Value has to be at least '%1' !!!",
$min->format($this->type != 'date')
), ''
);
),'');
$value = $min;
}
}
if(!empty($this->attrs['max']) && !empty($value))
if (!empty($this->attrs['max']) && !empty($value))
{
if(is_numeric($this->attrs['max']))
{
$max = new Api\DateTime(strtotime($this->attrs['max'] . 'days'));
$max = new Api\DateTime(strtotime( $this->attrs['max'] . 'days'));
}
elseif(preg_match('/[+-][[:digit:]]+[ymwd]/', $this->attrs['max']))
elseif (preg_match('/[+-][[:digit:]]+[ymwd]/',$this->attrs['max']))
{
// Relative date with periods
$max = new Api\DateTime(strtotime(str_replace(array('y', 'm', 'w', 'd'), array('years', 'months',
'weeks',
'days'), $this->attrs['max'])
)
);
$max = new Api\DateTime(strtotime(str_replace(array('y','m','w','d'), array('years','months','weeks','days'), $this->attrs['max'])));
}
else
{
@ -246,15 +225,14 @@ class Date extends Transformer
}
if($date > $max)
{
self::set_validation_error($form_name, lang(
self::set_validation_error($form_name,lang(
"Value has to be at maximum '%1' !!!",
$max->format($this->type != 'date')
), ''
);
),'');
$value = $max;
}
}
if($this->type == 'date-duration')
if ($this->type == 'date-duration')
{
$valid = (string)$value === '' ? '' : (int)$value;
}
@ -263,23 +241,22 @@ class Date extends Transformer
// Not null, blank
$value = '';
}
elseif($date && empty($this->attrs['data_format'])) // integer timestamp
elseif ($date && empty($this->attrs['data_format'])) // integer timestamp
{
$valid = $date->format('ts');
}
// string with formatting letters like for php's date() method
elseif($date && ($valid = $date->format($this->attrs['data_format'])))
elseif ($date && ($valid = $date->format($this->attrs['data_format'])))
{
// Nothing to do here
}
else
{
// this is not really a user error, but one of the clientside engine
self::set_validation_error($form_name, lang("'%1' is not a valid date !!!", $value) . ' ' . $this->data_format);
self::set_validation_error($form_name,lang("'%1' is not a valid date !!!", $value).' '.$this->data_format);
}
//error_log("$this : ($valid)" . Api\DateTime::to($valid));
}
}
}
\EGroupware\Api\Etemplate\Widget::registerWidget(__NAMESPACE__ . '\\Date', array('et2-date', 'time_or_date'));
\EGroupware\Api\Etemplate\Widget::registerWidget(__NAMESPACE__.'\\Date', array('time_or_date'));

View File

@ -205,15 +205,29 @@ class Template extends Etemplate\Widget
/**
* Convert relative template path from relPath to an url incl. cache-buster modification time postfix
*
* This adds the server-side modification of eTemplates for web-components /api/etemplate.php.
*
* @param string $path
* @return string url
*/
public static function rel2url($path)
{
return $GLOBALS['egw_info']['server']['webserver_url'].'/api/etemplate.php'.
($path[0] === '/' ? $path : Api\Vfs::parse_url($path, PHP_URL_PATH)).'?'.filemtime(self::rel2path($path));
if ($path)
{
if ($path[0] === '/')
{
$url = $GLOBALS['egw_info']['server']['webserver_url'].$path.'?'.filemtime(self::rel2path($path));
}
else
{
$url = Api\Vfs::download_url($path);
if ($url[0] == '/') $url = Api\Framework::link($url);
// mtime postfix has to use '?download=', as our WebDAV treats everything else literal and not ignore them like Apache for static files!
$url .= '?download='.filemtime($path);
}
}
//error_log(__METHOD__."('$path') returning $url");
return $url;
}
/**

View File

@ -179,6 +179,4 @@ class Textbox extends Etemplate\Widget
}
}
}
Etemplate\Widget::registerWidget(__NAMESPACE__ . '\\Textbox', array('et2-textarea', 'et2-textbox', 'textbox', 'text',
'int', 'integer', 'float', 'hidden', 'colorpicker',
'hidden'));
Etemplate\Widget::registerWidget(__NAMESPACE__.'\\Textbox', array('textbox','text','int','integer','float','hidden','colorpicker','hidden'));

View File

@ -3,7 +3,7 @@
<!-- $Id$ -->
<overlay>
<template id="infolog.edit.description" template="" lang="" group="0" version="1.6.001">
<et2-textarea id="info_des" no_lang="1" width="99.7%" height="245px"/>
<textbox multiline="true" id="info_des" no_lang="1" width="99.7%" height="245px"/>
<checkbox id="clean_history"/>
</template>
<template id="infolog.edit.links" template="" lang="" group="0" version="1.3.001">
@ -154,7 +154,7 @@
<rows>
<row class="dialogHeader">
<description value="Title" for="info_subject"/>
<et2-textbox statustext="a short subject for the entry" id="info_subject" class="et2_fullWidth et2_required" maxlength="255" span="4" tabindex="1"></et2-textbox>
<textbox statustext="a short subject for the entry" id="info_subject" class="et2_fullWidth et2_required" maxlength="255" span="4" tabindex="1"/>
<textbox type="integer" id="info_number" readonly="true"/>
<appicon src="infolog" for="info_number"/>
</row>
@ -165,7 +165,7 @@
</menulist>
<description/>
<description value="Startdate" for="info_startdate"/>
<et2-date statustext="when should the ToDo or Phonecall be started, it shows up from that date in the filter open or own open (startpage)" id="info_startdate" class="et2_fullWidth"></et2-date>
<date-time statustext="when should the ToDo or Phonecall be started, it shows up from that date in the filter open or own open (startpage)" id="info_startdate" class="et2_fullWidth"/>
</row>
<row class="dialogHeader3">
<description value="Contact"/>
@ -217,21 +217,25 @@
</row>
<row disabled="!@info_owner" class="dialogOperators">
<description value="Owner"/>
<et2-hbox>
<select-account id="info_owner" readonly="true"/>
<hbox width="100%">
<menulist>
<menupopup type="select-account" id="info_owner" readonly="true"/>
</menulist>
<date-time id="info_created" readonly="true" align="right"/>
</et2-hbox>
</hbox>
<description/>
<description value="Last modified"/>
<et2-hbox>
<select-account id="info_modifier" readonly="true"/>
<hbox width="100%">
<menulist>
<menupopup type="select-account" id="info_modifier" readonly="true"/>
</menulist>
<date-time id="info_datemodified" readonly="true" align="right"/>
</et2-hbox>
</hbox>
</row>
<row class="dialogFooterToolbar">
<hbox span="6">
<button statustext="Saves this entry" label="Save" id="button[save]" image="save" background_image="1"/>
<et2-button statustext="Apply the changes" label="Apply" id="button[apply]" image="apply" background_image="1"></et2-button>
<button statustext="Apply the changes" label="Apply" id="button[apply]" image="apply" background_image="1"/>
<button statustext="leave without saveing the entry" label="Cancel" id="button[cancel]" onclick="window.close();" image="cancel" background_image="1"/>
<menulist>
<menupopup statustext="Execute a further action for this entry" id="action" onchange="app.infolog.edit_actions()" options="Actions..."/>

47
lit/index.html Normal file
View File

@ -0,0 +1,47 @@
<!--
@license
Copyright IBM Corp. 2019
This source code is licensed under the Apache-2.0 license found in the
LICENSE file in the root directory of this source tree.
-->
<html>
<head>
<title>carbon-web-components example</title>
<meta charset="UTF-8" />
<style type="text/css">
body {
font-family: 'IBM Plex Sans', 'Helvetica Neue', Arial, sans-serif;
}
#app {
width: 300px;
}
bx-dropdown,
bx-dropdown-item {
visibility: hidden;
}
bx-dropdown:defined,
bx-dropdown-item:defined {
visibility: inherit;
}
</style>
</head>
<body>
<h1>Hello World! 👋</h1>
<div id="app">
<bx-dropdown trigger-content="Select an item">
<bx-dropdown-item value="all">Option 1</bx-dropdown-item>
<bx-dropdown-item value="cloudFoundry">Option 2</bx-dropdown-item>
<bx-dropdown-item value="staging">Option 3</bx-dropdown-item>
<bx-dropdown-item value="dea">Option 4</bx-dropdown-item>
<bx-dropdown-item value="router">Option 5</bx-dropdown-item>
</bx-dropdown>
</div>
<script type="module" src="js/app.min.js"></script>
</body>
</html>

11
lit/js/app.js Normal file
View File

@ -0,0 +1,11 @@
/**
* @license
*
* Copyright IBM Corp. 2020
*
* This source code is licensed under the Apache-2.0 license found in the
* LICENSE file in the root directory of this source tree.
*/
import 'carbon-web-components/es/components/dropdown/dropdown.js';
import 'carbon-web-components/es/components/dropdown/dropdown-item.js';

6178
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -6,25 +6,16 @@
"repository": {},
"scripts": {
"build": "rollup -c",
"build:watch": "rollup -cw",
"jstest": "web-test-runner",
"jstest:watch": "web-test-runner --watch"
"build:watch": "rollup -cw"
},
"devDependencies": {
"@babel/core": "^7.14.6",
"@babel/preset-typescript": "^7.14.5",
"@open-wc/testing": "^2.5.33",
"@rollup/plugin-babel": "^5.3.0",
"@rollup/plugin-node-resolve": "^13.0.0",
"@rollup/plugin-typescript": "^8.2.1",
"@types/chai": "^4.2.21",
"@types/jquery": "^3.5.5",
"@types/jqueryui": "^1.12.14",
"@types/mocha": "^8.2.3",
"@web/dev-server-esbuild": "^0.2.14",
"@web/dev-server-rollup": "^0.3.9",
"@web/test-runner": "^0.13.16",
"@web/test-runner-playwright": "^0.8.8",
"grunt": "^1.3.0",
"grunt-contrib-cssmin": "^2.2.1",
"grunt-newer": "^1.3.0",
@ -32,7 +23,6 @@
"rimraf": "^3.0.2",
"rollup": "^2.52.2",
"rollup-plugin-terser": "^7.0.2",
"sinon": "^11.1.2",
"terser": "^4.8.0",
"typescript": "^3.9.7"
},
@ -56,12 +46,6 @@
},
"dependencies": {
"@andxor/jquery-ui-touch-punch-fix": "^1.0.2",
"@lion/button": "^0.14.2",
"@lion/core": "^0.18.2",
"@lion/input": "^0.15.4",
"@lion/input-date": "^0.12.6",
"@lion/input-datepicker": "^0.23.6",
"@lion/textarea": "^0.13.4",
"jquery-ui-dist": "^1.12.1",
"jquery-ui-themes": "^1.12.0",
"jquery-ui-timepicker-addon": "^1.6.3",

View File

@ -21,19 +21,7 @@ import resolve from '@rollup/plugin-node-resolve';
rimraf.sync('./chunks/');
// Turn on minification
const do_minify = false;
function isBareSpecifier (id) {
if (id.startsWith("./") || id.startsWith("../") || id.startsWith("/"))
return false;
try {
new URL(id);
return false;
}
catch {
return true;
}
}
const do_minify = true;
const config = {
treeshake: false,
@ -70,18 +58,13 @@ const config = {
},
plugins: [{
resolveId (id, parentId) {
// Delegate bare specifiers to node_modules resolver
if (isBareSpecifier(id))
{
return;
}
if (!parentId || parentId.indexOf(path.sep + 'node_modules' + path.sep) !== -1)
{
return;
}
if (id.endsWith(".js"))
if(id.endsWith(".js"))
{
const tsPath = path.resolve(path.dirname(parentId), id.slice(0,-3) + '.ts');
const tsPath =path.resolve(path.dirname(parentId), id.slice(0,-3) + '.ts');
try {
readFileSync(tsPath);
console.warn(id + " is a TS file loaded with wrong extension. Remove the extension on the import in " + parentId);
@ -103,9 +86,7 @@ const config = {
}
},
// resolve (external) node modules from node_modules directory
resolve({
browser: true
}),
resolve(),
{
transform (code, id) {
if (id.endsWith('.ts'))

View File

@ -1,73 +0,0 @@
/**
* This is the configuration file for automatic TypeScript testing
*
* It uses "web-test-runner" to run the tests, which are written using
* Mocha (https://mochajs.org/) & Chai Assertion Library (https://www.chaijs.com/api/assert/)
* Playwright (https://playwright.dev/docs/intro) runs the tests in actual browsers.
*/
import fs from 'fs';
import {playwrightLauncher} from '@web/test-runner-playwright';
import {esbuildPlugin} from '@web/dev-server-esbuild';
// Get tests for web components (in their own directory)
const webComponents = fs.readdirSync('api/js/etemplate')
.filter(
dir => fs.statSync(`api/js/etemplate/${dir}`).isDirectory() && fs.existsSync(`api/js/etemplate/${dir}/test`),
)
.map(dir => `api/js/etemplate/${dir}/test`);
// Add any test files in app/js/test/
const appJS = fs.readdirSync('.')
.filter(
dir => fs.existsSync(`${dir}/js`) && fs.existsSync(`${dir}/js/test`) && fs.statSync(`${dir}/js/test`).isDirectory(),
)
export default {
nodeResolve: true,
coverageConfig: {
report: true,
reportDir: 'coverage',
threshold: {
statements: 90,
branches: 65,
functions: 80,
lines: 90,
},
},
testFramework: {
config: {
timeout: '3000',
},
},
browsers: [
playwrightLauncher({product: 'firefox', concurrency: 1}),
playwrightLauncher({product: 'chromium'}),
// Dependant on specific versions of shared libraries (libicuuc.so.66, latest is .67)
//playwrightLauncher({ product: 'webkit' }),
],
groups:
webComponents.map(pkg =>
{
return {
name: `${pkg}`,
files: `${pkg}/*.test.ts`
};
}).concat(
appJS.map(app =>
{
return {
name: app,
files: `${app}/js/**/*.test.ts`
}
}
)
)
,
plugins: [
// Handles typescript
esbuildPlugin({ts: true})
],
};