@webtides/element-js 1.0.0-alpha.1 → 1.0.0-alpha.10
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/index.js +9 -2
- package/package.json +15 -19
- package/src/BaseElement.js +201 -42
- package/src/StyledElement.js +67 -48
- package/src/TemplateElement.js +31 -1
- package/src/dom-parts/AttributePart.js +16 -9
- package/src/dom-parts/ChildNodePart.js +8 -22
- package/src/dom-parts/NodePart.js +1 -0
- package/src/dom-parts/TemplatePart.js +19 -25
- package/src/dom-parts/TemplateResult.js +46 -35
- package/src/dom-parts/directives.js +43 -16
- package/src/dom-parts/html.js +1 -1
- package/src/util/AttributeParser.js +54 -21
- package/src/util/DOMHelper.js +63 -14
- package/src/util/Directive.js +21 -1
- package/src/util/GlobalStylesStore.js +66 -0
- package/src/util/SerializeStateHelper.js +103 -0
- package/src/util/Store.js +111 -15
- package/src/util/defineElement.js +2 -2
- package/src/util/toString.js +2 -2
- package/dist/index.js +0 -1330
- package/dist/index.js.map +0 -7
- package/src/util/i18n.js +0 -20
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
### Events
|
|
2
|
+
|
|
3
|
+
In HTML you have two ways of communicating between elements. To send “information” down the tree we typically use attributes or properties on child elements. It is not advised to hold references to parent elements and communicate back up via public methods or properties. The preferred way of sending “information” up the tree is via DOM events. Do read all about them see [Introduction to events - Learn web development | MDN](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events) .
|
|
4
|
+
|
|
5
|
+
_element-js_ is nothing special or exceptional here. You can and should use DOM events and Custom DOM events like usual. We do however provide some nice APIs to make working with events a lot more streamlined.
|
|
6
|
+
|
|
7
|
+
#### Events map
|
|
8
|
+
|
|
9
|
+
By implementing an `events()` method in your elements class you can return an object with a list of events that should be registered on the element or its children, along with event handlers to be triggered once the event gets fired. You can use any of the native events or even custom events.
|
|
10
|
+
|
|
11
|
+
The outer key value pairs let you specify regular DOM selectors as the key for the element the event listeners should be registered on. In the inner object you can define key value pairs where the key is the event to be listened for on the element and the value is the callback to be triggered once the event fires. For every event you can get the event as variable passed to the callback.
|
|
12
|
+
|
|
13
|
+
```javascript
|
|
14
|
+
export class MyElement extends BaseElement {
|
|
15
|
+
events() {
|
|
16
|
+
return {
|
|
17
|
+
this: {
|
|
18
|
+
click: (e) => console.log('click', e),
|
|
19
|
+
customEvent: (e) => {
|
|
20
|
+
console.log('customEvent', e);
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
'[data-increment]': {
|
|
24
|
+
click: () => this.count++
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
The nice thing about this pattern is that you will only have one place for all the event logic. You don't have to declare event handlers in the template and then call dedicated methods defined in the element. Everything is nice and compact in one place.
|
|
32
|
+
|
|
33
|
+
_this_ When using arrow functions as callbacks in the `events()` map you can use `this` as usual and it will belong to the element itself.
|
|
34
|
+
|
|
35
|
+
_context_ When binding element methods directly as callbacks you would typically have to bind the context to the element itself.
|
|
36
|
+
|
|
37
|
+
```javascript
|
|
38
|
+
export class MyElement extends BaseElement {
|
|
39
|
+
events() {
|
|
40
|
+
return {
|
|
41
|
+
this: {
|
|
42
|
+
click: (e) => this.doStuff.bind(this)
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
_element-js_ will do that for you automatically behind the scenes. So can omit the `bind` in most cases.
|
|
50
|
+
|
|
51
|
+
```javascript
|
|
52
|
+
export class MyElement extends BaseElement {
|
|
53
|
+
events() {
|
|
54
|
+
return {
|
|
55
|
+
this: {
|
|
56
|
+
click: (e) => this.doStuff
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
If you need to bind a different context however you can do that of course.
|
|
64
|
+
|
|
65
|
+
_Special selectors_ Sometimes you will need to listen to more global events rather than on the element itself or its children. We therefore enable you to define the following selectors as event targets:
|
|
66
|
+
|
|
67
|
+
- window
|
|
68
|
+
- document
|
|
69
|
+
- this
|
|
70
|
+
|
|
71
|
+
Since most events will automatically bubble up the tree it is also possible to listen for events that are not directly related to your element.
|
|
72
|
+
|
|
73
|
+
```javascript
|
|
74
|
+
export class MyElement extends BaseElement {
|
|
75
|
+
events() {
|
|
76
|
+
return {
|
|
77
|
+
this: {
|
|
78
|
+
click: (e) => console.log('click', e)
|
|
79
|
+
},
|
|
80
|
+
window: {
|
|
81
|
+
scroll: (e) => console.log('scroll', e)
|
|
82
|
+
},
|
|
83
|
+
document: {
|
|
84
|
+
visibilitychange: (e) => {
|
|
85
|
+
if (document.visibilityState === 'visible') {
|
|
86
|
+
backgroundMusic.play();
|
|
87
|
+
} else {
|
|
88
|
+
backgroundMusic.pause();
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
For better control Events can be bound with listener options by using the "complex notation" The callback need to wrapped into an Object to pass the Options alongside.
|
|
98
|
+
|
|
99
|
+
```js
|
|
100
|
+
{
|
|
101
|
+
listener: (e) => console.log('scroll', e),
|
|
102
|
+
options
|
|
103
|
+
:
|
|
104
|
+
{
|
|
105
|
+
passive : true
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Naming is heavily inspired by the addEventListener signature https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener
|
|
111
|
+
|
|
112
|
+
```javascript
|
|
113
|
+
export class MyElement extends BaseElement {
|
|
114
|
+
events() {
|
|
115
|
+
return {
|
|
116
|
+
window: {
|
|
117
|
+
scroll: {
|
|
118
|
+
listener: (e) => console.log('scroll', e),
|
|
119
|
+
options: { passive: true }
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
> _element-js_ will smartly add and remove all the defined event listeners on every update/render cycle to ensure that events will always fire even though you might have changed the child DOM tree during render cycles.
|
|
128
|
+
|
|
129
|
+
#### Dispatching events
|
|
130
|
+
|
|
131
|
+
To make it easy to dispatch events from your elements itself _element-js_ implements a special method `dispatch()` .
|
|
132
|
+
|
|
133
|
+
```javascript
|
|
134
|
+
export class BaseElement {
|
|
135
|
+
dispatch(name, data, options = { bubbles: true, cancelable: true, composed: true }) {
|
|
136
|
+
const event = new CustomEvent(name, {
|
|
137
|
+
bubbles: options.bubbles,
|
|
138
|
+
cancelable: options.cancelable,
|
|
139
|
+
composed: options.composed,
|
|
140
|
+
detail: data
|
|
141
|
+
});
|
|
142
|
+
this.dispatchEvent(event);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
You can of course still always build them yourself like above, but it is much easier and faster to use like this:
|
|
148
|
+
|
|
149
|
+
```javascript
|
|
150
|
+
export class MyElement extends BaseElement {
|
|
151
|
+
someMethod() {
|
|
152
|
+
this.dispatch('customEvent', { key: 'value' });
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
```
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
#### Global Styles / CSS
|
|
2
|
+
|
|
3
|
+
More often than not you will be building elements for a specific design/layout rather than encapsulated and abstract elements. It is therefore common to style things globally - especially since the rise of utility based CSS frameworks. When elements render in shadow DOM - they will be encapsulated and won't get any styles applied to them like the rest of the document.
|
|
4
|
+
|
|
5
|
+
There is no official and preferred way of sharing styles between documents today. _element-js_ therefore has a custom solution to this problem. The `TemplateElement` from _element-js_ has another constructor option named: `adoptGlobalStyles` which is `true` by default. When set to `true` _element-js_ will look for all styles (<style></style> and <link rel="stylesheet" />) in the document (head and body) and apply them before any custom/element styles inside the shadow DOM.
|
|
6
|
+
|
|
7
|
+
> Under the hood _element-js_ will copy/clone the global styles and cache them efficiently as `CSSStyleSheets` so that they can be adopted by multiple shadow DOM elements.
|
|
8
|
+
|
|
9
|
+
Example:
|
|
10
|
+
|
|
11
|
+
```html
|
|
12
|
+
<html>
|
|
13
|
+
<head>
|
|
14
|
+
<style id="globalStylesId">
|
|
15
|
+
.red {
|
|
16
|
+
color: red;
|
|
17
|
+
}
|
|
18
|
+
</style>
|
|
19
|
+
<link class="globalStylesClass" rel="stylesheet" href="blue-styles.css" />
|
|
20
|
+
</head>
|
|
21
|
+
<body>
|
|
22
|
+
<style>
|
|
23
|
+
.green {
|
|
24
|
+
color: green;
|
|
25
|
+
}
|
|
26
|
+
</style>
|
|
27
|
+
<link rel="stylesheet" href="yellow-styles.css" />
|
|
28
|
+
</body>
|
|
29
|
+
</html>
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
```javascript
|
|
33
|
+
export class ShadowElement extends TemplateElement {
|
|
34
|
+
constructor() {
|
|
35
|
+
super({ shadowRender: true, adoptGlobalStyles: true });
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
template() {
|
|
39
|
+
return html`
|
|
40
|
+
<p class="red">
|
|
41
|
+
I will be red although I am in shadow DOM and my element did not provide any CSS itself :)
|
|
42
|
+
</p>
|
|
43
|
+
<p class="green">
|
|
44
|
+
I will be green although I am in shadow DOM and my element did not provide any CSS itself :)
|
|
45
|
+
</p>
|
|
46
|
+
<p class="blue">
|
|
47
|
+
I will be blue although I am in shadow DOM and my element did not provide any CSS itself :)
|
|
48
|
+
</p>
|
|
49
|
+
<p class="yellow">
|
|
50
|
+
I will be yellow although I am in shadow DOM and my element did not provide any CSS itself :)
|
|
51
|
+
</p>
|
|
52
|
+
`;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Instead of adopting all global styles it is also possible to only apply a selection of styles from the global document.
|
|
58
|
+
|
|
59
|
+
```javascript
|
|
60
|
+
export class ShadowElement extends TemplateElement {
|
|
61
|
+
constructor() {
|
|
62
|
+
super({ shadowRender: true, adoptGlobalStyles: '#globalStylesId' });
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
template() {
|
|
66
|
+
return html`
|
|
67
|
+
<p class="red">
|
|
68
|
+
I will be red although I am in shadow DOM and my element did not provide any CSS itself :)
|
|
69
|
+
</p>
|
|
70
|
+
`;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
or
|
|
76
|
+
|
|
77
|
+
```javascript
|
|
78
|
+
export class ShadowElement extends TemplateElement {
|
|
79
|
+
constructor() {
|
|
80
|
+
super({ shadowRender: true, adoptGlobalStyles: ['#globalStylesId', '.globalStylesClass'] });
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
template() {
|
|
84
|
+
return html`
|
|
85
|
+
<p class="red">
|
|
86
|
+
I will be red although I am in shadow DOM and my element did not provide any CSS itself :)
|
|
87
|
+
</p>
|
|
88
|
+
<p class="blue">
|
|
89
|
+
I will be blue although I am in shadow DOM and my element did not provide any CSS itself :)
|
|
90
|
+
</p>
|
|
91
|
+
`;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
There is one special case for adopted styles in the document. Not only can shadow roots have `.adoptedStyleSheets`, but also the `document`. In case there would be global styles added there you can adopt them by using the special `document` selector.
|
|
97
|
+
|
|
98
|
+
```javascript
|
|
99
|
+
const globalStyle = new CSSStyleSheet();
|
|
100
|
+
globalStyle.replaceSync(`.red { color: red;}`);
|
|
101
|
+
document.adoptedStyleSheets.push(globalStyle);
|
|
102
|
+
|
|
103
|
+
export class ShadowElement extends TemplateElement {
|
|
104
|
+
constructor() {
|
|
105
|
+
super({ shadowRender: true, adoptGlobalStyles: 'document' });
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
template() {
|
|
109
|
+
return html`
|
|
110
|
+
<p class="red">
|
|
111
|
+
I will be red although I am in shadow DOM and my element did not provide any CSS itself :)
|
|
112
|
+
</p>
|
|
113
|
+
`;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
By default, _element-js_ will only apply all style elements from the global document that are present in the initial HTML. It will not observe the document for any changes regarding style elements.
|
|
119
|
+
|
|
120
|
+
> Observing global styles is done via a MutationObserver on the whole document. This might impose performance issues.
|
|
121
|
+
|
|
122
|
+
To enable global style observation you can add the following (inline, sync) script block to yor HTML.
|
|
123
|
+
|
|
124
|
+
```html
|
|
125
|
+
<script>
|
|
126
|
+
/** @type {ElementJsConfig} */
|
|
127
|
+
globalThis.elementJsConfig = {
|
|
128
|
+
observeGlobalStyles: true
|
|
129
|
+
};
|
|
130
|
+
</script>
|
|
131
|
+
```
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
|
|
2
|
+
### Methods
|
|
3
|
+
|
|
4
|
+
Besides public properties you can also shape the API for your element with public methods.
|
|
5
|
+
|
|
6
|
+
```javascript
|
|
7
|
+
import {BaseElement, defineElement} from '@webtides/element-js';
|
|
8
|
+
|
|
9
|
+
export class ModalElement extends BaseElement {
|
|
10
|
+
open() {
|
|
11
|
+
// do the things to actually open the modal…
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
defineElement('modal-element', ModalElement);
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
```javascript
|
|
19
|
+
import {BaseElement, defineElement} from '@webtides/element-js';
|
|
20
|
+
|
|
21
|
+
export class OtherElement extends BaseElement {
|
|
22
|
+
events() {
|
|
23
|
+
return {
|
|
24
|
+
this: {
|
|
25
|
+
click: () => {
|
|
26
|
+
this.$refs.modal.open();
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
defineElement('other-element', OtherElement);
|
|
34
|
+
```
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
## Attributes/Properties
|
|
2
|
+
|
|
3
|
+
With attributes and properties you can build out and define the public API of an element. _element-js_ elements can have
|
|
4
|
+
three different types of properties.
|
|
5
|
+
|
|
6
|
+
```javascript
|
|
7
|
+
export class MyElement extends BaseElement {
|
|
8
|
+
_privatePropery = 'I am private (by convention)';
|
|
9
|
+
publicProperty = 'I am public';
|
|
10
|
+
|
|
11
|
+
properties() {
|
|
12
|
+
return {
|
|
13
|
+
reactivePublicProperty: 'I am public & reactive'
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
log() {
|
|
18
|
+
console.log(this._privatePropery);
|
|
19
|
+
console.log(this.publicProperty);
|
|
20
|
+
console.log(this.reactivePublicProperty);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
defineElement('my-element', MyElement);
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
#### Defining properties in JavaScript
|
|
28
|
+
|
|
29
|
+
Public properties for JavaScript classes where recently added by the JavaScript standard committee. For more information
|
|
30
|
+
see
|
|
31
|
+
the [Class fields documentation on MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/Class_fields)
|
|
32
|
+
.
|
|
33
|
+
|
|
34
|
+
On top of these fields _element-js_ adds another form of properties - reactive properties. By overwriting
|
|
35
|
+
the `properties()` method on your element and returning an object of key/value pairs you can define a list of reactive
|
|
36
|
+
public properties. Behind the scenes _element-js_ will automatically generate getters and setters for each property and
|
|
37
|
+
trigger an update/render for the element when these properties change.
|
|
38
|
+
|
|
39
|
+
All types of properties can be accessed via the `this` operator within the element class or the template function.
|
|
40
|
+
|
|
41
|
+
#### Using attributes/properties in HTML
|
|
42
|
+
|
|
43
|
+
_element-js_ will merge all attributes with the reactive properties map. Values set via attribute will always take
|
|
44
|
+
precedence over values defined in the properties map. Attributes will still be defined as properties even when they are
|
|
45
|
+
not set in the properties map.
|
|
46
|
+
|
|
47
|
+
HTML only has the concept of attributes and `string` values. _element-js_ will automatically convert attributes to their
|
|
48
|
+
correct types of `string`, `number`, `boolean`, `array` and `object`.
|
|
49
|
+
|
|
50
|
+
> In JavaScript you will typically use camelCase for declaring properties. HTML attributes however only can be
|
|
51
|
+
> lowercase. _element-js_ will therefore convert dash-case names to camelCase variables and vice versa.
|
|
52
|
+
|
|
53
|
+
#### Property options
|
|
54
|
+
|
|
55
|
+
Reactive properties can be fine-tuned further by providing options via the constructor. See `propertyOptions`
|
|
56
|
+
in [Constructor options](../concepts/classes.md).
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
|
|
2
|
+
### Refs
|
|
3
|
+
|
|
4
|
+
To avoid constant lookups of (child) elements from your elements _element-js_ will collect all elements with
|
|
5
|
+
a `[ref=“id”]` attribute and make them available as an object on the element itself via the special `this.$refs`
|
|
6
|
+
reference.
|
|
7
|
+
_element-js_ also provides referencing a list of Nodes by adding a dangling `[]` to the refs id `[ref=“entries[]”]`.
|
|
8
|
+
Be aware that list references will override singular references with the same name as they are considered to be more
|
|
9
|
+
explicit.
|
|
10
|
+
|
|
11
|
+
```html
|
|
12
|
+
|
|
13
|
+
<places-search>
|
|
14
|
+
<input type="text" ref="input"/>
|
|
15
|
+
<places-list ref="list">
|
|
16
|
+
<list-entry ref="entries[]">A</list-entry>
|
|
17
|
+
<list-entry ref="entries[]">B</list-entry>
|
|
18
|
+
<list-entry ref="entries[]">C</list-entry>
|
|
19
|
+
</places-list>
|
|
20
|
+
</places-search>
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
```javascript
|
|
24
|
+
import {BaseElement, defineElement, html} from '@webtides/element-js';
|
|
25
|
+
|
|
26
|
+
export class PlacesSearch extends BaseElement {
|
|
27
|
+
events() {
|
|
28
|
+
return {
|
|
29
|
+
input: {
|
|
30
|
+
blur: (e) => {
|
|
31
|
+
// fetch places...
|
|
32
|
+
const places = [];
|
|
33
|
+
// update singular ref
|
|
34
|
+
this.$refs.list = places;
|
|
35
|
+
// do something with a list reference
|
|
36
|
+
this.$refs.entries.forEach(entry => console.log(entry.innerText)); // A , B , C
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
defineElement('places-search', PlacesSearch);
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
> _element-js_ will smartly add and remove all refs on every update/render cycle to ensure that they will always be
|
|
47
|
+
> correct even though you might have changed the child DOM tree during render cycles.
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
|
|
2
|
+
### Not Rendering Elements / BaseElement
|
|
3
|
+
|
|
4
|
+
More often than not you won't actually need to render anything from your element but rather only trigger some changes on
|
|
5
|
+
other elements, fire events or fetch data and distribute it to related elements.
|
|
6
|
+
|
|
7
|
+
A good example for this pattern is to use container elements for fetching data and delegating it to child elements that
|
|
8
|
+
only take attributes/properties and simply render them.
|
|
9
|
+
|
|
10
|
+
```html
|
|
11
|
+
|
|
12
|
+
<places-search>
|
|
13
|
+
<input type=“text" ref=“input”/>
|
|
14
|
+
<places-list ref="list”></places-list>
|
|
15
|
+
</places-search>
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
```javascript
|
|
19
|
+
import {BaseElement, defineElement} from '@webtides/element-js';
|
|
20
|
+
|
|
21
|
+
export class PlacesSearch extends BaseElement {
|
|
22
|
+
events() {
|
|
23
|
+
return {
|
|
24
|
+
input: {
|
|
25
|
+
blur: (e) => {
|
|
26
|
+
// fetch places...
|
|
27
|
+
const places = ['list of place objects…'];
|
|
28
|
+
this.$refs.list = places;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
defineElement('places-search', PlacesSearch);
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Another good use case for renderless elements is when you need to change some classes or styles on child elements. It is
|
|
39
|
+
a bit like `jQuery` but much better because everything is declaratively composed of little elements instead of having a
|
|
40
|
+
big JavaScript file full of little mutations.
|
|
41
|
+
|
|
42
|
+
```html
|
|
43
|
+
|
|
44
|
+
<dropdown-element>
|
|
45
|
+
<button>Options</button>
|
|
46
|
+
<ul ref="“list”" class="“”hidden">
|
|
47
|
+
<li>Profile</li>
|
|
48
|
+
<li>Settings</li>
|
|
49
|
+
<li>Sign out</li>
|
|
50
|
+
</ul>
|
|
51
|
+
</dropdown-element>
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
```javascript
|
|
55
|
+
import {BaseElement, defineElement} from '@webtides/element-js';
|
|
56
|
+
|
|
57
|
+
class DropdownElement extends BaseElement {
|
|
58
|
+
events() {
|
|
59
|
+
return {
|
|
60
|
+
button: {
|
|
61
|
+
click: () => {
|
|
62
|
+
this.$refs.list.classList.remove('hidden');
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
defineElement('dropdown-element', DropdownElement);
|
|
70
|
+
```
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
#### Shared State / Reactive State / Store
|
|
2
|
+
|
|
3
|
+
When a certain property in the properties map is an instance of the provided class `Store` it is treated as an
|
|
4
|
+
external `Store`.
|
|
5
|
+
A `Store` can be created outside a components´ scope or in a dedicated Module to keep track of shared Application
|
|
6
|
+
State.
|
|
7
|
+
When a components´ property is an instance of a `Store` the instance will be added as an observer and
|
|
8
|
+
automatically updated when the store ist changed.
|
|
9
|
+
|
|
10
|
+
Instances of Store provide a way to share global state between as many components in an application as you like.
|
|
11
|
+
Shared State can be something very simple as (updated) Viewport Dimensions, Media Changes or complex fetched data from a REST Endpoint.
|
|
12
|
+
|
|
13
|
+
Stores can also be initialized with a primitive value (Numbers, Booleans). Such stores will switch to a single Property Mode and provide direct access tio the property value via the stores valueOf / toString Function for direct access.
|
|
14
|
+
|
|
15
|
+
##### store.js
|
|
16
|
+
|
|
17
|
+
```js
|
|
18
|
+
export const simpleStore = new Store({
|
|
19
|
+
value: 'simple'
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
class MoreComplexStore extends Store {
|
|
23
|
+
properties() {
|
|
24
|
+
return {
|
|
25
|
+
storeCount: 1
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
get sum() {
|
|
29
|
+
return this.storeCount + this.argumentCount;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
export const exampleStore = new MoreComplexStore({
|
|
33
|
+
argumentCount: 1
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class MediaStore extends Store {
|
|
38
|
+
constructor() {
|
|
39
|
+
super();
|
|
40
|
+
const mql = window.matchMedia('(max-width: 600px)');
|
|
41
|
+
mql.onchange = (e) => {
|
|
42
|
+
this.isMobile = e.matches;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
properties() {
|
|
46
|
+
return {
|
|
47
|
+
isMobile: true
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
export const mediaStore = new MediaStore()
|
|
52
|
+
|
|
53
|
+
export const scrollYStore = new Store(0)
|
|
54
|
+
window.addEventListener('scroll', () => {
|
|
55
|
+
scrollYStore.value = window.scrollY;
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
##### MyElement.js
|
|
62
|
+
|
|
63
|
+
```js
|
|
64
|
+
|
|
65
|
+
import { exampleStore, simpleStore, mediaStore } from './store.js';
|
|
66
|
+
|
|
67
|
+
export class MyElement extends TemplateElement {
|
|
68
|
+
properties() {
|
|
69
|
+
return {
|
|
70
|
+
store: simpleStore,
|
|
71
|
+
exampleStore,
|
|
72
|
+
mediaStore,
|
|
73
|
+
scrollYStore
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
template() {
|
|
78
|
+
return `
|
|
79
|
+
<div>Simple Store: ${this.store.value} == "simple" </div>
|
|
80
|
+
<div>Complex Store: ${this.exampleStore.sum} == 2</div>
|
|
81
|
+
<div>Media: ${this.mediaStore.isMobile ? "MOBILE" : "DESKTOP"}</div>
|
|
82
|
+
<div>Scoll Position: ${this.scrollYStore}</div>
|
|
83
|
+
`
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export class AnotherElement extends TemplateElement {
|
|
88
|
+
properties() {
|
|
89
|
+
return {
|
|
90
|
+
store: simpleStore,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
template() {
|
|
95
|
+
return `
|
|
96
|
+
<div>Simple Store: ${this.store.value} == the same as in MyElement's template.</div>
|
|
97
|
+
`
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
##### storeception
|
|
105
|
+
|
|
106
|
+
Changes too nested stores will trigger updates and watcher in ParentStore and ultimately transfer to all elements referencing the parent
|
|
107
|
+
|
|
108
|
+
```js
|
|
109
|
+
|
|
110
|
+
class NestedStore extends Store {
|
|
111
|
+
properties() {
|
|
112
|
+
return {
|
|
113
|
+
nestedCount: 0,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
class ParentStore extends Store {
|
|
119
|
+
properties() {
|
|
120
|
+
return {
|
|
121
|
+
count: 0,
|
|
122
|
+
nestedStore: new NestedStore(),
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
requestUpdate() {
|
|
127
|
+
// will be triggered on count OR nestedStore.nestedCount changes
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
watch() {
|
|
131
|
+
return {
|
|
132
|
+
count: (newCount, oldCount) => {
|
|
133
|
+
// will be triggered on count changes
|
|
134
|
+
},
|
|
135
|
+
nestedStore: () => {
|
|
136
|
+
// will be triggered on nestedStore changes
|
|
137
|
+
},
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
```
|
|
143
|
+
|