@webtides/element-js 1.0.0-alpha.4 → 1.0.0-alpha.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/README.md +174 -0
- package/docs/concepts/classes.md +167 -0
- package/docs/concepts/hierarchy.md +61 -0
- package/docs/concepts/lifecycle.md +45 -0
- package/docs/concepts/reactivity.md +14 -0
- package/docs/features/computed-properties.md +28 -0
- package/docs/features/dependency-injection-context-protocol.md +132 -0
- package/docs/features/events.md +155 -0
- package/docs/features/global-styles.md +131 -0
- package/docs/features/methods.md +34 -0
- package/docs/features/properties.md +56 -0
- package/docs/features/refs.md +47 -0
- package/docs/features/renderless.md +70 -0
- package/docs/features/store.md +143 -0
- package/docs/features/styles.md +169 -0
- package/docs/features/templates.md +308 -0
- package/docs/features/watch.md +54 -0
- package/docs/getting-started/upgrade-to-v1.md +243 -0
- package/docs/guide/guide.md +112 -0
- package/package.json +15 -19
- package/src/BaseElement.js +6 -6
- package/src/dom-parts/AttributePart.js +1 -0
- package/src/dom-parts/ChildNodePart.js +2 -0
- package/src/dom-parts/NodePart.js +1 -0
- package/src/dom-parts/TemplatePart.js +2 -1
- package/src/dom-parts/TemplateResult.js +33 -32
- package/src/dom-parts/directives.js +5 -3
- package/dist/index.js +0 -1554
- package/dist/index.js.map +0 -7
package/docs/README.md
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
# element-js
|
|
2
|
+
|
|
3
|
+
## Introduction
|
|
4
|
+
|
|
5
|
+
_element-js_ is a collection of base classes for Custom Elements with a beautiful API for building fast and lightweight
|
|
6
|
+
Web Components that work with any framework or without a framework at all - because they are just html and vanilla
|
|
7
|
+
javascript.
|
|
8
|
+
|
|
9
|
+
_element-js_ aims to combine the best concepts of current and popular frontend frameworks and web-standard into a single
|
|
10
|
+
toolset that will never be outdated because it is just html and javascript.
|
|
11
|
+
|
|
12
|
+
### Why Custom elements
|
|
13
|
+
|
|
14
|
+
By using web-standards, developers only need to learn the standard web API once rather than custom framework APIs over
|
|
15
|
+
and over again.
|
|
16
|
+
|
|
17
|
+
While popular frontend frameworks come and go, web components and custom elements will always be part of the web
|
|
18
|
+
standard API. And since they conform to the standard web API, they work in any major frontend framework.
|
|
19
|
+
|
|
20
|
+
Custom elements provide a component model for the web and enable developers to define their own elements. With custom
|
|
21
|
+
elements you get a mechanism to associate an ES class with a custom element name which then can be used in HTML
|
|
22
|
+
alongside any other element. Inside the class you can define the public API for the custom element.
|
|
23
|
+
|
|
24
|
+
### Why element-js
|
|
25
|
+
|
|
26
|
+
Using Custom Clements directly can be quite cumbersome because the underlying API is very low level. To prevent writing
|
|
27
|
+
a lot of boilerplate _element-js_ provides extra APIs that enable writing components fast with very little code.
|
|
28
|
+
|
|
29
|
+
On top of that it also brings some nice features like
|
|
30
|
+
|
|
31
|
+
- async update/render cycle
|
|
32
|
+
- automatic mapping of attributes to properties
|
|
33
|
+
- objects, arrays, numbers and booleans as attributes (instead of just strings)
|
|
34
|
+
- reflecting properties to attributes
|
|
35
|
+
- abstraction for writing templates
|
|
36
|
+
- mechanism for loading styles per element
|
|
37
|
+
- loading global styles into Shadow Dom
|
|
38
|
+
|
|
39
|
+
And the best thing? There is no magic! No Framework. No compiler. You only need to extend from an _element-js_ element
|
|
40
|
+
instead of the standard `HTMLElement`.
|
|
41
|
+
|
|
42
|
+
### Browser Support
|
|
43
|
+
|
|
44
|
+
_element-js_ Elements are natively supported in all modern Browsers including Chrome, Edge, Firefox, and Safari.
|
|
45
|
+
|
|
46
|
+
| Chrome | Edge | Firefox | Safari |
|
|
47
|
+
| ------ | ------ | ------- | -------- |
|
|
48
|
+
| ✓ | ✓ | ✓ | ✓ |
|
|
49
|
+
| v. 60+ | v. 79+ | v. 63+ | v. 10.1+ |
|
|
50
|
+
|
|
51
|
+
If you need to support older Browsers like IE 11 or Edge 16-18 you will have to load one or two polyfills for Custom
|
|
52
|
+
Elements to work seamlessly. _element-js_ does not bring or load any polyfills on its own. You will have to bundle or
|
|
53
|
+
load them yourself. For more information see [Bundling/Publishing](#bundlingpublishing).
|
|
54
|
+
|
|
55
|
+
> _element-js_ comes unbundled, unminified and uses a few `Babel` plugins. Browser support can also be limited for any
|
|
56
|
+
> of the `Babel` plugin features, you will probably have to use a build step and run your code through something
|
|
57
|
+
> like `Babel`. For a list of `Babel` plugins that are used, see [ES6 Classes](#es6-classes). For more information on
|
|
58
|
+
> bundling and publishing your elements see [Bundling/Publishing](#bundlingpublishing).
|
|
59
|
+
|
|
60
|
+
### Getting Started
|
|
61
|
+
|
|
62
|
+
+ #### [Upgrade Guide](./getting-started/upgrade-to-v1.md)
|
|
63
|
+
|
|
64
|
+
### Element Core Concepts
|
|
65
|
+
|
|
66
|
+
+ #### [ES6 Classes & Options](./concepts/classes.md)
|
|
67
|
+
+ #### [Lifecycle](./concepts/lifecycle.md)
|
|
68
|
+
+ #### [Reactivity](./concepts/reactivity.md)
|
|
69
|
+
+ #### [Hierarchy](./concepts/hierarchy.md)
|
|
70
|
+
|
|
71
|
+
### Features
|
|
72
|
+
|
|
73
|
+
+ #### [Reactive Attributes/Properties](./features/properties.md)
|
|
74
|
+
+ #### [Shared State / Reactive State / Store](./features/store.md)
|
|
75
|
+
+ #### [Watch Changes](./features/watch.md)
|
|
76
|
+
+ #### [Computed Properties](./features/computed-properties.md)
|
|
77
|
+
+ #### [Dependency Injection / Context Protocol](./features/dependency-injection-context-protocol.md)
|
|
78
|
+
+ #### [Methods](./features/methods.md)
|
|
79
|
+
+ #### [Rendering / TemplateElement](./features/templates.md)
|
|
80
|
+
+ #### [Not Rendering Elements / BaseElement](./features/methods.md)
|
|
81
|
+
+ #### [Dom References / Refs](./features/refs.md)
|
|
82
|
+
+ #### [Component Styles / CSS](./features/styles.md)
|
|
83
|
+
+ #### [Global Styles / CSS](./features/global-styles.md)
|
|
84
|
+
+ #### [Events](./features/events.md)
|
|
85
|
+
|
|
86
|
+
### Getting started
|
|
87
|
+
|
|
88
|
+
`npm install @webtides/element-js`
|
|
89
|
+
|
|
90
|
+
You can install _element-js_ into an existing project or create a new app from scratch.
|
|
91
|
+
|
|
92
|
+
### First Element
|
|
93
|
+
|
|
94
|
+
To define a custom element, create a class that extends _element-js_ and pass the class with a name to
|
|
95
|
+
the `defineElement` helper function.
|
|
96
|
+
|
|
97
|
+
> By specification, a custom element's name can only have lower case letters and must contain a dash.
|
|
98
|
+
|
|
99
|
+
Here is an example of what a _element-js_ element looks like:
|
|
100
|
+
|
|
101
|
+
```js
|
|
102
|
+
import {BaseElement, defineElement} from '@webtides/element-js';
|
|
103
|
+
|
|
104
|
+
class ExampleElement extends BaseElement {
|
|
105
|
+
// normal public properties
|
|
106
|
+
greeting = 'Hello';
|
|
107
|
+
name = 'John';
|
|
108
|
+
|
|
109
|
+
// lifecycle hook
|
|
110
|
+
connected() {
|
|
111
|
+
this.greet();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// reactive attributes/properties
|
|
115
|
+
properties() {
|
|
116
|
+
return {
|
|
117
|
+
familyName: 'Doe'
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// watchers for property changes
|
|
122
|
+
watch() {
|
|
123
|
+
return {
|
|
124
|
+
familyName: (newValue, oldValue) => {
|
|
125
|
+
console.log('familyName changed', newValue, oldValue);
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// computed property
|
|
131
|
+
get computedMsg() {
|
|
132
|
+
return `${this.greeting} ${this.name} ${this.familyName}`;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// method
|
|
136
|
+
greet() {
|
|
137
|
+
alert('greeting: ' + this.computedMsg);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
defineElement('example-element', ExampleElement);
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
> You don't need to fully understand everything just now. We'll explain each part in detail later on.
|
|
145
|
+
|
|
146
|
+
When the JS class is built, this element can be imported and used in HTML just like any other element tag.
|
|
147
|
+
|
|
148
|
+
```html
|
|
149
|
+
|
|
150
|
+
<example-element family-name="Smith"></example-element>
|
|
151
|
+
<script type="module" src="./example-element.js">
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
When rendered in the browser, it will log to the console: `Hello John Smith`
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
## [Guides/Tooling](./guide/guide.md)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
## Testing
|
|
163
|
+
|
|
164
|
+
### Overview
|
|
165
|
+
|
|
166
|
+
### Feature tests
|
|
167
|
+
|
|
168
|
+
### Unit tests
|
|
169
|
+
|
|
170
|
+
## Recommended Packages
|
|
171
|
+
|
|
172
|
+
### @webtides/element-library
|
|
173
|
+
|
|
174
|
+
### @webtides/layouts
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
### ES6 classes
|
|
2
|
+
|
|
3
|
+
Most of the _element-js_ API ist standard ES6 class Syntax with a few ES next proposals.
|
|
4
|
+
|
|
5
|
+
An _element-js_ element is nothing more than a regular JS class that extends from `HTMLElement`.
|
|
6
|
+
|
|
7
|
+
> For more information on JavaScript classes
|
|
8
|
+
> see: [Classes on MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes)
|
|
9
|
+
|
|
10
|
+
### Constructor & Options
|
|
11
|
+
|
|
12
|
+
For the most part you won't probably need a constructor when extending your elements from _element-js_.
|
|
13
|
+
|
|
14
|
+
Optionally you can overwrite the constructor and pass an object to the super call with various element-level options.
|
|
15
|
+
|
|
16
|
+
```json
|
|
17
|
+
{
|
|
18
|
+
"autoUpdate": true,
|
|
19
|
+
"deferUpdate": false,
|
|
20
|
+
"mutationObserverOptions": {
|
|
21
|
+
"attributes": true,
|
|
22
|
+
"childList": true,
|
|
23
|
+
"subtree": false
|
|
24
|
+
},
|
|
25
|
+
"propertyOptions": {},
|
|
26
|
+
"shadowRender": false,
|
|
27
|
+
"styles": []
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
#### autoUpdate
|
|
33
|
+
|
|
34
|
+
Type: `boolean` Default: `true`
|
|
35
|
+
|
|
36
|
+
When set to `true` the element will call the `requestUpdate()` method on the instance every time a property or attribute
|
|
37
|
+
was changed. This will re-evaluate everything on the element and trigger a re-render (if a template is provided) and
|
|
38
|
+
trigger the watchers for the affected properties/attributes.
|
|
39
|
+
|
|
40
|
+
#### deferUpdate
|
|
41
|
+
|
|
42
|
+
Type: `boolean` Default: `false`
|
|
43
|
+
|
|
44
|
+
When set to `true` the element will not call the `requestUpdate()` method upon connecting and therefore will not
|
|
45
|
+
render (if template was provided) initially. This might be necessary in some cases where you have to prepare and setup
|
|
46
|
+
your element before actually rendering for the first time. You will have to call the `requestUpdate` method manually at
|
|
47
|
+
the right lifecycle hook.
|
|
48
|
+
|
|
49
|
+
#### deferUpdate via attribute
|
|
50
|
+
|
|
51
|
+
deferUpdate can also be set to true by adding a "defer-update" attribute to the host element.
|
|
52
|
+
|
|
53
|
+
```html
|
|
54
|
+
<deferred-element defer-update></deferred-element>
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
#### deferConnected (⚠️)
|
|
58
|
+
|
|
59
|
+
Type: `boolean` Default: `false`
|
|
60
|
+
|
|
61
|
+
When set to `true` the element will early return from the native `connectedCallback()`.
|
|
62
|
+
Setting this to true will defer the entire initialization of `element-js` feature set.
|
|
63
|
+
To make the element useful at a later point again `connectedCallback()` must be called at a later point.
|
|
64
|
+
|
|
65
|
+
This is an advanced Setting and should be used with great caution.
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
#### deferConnected via attribute (⚠️)
|
|
69
|
+
|
|
70
|
+
deferConnected can also be set to true by adding a "defer-connected" attribute to the host element.
|
|
71
|
+
|
|
72
|
+
```html
|
|
73
|
+
<deferred-element defer-connected></deferred-element>
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
#### mutationObserverOptions
|
|
79
|
+
|
|
80
|
+
Type: `object` Default: `{ "attributes": true, "childList": true, "subtree": false }`
|
|
81
|
+
|
|
82
|
+
By default, the element will register a _Mutation Observer_, listen for attribute changes and call the `requestUpdate`
|
|
83
|
+
method if `autoUpdate` is enabled. When `childList` is set to `true` the element will also call the `requestUpdate`
|
|
84
|
+
method for childList modifications (eg. Adding or removing child elements).
|
|
85
|
+
The `mutationObserverOptions` takes a dictionary in the exact same form as
|
|
86
|
+
the [MutationObserverInit options](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserverInit).
|
|
87
|
+
By setting the `subtree` option to `true` the element will also call the `requestUpdate` method for attribute changes
|
|
88
|
+
and childList modifications on nested child elements.
|
|
89
|
+
|
|
90
|
+
> Please note that observing `subtree` mutations might have performance implications and use it only if necessary.
|
|
91
|
+
|
|
92
|
+
#### propertyOptions:
|
|
93
|
+
|
|
94
|
+
```js
|
|
95
|
+
{
|
|
96
|
+
parseDisabled: {
|
|
97
|
+
parse: false,
|
|
98
|
+
},
|
|
99
|
+
parseString: {
|
|
100
|
+
parse: (value) => value.toString(),
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Type: `object` Default `{}`
|
|
106
|
+
|
|
107
|
+
With the `propertyOptions` object you can fine tune the update behaviour for certain properties/attributes.
|
|
108
|
+
|
|
109
|
+
```json
|
|
110
|
+
{
|
|
111
|
+
"propertyA": {
|
|
112
|
+
"reflect": true
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
By default all options are `false` for all properties/attributes. Currently the following options are available:
|
|
118
|
+
|
|
119
|
+
```json
|
|
120
|
+
{
|
|
121
|
+
"reflect": false,
|
|
122
|
+
"parse": false,
|
|
123
|
+
"notify": false
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
_reflect_ When set to `true` the element will reflect property changes back to attributes if the attribute was not
|
|
128
|
+
present when connecting the element. By default all attributes that are present when connecting the element will be
|
|
129
|
+
reflected anyway.
|
|
130
|
+
|
|
131
|
+
_parse_ When set to `false` the element will not automatically try to parse the attributes string value to a complex
|
|
132
|
+
type (number, array, object).
|
|
133
|
+
|
|
134
|
+
_notify_ When set to `true` the element will dispatch `CustomEvent`s for every property/attribute change. The event name
|
|
135
|
+
will be the property name all lowercase and camel to dashes with a postfix of `-changed`. For example `propertyA` will
|
|
136
|
+
dispatch an event with the name `property-a-changed` . The event `detail` property will be the changed property value.
|
|
137
|
+
The event will `bubble` by default.
|
|
138
|
+
|
|
139
|
+
_parse_ and _reflect_ can also be provided with a callback function for total control of the parsing and/or reflection
|
|
140
|
+
of the attributes value
|
|
141
|
+
|
|
142
|
+
```js
|
|
143
|
+
|
|
144
|
+
propertyOptions = {
|
|
145
|
+
parseString: {
|
|
146
|
+
parse: (value) => value.toString()
|
|
147
|
+
},
|
|
148
|
+
reflectCustom: {
|
|
149
|
+
reflect: () => 'custom',
|
|
150
|
+
},
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
#### shadowRender
|
|
155
|
+
|
|
156
|
+
Type: `boolean` Default: `false`
|
|
157
|
+
|
|
158
|
+
When set to `true` the element will render the template (if provided) in the _Shadow DOM_ and therefore encapsulate the
|
|
159
|
+
element and styles from the rest of the document. For more information on shadow DOM
|
|
160
|
+
see [Using shadow DOM](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_shadow_DOM).
|
|
161
|
+
|
|
162
|
+
#### styles
|
|
163
|
+
|
|
164
|
+
Type `array` Default: `[]`
|
|
165
|
+
|
|
166
|
+
Via the `styles` option you can add multiple styles/stylesheets to your element. For more information on styling your
|
|
167
|
+
elements see the [Styles](../features/styles.md) section.
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
|
|
2
|
+
### Element Hierarchy
|
|
3
|
+
|
|
4
|
+
Pleas keep in mind that lifecycles _do not_ wait for child elements to be connected or updated/rendered.
|
|
5
|
+
|
|
6
|
+
In the example below we have a simple hierarchy of elements.
|
|
7
|
+
|
|
8
|
+
```html
|
|
9
|
+
|
|
10
|
+
<a-element>
|
|
11
|
+
<b-element>
|
|
12
|
+
<c-element></c-element>
|
|
13
|
+
</b-element>
|
|
14
|
+
</a-element>
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
The full lifecycle would be as follows:
|
|
18
|
+
|
|
19
|
+
1. a-element -> connected()
|
|
20
|
+
2. b-element -> connected()
|
|
21
|
+
3. c-element -> connected()
|
|
22
|
+
|
|
23
|
+
This loading/connecting behaviour is compliant with how other (normal) DOM elements are loaded and connected.
|
|
24
|
+
|
|
25
|
+
#### Example
|
|
26
|
+
|
|
27
|
+
In the example we render a clock and update the time every second.
|
|
28
|
+
|
|
29
|
+
```javascript
|
|
30
|
+
import {TemplateElement, defineElement, html} from '@webtides/element-js';
|
|
31
|
+
|
|
32
|
+
export class ClockElement extends TemplateElement {
|
|
33
|
+
timer = null;
|
|
34
|
+
|
|
35
|
+
properties() {
|
|
36
|
+
return {
|
|
37
|
+
time: Date.now()
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
connected() {
|
|
42
|
+
this.timer = window.setInterval(() => {
|
|
43
|
+
this.time = Date.now();
|
|
44
|
+
}, 1000);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
disconnected() {
|
|
48
|
+
window.clearInterval(this.timer);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
get formattedTime() {
|
|
52
|
+
return new Date(this.time).toLocaleTimeString();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
template() {
|
|
56
|
+
return html` <span>${this.formattedTime}</span> `;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
defineElement('clock-element', ClockElement);
|
|
61
|
+
```
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
|
|
2
|
+
### Lifecycle
|
|
3
|
+
|
|
4
|
+
Elements have several lifecycle methods which can be used to hook into different moments. By implementing one of the
|
|
5
|
+
following methods within the class the element will call them in the right order:
|
|
6
|
+
|
|
7
|
+
#### connected
|
|
8
|
+
|
|
9
|
+
Called when the element is connected to the DOM and _element-js_ is done initialising. Usually this hook will only be
|
|
10
|
+
called once for the lifetime of your element.
|
|
11
|
+
|
|
12
|
+
> Please note that the `connected` hook however /can/ be called more than once after the first time. It will also be
|
|
13
|
+
> invoked when the element is _attached_ or _moved_ in the DOM or to another DOM.
|
|
14
|
+
|
|
15
|
+
Use it to initialise the element and set public or "private" properties, query for other DOM elements, etc.
|
|
16
|
+
|
|
17
|
+
#### beforeUpdate
|
|
18
|
+
|
|
19
|
+
Called when the element is about to be updated. Whenever a /reactive/ property changed, the element will undergo an
|
|
20
|
+
update/render cycle. In this hook you can prepare any internal state that might be needed before the update or render
|
|
21
|
+
actually happens.
|
|
22
|
+
|
|
23
|
+
> Although the element will update/render after connecting to the DOM, the `beforeUpdate` hook won't be called for the
|
|
24
|
+
> first time. Only on subsequent update cycles.
|
|
25
|
+
|
|
26
|
+
Property changes in this callback will not be applied in the current frame. They will be queued and processed in the
|
|
27
|
+
next frame.
|
|
28
|
+
|
|
29
|
+
#### afterUpdate
|
|
30
|
+
|
|
31
|
+
Called after the element was updated/rendered. Implement this hook to perform any tasks that need to be done afterwards
|
|
32
|
+
like setting/toggling classes on child elements or invoking API methods on other elements.
|
|
33
|
+
|
|
34
|
+
#### disconnected
|
|
35
|
+
|
|
36
|
+
Called just before the element is about to be disconnected/removed from the DOM. Use this hook to clear or remove
|
|
37
|
+
everything that might be heavy on the browser like expensive event listeners etc.
|
|
38
|
+
|
|
39
|
+
_DISCALIMER_
|
|
40
|
+
|
|
41
|
+
The lifecycle hooks described here are custom methods provided by _element-js_. By default custom elements also have two
|
|
42
|
+
baked in hooks. `connectedCallback()` and `disconnectedCallback()`. We would encourage you to avoid these if possible.
|
|
43
|
+
Internally we use them of course to connect the element and trigger the `connected()` callback afterwards. We use them
|
|
44
|
+
to initialise the elements with all the boilerplate and prepare the API that _element-js_ provides. If you absolutely
|
|
45
|
+
must use one of them please keep in mind to call the original callback on the `super` element.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
|
|
2
|
+
### Reactivity
|
|
3
|
+
|
|
4
|
+
_element-js_ elements update asynchronously when attributes or reactive properties are changed. Changes will also be
|
|
5
|
+
batched if multiple updates occur. The update itself will always happen in the following frame.
|
|
6
|
+
|
|
7
|
+
An update lifecycle roughly looks like this:
|
|
8
|
+
|
|
9
|
+
1. A property is changed
|
|
10
|
+
2. Check whether the value actually changed and if so, request an update
|
|
11
|
+
3. Requested updates are batched in a queue
|
|
12
|
+
4. Perform the update in the next frame
|
|
13
|
+
5. Trigger Watchers for properties if defined
|
|
14
|
+
6. Render the template if defined
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
## Computed properties
|
|
2
|
+
|
|
3
|
+
Sometimes it is necessary to compute properties out of multiple other properties. You can simply define a getter on the
|
|
4
|
+
element and return a value.
|
|
5
|
+
|
|
6
|
+
```javascript
|
|
7
|
+
export class MyElement extends BaseElement {
|
|
8
|
+
properties() {
|
|
9
|
+
return {
|
|
10
|
+
reactivePublicProperty: 'I am public & reactive'
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
get computedProperty() {
|
|
15
|
+
return this.reactivePublicProperty + ' & computed';
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
log() {
|
|
19
|
+
console.log(this.computedProperty);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
For every subsequent update/render cycle a computed property will be evaluated again.
|
|
25
|
+
|
|
26
|
+
> Keep in mind that properties (public or private) that are not declared via the `properties()` map will not trigger an
|
|
27
|
+
> update/render cycle when changed. If you use only non reactive properties to compute a property, it will not be
|
|
28
|
+
> re-evaluated automatically.
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
### Dependency Injection
|
|
2
|
+
|
|
3
|
+
_element-js_ provides a simple way of Dependency Injection following the idea of the Context [Protocol Proposal](https://github.com/webcomponents-cg/community-protocols/blob/main/proposals/context.md).
|
|
4
|
+
|
|
5
|
+
#### Problem "Prop Drilling"
|
|
6
|
+
|
|
7
|
+
If Data has to be passed down the DOM Tree to descending Child Elements it is usually done by assigning data to the descendants attributes:
|
|
8
|
+
|
|
9
|
+
```js
|
|
10
|
+
export class PropDrilling extends TemplateElement {
|
|
11
|
+
properties() {
|
|
12
|
+
return {
|
|
13
|
+
data: {some: "data", which: "bloats", the: "attributes", in: "DOM" }
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
template() {
|
|
18
|
+
return html`
|
|
19
|
+
<!-- child element uses the same exacte data-set as the parent does -->
|
|
20
|
+
<child-element redundant-data="${data}">
|
|
21
|
+
<!-- grand child component uses parts of the parents or even the root elements dataset -->
|
|
22
|
+
<grand-child-element in-data="${data.in}"></grand-child-element>
|
|
23
|
+
</child-element>
|
|
24
|
+
<!-- child component uses parts of the parents dataset -->
|
|
25
|
+
<another-child-element some-data="${data.some}" the-data="${data.the}"></another-child-element>
|
|
26
|
+
`;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
While this is possible and totally fine for primitive values it can get confusing very fast with a deep nesting and complex data structures.
|
|
32
|
+
It also bloats up the DOM Tree as redundant data ist being passed to multiple descending Elements being copied along the way.
|
|
33
|
+
|
|
34
|
+
#### Dependency Injection for the rescue
|
|
35
|
+
|
|
36
|
+
To address the issues caused by "Prop Drilling" _element-js_ provides a simple Dependency Injection Implementation following the idea of the Context Protocol mentioned above.
|
|
37
|
+
|
|
38
|
+
##### Provide via provideProperties()
|
|
39
|
+
|
|
40
|
+
An Element can control whatever is to be "provided" to requesting Elements by implementing the provideProperties function provided by the `BaseElement`.
|
|
41
|
+
|
|
42
|
+
```js
|
|
43
|
+
|
|
44
|
+
export class AncestorElement extends TemplateElement {
|
|
45
|
+
|
|
46
|
+
properties() {
|
|
47
|
+
return {
|
|
48
|
+
data: {some: "data", which: "bloats", the: "attributes", in: "DOM" },
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
provideProperties() {
|
|
52
|
+
return {
|
|
53
|
+
data: this.data,
|
|
54
|
+
in: this.data.in,
|
|
55
|
+
static: 'string',
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
#### Vanilla JS Provide via Event Listener / Context Protocol
|
|
61
|
+
|
|
62
|
+
```html
|
|
63
|
+
<script>
|
|
64
|
+
document.addEventListener('request-context', (event) => {
|
|
65
|
+
// global value
|
|
66
|
+
const vanillaValue = 'vanillaValue';
|
|
67
|
+
for (const [key, value] of Object.entries(event.detail)) {
|
|
68
|
+
if(key === 'vanillaContext') {
|
|
69
|
+
if (typeof value === 'function') {
|
|
70
|
+
// call function with context value
|
|
71
|
+
value(vanillaValue);
|
|
72
|
+
} else {
|
|
73
|
+
// assign as a property to Requestee
|
|
74
|
+
event.target[key] = vanillaValue;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
</script>
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
##### Inject Properties via injectProperties()
|
|
85
|
+
|
|
86
|
+
An Element can define injection requests by implementing the injectProperties function provided by the `BaseElement`.
|
|
87
|
+
The initial values will also function as default values and will be replaced after the injection request is fulfilled by an Ancestor that provides a property with the requested name.
|
|
88
|
+
Injected properties will automatically become reactive properties within the elements scope. DEVs do not have to care about where the Value is actually coming from.
|
|
89
|
+
It will be just there at some Point after the Injection will trigger changes / updates just like regular [properties](./properties.md)..
|
|
90
|
+
|
|
91
|
+
```js
|
|
92
|
+
export class DescendingElement extends TemplateElement {
|
|
93
|
+
|
|
94
|
+
injectProperties() {
|
|
95
|
+
return {
|
|
96
|
+
in: '',
|
|
97
|
+
data: {},
|
|
98
|
+
static: '',
|
|
99
|
+
vanillaContext: '',
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
##### Request Context Values via manual context Request
|
|
107
|
+
|
|
108
|
+
If you want more control over what happens with an injected value after the Injection you can do a manual context Request by using the builtin requestContext function of the BaseElement.
|
|
109
|
+
```js
|
|
110
|
+
export class ManualRequestElement extends TemplateElement {
|
|
111
|
+
properties() {
|
|
112
|
+
return {
|
|
113
|
+
reversedArray: [],
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
connected() {
|
|
117
|
+
this.requestContext('regularArray', (injectedArray) => {
|
|
118
|
+
// do whatever you like with the value
|
|
119
|
+
const processedArray = injectedArray.reverse();
|
|
120
|
+
// OPTIONAL assign to a property to trigger an update and render the reversed values´
|
|
121
|
+
this.reversedArray = processedArray;
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Still looking for more Information ?
|
|
128
|
+
You might take a look at the [Test Implementation](../../test/unit/context-protocol.test.html) which should cover all cases
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
|