Skip to content

Commit 304d578

Browse files
committed
up
1 parent 79324d0 commit 304d578

File tree

4 files changed

+107
-67
lines changed

4 files changed

+107
-67
lines changed

Diff for: 8-web-components/1-webcomponents-intro/article.md

+12-14
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@ This section describes a set of modern standards for "web components".
44

55
As of now, these standards are under development. Some features are well-supported and integrated into the modern HTML/DOM standard, while others are yet in draft stage. You can try examples in any browser, Google Chrome is probably the most up to date with these features. Guess, that's because Google fellows are behind many of the related specifications.
66

7-
The whole component idea is nothing new. It's used in many frameworks and elsewhere.
8-
97
## What's common between...
108

9+
The whole component idea is nothing new. It's used in many frameworks and elsewhere.
10+
1111
Before we move to implementation details, take a look at this great achievement of humanity:
1212

1313
![](satellite.jpg)
@@ -24,11 +24,11 @@ The International Space Station:
2424
- The components are very complex, much more complicated than most websites.
2525
- Components are developed internationally, by teams from different countries, speaking different languages.
2626

27-
...And this thing is flying, keeping humans alive in space!
27+
...And this thing flies, keeps humans alive in space!
2828

2929
How such complex devices are created?
3030

31-
Which principles we could borrow, to make our development same-level reliable and scalable? Or, at least, close to it.
31+
Which principles we could borrow to make our development same-level reliable and scalable? Or, at least, close to it.
3232

3333
## Component architecture
3434

@@ -38,7 +38,7 @@ If something becomes complex -- split it into simpler parts and connect in the m
3838

3939
**A good architect is the one who can make the complex simple.**
4040

41-
We can split a user interface into components -- visual entities, each of them has own place on the page, can "do" a well-described task, and is separate from the others.
41+
We can split user interface into visual components: each of them has own place on the page, can "do" a well-described task, and is separate from the others.
4242

4343
Let's take a look at a website, for example Twitter.
4444

@@ -50,27 +50,25 @@ It naturally splits into components:
5050
2. User info.
5151
3. Follow suggestions.
5252
4. Submit form.
53-
5. And also 6, 7 - messages.
53+
5. (and also 6, 7) -- messages.
5454

5555
Components may have subcomponents, e.g. messages may be parts of a higher-level "message list" component. A clickable user picture itself may be a component, and so on.
5656

57-
How do we decide, what is a component? That comes from intuition, experience and common sense. In the case above, the page has blocks, each of them plays its own role.
58-
59-
So, what comprises a component?
57+
How do we decide, what is a component? That comes from intuition, experience and common sense. Usually it's a separate visual entity that we can describe in terms of what it does and how it interacts with the page. In the case above, the page has blocks, each of them plays its own role, it's logical to make these components.
6058

6159
- A component has its own JavaScript class.
6260
- DOM structure, managed solely by its class, outside code doesn't access it ("encapsulation" principle).
6361
- CSS styles, applied to the component.
6462
- API: events, class methods etc, to interact with other components.
6563

66-
"Web components" provide built-in browser capabilities for components:
64+
Once again, the whole "component" thing is nothing special.
65+
66+
There exist many frameworks and development methodologies to build them, each one with its own bells and whistles. Usually, special CSS classes and conventions are used to provide "component feel" -- CSS scoping and DOM encapsulation.
67+
68+
"Web components" provide built-in browser capabilities for that, so we don't have to emulate them any more.
6769

6870
- [Custom elements](https://html.spec.whatwg.org/multipage/custom-elements.html#custom-elements) -- to define custom HTML elements.
6971
- [Shadow DOM](https://dom.spec.whatwg.org/#shadow-trees) -- to create an internal DOM for the component, hidden from the others.
7072
- [CSS Scoping](https://drafts.csswg.org/css-scoping/) -- to declare styles that only apply inside the Shadow DOM of the component.
7173

72-
There exist many frameworks and development methodologies that aim to do the similar thing, each one with its own bells and whistles. Usually, special CSS classes and conventions are used to provide "component feel" -- CSS scoping and DOM encapsulation.
73-
74-
Web components provide built-in browser capabilities for that, so we don't have to emulate them any more.
75-
7674
In the next chapter we'll go into details of "Custom Elements" -- the fundamental and well-supported feature of web components, good on its own.

Diff for: 8-web-components/2-custom-elements/article.md

+85-44
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,31 @@
11

22
# Custom elements
33

4-
We can create our own class for a custom HTML element with its own methods and properties, events and so on.
4+
We can create custom HTML elements, described by our class, with its own methods and properties, events and so on.
5+
6+
Once an custom element is defined, we can use it on par with built-in HTML elements.
7+
8+
That's great, as HTML dictionary is rich, but not infinite. There are no `<easy-tabs>`, `<sliding-carousel>`, `<beautiful-upload>`... Just think of any other tag we might need.
9+
10+
We can define them with a special class, and then use as if they were always a part of HTML.
511

612
There are two kinds of custom elements:
713

814
1. **Autonomous custom elements** -- "all-new" elements, extending the abstract `HTMLElement` class.
915
2. **Customized built-in elements** -- extending built-in elements, like customized `HTMLButtonElement` etc.
1016

11-
First we'll see how autonomous elements are made, and then the customized built-in ones.
17+
First we'll create autonomous elements, and then customized built-in ones.
18+
19+
To create a custom element, we need to tell the browser several details about it: how to show it, what to do when the element is added or removed to page, etc.
1220

13-
For a class to describe an element, it should support so-called "custom element reactions" -- methods that the browser calls when our element is created/added/removed from DOM.
21+
That's done by making a class with special methods. That's easy, as there are only few methods, and all of them are optional.
1422

15-
That's easy, as there are only few of them. Here's a sketch with the full list:
23+
Here's a sketch with the full list:
1624

1725
```js
1826
class MyElement extends HTMLElement {
1927
constructor() {
28+
super();
2029
// element created
2130
}
2231

@@ -42,17 +51,19 @@ class MyElement extends HTMLElement {
4251
// called when the element is moved to a new document
4352
// (happens in document.adoptNode, very rarely used)
4453
}
54+
55+
// there can be other element methods and properties
4556
}
4657
```
4758

48-
Then we need to register the element:
59+
After that, we need to register the element:
4960

5061
```js
5162
// let the browser know that <my-element> is served by our new class
5263
customElements.define("my-element", MyElement);
5364
```
5465

55-
Now for any new elements with tag `my-element`, an instance of `MyElement` is created, and the aforementioned methods are called.
66+
Now for any HTML elements with tag `<my-element>`, an instance of `MyElement` is created, and the aforementioned methods are called. We also can `document.createElement('my-element')` in JavaScript.
5667

5768
```smart header="Custom element name must contain a hyphen `-`"
5869
Custom element name must have a hyphen `-`, e.g. `my-element` and `super-button` are valid names, but `myelement` is not.
@@ -64,12 +75,14 @@ That's to ensure that there are no name conflicts between built-in and custom HT
6475
6576
For example, there already exists `<time>` element in HTML, for date/time. But it doesn't do any formatting by itself.
6677
67-
Let's create `<time-formatted>` element that does the formatting:
78+
Let's create `<time-formatted>` element that displays the time in a nice, language-aware format:
6879
6980
7081
```html run height=50 autorun="no-epub"
7182
<script>
72-
class TimeFormatted extends HTMLElement {
83+
*!*
84+
class TimeFormatted extends HTMLElement { // (1)
85+
*/!*
7386
7487
connectedCallback() {
7588
let date = new Date(this.getAttribute('datetime') || Date.now());
@@ -87,51 +100,58 @@ class TimeFormatted extends HTMLElement {
87100
88101
}
89102
90-
customElements.define("time-formatted", TimeFormatted);
103+
*!*
104+
customElements.define("time-formatted", TimeFormatted); // (2)
105+
*/!*
91106
</script>
92107
93-
<time-formatted
94-
datetime="2019-12-01"
108+
<!-- (3) -->
109+
*!*
110+
<time-formatted datetime="2019-12-01"
111+
*/!*
95112
year="numeric" month="long" day="numeric"
96113
hour="numeric" minute="numeric" second="numeric"
97114
time-zone-name="short"
98115
></time-formatted>
99116
```
100117

101-
As the result, `<time-formatted>` shows a nicely formatted time, according to the browser timezone and locale. We use the built-in [Intl.DateTimeFormat](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DateTimeFormat) data formatter, well-supported across the browsers.
118+
1. The class has only one method `connectedCallback()` -- the browser calls it when `<time-formatted>` element is added to page (or when HTML parser detects it), and it uses the built-in [Intl.DateTimeFormat](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DateTimeFormat) data formatter, well-supported across the browsers, to show a nicely formatted time.
119+
2. We need to register our new element by `customElements.define(tag, class)`.
120+
3. And then we can use it everywhere.
121+
102122

103123
```smart header="Custom elements upgrade"
104-
If the browser encounters any `<time-formatted>` elements before `customElements.define` call, they are yet unknown, just like any non-standard tag.
124+
If the browser encounters any `<time-formatted>` elements before `customElements.define`, that's not an error. But the element is yet unknown, just like any non-standard tag.
105125
106-
They can be styled with CSS selector `:not(:defined)`.
126+
Such "undefined" elements can be styled with CSS selector `:not(:defined)`.
107127
108128
When `customElement.define` is called, they are "upgraded": a new instance of `TimeFormatted`
109129
is created for each, and `connectedCallback` is called. They become `:defined`.
110130
111-
To track custom elements from JavaScript, there are methods:
112-
- `customElements.get(name)` -- returns the class for a defined custom element with the given `name`,
113-
- `customElements.whenDefined(name)` -- returns a promise that resolves (without value) when a custom element with the given `name` is defined.
131+
To get the information about custom elements, there are methods:
132+
- `customElements.get(name)` -- returns the class for a custom element with the given `name`,
133+
- `customElements.whenDefined(name)` -- returns a promise that resolves (without value) when a custom element with the given `name` becomes defined.
114134
```
115135

116-
117-
118136
```smart header="Rendering in `connectedCallback`, not in `constructor`"
119137
In the example above, element content is rendered (created) in `connectedCallback`.
120138

121139
Why not in the `constructor`?
122140

123-
The reason is simple: when `constructor` is called, it's yet too early. The element instance is created, but not populated yet. We don't have attributes at this stage: calls to `getAttribute` always return `null`. So we can't really render there.
141+
The reason is simple: when `constructor` is called, it's yet too early. The element instance is created, but not populated yet. The browser did not yet process/assign attributes at this stage: calls to `getAttribute` would return `null`. So we can't really render there.
124142

125-
Besides, if you think about it, that's be tter performance-wise -- to delay the work until it's really needed.
143+
Besides, if you think about it, that's better performance-wise -- to delay the work until it's really needed.
126144

127-
The `connectedCallback` triggers when the element is in the document, not just appended to another element as a child. So we can build detached DOM, create elements and prepare them for later use. They will only be actually rendered when they make it into the page.
145+
The `connectedCallback` triggers when the element is added to the document. Not just appended to another element as a child, but actually becomes a part of the page. So we can build detached DOM, create elements and prepare them for later use. They will only be actually rendered when they make it into the page.
128146
```
129147
130148
## Observing attributes
131149
132-
Please note that in the current implementation, after the element is rendered, further attribute changes don't have any effect. That's strange for an HTML element. Usually, when we change an attribute, like `a.href`, the change is immediately visible. So let's fix this.
150+
In the current implementation of `<time-formatted>`, after the element is rendered, further attribute changes don't have any effect. That's strange for an HTML element. Usually, when we change an attribute, like `a.href`, we expect the change to be immediately visible. So let's fix this.
133151
134-
We can observe attributes by providing their list in `observedAttributes()` static getter, and then update the element in `attributeChangedCallback`. It's called for changes only in the listed attributes for performance reasons.
152+
We can observe attributes by providing their list in `observedAttributes()` static getter. For such attributes, `attributeChangedCallback` is called when they are modified. It doesn't trigger for an attribute for performance reasons.
153+
154+
Here's a new `<time-formatted>`, that auto-updates when attributes change:
135155
136156
```html run autorun="no-epub" height=50
137157
<script>
@@ -196,9 +216,9 @@ setInterval(() => elem.setAttribute('datetime', new Date()), 1000); // (5)
196216

197217
## Rendering order
198218

199-
When HTML parser builds the DOM, elements are processed one after another, parents before children. E.g. is we have `<outer><inner></inner></outer>`, then `<outer>` is created and connected to DOM first, and then `<inner>`.
219+
When HTML parser builds the DOM, elements are processed one after another, parents before children. E.g. if we have `<outer><inner></inner></outer>`, then `<outer>` element is created and connected to DOM first, and then `<inner>`.
200220

201-
That leads to important side effects for custom elements.
221+
That leads to important consequences for custom elements.
202222

203223
For example, if a custom element tries to access `innerHTML` in `connectedCallback`, it gets nothing:
204224

@@ -207,7 +227,9 @@ For example, if a custom element tries to access `innerHTML` in `connectedCallba
207227
customElements.define('user-info', class extends HTMLElement {
208228
209229
connectedCallback() {
210-
alert(this.innerHTML);
230+
*!*
231+
alert(this.innerHTML); // empty (*)
232+
*/!*
211233
}
212234
213235
});
@@ -220,7 +242,9 @@ customElements.define('user-info', class extends HTMLElement {
220242

221243
If you run it, the `alert` is empty.
222244

223-
That's exactly because there are no children on that stage, the DOM is unfinished yet. So, if we'd like to pass information to custom element, we can use attributes. They are available immediately.
245+
That's exactly because there are no children on that stage, the DOM is unfinished. HTML parser connected the custom element `<user-info>`, and will now proceed to its children, but just didn't yet.
246+
247+
If we'd like to pass information to custom element, we can use attributes. They are available immediately.
224248

225249
Or, if we really need the children, we can defer access to them with zero-delay `setTimeout`.
226250

@@ -232,7 +256,7 @@ customElements.define('user-info', class extends HTMLElement {
232256
233257
connectedCallback() {
234258
*!*
235-
setTimeout(() => alert(this.innerHTML));
259+
setTimeout(() => alert(this.innerHTML)); // John (*)
236260
*/!*
237261
}
238262
@@ -244,13 +268,13 @@ customElements.define('user-info', class extends HTMLElement {
244268
*/!*
245269
```
246270

247-
Now in `setTimeout` we can get the contents of the element and finish the initialization.
271+
Now the `alert` in line `(*)` shows "John", as we run it asynchronously, after the HTML parsing is complete. We can process children if needed and finish the initialization.
248272

249273
On the other hand, this solution is also not perfect. If nested custom elements also use `setTimeout` to initialize themselves, then they queue up: the outer `setTimeout` triggers first, and then the inner one.
250274

251275
So the outer element finishes the initialization before the inner one.
252276

253-
For example:
277+
Let's demonstrate that on example:
254278

255279
```html run height=0
256280
<script>
@@ -276,49 +300,66 @@ Output order:
276300
2. outer initialized.
277301
4. inner initialized.
278302

279-
If we'd like the outer element to wait for inner ones, then there's no built-in reliable solution. But we can invent one. For instance, inner elements can dispatch events like `initialized`, and outer ones can listen and react on them.
303+
We can clearly see that the outer element does not wait for the inner one.
304+
305+
There's no built-in callback that triggers after nested elements are ready. But we can implement such thing on our own. For instance, inner elements can dispatch events like `initialized`, and outer ones can listen and react on them.
280306

281307
## Customized built-in elements
282308

283-
New custom elements like `<time-formatted>` don't have any associated semantics. They are totally new to search engines and accessibility devices.
309+
New elements that we create, such as `<time-formatted>`, don't have any associated semantics. They are unknown to search engines, and accessibility devices can't handle them.
310+
311+
But such things can be important. E.g, a search engine would be interested to know that we actually show a time. And if we're making a special kind of button, why not reuse the existing `<button>` functionality?
284312

285-
We could use special [ARIA](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA) attributes to describe the semantic. But if we're going to make a special button, why not extend a `<button>` element itself?
313+
We can extend and customize built-in elements by inheriting from their classes.
286314

287-
Built-in elements can be customized by inheriting from their classes. HTML buttons are instances of `HTMLButtonElement`, so let's extend it:
315+
For example, buttons are instances of `HTMLButtonElement`, let's build upon it.
316+
317+
1. Extend `HTMLButtonElement` with our class:
318+
319+
```js
320+
class HelloButton extends HTMLButtonElement { /* custom element methods */ }
321+
```
322+
323+
2. Provide an third argument to `customElements.define`, that specifies the tag:
324+
```js
325+
customElements.define('hello-button', HelloButton, *!*{extends: 'button'}*/!*);
326+
```
327+
There exist different tags that share the same class, that's why it's needed.
328+
329+
3. At the end, to use our custom element, insert a regular `<button>` tag, but add `is="hello-button"` to it:
330+
```html
331+
<button is="hello-button">...</button>
332+
```
333+
334+
Here's a full example:
288335
289336
```html run autorun="no-epub"
290337
<script>
291338
// The button that says "hello" on click
292339
class HelloButton extends HTMLButtonElement {
293340
*!*
294-
constructor() { // (1)
341+
constructor() {
295342
*/!*
296343
super();
297344
this.addEventListener('click', () => alert("Hello!"));
298345
}
299346
}
300347
301348
*!*
302-
customElements.define('hello-button', HelloButton, {extends: 'button'}); // 2
349+
customElements.define('hello-button', HelloButton, {extends: 'button'});
303350
*/!*
304351
</script>
305352
306-
<!-- 3 -->
307353
*!*
308354
<button is="hello-button">Click me</button>
309355
*/!*
310356
311-
<!-- 4 -->
312357
*!*
313358
<button is="hello-button" disabled>Disabled</button>
314359
*/!*
315360
```
316361
317-
1. We constructor add an event listener to the element. Please note: we must call `super()` before anything else (that's pure JS requirement).
318-
2. To extend a built-in element, we must specify `{extends: '<tag>'}` in the define. Some tags share the same HTML class, so we need to be precise here.
319-
3. Now we can use a regular `<button>` tag, labelled with `is="hello-button"`.
320-
4. Our buttons extend built-in ones, so they retain the standard features like `disabled` attribute.
321-
362+
Our new button extends the built-in one. So it keeps the same styles and standard features like `disabled` attribute.
322363
323364
## Итого
324365

0 commit comments

Comments
 (0)