Creating a widget
ETemplate components are LitElements that are wrapped with our Et2Widget mixin, which adds properties and methods to support loading from our template files and returning values to the server. They should be (relatively) stand-alone.
Before implementing a component, review the Web Component Authoring standards.
Common components are in api/js/etemplate/. You can add application specific components in
<appname>/js/.
Choose the widget type
Before creating the component, decide its role:
- Extend
Et2Widgetfor display and container widgets. -
Extend
Et2InputWidgetwhen the widget has a value that must be loaded from and returned to an eTemplate.
Define the component’s public contract at the same time: its properties, slots, events, and value behavior. The Web Component Authoring standards cover the implementation details.
Create the files
myapp/
js/
MyWidget/
test/
MyWidget.ts
You should have automatic tests to verify your component and
avoid regressions in test/.
Create and register a custom element tag. Use et2-* for shared widgets, or an application
prefix for application-specific widgets:
import {html, LitElement} from "lit";
import {customElement} from "lit/decorators/custom-element.js";
import {Et2Widget} from "../../../api/js/etemplate/Et2Widget/Et2Widget";
/**
* @summary Briefly describe the widget.
*/
@customElement("et2-my-widget") // or "myapp-my-widget"
export class MyWidget extends Et2Widget(LitElement)
{
render()
{
return html`...`;
}
}
Get it loaded
To have EGroupware load your component, it must be included somewhere. Add a shared component to the import
block in /api/js/etemplate/etemplate2.ts. If the component is specific to one application,
import it from that application’s app.ts.
...
import './MyWidget/MyWidget.ts';
...
Once the widget is loaded, it can be used in an .xet template:
<et2-my-widget id="example"></et2-my-widget>
Add it to the DTD
The .xet files use doc/etemplate2/etemplate2.0.dtd for validation. A new tag must
be in the DTD before IDEs and XML validators will accept it in templates.
For most widgets, add the class documentation and @customElement() registration first, then
regenerate the component metadata and Relax NG schema:
npm run docs
php doc/etemplate2-rng.php > doc/etemplate2/etemplate2.0.rng
Then convert doc/etemplate2/etemplate2.0.rng to
doc/etemplate2/etemplate2.0.dtd with PhpStorm’s Tools > XML Actions > Convert Schema
action. Check that the new tag is included in the %Widgets; entity and has an
<!ELEMENT ...> / <!ATTLIST ...> declaration.
If the generated schema needs help with allowed children, attribute types, or compatibility names, add an
override in doc/etemplate2-rng.php and regenerate instead of hand-editing only the final DTD.
Value behaviour
Components do not need to have a value. For components based on Et2InputWidget,
widget.value is the normal way to access the value programmatically. The eTemplate framework
loads the initial value and uses widget.getValue() when submitting the template. Readonly or
disabled widgets do not return a value.
Add focused tests for the component’s public behaviour. Input widgets should reuse
inputBasicTests() where practical to verify standard value and readonly behaviour.
Namespaces
A namespace scopes a widget’s children to a nested section of the managed arrays (content,
select_options, readonlys, and modifications). The namespaced
widget’s id is used as the key. For example:
{
"address": {
"street": "123 Example Street",
"city": "Testville"
}
}
<et2-my-address id="address">
<et2-textbox id="street"></et2-textbox>
<et2-textbox id="city"></et2-textbox>
</et2-my-address>
If et2-my-address creates a namespace, its child widgets read from
address[street] and address[city] instead of the top-level street and
city entries. Their values are returned to the server with the same nested structure.
Namespaces are useful for container widgets, especially when the same group of child IDs can appear more
than once in a template.
To give a widget its own namespace, override _createNamespace():
export class MyAddress extends Et2Widget(LitElement)
{
_createNamespace() : boolean
{
return true;
}
}
The widget must have an id, because that ID identifies the namespace.
Et2Widget creates the scoped array-manager perspectives automatically when the ID is set, so
the widget should not call checkCreateNamespace() itself.
Most input widgets should not create a namespace: their ID normally identifies their own value. Namespace
support is primarily intended for widgets that contain other widgets. If namespace creation is conditional,
follow the Et2Template pattern and make _createNamespace() return the condition;
re-run checkCreateNamespace() when that condition changes.