Attribute parsing & basics of WebComponents looking like et2 widgets

This commit is contained in:
nathan 2021-07-14 09:49:36 -06:00
parent 2439e6de98
commit b0322c549a
6 changed files with 322 additions and 24 deletions

View File

@ -8,14 +8,47 @@
* @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
import BXButton from "../../../node_modules/carbon-web-components/es/components/button/button"
import {css} from "../../../node_modules/lit-element/lit-element.js";
import {Et2InputWidget} from "./et2_core_inputWidget";
import {Et2Widget} from "./et2_core_inheritance";
export class Et2Button extends Et2InputWidget(Et2Widget(BXButton))
{
size='small';
static get properties() {
return {
image: {type: String}
}
}
static get styles()
{
debugger;
return [
super.styles,
css`
/* Custom CSS */
`
];
}
constructor()
{
super();
this.image = '';
}
connectedCallback() {
super.connectedCallback();
this.classList.add("et2_button")
debugger;
if(this.image)
{
let icon = document.createElement("img");
icon.src = egw.image(this.image);
icon.slot="icon";
this.appendChild(icon);
}
}
}
customElements.define("et2-button",Et2Button);
*/

View File

@ -14,7 +14,10 @@
import {egw} from "../jsapi/egw_global";
import {et2_checkType, et2_no_init, et2_validateAttrib} from "./et2_core_common";
import {et2_implements_registry} from "./et2_core_interfaces";
import {et2_IDOMNode, et2_IInput, et2_IInputNode, et2_implements_registry} from "./et2_core_interfaces";
import {LitElement} from "lit-element";
import {et2_arrayMgr} from "./et2_core_arrayMgr";
import {et2_widget} from "./et2_core_widget";
// Needed for mixin
export function mix (superclass)
@ -303,3 +306,140 @@ export class ClassWithAttributes extends ClassWithInterfaces
return attributes;
}
}
/**
* This mixin will allow any LitElement to become an Et2Widget
*
* Usage:
* export class Et2Loading extends Et2Widget(BXLoading) {...}
*/
type Constructor<T = {}> = new (...args: any[]) => T;
export const Et2Widget = <T extends Constructor<LitElement>>(superClass: T) => {
class Et2WidgetClass extends superClass implements et2_IDOMNode {
protected _mgrs: et2_arrayMgr[] = [] ;
protected _parent: Et2WidgetClass|et2_widget|null = null;
iterateOver(callback: Function, context, _type)
{}
loadingFinished()
{}
getWidgetById(_id)
{
if (this.id == _id) {
return this;
}
}
setParent(new_parent: HTMLElement | 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 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]);
}
}
}
};
return Et2WidgetClass as unknown as Constructor<et2_IDOMNode> & T;
}

View File

@ -18,10 +18,11 @@ import {et2_no_init} from "./et2_core_common";
import { ClassWithAttributes } from "./et2_core_inheritance";
import { et2_widget, WidgetConfig } from "./et2_core_widget";
import { et2_valueWidget } from './et2_core_valueWidget'
import {et2_IInput, et2_ISubmitListener} from "./et2_core_interfaces";
import {et2_IInput, et2_IInputNode, et2_ISubmitListener} from "./et2_core_interfaces";
import {et2_compileLegacyJS} from "./et2_core_legacyJSFunctions";
// fixing circular dependencies by only importing the type (not in compiled .js)
import type {et2_tabbox} from "./et2_widget_tabs";
import {LitElement} from "lit-element";
export interface et2_input {
getInputNode() : HTMLInputElement|HTMLElement;
@ -383,3 +384,103 @@ export class et2_inputWidget extends et2_valueWidget implements et2_IInput, et2_
}
}
/**
* This mixin will allow any LitElement to become an Et2InputWidget
*
* Usage:
* export class Et2Button extends Et2InputWidget(BXButton) {...}
*/
type Constructor<T = {}> = new (...args: any[]) => T;
export const Et2InputWidget = <T extends Constructor<LitElement>>(superClass: T) => {
class Et2InputWidgetClass extends superClass implements et2_IInput, et2_IInputNode {
label: string = '';
protected value: string | number | Object;
protected _oldValue: string | number | Object;
getValue()
{
var node = this.getInputNode();
if (node)
{
var val = jQuery(node).val();
return val;
}
return this._oldValue;
}
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(typeof this._oldValue.length !== "undefined" &&
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()
{
return this.node;
}
/**
* These belongs somewhere else/higher, I'm just getting it to work
*/
iterateOver(callback: Function, context, _type)
{}
loadingFinished()
{}
getWidgetById(_id)
{
if (this.id == _id) {
return this;
}
}
};
return Et2InputWidgetClass as unknown as Constructor<et2_IInput> & T;
}

View File

@ -16,7 +16,7 @@
et2_core_arrayMgr;
*/
import {ClassWithAttributes} from './et2_core_inheritance';
import {ClassWithAttributes, Et2Widget} from './et2_core_inheritance';
import {et2_arrayMgr} from "./et2_core_arrayMgr";
import {egw, IegwAppLocal} from "../jsapi/egw_global";
import {et2_cloneObject, et2_csvSplit} from "./et2_core_common";
@ -239,7 +239,7 @@ export class et2_widget extends ClassWithAttributes
// The supported widget classes array defines a whitelist for all widget
// classes or interfaces child widgets have to support.
this.supportedWidgetClasses = [et2_widget];
this.supportedWidgetClasses = [et2_widget, HTMLElement];
if (_attrs["id"]) {
// Create a namespace for this object
@ -714,14 +714,14 @@ export class et2_widget extends ClassWithAttributes
constructor = et2_registry[_nodeName + "_ro"];
}
if(undefined == window.customElements.get(_nodeName))
{
// Parse the attributes from the given XML attributes object
this.parseXMLAttrs(_node.attributes, attributes, constructor.prototype);
// Do an sanity check for the attributes
ClassWithAttributes.generateAttributeSet(et2_attribute_registry[constructor.name], attributes);
if(undefined == window.customElements.get(_nodeName))
{
// Creates the new widget, passes this widget as an instance and
// passes the widgetType. Then it goes on loading the XML for it.
var widget = new constructor(this, attributes);
@ -731,7 +731,7 @@ export class et2_widget extends ClassWithAttributes
}
else
{
widget = this.loadWebComponent(_nodeName, _node, attributes);
widget = this.loadWebComponent(_nodeName, _node);
if(this.addChild)
{
@ -747,14 +747,38 @@ export class et2_widget extends ClassWithAttributes
* @param _nodeName
* @param _node
*/
loadWebComponent(_nodeName : string, _node, attributes : Object) : HTMLElement
loadWebComponent(_nodeName : string, _node) : HTMLElement
{
let widget = document.createElement(_nodeName);
let widget = <Et2WidgetClass> document.createElement(_nodeName);
widget.textContent = _node.textContent;
// Apply any set attributes
_node.getAttributeNames().forEach(attribute => widget.setAttribute(attribute, attributes[attribute]));
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");
// 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.properties[attribute]?.type == "Boolean") {
attrValue = mgr.parseBoolExpression(attrValue);
} else {
attrValue = mgr.expandName(attrValue);
}
widget.setAttribute(attribute, attrValue);
});
// Children need to be loaded
//this.loadFromXML(_node);
return widget;
}

View File

@ -23,7 +23,7 @@ import {et2_nextmatch, et2_nextmatch_header_bar} from "./et2_extension_nextmatch
import {et2_tabbox} from "./et2_widget_tabs";
import '../jsapi/egw_json.js';
import {egwIsMobile} from "../egw_action/egw_action_common.js";
//import './et2-button';
import './et2-button';
/* Include all widget classes here, we only care about them registering, not importing anything*/
import './et2_widget_vfs'; // Vfs must be first (before et2_widget_file) due to import cycle
import './et2_widget_template';

View File

@ -235,7 +235,7 @@
<row class="dialogFooterToolbar">
<hbox span="6">
<button statustext="Saves this entry" label="Save" id="button[save]" image="save" background_image="1"/>
<button statustext="Apply the changes" label="Apply" id="button[apply]" image="apply" background_image="1"/>
<et2-button statustext="Apply the changes" label="Apply" id="button[apply]" image="apply" background_image="1"></et2-button>
<button statustext="leave without saveing the entry" label="Cancel" id="button[cancel]" onclick="window.close();" image="cancel" background_image="1"/>
<menulist>
<menupopup statustext="Execute a further action for this entry" id="action" onchange="app.infolog.edit_actions()" options="Actions..."/>