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,10 +12,73 @@
et2_core_common; et2_core_common;
*/ */
import {egw, IegwAppLocal} from "../jsapi/egw_global"; import {egw} from "../jsapi/egw_global";
import {et2_checkType, et2_cloneObject, et2_no_init, et2_validateAttrib} from "./et2_core_common"; import {et2_checkType, et2_no_init, et2_validateAttrib} from "./et2_core_common";
import {et2_IDOMNode, et2_IInput, et2_IInputNode, et2_implements_registry} from "./et2_core_interfaces"; 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 export class ClassWithInterfaces
{ {
/** /**
@ -70,19 +133,13 @@ export class ClassWithAttributes extends ClassWithInterfaces
getAttribute(_name) getAttribute(_name)
{ {
if (typeof this.attributes[_name] != "undefined" && if (typeof this.attributes[_name] != "undefined" &&
!this.attributes[_name].ignore) !this.attributes[_name].ignore) {
{ if (typeof this["get_" + _name] == "function") {
if (typeof this["get_" + _name] == "function")
{
return this["get_" + _name](); return this["get_" + _name]();
} } else {
else
{
return this[_name]; return this[_name];
} }
} } else {
else
{
egw.debug("error", this, "Attribute '" + _name + "' does not exist!"); egw.debug("error", this, "Attribute '" + _name + "' does not exist!");
} }
} }
@ -99,30 +156,22 @@ export class ClassWithAttributes extends ClassWithInterfaces
*/ */
setAttribute(_name, _value, _override) setAttribute(_name, _value, _override)
{ {
if (typeof this.attributes[_name] != "undefined") if (typeof this.attributes[_name] != "undefined") {
{ if (!this.attributes[_name].ignore) {
if (!this.attributes[_name].ignore) if (typeof _override == "undefined") {
{
if (typeof _override == "undefined")
{
_override = true; _override = true;
} }
var val = et2_checkType(_value, this.attributes[_name].type, var val = et2_checkType(_value, this.attributes[_name].type,
_name, this); _name, this);
if (typeof this["set_" + _name] == "function") if (typeof this["set_" + _name] == "function") {
{
this["set_" + _name](val); this["set_" + _name](val);
} } else if (_override || typeof this[_name] == "undefined") {
else if (_override || typeof this[_name] == "undefined")
{
this[_name] = val; this[_name] = val;
} }
} }
} } else {
else
{
egw.debug("warn", this, "Attribute '" + _name + "' does not exist!"); egw.debug("warn", this, "Attribute '" + _name + "' does not exist!");
} }
} }
@ -137,18 +186,13 @@ export class ClassWithAttributes extends ClassWithInterfaces
static generateAttributeSet(widget, _attrs) static generateAttributeSet(widget, _attrs)
{ {
// Sanity check and validation // Sanity check and validation
for (var key in _attrs) for (var key in _attrs) {
{ if (typeof widget[key] != "undefined") {
if (typeof widget[key] != "undefined") if (!widget[key].ignore) {
{
if (!widget[key].ignore)
{
_attrs[key] = et2_checkType(_attrs[key], widget[key].type, _attrs[key] = et2_checkType(_attrs[key], widget[key].type,
key, this); key, this);
} }
} } else {
else
{
// Key does not exist - delete it and issue a warning // Key does not exist - delete it and issue a warning
delete (_attrs[key]); delete (_attrs[key]);
egw.debug("warn", this, "Attribute '" + key + egw.debug("warn", this, "Attribute '" + key +
@ -157,13 +201,10 @@ export class ClassWithAttributes extends ClassWithInterfaces
} }
// Include default values or already set values for this attribute // Include default values or already set values for this attribute
for (var key in widget) for (var key in widget) {
{ if (typeof _attrs[key] == "undefined") {
if (typeof _attrs[key] == "undefined")
{
var _default = widget[key]["default"]; var _default = widget[key]["default"];
if (_default == et2_no_init) if (_default == et2_no_init) {
{
_default = undefined; _default = undefined;
} }
@ -184,10 +225,8 @@ export class ClassWithAttributes extends ClassWithInterfaces
*/ */
initAttributes(_attrs) initAttributes(_attrs)
{ {
for (var key in _attrs) for (var key in _attrs) {
{ if (typeof this.attributes[key] != "undefined" && !this.attributes[key].ignore && !(_attrs[key] == undefined)) {
if (typeof this.attributes[key] != "undefined" && !this.attributes[key].ignore && !(_attrs[key] == undefined))
{
this.setAttribute(key, _attrs[key], false); this.setAttribute(key, _attrs[key], false);
} }
} }
@ -198,16 +237,13 @@ export class ClassWithAttributes extends ClassWithInterfaces
let class_tree = []; let class_tree = [];
let attributes = {}; let attributes = {};
let n = 0; let n = 0;
do do {
{
n++; n++;
class_tree.push(class_prototype); class_tree.push(class_prototype);
class_prototype = Object.getPrototypeOf(class_prototype); class_prototype = Object.getPrototypeOf(class_prototype);
} } while (class_prototype !== ClassWithAttributes && n < 50);
while (class_prototype !== ClassWithAttributes && n < 50);
for (let i = class_tree.length - 1; i >= 0; i--) for (let i = class_tree.length - 1; i >= 0; i--) {
{
attributes = ClassWithAttributes.extendAttributes(attributes, class_tree[i]._attributes); attributes = ClassWithAttributes.extendAttributes(attributes, class_tree[i]._attributes);
} }
return attributes; return attributes;
@ -228,19 +264,15 @@ export class ClassWithAttributes extends ClassWithInterfaces
var result = {}; var result = {};
// Copy the new object // Copy the new object
if (typeof _new != "undefined") if (typeof _new != "undefined") {
{ for (var key in _new) {
for (var key in _new)
{
result[key] = _new[key]; result[key] = _new[key];
} }
} }
// Merge the old object // Merge the old object
for (var key in _old) for (var key in _old) {
{ if (typeof result[key] == "undefined") {
if (typeof result[key] == "undefined")
{
result[key] = _old[key]; result[key] = _old[key];
} }
} }
@ -251,27 +283,23 @@ export class ClassWithAttributes extends ClassWithInterfaces
var attributes = {}; var attributes = {};
// Copy the old attributes // Copy the old attributes
for (var key in _attributes) for (var key in _attributes) {
{
attributes[key] = _copyMerge({}, _attributes[key]); attributes[key] = _copyMerge({}, _attributes[key]);
} }
// Add the old attributes to the new ones. If the attributes already // Add the old attributes to the new ones. If the attributes already
// exist, they are merged. // exist, they are merged.
for (var key in _parent) for (var key in _parent) {
{
var _old = _parent[key]; var _old = _parent[key];
attributes[key] = _copyMerge(attributes[key], _old); attributes[key] = _copyMerge(attributes[key], _old);
} }
// Validate the attributes // Validate the attributes
for (var key in attributes) for (var key in attributes) {
{
et2_validateAttrib(key, attributes[key]); et2_validateAttrib(key, attributes[key]);
} }
return attributes; return attributes;
} }
} }

View File

@ -18,16 +18,14 @@ import {et2_no_init} from "./et2_core_common";
import { ClassWithAttributes } from "./et2_core_inheritance"; import { ClassWithAttributes } from "./et2_core_inheritance";
import { et2_widget, WidgetConfig } from "./et2_core_widget"; import { et2_widget, WidgetConfig } from "./et2_core_widget";
import { et2_valueWidget } from './et2_core_valueWidget' import { et2_valueWidget } from './et2_core_valueWidget'
import {et2_IInput, et2_IInputNode, et2_ISubmitListener} from "./et2_core_interfaces"; import {et2_IInput, et2_ISubmitListener} from "./et2_core_interfaces";
import {et2_compileLegacyJS} from "./et2_core_legacyJSFunctions"; import {et2_compileLegacyJS} from "./et2_core_legacyJSFunctions";
// fixing circular dependencies by only importing the type (not in compiled .js) // fixing circular dependencies by only importing the type (not in compiled .js)
import type {et2_tabbox} from "./et2_widget_tabs"; import type {et2_tabbox} from "./et2_widget_tabs";
export interface et2_input export interface et2_input {
{
getInputNode() : HTMLInputElement|HTMLElement; getInputNode() : HTMLInputElement|HTMLElement;
} }
/** /**
* et2_inputWidget derrives from et2_simpleWidget and implements the IInput * et2_inputWidget derrives from et2_simpleWidget and implements the IInput
* interface. When derriving from this class, call setDOMNode with an input * interface. When derriving from this class, call setDOMNode with an input
@ -143,12 +141,10 @@ export class et2_inputWidget extends et2_valueWidget implements et2_IInput, et2_
{ {
jQuery(node) jQuery(node)
.off('.et2_inputWidget') .off('.et2_inputWidget')
.bind("change.et2_inputWidget", this, function (e) .bind("change.et2_inputWidget", this, function(e) {
{
e.data.change.call(e.data, this); 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); e.data.focus.call(e.data, this);
}); });
} }
@ -184,9 +180,7 @@ export class et2_inputWidget extends et2_valueWidget implements et2_IInput, et2_
if(args.indexOf(this) == -1) args.push(this); if(args.indexOf(this) == -1) args.push(this);
return this.onchange.apply(this, args); return this.onchange.apply(this, args);
} } else {
else
{
return (et2_compileLegacyJS(this.options.onchange, this, _node))(); return (et2_compileLegacyJS(this.options.onchange, this, _node))();
} }
} }
@ -255,12 +249,9 @@ export class et2_inputWidget extends et2_valueWidget implements et2_IInput, et2_
var node = this.getInputNode(); var node = this.getInputNode();
if (node) if (node)
{ {
if (_value && !this.options.readonly) if(_value && !this.options.readonly) {
{
jQuery(node).attr("required", "required"); jQuery(node).attr("required", "required");
} } else {
else
{
node.removeAttribute("required"); node.removeAttribute("required");
} }
} }
@ -391,3 +382,4 @@ export class et2_inputWidget extends et2_valueWidget implements et2_IInput, et2_
return valid; 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_cloneObject, et2_csvSplit} from "./et2_core_common";
import {et2_compileLegacyJS} from "./et2_core_legacyJSFunctions"; import {et2_compileLegacyJS} from "./et2_core_legacyJSFunctions";
import {et2_IDOMNode, et2_IInputNode} from "./et2_core_interfaces"; import {et2_IDOMNode, et2_IInputNode} from "./et2_core_interfaces";
import {loadWebComponent} from "./Et2Widget/Et2Widget";
// fixing circular dependencies by only importing type // fixing circular dependencies by only importing type
import type {et2_container} from "./et2_core_baseWidget"; 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 * The registry contains all XML tag names and the corresponding widget
@ -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) // 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; if (typeof window.et2_createWidget === 'undefined') window['et2_createWidget'] = et2_createWidget;
export interface WidgetConfig export interface WidgetConfig {
{
type?: string; type?: string;
readonly?: boolean; readonly?: boolean;
width?: number; width?: number;
[propName: string]: any; [propName: string]: any;
} }
@ -216,18 +214,15 @@ export class et2_widget extends ClassWithAttributes
this.attributes = ClassWithAttributes.extendAttributes(et2_widget._attributes, _child || {}); this.attributes = ClassWithAttributes.extendAttributes(et2_widget._attributes, _child || {});
// Check whether all attributes are available // Check whether all attributes are available
if (typeof _parent == "undefined") if (typeof _parent == "undefined") {
{
_parent = null; _parent = null;
} }
if (typeof _attrs == "undefined") if (typeof _attrs == "undefined") {
{
_attrs = {}; _attrs = {};
} }
if (_attrs.attributes) if (_attrs.attributes) {
{
jQuery.extend(_attrs, _attrs.attributes); jQuery.extend(_attrs, _attrs.attributes);
} }
// Initialize all important parameters // Initialize all important parameters
@ -238,26 +233,22 @@ export class et2_widget extends ClassWithAttributes
this.id = _attrs["id"]; this.id = _attrs["id"];
// Add this widget to the given parent widget // Add this widget to the given parent widget
if (_parent != null) if (_parent != null) {
{
_parent.addChild(this); _parent.addChild(this);
} }
// The supported widget classes array defines a whitelist for all widget // The supported widget classes array defines a whitelist for all widget
// classes or interfaces child widgets have to support. // 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 // Create a namespace for this object
if (this._createNamespace()) if (this._createNamespace()) {
{
this.checkCreateNamespace(_attrs); this.checkCreateNamespace(_attrs);
} }
} }
if (this.id) if (this.id) {
{
//this.id = this.id.replace(/\[/g,'&#x5B;').replace(/]/g,'&#x5D;'); //this.id = this.id.replace(/\[/g,'&#x5B;').replace(/]/g,'&#x5D;');
} }
@ -280,22 +271,18 @@ export class et2_widget extends ClassWithAttributes
destroy() destroy()
{ {
// Call the destructor of all children // 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(); this._children[i].destroy();
} }
// Remove this element from the parent, if it exists // 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); this._parent.removeChild(this);
} }
// Free the array managers if they belong to this widget // Free the array managers if they belong to this widget
for (var key in this._mgrs) for (var key in this._mgrs) {
{ if (this._mgrs[key] && this._mgrs[key].owner == this) {
if (this._mgrs[key] && this._mgrs[key].owner == this)
{
this._mgrs[key].destroy(); this._mgrs[key].destroy();
} }
} }
@ -321,8 +308,7 @@ export class et2_widget extends ClassWithAttributes
clone(_parent) clone(_parent)
{ {
// Default _parent to null // Default _parent to null
if (typeof _parent == "undefined") if (typeof _parent == "undefined") {
{
_parent = null; _parent = null;
} }
@ -337,14 +323,12 @@ export class et2_widget extends ClassWithAttributes
assign(_obj) assign(_obj)
{ {
if (typeof _obj._children == "undefined") if (typeof _obj._children == "undefined") {
{
this.egw().debug("log", "Foo!"); this.egw().debug("log", "Foo!");
} }
// Create a clone of all child elements of the given object // 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); _obj._children[i].clone(this);
} }
@ -377,12 +361,9 @@ export class et2_widget extends ClassWithAttributes
*/ */
getRoot() : et2_container getRoot() : et2_container
{ {
if (this._parent != null) if (this._parent != null) {
{
return this._parent.getRoot(); return this._parent.getRoot();
} } else {
else
{
return <et2_container><unknown>this; return <et2_container><unknown>this;
} }
} }
@ -408,19 +389,15 @@ export class et2_widget extends ClassWithAttributes
insertChild(_node : et2_widget, _idx: number) insertChild(_node : et2_widget, _idx: number)
{ {
// Check whether the node is one of the supported widget classes. // 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 // Remove the node from its original parent
if (_node._parent) if (_node._parent) {
{
_node._parent.removeChild(_node); _node._parent.removeChild(_node);
} }
_node._parent = this; _node._parent = this;
this._children.splice(_idx, 0, _node); this._children.splice(_idx, 0, _node);
} } else {
else
{
this.egw().debug("error", "Widget " + _node._type + " is not supported by this widget class", this); this.egw().debug("error", "Widget " + _node._type + " is not supported by this widget class", this);
// throw("Widget is not supported by this widget class!"); // 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 // Retrieve the child from the child list
var idx = this._children.indexOf(_node); var idx = this._children.indexOf(_node);
if (idx >= 0) if (idx >= 0) {
{
// This element is no longer parent of the child // This element is no longer parent of the child
_node._parent = null; _node._parent = null;
@ -452,27 +428,22 @@ export class et2_widget extends ClassWithAttributes
*/ */
getWidgetById(_id) : et2_widget | null getWidgetById(_id) : et2_widget | null
{ {
if (this.id == _id) if (this.id == _id) {
{
return this; return this;
} }
if (!this._children) return null; 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); var elem = this._children[i].getWidgetById(_id);
if (elem != null) if (elem != null) {
{
return elem; 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 ids = (new et2_arrayMgr()).explodeKey(_id);
var widget : et2_widget = this; var widget : et2_widget = this;
for (var i = 0; i < ids.length && widget !== null; i++) for (var i = 0; i < ids.length && widget !== null; i++) {
{
widget = widget.getWidgetById(ids[i]); widget = widget.getWidgetById(ids[i]);
} }
return widget; return widget;
@ -491,18 +462,15 @@ export class et2_widget extends ClassWithAttributes
*/ */
iterateOver(_callback, _context, _type?) iterateOver(_callback, _context, _type?)
{ {
if (typeof _type == "undefined") if (typeof _type == "undefined") {
{
_type = et2_widget; _type = et2_widget;
} }
if (this.isInTree() && this.instanceOf(_type)) if (this.isInTree() && this.instanceOf(_type)) {
{
_callback.call(_context, this); _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); this._children[i].iterateOver(_callback, _context, _type);
} }
} }
@ -520,13 +488,11 @@ export class et2_widget extends ClassWithAttributes
*/ */
isInTree(_sender?, _vis? : boolean) isInTree(_sender?, _vis? : boolean)
{ {
if (typeof _vis == "undefined") if (typeof _vis == "undefined") {
{
_vis = true; _vis = true;
} }
if (this._parent) if (this._parent) {
{
return _vis && this._parent.isInTree(this); return _vis && this._parent.isInTree(this);
} }
@ -535,10 +501,8 @@ export class et2_widget extends ClassWithAttributes
isOfSupportedWidgetClass(_obj) isOfSupportedWidgetClass(_obj)
{ {
for (var i = 0; i < this.supportedWidgetClasses.length; i++) for (var i = 0; i < this.supportedWidgetClasses.length; i++) {
{ if (_obj instanceof this.supportedWidgetClasses[i]) {
if (_obj instanceof this.supportedWidgetClasses[i])
{
return true; return true;
} }
} }
@ -557,55 +521,47 @@ export class et2_widget extends ClassWithAttributes
parseXMLAttrs(_attrsObj, _target, _proto) parseXMLAttrs(_attrsObj, _target, _proto)
{ {
// Check whether the attributes object is really existing, if not abort // Check whether the attributes object is really existing, if not abort
if (typeof _attrsObj == "undefined") if (typeof _attrsObj == "undefined") {
{
return; return;
} }
// Iterate over the given attributes and parse them // Iterate over the given attributes and parse them
var mgr = this.getArrayMgr("content"); 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 attrName = _attrsObj[i].name;
var attrValue = _attrsObj[i].value; var attrValue = _attrsObj[i].value;
// Special handling for the legacy options // 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 legacy = _proto.constructor.legacyOptions || [];
let attrs = et2_attribute_registry[Object.getPrototypeOf(_proto).constructor.name] || {}; let attrs = et2_attribute_registry[Object.getPrototypeOf(_proto).constructor.name] || {};
// Check for modifications on legacy options here. Normal modifications // Check for modifications on legacy options here. Normal modifications
// are handled in widget constructor, but it's too late for legacy options then // are handled in widget constructor, but it's too late for legacy options then
if (_target.id && this.getArrayMgr("modifications").getEntry(_target.id)) if (_target.id && this.getArrayMgr("modifications").getEntry(_target.id)) {
{
var mod : any = 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; if (typeof mod.options != "undefined") attrValue = _attrsObj[i].value = mod.options;
} }
// expand legacyOptions with content // expand legacyOptions with content
if (attrValue.charAt(0) == '@' || attrValue.indexOf('$') != -1) if (attrValue.charAt(0) == '@' || attrValue.indexOf('$') != -1) {
{
attrValue = mgr.expandName(attrValue); attrValue = mgr.expandName(attrValue);
} }
// Parse the legacy options (as a string, other types not allowed) // Parse the legacy options (as a string, other types not allowed)
var splitted = et2_csvSplit(attrValue + ""); 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 // Blank = not set, unless there's more legacy options provided after
if (splitted[j].trim().length === 0 && legacy.length >= splitted.length) continue; 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 // 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]; attrValue = splitted[j];
/** /**
If more legacy options than expected, stuff them all in the last legacy option If more legacy options than expected, stuff them all in the last legacy option
Some legacy options take a comma separated list. 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); attrValue = splitted.slice(j);
} }
@ -613,37 +569,26 @@ export class et2_widget extends ClassWithAttributes
// If the attribute is marked as boolean, parse the // If the attribute is marked as boolean, parse the
// expression as bool expression. // expression as bool expression.
if (attr.type == "boolean") if (attr.type == "boolean") {
{
attrValue = mgr.parseBoolExpression(attrValue); attrValue = mgr.parseBoolExpression(attrValue);
} } else if (typeof attrValue != "object") {
else if (typeof attrValue != "object")
{
attrValue = mgr.expandName(attrValue); attrValue = mgr.expandName(attrValue);
} }
_target[legacy[j]] = 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 // do NOT overwrite already evaluated readonly attribute
} } else {
else
{
let attrs = et2_attribute_registry[_proto.constructor.name] || {}; 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]; var attr = attrs[attrName];
// If the attribute is marked as boolean, parse the // If the attribute is marked as boolean, parse the
// expression as bool expression. // expression as bool expression.
if (attr.type == "boolean") if (attr.type == "boolean") {
{
attrValue = mgr.parseBoolExpression(attrValue); attrValue = mgr.parseBoolExpression(attrValue);
} } else {
else
{
attrValue = mgr.expandName(attrValue); attrValue = mgr.expandName(attrValue);
} }
} }
@ -664,26 +609,20 @@ export class et2_widget extends ClassWithAttributes
{ {
// Apply the content of the modifications array // Apply the content of the modifications array
if (this.id) if (this.id) {
{ if (typeof this.id != "string") {
if (typeof this.id != "string")
{
console.log(this.id); console.log(this.id);
} }
if (this.getArrayMgr("modifications")) if (this.getArrayMgr("modifications")) {
{
var data = this.getArrayMgr("modifications").getEntry(this.id); var data = this.getArrayMgr("modifications").getEntry(this.id);
// Check for already inside namespace // 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; data = this.getArrayMgr("modifications").data;
} }
if (typeof data === 'object') if (typeof data === 'object') {
{ for (var key in data) {
for (var key in data)
{
_attrs[key] = data[key]; _attrs[key] = data[key];
} }
} }
@ -691,13 +630,10 @@ export class et2_widget extends ClassWithAttributes
} }
// Translate the attributes // Translate the attributes
for (var key in _attrs) for (var key in _attrs) {
{ if (_attrs[key] && typeof this.attributes[key] != "undefined") {
if (_attrs[key] && typeof this.attributes[key] != "undefined")
{
if (this.attributes[key].translate === true || 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]; let value = _attrs[key];
// allow statustext to contain multiple translated sub-strings eg: {Firstname}.{Lastname} // allow statustext to contain multiple translated sub-strings eg: {Firstname}.{Lastname}
if (value.indexOf('{') !== -1) if (value.indexOf('{') !== -1)
@ -746,26 +682,20 @@ export class et2_widget extends ClassWithAttributes
// Check to see if modifications change type // Check to see if modifications change type
var modifications = this.getArrayMgr("modifications"); var modifications = this.getArrayMgr("modifications");
if (modifications && _node.getAttribute("id")) if (modifications && _node.getAttribute("id")) {
{
var entry : any = modifications.getEntry(_node.getAttribute("id")); var entry : any = modifications.getEntry(_node.getAttribute("id"));
if (entry == null) if (entry == null) {
{
// Try again, but skip the fancy stuff // Try again, but skip the fancy stuff
// TODO: Figure out why the getEntry() call doesn't always work // TODO: Figure out why the getEntry() call doesn't always work
var entry = modifications.data[_node.getAttribute("id")]; 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); 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 // Try the root, in case a namespace got missed
entry = modifications.getRoot().getEntry(_node.getAttribute("id")); 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; _nodeName = attributes["type"] = entry.type;
} }
entry = null; entry = null;
@ -773,27 +703,25 @@ export class et2_widget extends ClassWithAttributes
// if _nodeName / type-attribute contains something to expand (eg. type="@${row}[type]"), // 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! // 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); _nodeName = attributes["type"] = this.getArrayMgr('content').expandName(_nodeName);
} }
// Get the constructor - if the widget is readonly, use the special "_ro" // Get the constructor - if the widget is readonly, use the special "_ro"
// constructor if it is available // constructor if it is available
var constructor = et2_registry[typeof et2_registry[_nodeName] == "undefined" ? 'placeholder' : _nodeName]; 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"]; constructor = et2_registry[_nodeName + "_ro"];
} }
if (undefined == window.customElements.get(_nodeName))
{
// Parse the attributes from the given XML attributes object // Parse the attributes from the given XML attributes object
this.parseXMLAttrs(_node.attributes, attributes, constructor.prototype); this.parseXMLAttrs(_node.attributes, attributes, constructor.prototype);
// Do an sanity check for the attributes // Do an sanity check for the attributes
ClassWithAttributes.generateAttributeSet(et2_attribute_registry[constructor.name], attributes); ClassWithAttributes.generateAttributeSet(et2_attribute_registry[constructor.name], attributes);
if(undefined == window.customElements.get(_nodeName))
{
// Creates the new widget, passes this widget as an instance and // Creates the new widget, passes this widget as an instance and
// passes the widgetType. Then it goes on loading the XML for it. // passes the widgetType. Then it goes on loading the XML for it.
var widget = new constructor(this, attributes); var widget = new constructor(this, attributes);
@ -803,7 +731,7 @@ export class et2_widget extends ClassWithAttributes
} }
else else
{ {
widget = loadWebComponent(_nodeName, _node, this); widget = this.loadWebComponent(_nodeName, _node, attributes);
if(this.addChild) if(this.addChild)
{ {
@ -814,6 +742,22 @@ export class et2_widget extends ClassWithAttributes
return widget; 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 * Loads the widget tree from an XML node
* *
@ -822,20 +766,16 @@ export class et2_widget extends ClassWithAttributes
loadFromXML(_node) loadFromXML(_node)
{ {
// Load the child nodes. // 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 node = _node.childNodes[i];
var widgetType = node.nodeName.toLowerCase(); var widgetType = node.nodeName.toLowerCase();
if (widgetType == "#comment") if (widgetType == "#comment") {
{
continue; continue;
} }
if (widgetType == "#text") if (widgetType == "#text") {
{ if (node.data.replace(/^\s+|\s+$/g, '')) {
if (node.data.replace(/^\s+|\s+$/g, ''))
{
this.loadContent(node.data); this.loadContent(node.data);
} }
continue; continue;
@ -883,39 +823,29 @@ export class et2_widget extends ClassWithAttributes
// Make sure promises is defined to avoid errors. // Make sure promises is defined to avoid errors.
// We'll warn (below) if programmer should have passed it. // We'll warn (below) if programmer should have passed it.
if (typeof promises == "undefined") if (typeof promises == "undefined") {
{
promises = []; promises = [];
var warn_if_deferred = true; var warn_if_deferred = true;
} }
var loadChildren = function () var loadChildren = function () {
{
// Descend recursively into the tree // Descend recursively into the tree
for (var i = 0; i < this._children.length; i++) for (var i = 0; i < this._children.length; i++) {
{ try {
try
{
this._children[i].loadingFinished(promises); 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); 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(); var result = this.doLoadingFinished();
if (typeof result == "boolean" && result) if (typeof result == "boolean" && result) {
{
// Simple widget finishes nicely // Simple widget finishes nicely
loadChildren.apply(this, arguments); loadChildren.apply(this, arguments);
} } else if (typeof result == "object" && result.done) {
else if (typeof result == "object" && result.done)
{
// Warn if list was not provided // 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 // 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); egw.debug("warn", "Loading was deferred for widget %o, but creator is not checking. Pass a list to loadingFinished().", this);
} }
@ -938,14 +868,11 @@ export class et2_widget extends ClassWithAttributes
*/ */
initAttributes(_attrs) initAttributes(_attrs)
{ {
for (var key in _attrs) for (var key in _attrs) {
{ if (typeof this.attributes[key] != "undefined" && !this.attributes[key].ignore && !(_attrs[key] == undefined)) {
if (typeof this.attributes[key] != "undefined" && !this.attributes[key].ignore && !(_attrs[key] == undefined))
{
var val = _attrs[key]; var val = _attrs[key];
// compile string values of attribute type "js" to functions // 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, val = et2_compileLegacyJS(val, this,
this.implements(et2_IInputNode) ? (<et2_inputWidget><unknown>this).getInputNode() : 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));
@ -979,20 +906,16 @@ export class et2_widget extends ClassWithAttributes
egw() : IegwAppLocal egw() : IegwAppLocal
{ {
// The _egw property is not set // The _egw property is not set
if (typeof this._egw === 'undefined') if (typeof this._egw === 'undefined') {
{ if (this._parent != null) {
if (this._parent != null)
{
return this._parent.egw(); return this._parent.egw();
} }
// Get the window this object belongs to // Get the window this object belongs to
var wnd = null; var wnd = null;
if (this.implements(et2_IDOMNode)) if (this.implements(et2_IDOMNode)) {
{
var node = (<et2_IDOMNode><unknown>this).getDOMNode(); var node = (<et2_IDOMNode><unknown>this).getDOMNode();
if (node && node.ownerDocument) if (node && node.ownerDocument) {
{
wnd = node.ownerDocument.parentNode || node.ownerDocument.defaultView; wnd = node.ownerDocument.parentNode || node.ownerDocument.defaultView;
} }
} }
@ -1035,24 +958,20 @@ export class et2_widget extends ClassWithAttributes
*/ */
getArrayMgrs(_mgrs? : object) getArrayMgrs(_mgrs? : object)
{ {
if (typeof _mgrs == "undefined") if (typeof _mgrs == "undefined") {
{
_mgrs = {}; _mgrs = {};
} }
// Add all managers of this object to the result, if they have not already // Add all managers of this object to the result, if they have not already
// been set in the result // been set in the result
for (var key in this._mgrs) for (var key in this._mgrs) {
{ if (typeof _mgrs[key] == "undefined") {
if (typeof _mgrs[key] == "undefined")
{
_mgrs[key] = this._mgrs[key]; _mgrs[key] = this._mgrs[key];
} }
} }
// Recursively applies this function to the parent widget // Recursively applies this function to the parent widget
if (this._parent) if (this._parent) {
{
this._parent.getArrayMgrs(_mgrs); this._parent.getArrayMgrs(_mgrs);
} }
@ -1077,12 +996,9 @@ export class et2_widget extends ClassWithAttributes
*/ */
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]; return this._mgrs[managed_array_type];
} } else if (this._parent) {
else if (this._parent)
{
return this._parent.getArrayMgr(managed_array_type); return this._parent.getArrayMgr(managed_array_type);
} }
@ -1101,27 +1017,22 @@ export class et2_widget extends ClassWithAttributes
// Get the content manager // Get the content manager
var mgrs = this.getArrayMgrs(); var mgrs = this.getArrayMgrs();
for (var key in mgrs) for (var key in mgrs) {
{
var mgr = mgrs[key]; var mgr = mgrs[key];
// Get the original content manager if we have already created a // Get the original content manager if we have already created a
// perspective for this node // 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; mgr = mgr.parentMgr;
} }
// Check whether the manager has a namespace for the id of this object // Check whether the manager has a namespace for the id of this object
var entry = mgr.getEntry(this.id); 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 // The content manager has an own node for this object, so
// create an own perspective. // create an own perspective.
this._mgrs[key] = mgr.openPerspective(this, this.id); this._mgrs[key] = mgr.openPerspective(this, this.id);
} } else {
else
{
// The current content manager does not have an own namespace for // The current content manager does not have an own namespace for
// this element, so use the content manager of the parent. // this element, so use the content manager of the parent.
delete (this._mgrs[key]); delete (this._mgrs[key]);
@ -1166,12 +1077,9 @@ export class et2_widget extends ClassWithAttributes
*/ */
getInstanceManager() getInstanceManager()
{ {
if (this._inst != null) if (this._inst != null) {
{
return this._inst; return this._inst;
} } else if (this._parent) {
else if (this._parent)
{
return this._parent.getInstanceManager(); 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_baseWidget} from './et2_core_baseWidget'
import {et2_inputWidget} from "./et2_core_inputWidget"; import {et2_inputWidget} from "./et2_core_inputWidget";
import {expose} from "./expose"; 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"; 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 = this.getRoot().getWidgetById(this.options.for))
) && for_widget && for_widget.id) ) && 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; for_id = for_widget.dom_id;
if(for_widget.instanceOf(et2_IInputNode) && for_widget.getInputNode() && for_id !== for_widget.getInputNode().id) if(for_widget.instanceOf(et2_inputWidget) && for_widget.getInputNode() && for_widget.dom_id !== for_widget.getInputNode().id)
{ {
for_id = for_widget.getInputNode().id; for_id = for_widget.getInputNode().id;
} }

View File

@ -119,7 +119,6 @@ export class et2_grid extends et2_DOMWidget implements et2_IDetachedDOM, et2_IAl
private lastRowNode: null; private lastRowNode: null;
private sortablejs : Sortable = null; private sortablejs : Sortable = null;
/** /**
* Constructor * Constructor
* *
@ -140,8 +139,7 @@ export class et2_grid extends et2_DOMWidget implements et2_IDetachedDOM, et2_IAl
.appendTo(this.table); .appendTo(this.table);
} }
_initCells(_colData : ColumnEntry[], _rowData : RowEntry[]) _initCells(_colData : ColumnEntry[], _rowData : RowEntry[]) {
{
// Copy the width and height // Copy the width and height
const w = _colData.length; const w = _colData.length;
const h = _rowData.length; const h = _rowData.length;
@ -236,8 +234,7 @@ export class et2_grid extends et2_DOMWidget implements et2_IDetachedDOM, et2_IAl
} }
// Parse the columns tag // Parse the columns tag
et2_filteredNodeIterator(columns, function(node, nodeName) et2_filteredNodeIterator(columns, function(node, nodeName) {
{
const colDataEntry = this._getColDataEntry(); const colDataEntry = this._getColDataEntry();
// This cannot be done inside a nm, it will expand it later // This cannot be done inside a nm, it will expand it later
colDataEntry["disabled"] = nm ? colDataEntry["disabled"] = nm ?
@ -269,8 +266,7 @@ export class et2_grid extends et2_DOMWidget implements et2_IDetachedDOM, et2_IAl
}, this); }, this);
// Parse the rows tag // Parse the rows tag
et2_filteredNodeIterator(rows, function(node, nodeName) et2_filteredNodeIterator(rows, function(node, nodeName) {
{
const rowDataEntry = this._getRowDataEntry(); const rowDataEntry = this._getRowDataEntry();
rowDataEntry["disabled"] = this.getArrayMgr("content") rowDataEntry["disabled"] = this.getArrayMgr("content")
.parseBoolExpression(et2_readAttrWithDefault(node, "disabled", "")); .parseBoolExpression(et2_readAttrWithDefault(node, "disabled", ""));
@ -332,42 +328,34 @@ export class et2_grid extends et2_DOMWidget implements et2_IDetachedDOM, et2_IAl
// Not in a nextmatch, so we can expand with abandon // Not in a nextmatch, so we can expand with abandon
const currentPerspective = jQuery.extend({}, content.perspectiveData); const currentPerspective = jQuery.extend({}, content.perspectiveData);
const check = function(node, nodeName) const check = function (node, nodeName) {
{ if (nodeName == 'box' || nodeName == 'hbox' || nodeName == 'vbox') {
if(nodeName == 'box' || nodeName == 'hbox' || nodeName == 'vbox')
{
return et2_filteredNodeIterator(node, check, this); return et2_filteredNodeIterator(node, check, this);
} }
content.perspectiveData.row = rowIndex; 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, ""); const value = et2_readAttrWithDefault(node, node.attributes[attr].name, "");
// Don't include first char, those should be handled by normal means // Don't include first char, those should be handled by normal means
// and it would break nextmatch // 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. // Ok, we found something. How many? Check for values.
let ident = content.expandName(value); let ident = content.expandName(value);
// expandName() handles index into content (@), but we have to look up // expandName() handles index into content (@), but we have to look up
// regular values // regular values
if(value[0] != '@') if (value[0] != '@') {
{
// Returns null if there isn't an actual value // Returns null if there isn't an actual value
ident = content.getEntry(ident, false, true); ident = content.getEntry(ident, false, true);
} }
while(ident != null && rowIndex < 1000) while (ident != null && rowIndex < 1000) {
{
rowData[rowIndex] = jQuery.extend({}, rowDataEntry); rowData[rowIndex] = jQuery.extend({}, rowDataEntry);
content.perspectiveData.row = ++rowIndex; content.perspectiveData.row = ++rowIndex;
ident = content.expandName(value); ident = content.expandName(value);
if(value[0] != '@') if (value[0] != '@') {
{
// Returns null if there isn't an actual value // Returns null if there isn't an actual value
ident = content.getEntry(ident, false, true); 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); egw.debug("error", "Problem in autorepeat fallback: too many rows for '%s'. Use a nextmatch, or start debugging.", value);
} }
return; return;
@ -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 h = cells.length;
const w = (h > 0) ? cells[0].length : 0; const w = (h > 0) ? cells[0].length : 0;
const currentPerspective = jQuery.extend({}, this.getArrayMgr("content").perspectiveData); const currentPerspective = jQuery.extend({}, this.getArrayMgr("content").perspectiveData);
@ -402,11 +389,9 @@ export class et2_grid extends et2_DOMWidget implements et2_IDetachedDOM, et2_IAl
// Read the elements inside the columns // Read the elements inside the columns
let x = 0; let x = 0;
et2_filteredNodeIterator(columns, function(node, nodeName) et2_filteredNodeIterator(columns, function(node, nodeName) {
{
function _readColNode(node, nodeName) function _readColNode(node, nodeName) {
{
if (y >= h) if (y >= h)
{ {
this.egw().debug("warn", "Skipped grid cell in column, '" + this.egw().debug("warn", "Skipped grid cell in column, '" +
@ -470,11 +455,9 @@ export class et2_grid extends et2_DOMWidget implements et2_IDetachedDOM, et2_IAl
nm = (widget.getType() == 'nextmatch'); nm = (widget.getType() == 'nextmatch');
widget = widget.getParent(); widget = widget.getParent();
} }
et2_filteredNodeIterator(rows, function(node, nodeName) et2_filteredNodeIterator(rows, function(node, nodeName) {
{
readRowNode = function _readRowNode(node, nodeName) readRowNode = function _readRowNode(node, nodeName) {
{
if (x >= w) if (x >= w)
{ {
if(nodeName != "description") if(nodeName != "description")
@ -612,8 +595,7 @@ export class et2_grid extends et2_DOMWidget implements et2_IDetachedDOM, et2_IAl
}, this); }, this);
// Extra content rows // Extra content rows
for(y; y < h; y++) for(y; y < h; y++) {
{
x = 0; x = 0;
et2_filteredNodeIterator(this.lastRowNode, readRowNode, this); et2_filteredNodeIterator(this.lastRowNode, readRowNode, this);
@ -673,7 +655,6 @@ export class et2_grid extends et2_DOMWidget implements et2_IDetachedDOM, et2_IAl
{ {
return true; return true;
} }
/** /**
* As the does not fit very well into the default widget structure, we're * As the does not fit very well into the default widget structure, we're
* overwriting the loadFromXML function and doing a two-pass reading - * 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 * @param {object} _node xml node to process
*/ */
loadFromXML(_node) loadFromXML(_node ) {
{
// Keep the node for later changing / reloading // Keep the node for later changing / reloading
this.template_node = _node; this.template_node = _node;
@ -786,16 +766,8 @@ export class et2_grid extends et2_DOMWidget implements et2_IDetachedDOM, et2_IAl
if (cell.disabled) if (cell.disabled)
{ {
td.hide(); td.hide();
// Need to do different things with webComponents
if(typeof cell.widget.options !== "undefined")
{
cell.widget.options.disabled = cell.disabled; cell.widget.options.disabled = cell.disabled;
} }
else if(cell.widget.nodeName)
{
cell.widget.disabled = cell.disabled;
}
}
if (cell.width != "auto") if (cell.width != "auto")
{ {
@ -819,13 +791,11 @@ 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); const rs = (y == h - 1) ? h - y : Math.min(h - y, cell.rowSpan);
// Set the col and row span values // Set the col and row span values
if(cs > 1) if (cs > 1) {
{
td.attr("colspan", cs); td.attr("colspan", cs);
} }
if(rs > 1) if (rs > 1) {
{
td.attr("rowspan", rs); td.attr("rowspan", rs);
} }
@ -919,8 +889,7 @@ export class et2_grid extends et2_DOMWidget implements et2_IDetachedDOM, et2_IAl
} }
wrapper.css('overflow', _value); wrapper.css('overflow', _value);
if(wrapper.length && (!_value || _value === 'visible')) if(wrapper.length && (!_value || _value === 'visible')) {
{
this.table.unwrap(); this.table.unwrap();
} }
} }
@ -999,22 +968,17 @@ export class et2_grid extends et2_DOMWidget implements et2_IDetachedDOM, et2_IAl
filter: this.options.sortable_cancel, filter: this.options.sortable_cancel,
ghostClass: this.options.sortable_placeholder, ghostClass: this.options.sortable_placeholder,
dataIdAttr: 'id', dataIdAttr: 'id',
onAdd: function(event) onAdd:function (event) {
{ if (typeof self.options.sortable_recieveCallback == 'function') {
if(typeof self.options.sortable_recieveCallback == 'function')
{
self.options.sortable_recieveCallback.call(self, event, this, self.id); self.options.sortable_recieveCallback.call(self, event, this, self.id);
} }
}, },
onStart: function(event, ui) onStart: function (event, ui) {
{ if (typeof self.options.sortable_startCallback == 'function') {
if(typeof self.options.sortable_startCallback == 'function')
{
self.options.sortable_startCallback.call(self, event, this, self.id); self.options.sortable_startCallback.call(self, event, this, self.id);
} }
}, },
onSort: function(event) onSort: function (event) {
{
self.egw().json(sortable,[ self.egw().json(sortable,[
self.getInstanceManager().etemplate_exec_id, self.getInstanceManager().etemplate_exec_id,
self.sortablejs.toArray(), self.sortablejs.toArray(),
@ -1040,8 +1004,7 @@ export class et2_grid extends et2_DOMWidget implements et2_IDetachedDOM, et2_IAl
let objectManager = egw_getAppObjectManager(true, this.getInstanceManager().app); 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); 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 // Add a new container to the object manager which will hold the widget
// objects // objects
widget_object = objectManager.insertObject(false, new egwActionObject( widget_object = objectManager.insertObject(false, new egwActionObject(
@ -1166,10 +1129,7 @@ export class et2_grid extends et2_DOMWidget implements et2_IDetachedDOM, et2_IAl
if (!row[c].widget) continue; if (!row[c].widget) continue;
let found = row[c].widget === _sender; let found = row[c].widget === _sender;
if(!found) row[c].widget.iterateOver(function(_widget) if (!found) row[c].widget.iterateOver(function(_widget) {if (_widget === _sender) found = true;});
{
if(_widget === _sender) found = true;
});
if (found) if (found)
{ {
// return a fake row object allowing to iterate over it's children // return a fake row object allowing to iterate over it's children
@ -1180,8 +1140,7 @@ export class et2_grid extends et2_DOMWidget implements et2_IDetachedDOM, et2_IAl
} }
row_obj.isInTree = jQuery.proxy(this.isInTree, this); row_obj.isInTree = jQuery.proxy(this.isInTree, this);
// we must not free the children! // we must not free the children!
row_obj.destroy = function() row_obj.destroy = function(){
{
// @ts-ignore // @ts-ignore
delete row_obj._children; delete row_obj._children;
}; };
@ -1194,12 +1153,10 @@ 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... * Needed for the align interface, but we're not doing anything with it...
*/ */
get_align() : string get_align(): string {
{
return ""; return "";
} }
} }
et2_register_widget(et2_grid, ["grid"]); et2_register_widget(et2_grid, ["grid"]);
interface ColumnEntry interface ColumnEntry

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 {et2_tabbox} from "./et2_widget_tabs";
import '../jsapi/egw_json.js'; import '../jsapi/egw_json.js';
import {egwIsMobile} from "../egw_action/egw_action_common.js"; import {egwIsMobile} from "../egw_action/egw_action_common.js";
import './Et2Box/Et2Box'; //import './et2-button';
import './Et2Button/Et2Button';
import './Et2Date/Et2Date';
import './Et2Textarea/Et2Textarea';
import './Et2Textbox/Et2Textbox';
import './Et2Colorpicker/Et2Colorpicker';
/* Include all widget classes here, we only care about them registering, not importing anything*/ /* 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_vfs'; // Vfs must be first (before et2_widget_file) due to import cycle
import './et2_widget_template'; import './et2_widget_template';
@ -169,15 +164,13 @@ export class etemplate2
* *
* @param {jQuery.event} e * @param {jQuery.event} e
*/ */
public resize(e) public resize(e) {
{
const event = e; const event = e;
const self = this; const self = this;
let excess_height: number | boolean = false; let excess_height: number | boolean = false;
// Check if the framework has an specific excess height calculation // 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); excess_height = window.framework.get_wExcessHeight(window);
} }
@ -513,15 +506,12 @@ export class etemplate2
// require necessary translations from server AND the app.js file, if not already loaded // 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 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)); promisses.push(egw(currentapp, window).langRequire(window, _data.langRequire));
} }
return Promise.all(promisses).catch((err) => return Promise.all(promisses).catch((err) => {
{
console.log("et2.load(): error loading lang-files and app.js: "+err.message); console.log("et2.load(): error loading lang-files and app.js: "+err.message);
}).then(() => }).then(() => {
{
this.clear(); this.clear();
// Initialize application js // Initialize application js
@ -573,8 +563,7 @@ export class etemplate2
const _load = function () const _load = function ()
{ {
egw.debug("log", "Loading template..."); egw.debug("log", "Loading template...");
if (egw.debug_level() >= 4 && console.timeStamp) if (egw.debug_level() >= 4 && console.timeStamp) {
{
console.timeStamp("Begin rendering template"); console.timeStamp("Begin rendering template");
} }
@ -680,12 +669,10 @@ export class etemplate2
// Profiling // Profiling
if (egw.debug_level() >= 4) if (egw.debug_level() >= 4)
{ {
if (console.timeEnd) if (console.timeEnd) {
{
console.timeEnd(_name); console.timeEnd(_name);
} }
if (console.profileEnd) if (console.profileEnd) {
{
console.profileEnd(_name); console.profileEnd(_name);
} }
const end_time = (new Date).getTime(); const end_time = (new Date).getTime();
@ -831,8 +818,7 @@ export class etemplate2
indexes = [indexes.shift(), indexes.join('[')]; indexes = [indexes.shift(), indexes.join('[')];
indexes[1] = indexes[1].substring(0, indexes[1].length - 1); indexes[1] = indexes[1].substring(0, indexes[1].length - 1);
const children = indexes[1].split(']['); const children = indexes[1].split('][');
if (children.length) if (children.length) {
{
indexes = jQuery.merge([indexes[0]], children); indexes = jQuery.merge([indexes[0]], children);
} }
} }
@ -1000,8 +986,7 @@ export class etemplate2
_root.iterateOver(function (_widget) _root.iterateOver(function (_widget)
{ {
// The widget must have an id to be included in the values array // 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; return;
} }
@ -1016,8 +1001,7 @@ export class etemplate2
indexes = [indexes.shift(), indexes.join('[')]; indexes = [indexes.shift(), indexes.join('[')];
indexes[1] = indexes[1].substring(0, indexes[1].length - 1); indexes[1] = indexes[1].substring(0, indexes[1].length - 1);
const children = indexes[1].split(']['); const children = indexes[1].split('][');
if (children.length) if (children.length) {
{
indexes = jQuery.merge([indexes[0]], children); indexes = jQuery.merge([indexes[0]], children);
} }
path = path.concat(indexes); path = path.concat(indexes);
@ -1419,48 +1403,38 @@ export class etemplate2
* @returns {Boolean} Handled by this plugin * @returns {Boolean} Handled by this plugin
* @throws Invalid parameters if the required res.data parameters are missing * @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 //type, req; // unused, but required by plugin signature
//Check whether all needed parameters have been passed and call the alertHandler function //Check whether all needed parameters have been passed and call the alertHandler function
if ((typeof res.data.id != 'undefined') && if ((typeof res.data.id != 'undefined') &&
(typeof res.data.key != 'undefined') && (typeof res.data.key != 'undefined') &&
(typeof res.data.value != 'undefined') (typeof res.data.value != 'undefined')
) ) {
{
if (typeof res.data.etemplate_exec_id == '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 // Not for this etemplate, but not an error
return false; return false;
} }
if (res.data.key == 'etemplate_exec_id') if (res.data.key == 'etemplate_exec_id') {
{
this._etemplate_exec_id = res.data.value; this._etemplate_exec_id = res.data.value;
return true; return true;
} }
if (this._widgetContainer == null) if (this._widgetContainer == null) {
{
// Right etemplate, but it's already been cleared. // Right etemplate, but it's already been cleared.
egw.debug('warn', "Tried to call assign on an un-loaded etemplate", res.data); egw.debug('warn', "Tried to call assign on an un-loaded etemplate", res.data);
return false; return false;
} }
const widget = this._widgetContainer.getWidgetById(res.data.id); const widget = this._widgetContainer.getWidgetById(res.data.id);
if (widget) if (widget) {
{ if (typeof widget['set_' + res.data.key] != 'function') {
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); 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; return false;
} }
try try {
{
widget['set_' + res.data.key].call(widget, res.data.value); widget['set_' + res.data.key].call(widget, res.data.value);
return true; 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); 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 * It is important to remove all tooltips from all elements which are
* no longer needed, in order to prevent memory leaks. * 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. * @param _html is the html code which should be shown as tooltip.
*/ */
tooltipBind: function(_elem, _html, _isHtml) { tooltipBind: function(_elem, _html, _isHtml) {
_elem = jQuery(_elem);
if (_html != '') if (_html != '')
{ {
_elem.bind('mouseenter.tooltip', function(e) { _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. * removed. _elem has to be a jQuery node.
*/ */
tooltipUnbind: function(_elem) { tooltipUnbind: function(_elem) {
_elem = jQuery(_elem);
if (current_elem == _elem) if (current_elem == _elem)
{ {
hide(); 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

@ -87,20 +87,14 @@ class Date extends Transformer
* @param array $expand * @param array $expand
* @param array $data Row data * @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') if($this->type == 'date-duration') return;
{
return;
}
$form_name = self::form_name($cname, $this->id, $expand); $form_name = self::form_name($cname, $this->id, $expand);
$value =& $this->get_array($data, $form_name, true); $value =& $this->get_array($data, $form_name, true);
if(true) if (true) $value = $this->format_date($value);
{
$value = $this->format_date($value);
}
} }
/** /**
@ -110,10 +104,7 @@ class Date extends Transformer
*/ */
public function format_date($value) public function format_date($value)
{ {
if(!$value) if (!$value) return $value; // otherwise we will get current date or 1970-01-01 instead of an empty 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 // for DateTime objects (regular PHP and Api\DateTime ones), set user timezone
if ($value instanceof \DateTime) if ($value instanceof \DateTime)
@ -167,10 +158,7 @@ class Date extends Transformer
{ {
try try
{ {
if(substr($value, -1) === 'Z') if (substr($value, -1) === 'Z') $value = substr($value, 0, -1);
{
$value = substr($value, 0, -1);
}
$date = new Api\DateTime($value); $date = new Api\DateTime($value);
} }
catch(\Exception $e) catch(\Exception $e)
@ -205,11 +193,7 @@ class Date extends Transformer
elseif (preg_match('/[+-][[:digit:]]+[ymwd]/',$this->attrs['min'])) elseif (preg_match('/[+-][[:digit:]]+[ymwd]/',$this->attrs['min']))
{ {
// Relative date with periods // Relative date with periods
$min = new Api\DateTime(strtotime(str_replace(array('y', 'm', 'w', 'd'), array('years', 'months', $min = new Api\DateTime(strtotime(str_replace(array('y','m','w','d'), array('years','months','weeks','days'), $this->attrs['min'])));
'weeks',
'days'), $this->attrs['min'])
)
);
} }
else else
{ {
@ -220,8 +204,7 @@ class Date extends Transformer
self::set_validation_error($form_name,lang( self::set_validation_error($form_name,lang(
"Value has to be at least '%1' !!!", "Value has to be at least '%1' !!!",
$min->format($this->type != 'date') $min->format($this->type != 'date')
), '' ),'');
);
$value = $min; $value = $min;
} }
} }
@ -234,11 +217,7 @@ class Date extends Transformer
elseif (preg_match('/[+-][[:digit:]]+[ymwd]/',$this->attrs['max'])) elseif (preg_match('/[+-][[:digit:]]+[ymwd]/',$this->attrs['max']))
{ {
// Relative date with periods // Relative date with periods
$max = new Api\DateTime(strtotime(str_replace(array('y', 'm', 'w', 'd'), array('years', 'months', $max = new Api\DateTime(strtotime(str_replace(array('y','m','w','d'), array('years','months','weeks','days'), $this->attrs['max'])));
'weeks',
'days'), $this->attrs['max'])
)
);
} }
else else
{ {
@ -249,8 +228,7 @@ class Date extends Transformer
self::set_validation_error($form_name,lang( self::set_validation_error($form_name,lang(
"Value has to be at maximum '%1' !!!", "Value has to be at maximum '%1' !!!",
$max->format($this->type != 'date') $max->format($this->type != 'date')
), '' ),'');
);
$value = $max; $value = $max;
} }
} }
@ -281,5 +259,4 @@ class Date extends Transformer
} }
} }
} }
\EGroupware\Api\Etemplate\Widget::registerWidget(__NAMESPACE__.'\\Date', array('time_or_date'));
\EGroupware\Api\Etemplate\Widget::registerWidget(__NAMESPACE__ . '\\Date', array('et2-date', '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 * 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 * @param string $path
* @return string url * @return string url
*/ */
public static function rel2url($path) public static function rel2url($path)
{ {
return $GLOBALS['egw_info']['server']['webserver_url'].'/api/etemplate.php'. if ($path)
($path[0] === '/' ? $path : Api\Vfs::parse_url($path, PHP_URL_PATH)).'?'.filemtime(self::rel2path($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', Etemplate\Widget::registerWidget(__NAMESPACE__.'\\Textbox', array('textbox','text','int','integer','float','hidden','colorpicker','hidden'));
'int', 'integer', 'float', 'hidden', 'colorpicker',
'hidden'));

View File

@ -3,7 +3,7 @@
<!-- $Id$ --> <!-- $Id$ -->
<overlay> <overlay>
<template id="infolog.edit.description" template="" lang="" group="0" version="1.6.001"> <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"/> <checkbox id="clean_history"/>
</template> </template>
<template id="infolog.edit.links" template="" lang="" group="0" version="1.3.001"> <template id="infolog.edit.links" template="" lang="" group="0" version="1.3.001">
@ -154,7 +154,7 @@
<rows> <rows>
<row class="dialogHeader"> <row class="dialogHeader">
<description value="Title" for="info_subject"/> <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"/> <textbox type="integer" id="info_number" readonly="true"/>
<appicon src="infolog" for="info_number"/> <appicon src="infolog" for="info_number"/>
</row> </row>
@ -165,7 +165,7 @@
</menulist> </menulist>
<description/> <description/>
<description value="Startdate" for="info_startdate"/> <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>
<row class="dialogHeader3"> <row class="dialogHeader3">
<description value="Contact"/> <description value="Contact"/>
@ -217,21 +217,25 @@
</row> </row>
<row disabled="!@info_owner" class="dialogOperators"> <row disabled="!@info_owner" class="dialogOperators">
<description value="Owner"/> <description value="Owner"/>
<et2-hbox> <hbox width="100%">
<select-account id="info_owner" readonly="true"/> <menulist>
<menupopup type="select-account" id="info_owner" readonly="true"/>
</menulist>
<date-time id="info_created" readonly="true" align="right"/> <date-time id="info_created" readonly="true" align="right"/>
</et2-hbox> </hbox>
<description/> <description/>
<description value="Last modified"/> <description value="Last modified"/>
<et2-hbox> <hbox width="100%">
<select-account id="info_modifier" readonly="true"/> <menulist>
<menupopup type="select-account" id="info_modifier" readonly="true"/>
</menulist>
<date-time id="info_datemodified" readonly="true" align="right"/> <date-time id="info_datemodified" readonly="true" align="right"/>
</et2-hbox> </hbox>
</row> </row>
<row class="dialogFooterToolbar"> <row class="dialogFooterToolbar">
<hbox span="6"> <hbox span="6">
<button statustext="Saves this entry" label="Save" id="button[save]" image="save" background_image="1"/> <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"/> <button statustext="leave without saveing the entry" label="Cancel" id="button[cancel]" onclick="window.close();" image="cancel" background_image="1"/>
<menulist> <menulist>
<menupopup statustext="Execute a further action for this entry" id="action" onchange="app.infolog.edit_actions()" options="Actions..."/> <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": {}, "repository": {},
"scripts": { "scripts": {
"build": "rollup -c", "build": "rollup -c",
"build:watch": "rollup -cw", "build:watch": "rollup -cw"
"jstest": "web-test-runner",
"jstest:watch": "web-test-runner --watch"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.14.6", "@babel/core": "^7.14.6",
"@babel/preset-typescript": "^7.14.5", "@babel/preset-typescript": "^7.14.5",
"@open-wc/testing": "^2.5.33",
"@rollup/plugin-babel": "^5.3.0", "@rollup/plugin-babel": "^5.3.0",
"@rollup/plugin-node-resolve": "^13.0.0", "@rollup/plugin-node-resolve": "^13.0.0",
"@rollup/plugin-typescript": "^8.2.1", "@rollup/plugin-typescript": "^8.2.1",
"@types/chai": "^4.2.21",
"@types/jquery": "^3.5.5", "@types/jquery": "^3.5.5",
"@types/jqueryui": "^1.12.14", "@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": "^1.3.0",
"grunt-contrib-cssmin": "^2.2.1", "grunt-contrib-cssmin": "^2.2.1",
"grunt-newer": "^1.3.0", "grunt-newer": "^1.3.0",
@ -32,7 +23,6 @@
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"rollup": "^2.52.2", "rollup": "^2.52.2",
"rollup-plugin-terser": "^7.0.2", "rollup-plugin-terser": "^7.0.2",
"sinon": "^11.1.2",
"terser": "^4.8.0", "terser": "^4.8.0",
"typescript": "^3.9.7" "typescript": "^3.9.7"
}, },
@ -56,12 +46,6 @@
}, },
"dependencies": { "dependencies": {
"@andxor/jquery-ui-touch-punch-fix": "^1.0.2", "@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-dist": "^1.12.1",
"jquery-ui-themes": "^1.12.0", "jquery-ui-themes": "^1.12.0",
"jquery-ui-timepicker-addon": "^1.6.3", "jquery-ui-timepicker-addon": "^1.6.3",

View File

@ -21,19 +21,7 @@ import resolve from '@rollup/plugin-node-resolve';
rimraf.sync('./chunks/'); rimraf.sync('./chunks/');
// Turn on minification // Turn on minification
const do_minify = false; const do_minify = true;
function isBareSpecifier (id) {
if (id.startsWith("./") || id.startsWith("../") || id.startsWith("/"))
return false;
try {
new URL(id);
return false;
}
catch {
return true;
}
}
const config = { const config = {
treeshake: false, treeshake: false,
@ -70,11 +58,6 @@ const config = {
}, },
plugins: [{ plugins: [{
resolveId (id, parentId) { 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) if (!parentId || parentId.indexOf(path.sep + 'node_modules' + path.sep) !== -1)
{ {
return; return;
@ -103,9 +86,7 @@ const config = {
} }
}, },
// resolve (external) node modules from node_modules directory // resolve (external) node modules from node_modules directory
resolve({ resolve(),
browser: true
}),
{ {
transform (code, id) { transform (code, id) {
if (id.endsWith('.ts')) 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})
],
};