mirror of
https://github.com/EGroupware/egroupware.git
synced 2024-12-27 00:58:55 +01:00
Attribute parsing & basics of WebComponents looking like et2 widgets
This commit is contained in:
parent
2439e6de98
commit
b0322c549a
@ -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);
|
||||
|
||||
*/
|
@ -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;
|
||||
}
|
@ -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;
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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';
|
||||
|
@ -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..."/>
|
||||
|
Loading…
Reference in New Issue
Block a user