Add et2-box (& et2-hbox & et2-vbox) WebComponent

Box can have legacy & webcomponent children.
Used here in infolog edit created & modified row
This commit is contained in:
nathan
2021-08-10 15:02:52 -06:00
parent 30d835fa39
commit db7828ad1d
8 changed files with 2354 additions and 1683 deletions

View File

@@ -0,0 +1,83 @@
/**
* 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 "../../../node_modules/@lion/core/index.js";
import {Et2Widget} from "./et2_core_webComponent";
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;
}
::slotted(*) {
/* CSS for child elements */
}`,
];
}
render()
{
return html`
<div class="et2_box" ${this.id ? html`id="${this.id}"` : ''}>
<slot><p>Empty box</p></slot>
</div> `;
}
_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

@@ -12,7 +12,7 @@
import {css, html} from "../../../node_modules/@lion/core/index.js"; import {css, html} from "../../../node_modules/@lion/core/index.js";
import {LionButton} from "../../../node_modules/@lion/button/index.js"; import {LionButton} from "../../../node_modules/@lion/button/index.js";
import {Et2InputWidget} from "./et2_core_inputWidget"; import {Et2InputWidget} from "./et2_core_inputWidget";
import {Et2Widget} from "./et2_core_inheritance"; import {Et2Widget} from "./et2_core_webComponent";
export class Et2Button extends Et2InputWidget(Et2Widget(LionButton)) export class Et2Button extends Et2InputWidget(Et2Widget(LionButton))
{ {
@@ -20,7 +20,8 @@ export class Et2Button extends Et2InputWidget(Et2Widget(LionButton))
protected clicked: boolean = false; protected clicked: boolean = false;
private image: string; private image: string;
static get styles() { static get styles()
{
return [ return [
...super.styles, ...super.styles,
css` css`
@@ -38,7 +39,8 @@ export class Et2Button extends Et2InputWidget(Et2Widget(LionButton))
]; ];
} }
static get properties() { static get properties()
{
return { return {
image: {type: String}, image: {type: String},
onclick: {type: Function} onclick: {type: Function}
@@ -59,12 +61,14 @@ export class Et2Button extends Et2InputWidget(Et2Widget(LionButton))
// Define a default click handler // Define a default click handler
// If a different one gets set via attribute, it will be used instead // If a different one gets set via attribute, it will be used instead
this.onclick = (typeof this.onclick === "function") ? this.onclick : () => { this.onclick = (typeof this.onclick === "function") ? this.onclick : () =>
{
return this.getInstanceManager().submit(); return this.getInstanceManager().submit();
}; };
} }
connectedCallback() { connectedCallback()
{
super.connectedCallback(); super.connectedCallback();
//this.classList.add("et2_button") //this.classList.add("et2_button")
@@ -104,12 +108,15 @@ export class Et2Button extends Et2InputWidget(Et2Widget(LionButton))
return true; return true;
} }
render() { render()
return html` <div class="button-content et2_button" id="${this._buttonId}"> {
return html`
<div class="button-content et2_button" id="${this._buttonId}">
<slot name="icon"></slot> <slot name="icon"></slot>
<slot></slot> <slot></slot>
</div> `; </div> `;
} }
/** /**
* Implementation of the et2_IInput interface * Implementation of the et2_IInput interface
*/ */
@@ -138,4 +145,5 @@ export class Et2Button extends Et2InputWidget(Et2Widget(LionButton))
return null; return null;
} }
} }
customElements.define("et2-button", Et2Button); customElements.define("et2-button", Et2Button);

View File

@@ -12,12 +12,13 @@
import {css, html} from "../../../node_modules/@lion/core/index.js"; import {css, html} from "../../../node_modules/@lion/core/index.js";
import {LionInput} from "../../../node_modules/@lion/input/index.js"; import {LionInput} from "../../../node_modules/@lion/input/index.js";
import {Et2InputWidget} from "./et2_core_inputWidget"; import {Et2InputWidget} from "./et2_core_inputWidget";
import {Et2Widget} from "./et2_core_inheritance"; import {Et2Widget} from "./et2_core_webComponent";
export class Et2Textbox extends Et2InputWidget(Et2Widget(LionInput)) export class Et2Textbox extends Et2InputWidget(Et2Widget(LionInput))
{ {
static get styles() { static get styles()
{
return [ return [
...super.styles, ...super.styles,
css` css`
@@ -26,7 +27,8 @@ export class Et2Textbox extends Et2InputWidget(Et2Widget(LionInput))
]; ];
} }
static get properties() { static get properties()
{
return { return {
...super.properties, ...super.properties,
value: {attribute: true}, value: {attribute: true},
@@ -47,4 +49,5 @@ export class Et2Textbox extends Et2InputWidget(Et2Widget(LionInput))
} }
} }
customElements.define("et2-textbox", Et2Textbox); customElements.define("et2-textbox", Et2Textbox);

View File

@@ -13,13 +13,8 @@
*/ */
import {egw, IegwAppLocal} from "../jsapi/egw_global"; import {egw, IegwAppLocal} from "../jsapi/egw_global";
import {et2_checkType, et2_no_init, et2_validateAttrib} from "./et2_core_common"; import {et2_checkType, et2_cloneObject, et2_no_init, et2_validateAttrib} from "./et2_core_common";
import {et2_IDOMNode, et2_IInput, et2_IInputNode, et2_implements_registry} from "./et2_core_interfaces"; import {et2_IDOMNode, et2_IInput, et2_IInputNode, et2_implements_registry} from "./et2_core_interfaces";
import {LitElement} from "../../../node_modules/lit-element/lit-element.js";
import {et2_arrayMgr} from "./et2_core_arrayMgr";
import {et2_widget} from "./et2_core_widget";
import {et2_compileLegacyJS} from "./et2_core_legacyJSFunctions";
import {etemplate2} from "./etemplate2";
export class ClassWithInterfaces export class ClassWithInterfaces
{ {
@@ -75,13 +70,19 @@ 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!");
} }
} }
@@ -98,22 +99,30 @@ 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 (typeof _override == "undefined") { if (!this.attributes[_name].ignore)
{
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!");
} }
} }
@@ -128,13 +137,18 @@ 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 (!widget[key].ignore) { if (typeof widget[key] != "undefined")
{
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 +
@@ -143,10 +157,13 @@ 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;
} }
@@ -167,8 +184,10 @@ 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);
} }
} }
@@ -179,13 +198,16 @@ 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;
@@ -206,15 +228,19 @@ 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];
} }
} }
@@ -225,20 +251,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]);
} }
@@ -246,343 +275,3 @@ export class ClassWithAttributes extends ClassWithInterfaces
} }
} }
/**
* 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/
*/
type Constructor<T = {}> = new (...args: any[]) => T;
export const Et2Widget = <T extends Constructor<LitElement>>(superClass: T) => {
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;
/** WebComponent **/
static get properties() {
return {
...super.properties,
/**
* Tooltip which is shown for this element on hover
*/
statustext: {type: String},
label: {type: String},
onclick: {
type: Function,
converter: (value) => {
debugger;
return et2_compileLegacyJS(value, this, this);
}
}
};
}
/**
* Widget Mixin constructor
*
* Note the ...args parameter and super() call
*
* @param args
*/
constructor(...args: any[]) {
super(...args);
// Provide *default* property values in constructor
this.label = "";
this.statustext = "";
}
connectedCallback()
{
super.connectedCallback();
this.set_label(this.label);
if(this.statustext)
{
this.egw().tooltipBind(this,this.statustext);
}
}
disconnectedCallback()
{
this.egw().tooltipUnbind(this);
}
/**
* NOT the setter, since we cannot add to the DOM before connectedCallback()
*
* @param value
*/
set_label(value)
{
let oldValue = this.label;
// Remove old
let oldLabels = this.getElementsByClassName("et2_label");
while(oldLabels[0])
{
this.removeChild(oldLabels[0]);
}
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);
}
/**
* 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;
}
iterateOver(_callback: Function, _context, _type)
{
if(et2_implements_registry[_type](this))
{
_callback.call(_context, this);
}
// TODO: children
}
loadingFinished()
{
/**
* 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 createion, and they handle the
* rest themselves with their normal lifecycle (especially connectedCallback(), which is kind
* of the equivalent of doLoadingFinished()
*/
this.getParent().getDOMNode(this).append(this);
}
getWidgetById(_id)
{
if (this.id == _id) {
return this;
}
}
setParent(new_parent: Et2WidgetClass | et2_widget)
{
this._parent = new_parent;
}
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;
}
getDOMNode(): HTMLElement {
return this;
}
/**
* 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;
}
/**
* 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(_attrs? : any)
{
// 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 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()
{
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 egw('phpgwapi', wnd);
}
};
function applyMixins(derivedCtor: any, baseCtors: any[]) {
baseCtors.forEach(baseCtor => {
Object.getOwnPropertyNames(baseCtor.prototype).forEach(name => {
if (name !== 'constructor') {
derivedCtor.prototype[name] = baseCtor.prototype[name];
}
});
});
}
// Add some more stuff in
applyMixins(Et2WidgetClass, [ClassWithInterfaces]);
return Et2WidgetClass as unknown as Constructor<et2_IDOMNode> & T;
}

View File

@@ -0,0 +1,778 @@
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 {etemplate2} from "./etemplate2";
import {et2_compileLegacyJS} from "./et2_core_legacyJSFunctions";
import {et2_cloneObject, et2_csvSplit} from "./et2_core_common";
// @ts-ignore
import {IegwAppLocal} from "../jsapi/egw_global";
import {ClassWithAttributes, ClassWithInterfaces} from "./et2_core_inheritance";
import {LitElement} from "../../../node_modules/lit-element/lit-element.js";
import {forEach} from "carbon-web-components/es/globals/internal/collection-helpers";
/**
* 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/
*/
type Constructor<T = {}> = new (...args: any[]) => T;
export const Et2Widget = <T extends Constructor<LitElement>>(superClass: T) =>
{
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
*/
private label: string = "";
private statustext: string = "";
/** WebComponent **/
static get properties()
{
return {
...super.properties,
/**
* Tooltip which is shown for this element on hover
*/
statustext: {type: String},
label: {type: String},
onclick: {
type: Function,
converter: (value) =>
{
debugger;
return et2_compileLegacyJS(value, this, this);
}
}
};
}
/**
* Widget Mixin constructor
*
* Note the ...args parameter and super() call
*
* @param args
*/
constructor(...args: any[])
{
super(...args);
}
connectedCallback()
{
super.connectedCallback();
this.set_label(this.label);
if (this.statustext)
{
this.egw().tooltipBind(this, this.statustext);
}
}
disconnectedCallback()
{
this.egw().tooltipUnbind(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)
{
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);
}
}
/**
* 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 = {};
debugger;
// Parse the "readonly" and "type" flag for this element here, as they
// determine which constructor is used
var _nodeName = attributes["type"] = _node.getAttribute("type") ?
_node.getAttribute("type") : _node.nodeName.toLowerCase();
var 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
{
widget = this.loadWebComponent(_nodeName, _node);
if (this.addChild)
{
// webcomponent going into old et2_widget
this.addChild(widget);
}
}
return widget;
}
/**
* Load a Web Component
* @param _nodeName
* @param _node
*/
loadWebComponent(_nodeName: string, _node): HTMLElement
{
let widget = <Et2WidgetClass>document.createElement(_nodeName);
widget.textContent = _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(this);
var mgr = widget.getArrayMgr("content");
debugger;
// Apply any set attributes - widget will do its own coercion
_node.getAttributeNames().forEach(attribute =>
{
let attrValue = _node.getAttribute(attribute);
// If there is not attribute set, ignore it. Widget sets its own default.
if (typeof attrValue === "undefined") return;
// If the attribute is marked as boolean, parse the
// expression as bool expression.
if (widget_class.getPropertyOptions(attribute).type == "Boolean")
{
attrValue = mgr.parseBoolExpression(attrValue);
}
else
{
attrValue = mgr.expandName(attrValue);
}
widget.setAttribute(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.setAttribute("value", val);
}
}
// Check for already inside namespace
if (this._createNamespace() && this.getArrayMgr("content").perspectiveData.owner == this)
{
widget.setAttribute("value", this.getArrayMgr("content").data);
}
}
// Children need to be loaded
widget.loadFromXML(_node);
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);
}
// TODO: children
}
/**
* 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).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 = typeof child.getDOMNode !== "undefined" ? child.getDOMNode(child) : null;
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;
}
/**
* 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 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 egw('phpgwapi', wnd);
}
};
function applyMixins(derivedCtor: any, baseCtors: any[])
{
baseCtors.forEach(baseCtor =>
{
Object.getOwnPropertyNames(baseCtor.prototype).forEach(name =>
{
if (name !== 'constructor')
{
derivedCtor.prototype[name] = baseCtor.prototype[name];
}
});
});
}
// Add some more stuff in
applyMixins(Et2WidgetClass, [ClassWithInterfaces]);
return Et2WidgetClass as unknown as Constructor<et2_IDOMNode> & T;
}

View File

@@ -16,7 +16,7 @@
et2_core_arrayMgr; et2_core_arrayMgr;
*/ */
import {ClassWithAttributes, Et2Widget} from './et2_core_inheritance'; import {ClassWithAttributes} from './et2_core_inheritance';
import {et2_arrayMgr} from "./et2_core_arrayMgr"; import {et2_arrayMgr} from "./et2_core_arrayMgr";
import {egw, IegwAppLocal} from "../jsapi/egw_global"; 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";
@@ -24,8 +24,7 @@ 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";
// 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, et2_input} from "./et2_core_inputWidget"; import type {et2_inputWidget} from "./et2_core_inputWidget";
import {Et2InputWidget} from "./et2_core_inputWidget";
/** /**
* The registry contains all XML tag names and the corresponding widget * The registry contains all XML tag names and the corresponding widget
@@ -119,10 +118,12 @@ 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;
} }
@@ -214,15 +215,18 @@ 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
@@ -233,7 +237,8 @@ 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);
} }
@@ -241,14 +246,17 @@ export class et2_widget extends ClassWithAttributes
// 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, HTMLElement];
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;');
} }
@@ -271,18 +279,22 @@ 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();
} }
} }
@@ -308,7 +320,8 @@ 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;
} }
@@ -323,12 +336,14 @@ 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);
} }
@@ -361,9 +376,12 @@ 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;
} }
} }
@@ -389,15 +407,19 @@ 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!");
} }
@@ -413,7 +435,8 @@ 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;
@@ -428,22 +451,27 @@ 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;
@@ -462,15 +490,18 @@ 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);
} }
} }
@@ -488,11 +519,13 @@ 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);
} }
@@ -501,8 +534,10 @@ 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;
} }
} }
@@ -521,47 +556,55 @@ 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);
} }
@@ -569,26 +612,37 @@ 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);
} }
} }
@@ -609,20 +663,26 @@ 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];
} }
} }
@@ -630,10 +690,13 @@ 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)
@@ -682,20 +745,26 @@ 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;
@@ -703,14 +772,16 @@ 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"];
} }
@@ -761,7 +832,8 @@ export class et2_widget extends ClassWithAttributes
var mgr = widget.getArrayMgr("content"); var mgr = widget.getArrayMgr("content");
debugger; debugger;
// Apply any set attributes - widget will do its own coercion // Apply any set attributes - widget will do its own coercion
_node.getAttributeNames().forEach(attribute => { _node.getAttributeNames().forEach(attribute =>
{
let attrValue = _node.getAttribute(attribute); let attrValue = _node.getAttribute(attribute);
// If there is not attribute set, ignore it. Widget sets its own default. // If there is not attribute set, ignore it. Widget sets its own default.
@@ -769,9 +841,12 @@ debugger;
// 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 (widget_class.getPropertyOptions(attribute).type == "Boolean") { if (widget_class.getPropertyOptions(attribute).type == "Boolean")
{
attrValue = mgr.parseBoolExpression(attrValue); attrValue = mgr.parseBoolExpression(attrValue);
} else { }
else
{
attrValue = mgr.expandName(attrValue); attrValue = mgr.expandName(attrValue);
} }
widget.setAttribute(attribute, attrValue); widget.setAttribute(attribute, attrValue);
@@ -779,7 +854,8 @@ debugger;
if (widget_class.getPropertyOptions("value") && widget.set_value) if (widget_class.getPropertyOptions("value") && widget.set_value)
{ {
if (mgr != null) { if (mgr != null)
{
let val = mgr.getEntry(widget.id, false, true); let val = mgr.getEntry(widget.id, false, true);
if (val !== null) if (val !== null)
{ {
@@ -794,7 +870,8 @@ debugger;
} }
// Children need to be loaded // Children need to be loaded
//this.loadFromXML(_node); widget.loadFromXML(_node);
return widget; return widget;
} }
@@ -806,16 +883,20 @@ debugger;
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;
@@ -863,29 +944,39 @@ debugger;
// 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);
} }
@@ -908,11 +999,14 @@ debugger;
*/ */
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));
@@ -946,16 +1040,20 @@ debugger;
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;
} }
} }
@@ -998,20 +1096,24 @@ debugger;
*/ */
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);
} }
@@ -1036,9 +1138,12 @@ debugger;
*/ */
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);
} }
@@ -1057,22 +1162,27 @@ debugger;
// 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]);
@@ -1117,9 +1227,12 @@ debugger;
*/ */
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

@@ -23,6 +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 './et2-box';
import './et2-button'; import './et2-button';
import './et2-textbox'; import './et2-textbox';
/* 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*/

View File

@@ -217,20 +217,16 @@
</row> </row>
<row disabled="!@info_owner" class="dialogOperators"> <row disabled="!@info_owner" class="dialogOperators">
<description value="Owner"/> <description value="Owner"/>
<hbox width="100%"> <et2-hbox>
<menulist> <select-account id="info_owner" readonly="true"/>
<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"/>
</hbox> </et2-hbox>
<description/> <description/>
<description value="Last modified"/> <description value="Last modified"/>
<hbox width="100%"> <et2-hbox>
<menulist> <select-account id="info_modifier" readonly="true"/>
<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"/>
</hbox> </et2-hbox>
</row> </row>
<row class="dialogFooterToolbar"> <row class="dialogFooterToolbar">
<hbox span="6"> <hbox span="6">