This commit is contained in:
Alfiya 2019-06-24 08:28:19 +03:00
commit 5404c22613
119 changed files with 2504 additions and 899 deletions

View file

@ -34,7 +34,7 @@ alert(window.innerHeight); // inner window height
There are more window-specific methods and properties, we'll cover them later.
## Document Object Model (DOM)
## DOM (Document Object Model)
The `document` object gives access to the page content. We can change or create anything on the page using it.
@ -47,20 +47,9 @@ document.body.style.background = "red";
setTimeout(() => document.body.style.background = "", 1000);
```
Here we used `document.body.style`, but there's much, much more. Properties and methods are described in the specification. There happen to be two working groups who develop it:
Here we used `document.body.style`, but there's much, much more. Properties and methods are described in the specification:
1. [W3C](https://en.wikipedia.org/wiki/World_Wide_Web_Consortium) -- the documentation is at <https://www.w3.org/TR/dom>.
2. [WhatWG](https://en.wikipedia.org/wiki/WHATWG), publishing at <https://dom.spec.whatwg.org>.
As it happens, the two groups don't always agree, so it's like we have two sets of standards. But they are very similar and eventually things merge. The documentation that you can find on the given resources is very similar, with about a 99% match. There are very minor differences that you probably won't notice.
Personally, I find <https://dom.spec.whatwg.org> more pleasant to use.
In the ancient past, there was no standard at all -- each browser implemented however it wanted. Different browsers had different sets, methods, and properties for the same thing, and developers had to write different code for each of them. Dark, messy times.
Even now we can sometimes meet old code that uses browser-specific properties and works around incompatibilities. But, in this tutorial we'll use modern stuff: there's no need to learn old things until you really need to (chances are high that you won't).
Then the DOM standard appeared, in an attempt to bring everyone to an agreement. The first version was "DOM Level 1", then it was extended by DOM Level 2, then DOM Level 3, and now it's reached DOM Level 4. People from WhatWG group got tired of version numbers and are calling it just "DOM", without a number. So we'll do the same.
- **DOM Living Standard** at <https://dom.spec.whatwg.org>
```smart header="DOM is not only for browsers"
The DOM specification explains the structure of a document and provides objects to manipulate it. There are non-browser instruments that use it too.
@ -74,7 +63,7 @@ CSS rules and stylesheets are not structured like HTML. There's a separate speci
CSSOM is used together with DOM when we modify style rules for the document. In practice though, CSSOM is rarely required, because usually CSS rules are static. We rarely need to add/remove CSS rules from JavaScript, so we won't cover it right now.
```
## BOM (part of HTML spec)
## BOM (Browser object model)
Browser Object Model (BOM) are additional objects provided by the browser (host environment) to work with everything except the document.
@ -94,12 +83,9 @@ if (confirm("Go to wikipedia?")) {
Functions `alert/confirm/prompt` are also a part of BOM: they are directly not related to the document, but represent pure browser methods of communicating with the user.
```smart header="HTML specification"
BOM is the part of the general [HTML specification](https://html.spec.whatwg.org).
Yes, you heard that right. The HTML spec at <https://html.spec.whatwg.org> is not only about the "HTML language" (tags, attributes), but also covers a bunch of objects, methods and browser-specific DOM extensions. That's "HTML in broad terms".
```
Yes, you heard that right. The HTML spec at <https://html.spec.whatwg.org> is not only about the "HTML language" (tags, attributes), but also covers a bunch of objects, methods and browser-specific DOM extensions. That's "HTML in broad terms". Also, some parts have additional specs listed at <https://spec.whatwg.org>.
## Summary
@ -114,8 +100,12 @@ CSSOM specification
HTML specification
: Describes the HTML language (e.g. tags) and also the BOM (browser object model) -- various browser functions: `setTimeout`, `alert`, `location` and so on, see <https://html.spec.whatwg.org>. It takes the DOM specification and extends it with many additional properties and methods.
Additionally, some classes are described separately at <https://spec.whatwg.org/>.
Please note these links, as there's so much stuff to learn it's impossible to cover and remember everything.
When you'd like to read about a property or a method, the Mozilla manual at <https://developer.mozilla.org/en-US/search> is also a nice resource, but the corresponding spec may be better: it's more complex and longer to read, but will make your fundamental knowledge sound and complete.
To find something, it's often convenient to use an internet search "WHATWG [term]" or "MDN [term]", e.g <https://google.com?q=whatwg+localstorage>, <https://google.com?q=mdn+localstorage>.
Now we'll get down to learning DOM, because the document plays the central role in the UI.
Please note the links above, as there's so much stuff to learn it's impossible to cover and remember everything.
When you'd like to read about a property or a method, the Mozilla manual at <https://developer.mozilla.org/en-US/search> is a nice resource, but reading the corresponding spec may be better: it's more complex and longer to read, but will make your fundamental knowledge sound and complete.

View file

@ -6,7 +6,9 @@ for (let li of document.querySelectorAll('li')) {
}
```
In the loop we need to get the text inside every `li`. We can read it directly from the first child node, that is the text node:
In the loop we need to get the text inside every `li`.
We can read the text from the first child node of `li`, that is the text node:
```js
for (let li of document.querySelectorAll('li')) {
@ -16,4 +18,4 @@ for (let li of document.querySelectorAll('li')) {
}
```
Then we can get the number of descendants `li.getElementsByTagName('li')`.
Then we can get the number of descendants as `li.getElementsByTagName('li').length`.

View file

@ -27,7 +27,7 @@ Also, there's a reference to the constructor function inside the `prototype`:
alert(HTMLDocument.prototype.constructor === HTMLDocument); // true
```
For built-in classes in all prototypes there's a `constructor` reference, and we can get `constructor.name` to see the name of the class. Let's do it for all objects in the `document` prototype chain:
To get a name of the class as a string, we can use `constructor.name`. Let's do it for the whole `document` prototype chain, till class `Node`:
```js run
alert(HTMLDocument.prototype.constructor.name); // HTMLDocument

View file

@ -20,7 +20,7 @@ The classes are:
- [EventTarget](https://dom.spec.whatwg.org/#eventtarget) -- is the root "abstract" class. Objects of that class are never created. It serves as a base, so that all DOM nodes support so-called "events", we'll study them later.
- [Node](http://dom.spec.whatwg.org/#interface-node) -- is also an "abstract" class, serving as a base for DOM nodes. It provides the core tree functionality: `parentNode`, `nextSibling`, `childNodes` and so on (they are getters). Objects of `Node` class are never created. But there are concrete node classes that inherit from it, namely: `Text` for text nodes, `Element` for element nodes and more exotic ones like `Comment` for comment nodes.
- [Element](http://dom.spec.whatwg.org/#interface-element) -- is a base class for DOM elements. It provides element-level navigation like `nextElementSibling`, `children` and searching methods like `getElementsByTagName`, `querySelector`. In the browser there may be not only HTML, but also XML and SVG documents. The `Element` class serves as a base for more specific classes: `SVGElement`, `XMLElement` and `HTMLElement`.
- [Element](http://dom.spec.whatwg.org/#interface-element) -- is a base class for DOM elements. It provides element-level navigation like `nextElementSibling`, `children` and searching methods like `getElementsByTagName`, `querySelector`. A browser supports not only HTML, but also XML and SVG. The `Element` class serves as a base for more specific classes: `SVGElement`, `XMLElement` and `HTMLElement`.
- [HTMLElement](https://html.spec.whatwg.org/multipage/dom.html#htmlelement) -- is finally the basic class for all HTML elements. It is inherited by various HTML elements:
- [HTMLInputElement](https://html.spec.whatwg.org/multipage/forms.html#htmlinputelement) -- the class for `<input>` elements,
- [HTMLBodyElement](https://html.spec.whatwg.org/multipage/semantics.html#htmlbodyelement) -- the class for `<body>` elements,
@ -76,7 +76,7 @@ Try it on `document.body`.
```
````smart header="IDL in the spec"
In the specification, classes are described not using JavaScript, but a special [Interface description language](https://en.wikipedia.org/wiki/Interface_description_language) (IDL), that is usually easy to understand.
In the specification, DOM classes are described not using JavaScript, but a special [Interface description language](https://en.wikipedia.org/wiki/Interface_description_language) (IDL), that is usually easy to understand.
In IDL all properties are prepended with their types. For instance, `DOMString`, `boolean` and so on.
@ -156,7 +156,7 @@ alert( document.body.nodeName ); // BODY
alert( document.body.tagName ); // BODY
```
Is there any difference between tagName and nodeName?
Is there any difference between `tagName` and `nodeName`?
Sure, the difference is reflected in their names, but is indeed a bit subtle.
@ -175,11 +175,11 @@ For instance, let's compare `tagName` and `nodeName` for the `document` and a co
<script>
// for comment
alert( document.body.firstChild.tagName ); // undefined (no element)
alert( document.body.firstChild.tagName ); // undefined (not an element)
alert( document.body.firstChild.nodeName ); // #comment
// for document
alert( document.tagName ); // undefined (not element)
alert( document.tagName ); // undefined (not an element)
alert( document.nodeName ); // #document
</script>
</body>
@ -232,14 +232,12 @@ We can try to insert invalid HTML, the browser will fix our errors:
```
```smart header="Scripts don't execute"
If `innerHTML` inserts a `<script>` tag into the document -- it doesn't execute.
It becomes a part of HTML, just as a script that has already run.
If `innerHTML` inserts a `<script>` tag into the document -- it becomes a part of HTML, but doesn't execute.
```
### Beware: "innerHTML+=" does a full overwrite
We can append "more HTML" by using `elem.innerHTML+="something"`.
We can append HTML to an element by using `elem.innerHTML+="more html"`.
Like this:
@ -327,7 +325,7 @@ The `innerHTML` property is only valid for element nodes.
Other node types have their counterpart: `nodeValue` and `data` properties. These two are almost the same for practical use, there are only minor specification differences. So we'll use `data`, because it's shorter.
We can read it, like this:
An example of reading the content of a text node and a comment:
```html run height="50"
<body>
@ -347,7 +345,7 @@ We can read it, like this:
</body>
```
For text nodes we can imagine a reason to read or modify them, but why comments? Usually, they are not interesting at all, but sometimes developers embed information into HTML in them, like this:
For text nodes we can imagine a reason to read or modify them, but why comments? Usually, they are not interesting at all, but sometimes developers embed information or template instructions into HTML in them, like this:
```html
<!-- if isAdmin -->
@ -470,7 +468,7 @@ Each DOM node belongs to a certain class. The classes form a hierarchy. The full
Main DOM node properties are:
`nodeType`
: We can get `nodeType` from the DOM object class, but often we need just to see if it is a text or element node. The `nodeType` property is good for that. It has numeric values, most important are: `1` -- for elements,`3` -- for text nodes. Read-only.
: We can use it to see if a node is a text or an element node. It has a numeric value: `1` -- for elements,`3` -- for text nodes, and few other for other node types. Read-only.
`nodeName/tagName`
: For elements, tag name (uppercased unless XML-mode). For non-element nodes `nodeName` describes what it is. Read-only.
@ -485,11 +483,11 @@ Main DOM node properties are:
: The content of a non-element node (text, comment). These two are almost the same, usually we use `data`. Can be modified.
`textContent`
: The text inside the element, basically HTML minus all `<tags>`. Writing into it puts the text inside the element, with all special characters and tags treated exactly as text. Can safely insert user-generated text and protect from unwanted HTML insertions.
: The text inside the element: HTML minus all `<tags>`. Writing into it puts the text inside the element, with all special characters and tags treated exactly as text. Can safely insert user-generated text and protect from unwanted HTML insertions.
`hidden`
: When set to `true`, does the same as CSS `display:none`.
DOM nodes also have other properties depending on their class. For instance, `<input>` elements (`HTMLInputElement`) support `value`, `type`, while `<a>` elements (`HTMLAnchorElement`) support `href` etc. Most standard HTML attributes have a corresponding DOM property.
But HTML attributes and DOM properties are not always the same, as we'll see in the next chapter.
Although, HTML attributes and DOM properties are not always the same, as we'll see in the next chapter.

View file

@ -17,7 +17,7 @@
"oak": {}
},
"Flowering": {
"redbud": {},
"apple tree": {},
"magnolia": {}
}
}

View file

@ -17,7 +17,7 @@
"oak": {}
},
"Flowering": {
"redbud": {},
"apple tree": {},
"magnolia": {}
}
}

View file

@ -28,7 +28,7 @@
</li>
<li>Flowering
<ul>
<li>redbud</li>
<li>apple tree</li>
<li>magnolia</li>
</ul>
</li>
@ -51,7 +51,7 @@
"oak": {}
},
"Flowering": {
"redbud": {},
"apple tree": {},
"magnolia": {}
}
}

View file

@ -21,7 +21,7 @@ let data = {
"oak": {}
},
"Flowering": {
"redbud": {},
"apple tree": {},
"magnolia": {}
}
}

View file

@ -38,7 +38,7 @@ That was an HTML example. Now let's create the same `div` with JavaScript (assum
To create DOM nodes, there are two methods:
`document.createElement(tag)`
: Creates a new element with the given tag:
: Creates a new *element node* with the given tag:
```js
let div = document.createElement('div');
@ -67,7 +67,7 @@ After that, we have our DOM element ready. Right now it is just in a variable an
To make the `div` show up, we need to insert it somewhere into `document`. For instance, in `document.body`.
There's a special method for that: `document.body.appendChild(div)`.
There's a special method `appendChild` for that: `document.body.appendChild(div)`.
Here's the full code:
@ -146,9 +146,9 @@ Here's a brief list of methods to insert a node into a parent element (`parentEl
All these methods return the inserted node. In other words, `parentElem.appendChild(node)` returns `node`. But usually the returned value is not used, we just run the method.
These methods are "old school": they exist from the ancient times and we can meet them in many old scripts. Unfortunately, there are some tasks that are hard to solve with them.
These methods are "old school": they exist from the ancient times and we can meet them in many old scripts. Unfortunately, they are not flexible enough.
For instance, how to insert *html* if we have it as a string? Or, given a node, how to insert another node *before* it? Of course, all that is doable, but not in an elegant way.
For instance, how to insert *html* if we have it as a string? Or, given a node, without reference to its parent, how to remove it? Of course, that's doable, but not in an elegant way.
So there exist two other sets of insertion methods to handle all cases easily.
@ -162,6 +162,8 @@ This set of methods provides more flexible insertions:
- `node.after(...nodes or strings)` - insert nodes or strings after the `node`,
- `node.replaceWith(...nodes or strings)` - replaces `node` with the given nodes or strings.
All of them accept a list of DOM nodes and/or text strings. If a string is given it's inserted as a text node.
Here's an example of using these methods to add more items to a list and the text before/after it:
```html autorun
@ -236,14 +238,14 @@ But what if we want to insert HTML "as html", with all tags and stuff working, l
There's another, pretty versatile method: `elem.insertAdjacentHTML(where, html)`.
The first parameter is a string, specifying where to insert. Must be one of the following:
The first parameter is a code word, specifying where to insert relative to `elem`. Must be one of the following:
- `"beforebegin"` -- insert `html` before `elem`,
- `"beforebegin"` -- insert `html` immediately before `elem`,
- `"afterbegin"` -- insert `html` into `elem`, at the beginning,
- `"beforeend"` -- insert `html` into `elem`, at the end,
- `"afterend"` -- insert `html` after `elem`.
- `"afterend"` -- insert `html` immediately after `elem`.
The second parameter is an HTML string, inserted "as is".
The second parameter is an HTML string, that is inserted "as HTML".
For instance:
@ -338,9 +340,9 @@ An example of copying the message:
## DocumentFragment [#document-fragment]
`DocumentFragment` is a special DOM node that serves as a wrapper to pass around groups of nodes.
`DocumentFragment` is a special DOM node that serves as a wrapper to pass around lists of nodes.
We can append other nodes to it, but when we insert it somewhere, then it "disappears", leaving its content inserted instead.
We can append other nodes to it, but when we insert it somewhere, then its content is inserted instead.
For example, `getListContent` below generates a fragment with `<li>` items, that are later inserted into `<ul>`:
@ -502,7 +504,7 @@ So it's kind of unusable at "after loaded" stage, unlike other DOM methods we co
That was the downside.
Technically, when `document.write` is called while the browser is still reading HTML, it appends something to it, and the browser consumes it just as it were initially there.
Technically, when `document.write` is called while the browser is reading ("parsing") incoming HTML, and it writes something, the browser consumes it just as it were initially there, in the HTML text.
That gives us the upside -- it works blazingly fast, because there's *no DOM modification*. It writes directly into the page text, while the DOM is not yet built, and the browser puts it into DOM at generation-time.

View file

@ -66,10 +66,10 @@ So we can operate both on the full class string using `className` or on individu
Methods of `classList`:
- `elem.classList.add/remove("class")` -- adds/removes the class.
- `elem.classList.toggle("class")` -- if the class exists, then removes it, otherwise adds it.
- `elem.classList.toggle("class")` -- adds the class if it doesn't exist, otherwise removes it.
- `elem.classList.contains("class")` -- returns `true/false`, checks for the given class.
Besides that, `classList` is iterable, so we can list all classes like this:
Besides, `classList` is iterable, so we can list all classes with `for..of`, like this:
```html run
<body class="main page">
@ -147,7 +147,7 @@ To set the full style as a string, there's a special property `style.cssText`:
</script>
```
We rarely use it, because such assignment removes all existing styles: it does not add, but replaces them. May occasionally delete something needed. But still can be done for new elements when we know we won't delete something important.
This property is rarely used, because such assignment removes all existing styles: it does not add, but replaces them. May occasionally delete something needed. But we can safely use it for new elements, when we know we won't delete an existing style.
The same can be accomplished by setting an attribute: `div.setAttribute('style', 'color: red...')`.
````

View file

@ -17,11 +17,11 @@ For instance, this button shows the height of your window:
```
````warn header="Not `window.innerWidth/Height`"
Browsers also support properties `window.innerWidth/innerHeight`. They look like what we want. So what's the difference?
Browsers also support properties `window.innerWidth/innerHeight`. They look like what we want. So why not to use them instead?
If there's a scrollbar occupying some space, `clientWidth/clientHeight` provide the width/height inside it. In other words, they return width/height of the visible part of the document, available for the content.
If there exists a scrollbar, and it occupies some space, `clientWidth/clientHeight` provide the width/height without it (subtract it). In other words, they return width/height of the visible part of the document, available for the content.
And `window.innerWidth/innerHeight` ignore the scrollbar.
...And `window.innerWidth/innerHeight` ignore the scrollbar.
If there's a scrollbar, and it occupies some space, then these two lines show different values:
```js run
@ -42,9 +42,9 @@ In modern HTML we should always write `DOCTYPE`. Generally that's not a JavaScri
Theoretically, as the root document element is `documentElement.clientWidth/Height`, and it encloses all the content, we could measure its full size as `documentElement.scrollWidth/scrollHeight`.
These properties work well for regular elements. But for the whole page these properties do not work as intended. In Chrome/Safari/Opera if there's no scroll, then `documentElement.scrollHeight` may be even less than `documentElement.clientHeight`! For regular elements that's a nonsense.
These properties work well for regular elements. But for the whole page these properties do not work as intended. In Chrome/Safari/Opera if there's no scroll, then `documentElement.scrollHeight` may be even less than `documentElement.clientHeight`! Sounds like a nonsense, weird, right?
To have a reliable result on the full document height, we should take the maximum of these properties:
To reliably obtain the full document height, we should take the maximum of these properties:
```js run
let scrollHeight = Math.max(
@ -60,11 +60,11 @@ Why so? Better don't ask. These inconsistencies come from ancient times, not a "
## Get the current scroll [#page-scroll]
Regular elements have their current scroll state in `elem.scrollLeft/scrollTop`.
DOM elements have their current scroll state in `elem.scrollLeft/scrollTop`.
What's with the page? Most browsers provide `documentElement.scrollLeft/Top` for the document scroll, but Chrome/Safari/Opera have bugs (like [157855](https://code.google.com/p/chromium/issues/detail?id=157855), [106133](https://bugs.webkit.org/show_bug.cgi?id=106133)) and we should use `document.body` instead of `document.documentElement` there.
For document scroll `document.documentElement.scrollLeft/Top` works in most browsers, except oldler WebKit-based ones, like Safari (bug [5991](https://bugs.webkit.org/show_bug.cgi?id=5991)), where we should use `document.body` instead of `document.documentElement` there.
Luckily, we don't have to remember these peculiarities at all, because of the special properties `window.pageXOffset/pageYOffset`:
Luckily, we don't have to remember these peculiarities at all, because the scroll is available in the special properties `window.pageXOffset/pageYOffset`:
```js run
alert('Current scroll from the top: ' + window.pageYOffset);
@ -83,11 +83,11 @@ For instance, if we try to scroll the page from the script in `<head>`, it won't
Regular elements can be scrolled by changing `scrollTop/scrollLeft`.
We can do the same for the page:
- For all browsers except Chrome/Safari/Opera: modify `document.documentElement.scrollTop/Left`.
- In Chrome/Safari/Opera: use `document.body.scrollTop/Left` instead.
We can do the same for the page, but as explained above:
- For most browsers (except older Webkit-based) `document.documentElement.scrollTop/Left` is the right property.
- Otherwise, `document.body.scrollTop/Left`.
It should work, but smells like cross-browser incompatibilities. Not good. Fortunately, there's a simpler, more universal solution: special methods [window.scrollBy(x,y)](mdn:api/Window/scrollBy) and [window.scrollTo(pageX,pageY)](mdn:api/Window/scrollTo).
These cross-browser incompatibilities are not good. Fortunately, there's a simpler, universal solution: special methods [window.scrollBy(x,y)](mdn:api/Window/scrollBy) and [window.scrollTo(pageX,pageY)](mdn:api/Window/scrollTo).
- The method `scrollBy(x,y)` scrolls the page relative to its current position. For instance, `scrollBy(0,10)` scrolls the page `10px` down.
@ -96,7 +96,7 @@ It should work, but smells like cross-browser incompatibilities. Not good. Fortu
<button onclick="window.scrollBy(0,10)">window.scrollBy(0,10)</button>
```
- The method `scrollTo(pageX,pageY)` scrolls the page relative to the document's top-left corner. It's like setting `scrollLeft/scrollTop`.
- The method `scrollTo(pageX,pageY)` scrolls the page to absolute coordinates, so that the top-left corner of the visible part has coordinates `(pageX, pageY)` relative to the document's top-left corner. It's like setting `scrollLeft/scrollTop`.
To scroll to the very beginning, we can use `scrollTo(0,0)`.

View file

@ -151,9 +151,7 @@ There are two possible values of the `capture` option:
Note that while formally there are 3 phases, the 2nd phase ("target phase": the event reached the element) is not handled separately: handlers on both capturing and bubbling phases trigger at that phase.
If one puts capturing and bubbling handlers on the target element, the capture handler triggers last in the capturing phase and the bubble handler triggers first in the bubbling phase.
Let's see it in action:
Let's see both capturing and bubbling in action:
```html run autorun height=140 edit
<style>

View file

@ -18,7 +18,7 @@ Should work like this:
[iframe src="solution" height=200 border=1]
In this task we assume that all elements with `data-tooltip` have only text inside. No nested tags.
In this task we assume that all elements with `data-tooltip` have only text inside. No nested tags (yet).
Details:
@ -33,4 +33,4 @@ Please use event delegation: set up two handlers on `document` to track all "ove
After the behavior is implemented, even people unfamiliar with JavaScript can add annotated elements.
P.S. To keep things natural and simple: only one tooltip may show up at a time.
P.S. Only one tooltip may show up at a time.

View file

@ -182,7 +182,7 @@ The pattern has two parts:
### Counter
For instance, here the attribute `data-counter` adds a behavior: "increase on click" to buttons:
For instance, here the attribute `data-counter` adds a behavior: "increase value on click" to buttons:
```html run autorun height=60
Counter: <input type="button" value="1" data-counter>

View file

@ -104,7 +104,7 @@ So, `event.code` will equal `KeyZ` for people with German layout when they press
That sounds odd, but so it is. The [specification](https://www.w3.org/TR/uievents-code/#table-key-code-alphanumeric-writing-system) explicitly mentions such behavior.
- `event.code` has the benefit of staying always the same, bound to the physical key location, even if the visitor changes languages. So hotkeys that rely on it work well even in case of a language switch.
- `event.code` may match a wrong character for unexpected layout. Same letters in different layouts may map to different physical keys, leading to different codes. That happens for several codes, e.g. `keyA`, `keyQ`, `keyZ` (as we've seen). You can find the list in the [specification](https://www.w3.org/TR/uievents-code/#table-key-code-alphanumeric-writing-system).
- `event.code` may match a wrong character for unexpected layout. Same letters in different layouts may map to different physical keys, leading to different codes. Luckily, that happens only with several codes, e.g. `keyA`, `keyQ`, `keyZ` (as we've seen), and doesn't happen with special keys such as `Shift`. You can find the list in the [specification](https://www.w3.org/TR/uievents-code/#table-key-code-alphanumeric-writing-system).
So, to reliably track layout-dependent characters, `event.key` may be a better way.
@ -163,9 +163,9 @@ Now arrows and deletion works well.
In the past, there was a `keypress` event, and also `keyCode`, `charCode`, `which` properties of the event object.
There were so many browser incompatibilities that developers of the specification decided to deprecate all of them. The old code still works, as browsers keep supporting them, but there's totally no need to use those any more.
There were so many browser incompatibilities while working with them, that developers of the specification had no way, other than deprecating all of them and creating new, modern events (described above in this chapter). The old code still works, as browsers keep supporting them, but there's totally no need to use those any more.
There was a time when this chapter included their detailed description. But as of now it was removed and replaced with more details about the modern event handling.
There was a time when this chapter included their detailed description. But, as of now, browsers support modern events, so it was removed and replaced with more details about the modern event handling.
## Summary
@ -179,7 +179,7 @@ Keyboard events:
Main keyboard event properties:
- `code` -- the "key code" (`"KeyA"`, `"ArrowLeft"` and so on), specific to the physical location of the key on keyboard.
- `key` -- the character (`"A"`, `"a"` and so on), for non-character keys usually has the same value as `code`.
- `key` -- the character (`"A"`, `"a"` and so on), for non-character keys, such as `key:Esc`, usually has the same value as `code`.
In the past, keyboard events were sometimes used to track user input in form fields. That's not reliable, because the input can come from various sources. We have `input` and `change` events to handle any input (covered later in the chapter <info:events-change-input>). They trigger after any kind of input, including copy-pasting or speech recognition.

View file

@ -32,6 +32,8 @@ For instance:
- `wheel` event -- a mouse wheel roll (a "scrolling" touchpad action generates it too).
- `keydown` event for `key:pageUp` and `key:pageDown`.
Sometimes that may help. But there are more ways to scroll, so it's quite hard to handle all of them. So it's more reliable to use CSS to make something unscrollable, like `overflow` property.
If we add an event handler to these events and `event.preventDefault()` in it, then the scroll won't start.
Sometimes that may help, but it's more reliable to use CSS to make something unscrollable, such as the `overflow` property.
Here are few tasks that you can solve or look through to see the applications on `onscroll`.

View file

@ -2,13 +2,13 @@
Forms and control elements, such as `<input>` have a lot of special properties and events.
Working with forms can be much more convenient if we know them.
Working with forms will be much more convenient when we learn them.
## Navigation: form and elements
Document forms are members of the special collection `document.forms`.
That's a *named* collection: we can use both the name and the number to get the form.
That's a so-called "named collection": it's both named and ordered. We can use both the name or the number in the document to get the form.
```js no-beautify
document.forms.my - the form with name="my"
@ -154,7 +154,7 @@ Let's talk about form controls, pay attention to their specific features.
### input and textarea
Normally, we can access the value as `input.value` or `input.checked` for checkboxes.
We can access their value as `input.value` (string) or `input.checked` (boolean) for checkboxes.
Like this:
@ -166,7 +166,7 @@ input.checked = true; // for a checkbox or radio button
```
```warn header="Use `textarea.value`, not `textarea.innerHTML`"
Please note that we should never use `textarea.innerHTML`: it stores only the HTML that was initially on the page, not the current value.
Please note that even though `<textarea>...</textarea>` holds its value as nested HTML, we should never use `textarea.innerHTML`. It stores only the HTML that was initially on the page, not the current value.
```
### select and option
@ -174,8 +174,8 @@ Please note that we should never use `textarea.innerHTML`: it stores only the HT
A `<select>` element has 3 important properties:
1. `select.options` -- the collection of `<option>` elements,
2. `select.value` -- the value of the chosen option,
3. `select.selectedIndex` -- the number of the selected option.
2. `select.value` -- the value of the currently selected option,
3. `select.selectedIndex` -- the number of the currently selected option.
So we have three ways to set the value of a `<select>`:
@ -223,10 +223,12 @@ Like this:
</script>
```
The full specification of the `<select>` element is available at <https://html.spec.whatwg.org/multipage/forms.html#the-select-element>.
The full specification of the `<select>` element is available in the specification <https://html.spec.whatwg.org/multipage/forms.html#the-select-element>.
### new Option
This is rarely used on its own. But there's still an interesting thing.
In the specification of [the option element](https://html.spec.whatwg.org/multipage/forms.html#the-option-element) there's a nice short syntax to create `<option>` elements:
```js
@ -237,7 +239,7 @@ Parameters:
- `text` -- the text inside the option,
- `value` -- the option value,
- `defaultSelected` -- if `true`, then `selected` attribute is created,
- `defaultSelected` -- if `true`, then `selected` HTML-attribute is created,
- `selected` -- if `true`, then the option is selected.
For instance:
@ -281,6 +283,6 @@ Form navigation:
Value is available as `input.value`, `textarea.value`, `select.value` etc, or `input.checked` for checkboxes and radio buttons.
For `<select>` we can also get the value by the index `select.selectedIndex` or through the options collection `select.options`. The full specification of this and other elements is at <https://html.spec.whatwg.org/multipage/forms.html>.
For `<select>` we can also get the value by the index `select.selectedIndex` or through the options collection `select.options`. The full specification of this and other elements is in the specification <https://html.spec.whatwg.org/multipage/forms.html>.
These are the basics to start working with forms. In the next chapter we'll cover `focus` and `blur` events that may occur on any element, but are mostly handled on forms.
These are the basics to start working with forms. We'll meet many examples further in the tutorial. In the next chapter we'll cover `focus` and `blur` events that may occur on any element, but are mostly handled on forms.

View file

@ -188,8 +188,11 @@ But there are also essential differences between them:
| `defer` | *Document order* (as they go in the document). | Execute after the document is loaded and parsed (they wait if needed), right before `DOMContentLoaded`. |
```warn header="Page without scripts should be usable"
Please note that if you're using `defer`, then the page is visible before the script loads and enables all the graphical components.
Please note that if you're using `defer`, then the page is visible *before* the script loads.
So, buttons should be disabled by CSS or by other means, to let the user
So the user may read the page, but some graphical components are probably not ready yet.
There should be "loading" indication in proper places, not-working buttons disabled, to clearly show the user what's ready and what's not.
```
In practice, `defer` is used for scripts that need the whole DOM and/or their relative execution order is important. And `async` is used for independent scripts, like counters or ads. And their relative execution order does not matter.

View file

@ -0,0 +1,250 @@
# Mutation observer
`MutationObserver` is a built-in object that observes a DOM element and fires a callback in case of changes.
We'll first see syntax, and then explore a real-world use case.
## Syntax
`MutationObserver` is easy to use.
First, we create an observer with a callback-function:
```js
let observer = new MutationObserver(callback);
```
And then attach it to a DOM node:
```js
observer.observe(node, config);
```
`config` is an object with boolean options "what kind of changes to react on":
- `childList` -- changes in the direct children of `node`,
- `subtree` -- in all descendants of `node`,
- `attributes` -- attributes of `node`,
- `attributeOldValue` -- record the old value of attribute (infers `attributes`),
- `characterData` -- whether to observe `node.data` (text content),
- `characterDataOldValue` -- record the old value of `node.data` (infers `characterData`),
- `attributeFilter` -- an array of attribute names, to observe only selected ones.
Then after any changes, the `callback` is executed, with a list of [MutationRecord](https://dom.spec.whatwg.org/#mutationrecord) objects as the first argument, and the observer itself as the second argument.
[MutationRecord](https://dom.spec.whatwg.org/#mutationrecord) objects have properties:
- `type` -- mutation type, one of
- `"attributes"`: attribute modified
- `"characterData"`: data modified, used for text nodes,
- `"childList"`: child elements added/removed,
- `target` -- where the change occurred: an element for "attributes", or text node for "characterData", or an element for a "childList" mutation,
- `addedNodes/removedNodes` -- nodes that were added/removed,
- `previousSibling/nextSibling` -- the previous and next sibling to added/removed nodes,
- `attributeName/attributeNamespace` -- the name/namespace (for XML) of the changed attribute,
- `oldValue` -- the previous value, only for attribute or text changes.
For example, here's a `<div>` with `contentEditable` attribute. That attribute allows us to focus on it and edit.
```html run
<div contentEditable id="elem">Click and <b>edit</b>, please</div>
<script>
let observer = new MutationObserver(mutationRecords => {
console.log(mutationRecords); // console.log(the changes)
});
observer.observe(elem, {
// observe everything except attributes
childList: true,
subtree: true,
characterDataOldValue: true
});
</script>
```
If we change the text inside `<b>me</b>`, we'll get a single mutation:
```js
mutationRecords = [{
type: "characterData",
oldValue: "me",
target: <text node>,
// other properties empty
}];
```
If we select and remove the `<b>me</b>` altogether, we'll get multiple mutations:
```js
mutationRecords = [{
type: "childList",
target: <div#elem>,
removedNodes: [<b>],
nextSibling: <text node>,
previousSibling: <text node>
// other properties empty
}, {
type: "characterData"
target: <text node>
// ...details depend on how the browser handles the change
// it may coalesce two adjacent text nodes "Edit " and ", please" into one node
// or it can just delete the extra space after "Edit".
// may be one mutation or a few
}];
```
## Observer use case
When `MutationObserver` is needed? Is there a scenario when such thing can be useful?
We can track something like `contentEditable` and implement "undo/redo" functionality (record mutations and rollback/redo them on demand). There are also cases when `MutationObserver` is good from architectural standpoint.
Let's say we're making a website about programming. Naturally, articles and other materials may contain source code snippets.
An HTML markup of a code snippet looks like this:
```html
...
<pre class="language-javascript"><code>
// here's the code
let hello = "world";
</code></pre>
...
```
Also we'll use a JavaScript highlighting library on our site, e.g. [Prism.js](https://prismjs.com/). A call to `Prism.highlightElem(pre)` examines the contents of such `pre` elements and adds into them special tags and styles for colored syntax highlighting, similar to what you see in examples here, at this page.
When to run that method? We can do it on `DOMContentLoaded` event, or at the bottom of the page. At that moment we have DOM ready, can search for elements `pre[class*="language"]` and call `Prism.highlightElem` on them:
```js
// highlight all code snippets on the page
document.querySelectorAll('pre[class*="language"]').forEach(Prism.highlightElem);
```
Now the `<pre>` snippet looks like this (without line numbers by default):
```js
// here's the code
let hello = "world";
```
Everything's simple so far, right? There are `<pre>` code snippets in HTML, we highlight them.
Now let's go on. Let's say we're going to dynamically fetch materials from a server. We'll study methods for that [later in the tutorial](info:fetch-basics). For now it only matters that we fetch an HTML article from a webserver and display it on demand:
```js
let article = /* fetch new content from server */
articleElem.innerHTML = article;
```
The new `article` HTML may contain code snippets. We need to call `Prism.highlightElem` on them, otherwise they won't get highlighted.
**Where and when to call `Prism.highlightElem` for a dynamically loaded article?**
We could append that call to the code that loads an article, like this:
```js
let article = /* fetch new content from server */
articleElem.innerHTML = article;
*!*
let snippets = articleElem.querySelectorAll('pre[class*="language-"]');
snippets.forEach(Prism.highlightElem);
*/!*
```
...But imagine, we have many places in the code where we load contents: articles, quizzes, forum posts. Do we need to put the highlighting call everywhere? That's not very convenient, and also easy to forget.
And what if the content is loaded by a third-party module? E.g. we have a forum written by someone else, that loads contents dynamically, and we'd like to add syntax highlighting to it. No one likes to patch third-party scripts.
Luckily, there's another option.
We can use `MutationObserver` to automatically detect code snippets inserted in the page and highlight them.
So we'll handle the highlighting functionality in one place, relieving us from the need to integrate it.
## Dynamic highlight demo
Here's the working example.
If you run this code, it starts observing the element below and highlighting any code snippets that appear there:
```js run
let observer = new MutationObserver(mutations => {
for(let mutation of mutations) {
// examine new nodes
for(let node of mutation.addedNodes) {
// we track only elements, skip other nodes (e.g. text nodes)
if (!(node instanceof HTMLElement)) continue;
// check the inserted element for being a code snippet
if (node.matches('pre[class*="language-"]')) {
Prism.highlightElement(node);
}
// maybe there's a code snippet somewhere in its subtree?
for(let elem of node.querySelectorAll('pre[class*="language-"]')) {
Prism.highlightElement(elem);
}
}
}
});
let demoElem = document.getElementById('highlight-demo');
observer.observe(demoElem, {childList: true, subtree: true});
```
<p id="highlight-demo" style="border: 1px solid #ddd">Demo element with <code>id="highlight-demo"</code>, obverved by the example above.</p>
The code below populates `innerHTML`. Please run the code above first, it will watch and highlight the new content:
```js run
let demoElem = document.getElementById('highlight-demo');
// dynamically insert content with code snippets
demoElem.innerHTML = `A code snippet is below:
<pre class="language-javascript"><code> let hello = "world!"; </code></pre>
<div>Another one:</div>
<div>
<pre class="language-css"><code>.class { margin: 5px; } </code></pre>
</div>
`;
```
Now we have `MutationObserver` that can track all highlighting in observed elements or the whole `document`. We can add/remove code snippets in HTML without thinking about it.
## Additional methods
There's a method to stop observing the node:
- `observer.disconnect()` -- stops the observation.
Additionally:
- `mutationRecords = observer.takeRecords()` -- gets a list of unprocessed mutation records, those that happened, but the callback did not handle them.
```js
// we're going to disconnect the observer
// it might have not yet handled some mutations
let mutationRecords = observer.takeRecords();
// process mutationRecords
// now all handled, disconnect
observer.disconnect();
```
## Garbage collection
Observers use weak references to nodes internally. That is: if a node is removed from DOM, and becomes unreachable, then it becomes garbage collected, an observer doesn't prevent that.
## Summary
`MutationObserver` can react on changes in DOM: attributes, added/removed elements, text content.
We can use it to track changes introduced by other parts of our own or 3rd-party code.
For example, to post-process dynamically inserted content, as demo `innerHTML`, like highlighting in the example above.

2
2-ui/99-ui-misc/index.md Normal file
View file

@ -0,0 +1,2 @@
# Miscellaneous