This commit is contained in:
Ilya Kantor 2017-02-28 12:54:48 +03:00
parent 4272b7bb13
commit 508969c13f
168 changed files with 340 additions and 10 deletions

View file

@ -0,0 +1,27 @@
There are many ways, for instance:
The `<div>` DOM node:
```js
document.body.firstElementChild
// or
document.body.children[0]
// or (the first node is space, so we take 2nd)
document.body.childNodes[1]
```
The `<ul>` DOM node:
```js
document.body.lastElementChild
// or
document.body.children[1]
```
The second `<li>` (with Pete):
```js
// get <ul>, and then get its last element child
document.body.lastElementChild.lastElementChild
```

View file

@ -0,0 +1,24 @@
importance: 5
---
# DOM children
For the page:
```html
<html>
<body>
<div>Users:</div>
<ul>
<li>John</li>
<li>Pete</li>
</ul>
</body>
</html>
```
How to access:
- The `<div>` DOM node?
- The `<ul>` DOM node?
- The second `<li>` (with Pete)?

View file

@ -0,0 +1,4 @@
1. Yes, true. The element `elem.lastChild` is always the last one, it has no `nextSibling`, so if there are children, then yes.
2. No, wrong, because `elem.children[0]` is the first child among elements. But there may be non-element nodes before it. So `previousSibling` may be a text node.
Please note that for both cases if there are no children, then there will be an error. For instance, if `elem.lastChild` is `null`, we can't access `elem.lastChild.nextSibling`.

View file

@ -0,0 +1,10 @@
importance: 5
---
# The sibling question
If `elem` -- is an arbitrary DOM element node...
- Is it true that `elem.lastChild.nextSibling` is always `null`?
- Is it true that `elem.children[0].previousSibling` is always `null` ?

View file

@ -0,0 +1 @@
We'll be using `rows` and `cells` properties to access diagonal table cells.

View file

@ -0,0 +1,63 @@
<!DOCTYPE HTML>
<html>
<head>
<style>
table {
border-collapse: collapse;
}
td {
border: 1px solid black;
padding: 3px 5px;
}
</style>
</head>
<body>
<table>
<tr>
<td>1:1</td>
<td>2:1</td>
<td>3:1</td>
<td>4:1</td>
<td>5:1</td>
</tr>
<tr>
<td>1:2</td>
<td>2:2</td>
<td>3:2</td>
<td>4:2</td>
<td>5:2</td>
</tr>
<tr>
<td>1:3</td>
<td>2:3</td>
<td>3:3</td>
<td>4:3</td>
<td>5:3</td>
</tr>
<tr>
<td>1:4</td>
<td>2:4</td>
<td>3:4</td>
<td>4:4</td>
<td>5:4</td>
</tr>
<tr>
<td>1:5</td>
<td>2:5</td>
<td>3:5</td>
<td>4:5</td>
<td>5:5</td>
</tr>
</table>
<script>
let table = document.body.firstElementChild;
for (let i = 0; i < table.rows.length; i++) {
let row = table.rows[i];
row.cells[i].style.backgroundColor = 'red';
}
</script>
</body>
</html>

View file

@ -0,0 +1,60 @@
<!DOCTYPE HTML>
<html>
<head>
<style>
table {
border-collapse: collapse;
}
td {
border: 1px solid black;
padding: 3px 5px;
}
</style>
</head>
<body>
<table>
<tr>
<td>1:1</td>
<td>2:1</td>
<td>3:1</td>
<td>4:1</td>
<td>5:1</td>
</tr>
<tr>
<td>1:2</td>
<td>2:2</td>
<td>3:2</td>
<td>4:2</td>
<td>5:2</td>
</tr>
<tr>
<td>1:3</td>
<td>2:3</td>
<td>3:3</td>
<td>4:3</td>
<td>5:3</td>
</tr>
<tr>
<td>1:4</td>
<td>2:4</td>
<td>3:4</td>
<td>4:4</td>
<td>5:4</td>
</tr>
<tr>
<td>1:5</td>
<td>2:5</td>
<td>3:5</td>
<td>4:5</td>
<td>5:5</td>
</tr>
</table>
<script>
let table = document.body.firstElementChild;
// your code
</script>
</body>
</html>

View file

@ -0,0 +1,18 @@
importance: 5
---
# Select all diagonal cells
Write the code to paint all diagonal table cells in red.
You'll need to get all diagonal `<td>` from the `<table>` and paint them using the code:
```js
// td should be the reference to the table cell
td.style.backgroundColor = 'red';
```
The result should be:
[iframe src="solution" height=180]

View file

@ -0,0 +1,286 @@
libs:
- d3
- domtree
---
# DOM Navigation
DOM allows to do anything with HTML-element and its contents, but first we need to reach it.
All operations on DOM start with the `document` object. From it we can access any node.
[cut]
Here's a picture of links that allow to walk between DOM nodes:
![](dom-links.png)
Let's discuss them in more detail.
## On top: documentElement and body
The topmost tree nodes are available directly as `document` properties:
`<html>` = `document.documentElement`
: The topmost document node is `document.documentElement`. That's DOM node of `<html>` tag.
`<body>` = `document.body`
: Another widely used DOM node is the `<body>` element -- `document.body`.
`<head>` = `document.head`
: The `<head>` tag is available as `document.head`.
````warn header="There's a catch: `document.body` can be `null`"
A script cannot access an element that doesn't exist at the moment of running.
In particular, if a script is inside `<head>`, then `document.body` is unavailable, because the browser did not reach it yet.
So, in the example below `alert` will show `null`:
```html run
<html>
<head>
<script>
*!*
alert( "From HEAD: " + document.body ); // null, there's no <body> yet
*/!*
</script>
</head>
<body>
<script>
alert( "From BODY: " + document.body ); // HTMLBodyElement, now it exists
</script>
</body>
</html>
```
````
```smart header="In the DOM world `null` is actively used"
In the DOM, the value meaning "no such node" is `null`, not `undefined`.
```
## Children: childNodes, firstChild, lastChild
There are two tree terms that we'll use:
- **Child nodes (or children)** -- elements that are nested exactly 1 level below the given one. For instance, `<head>` and `<body>` are children of `<html>` element.
- **Descendants** -- all elements that are nested in the given one, including children, their children and so on. That is: the DOM subtree.
The `childNodes` collection provides access to all child nodes, including text nodes.
The example below shows children of `document.body`:
```html run
<html>
<body>
<div>Begin</div>
<ul>
<li>Information</li>
</ul>
<div>End</div>
<script>
*!*
for (let i = 0; i < document.body.childNodes.length; i++) {
alert( document.body.childNodes[i] ); // Text, DIV, Text, UL, ..., SCRIPT
}
*/!*
</script>
...more stuff...
</body>
</html>
```
Please note an interesting detail here. If we run the example above, the last element shown is `<script>`. In fact, the document has more stuff below, but at the moment of the execution the browser did not read it yet, so the script doesn't see it.
Properties `firstChild` and `lastChild` give fast access to the first and last elements.
If there are child nodes, then the following is always true:
```js
elem.childNodes[0] === elem.firstChild
elem.childNodes[elem.childNodes.length - 1] === elem.lastChild
```
There's also a special function `elem.hasChildNodes()` to check whether an element is empty.
### DOM collections
As we can see, `childNodes` looks like an array. But actually it's not an array, but rather a *collection* -- a special array-like iterable object.
There are two important consequences:
1. We can use `for..of` to iterate over it:
```js
for (let node of document.body.childNodes) {
alert(node); // will work the same as above
}
```
That's because it's iterable -- supports the `Symbol.iterator` property, as required.
2. Array methods won't work, because it's not an array:
```js run
alert(document.body.childNodes.filter); // undefined (there's no filter method!)
```
The first thing is nice. The second is tolerable, because we can use `Array.from` to create a "real" array from it:
```js run
alert( Array.from(document.body.childNodes).filter ); // now it's there
```
```warn header="DOM collections are read-only"
Even more -- *all* navigation properties listed in this chapter are read-only.
We can't replace an element by assigning `childNodes[i] = ...`.
Changing DOM is accomplished with other methods that we'll see in the next chapter.
```
```warn header="DOM collections are live"
Almost all DOM collections with minor exceptions are *live*. In other words, they reflect the current state of DOM.
If new elements appear or get removed from DOM, they are refreshed automatically.
```
```warn header="Shouldn't use `for..in` to loop over collections"
Collections are iterable using `for..of`. Sometimes people try to use `for..in` for that.
Please, don't. The `for..in` loop iterates over all enumerable properties. And collections some that we usually do not want to get.
```
## Siblings and the parent
The parent is available as `parentNode`. The next node in the same parent (sibling) is `nextSibling`, and the previous one is `previousSibling`.
For instance:
```js run
// <html><head>...</head><body>...</body></html>
// parent of <body> is <html>
alert( document.body.parentNode === document.documentElement ); // true
// after <head> goes <body>
alert( document.head.nextSibling ); // HTMLBodyElement
// before <body> goes <head>
alert( document.body.previousSibling ); // HTMLHeadElement
```
## Element-only navigation
Navigation properties listed above refer to *all* nodes. For instance, in `childNodes` we can see both text nodes, element nodes, and even comment nodes if there exist.
But for many tasks we don't want text or comment nodes. We want to manipulate element nodes that represent tags and structure of the page.
So let's see more navigation links that only take *element nodes* into account:
![](dom-links-elements.png)
The links are similar to those given above, just with `Element` word inside:
- `children` -- only those children that are element nodes.
- `firstElementChild`, `lastElementChild` -- first and last element children.
- `previousElementSibling`, `nextElementSibling` -- neighbour elements.
- `parentElement` -- parent element.
````smart header="Why `parentElement`? Can the parent be *not* an element?"
The `parentElement` property returns the element parent, while `parentNode` returns "any node" parent. These properties are always the same, with one exception:
```js run
alert( document.documentElement.parentNode ); // document
alert( document.documentElement.parentElement ); // null
```
In other words, the `documentElement` (`<html>`) is the root node. Formally, it has `document` as its parent. But `document` is not an element, so `parentNode` returns it and `parentElement` does not.
Sometimes that matters when we're walking over the chain of parents and call a method on each of them, but `document` doesn't have it, so we don't want it.
````
Let's modify one of examples above: replace `childNodes` with `children`. Now it will show only elements:
```html run
<html>
<body>
<div>Begin</div>
<ul>
<li>Information</li>
</ul>
<div>End</div>
<script>
*!*
for (let elem of document.body.children) {
alert(elem); // DIV, UL, DIV, SCRIPT
}
*/!*
</script>
...
</body>
</html>
```
## More links: tables [#dom-navigation-tables]
Till now we described the basic navigation properties. Certain types of DOM elements may provide more, specific to their type, for better convenience.
Tables are a great example and important particular case of that.
**`<table>`** elements support these properties:
- `table.rows` -- the collection of `<tr>` elements of the table.
- `table.caption/tHead/tFoot` -- references to elements `<caption>`, `<thead>`, `<tfoot>`.
- `table.tBodies` -- the collection of `<tbody>` elements (can be many according to the standard).
**`<thead>`, `<tfoot>`, `<tbody>`** elements provide:
- `tbody.rows` -- the collection of `<tr>` inside them.
**`<tr>`:**
- `tr.cells` -- the collection of `<td>` and `<th>` cells inside the given `<tr>`.
- `tr.sectionRowIndex` -- the number of the given `<tr>` inside the enclosing `<thead>/<tbody>`.
- `tr.rowIndex` -- the number of the `<tr>` in the table.
**`<td>` and `<th>`:**
- `td.cellIndex` -- the number of the cell inside the enclosing `<tr>`.
An example of usage:
```html run height=100
<table id="table">
<tr>
<td>one</td><td>two</td>
</tr>
<tr>
<td>three</td><td>four</td>
</tr>
</table>
<script>
// get the content of the first row, second cell
alert( table.*!*rows[0].cells[1]*/!*.innerHTML ) // "two"
</script>
```
The specification: [tabular data](https://html.spec.whatwg.org/multipage/tables.html).
There are also additional navigation properties for HTML forms. We'll look at them later when start working with forms.
# Summary
Given a DOM node, we can go to its immediate neighbours using navigation properties.
There are two main sets of them:
- For all nodes: `parentNode`, `childNodes`, `firstChild`, `lastChild`, `previousSibling`, `nextSibling`.
- For element nodes only: `parentElement`, `children`, `firstElementChild`, `lastElementChild`, `previousElementSibling`, `nextElementSibling`.
Some types of DOM elements, e.g. tables, provide additional properties and collections to access their content.

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

View file

@ -0,0 +1,8 @@
<style>
#travel-dom-comment {
font-style: italic;
}
#travel-dom-control ul {
margin: 6px 0;
}
</style>