This commit is contained in:
Ilya Kantor 2017-02-17 03:30:25 +03:00
parent 5ef1b92e8b
commit d7d25f4d8b
36 changed files with 1239 additions and 25 deletions

View file

@ -0,0 +1,18 @@
Answer: **1 and 3**.
Both commands result in adding `text` "as text" into the `elem`.
Here's an example:
```html run height=80
<div id="elem1"></div>
<div id="elem2"></div>
<div id="elem3"></div>
<script>
let text = '<b>text</b>';
elem1.append(document.createTextNode(text));
elem2.textContent = text;
elem3.innerHTML = text;
</script>
```

View file

@ -0,0 +1,13 @@
importance: 5
---
# createTextNode vs innerHTML vs textContent
We have an empty DOM element `elem` and a string `text`.
Which of these 3 commands do exactly the same?
1. `elem.append(document.createTextNode(text))`
2. `elem.innerHTML = text`
3. `elem.textContent = text`

View file

@ -0,0 +1,53 @@
First, let's make HTML/CSS.
Each component of the time would look great in its own `<span>`:
```html
<div id="clock">
<span class="hour">hh</span>:<span class="min">mm</span>:<span class="sec">ss</span>
</div>
```
Also we'll need CSS to color them.
The `update` function will refresh the clock, to be called by `setInterval` every second:
```js
function update() {
let clock = document.getElementById('clock');
*!*
let date = new Date(); // (*)
*/!*
let hours = date.getHours();
if (hours < 10) hours = '0' + hours;
clock.children[0].innerHTML = hours;
let minutes = date.getMinutes();
if (minutes < 10) minutes = '0' + minutes;
clock.children[1].innerHTML = minutes;
let seconds = date.getSeconds();
if (seconds < 10) seconds = '0' + seconds;
clock.children[2].innerHTML = seconds;
}
```
In the line `(*)` we every time check the current date. The calls to `setInterval` are not reliable: they may happen with delays.
The clock-managing functions:
```js
let timerId;
function clockStart() { // run the clock
timerId = setInterval(update, 1000);
update(); // (*)
}
function clockStop() {
clearInterval(timerId);
timerId = null;
}
```
Please note that the call to `update()` is not only scheduled in `clockStart()`, but immediately run in the line `(*)`. Otherwise the visitor would have to wait till the first execution of `setInterval`. And the clock would be empty till then.

View file

@ -0,0 +1,58 @@
<!DOCTYPE HTML>
<html>
<head>
<style>
.hour {
color: red
}
.min {
color: green
}
.sec {
color: blue
}
</style>
</head>
<body>
<div id="clock">
<span class="hour">hh</span>:<span class="min">mm</span>:<span class="sec">ss</span>
</div>
<script>
let timerId;
function update() {
let clock = document.getElementById('clock');
let date = new Date();
let hours = date.getHours();
if (hours < 10) hours = '0' + hours;
clock.children[0].innerHTML = hours;
let minutes = date.getMinutes();
if (minutes < 10) minutes = '0' + minutes;
clock.children[1].innerHTML = minutes;
let seconds = date.getSeconds();
if (seconds < 10) seconds = '0' + seconds;
clock.children[2].innerHTML = seconds;
}
function clockStart() {
timerId = setInterval(update, 1000);
update(); // <-- start right now, don't wait 1 second till the first setInterval works
}
function clockStop() {
clearInterval(timerId);
}
clockStart();
</script>
</body>
</html>

View file

@ -0,0 +1,12 @@
<!DOCTYPE HTML>
<html>
<body>
<!-- click on this button calls clockStart() -->
<input type="button" onclick="clockStart()" value="Start">
<!-- click on this button calls clockStop() -->
<input type="button" onclick="clockStop()" value="Stop">
</body>
</html>

View file

@ -0,0 +1,9 @@
importance: 4
---
# Colored clock with setInterval
Create a colored clock like here:
[iframe src="solution" height=100]

View file

@ -0,0 +1,32 @@
First, let's see how not to do it:
```js
function clear(elem) {
for (let i=0; i < elem.childNodes.length; i++) {
elem.childNodes[i].remove();
}
}
```
That won't work, because the call to `remove()` shifts the collection `elem.childNodes` every time, so elements every time start from index `0`. So `i` should not increase in the loop at all.
The `for..of` loop also does the same.
The right variant would be:
```js
function clear(elem) {
while (elem.firstChild) {
elem.firstChild.remove();
}
}
```
And also there's a simpler variant:
```js
function clear(elem) {
elem.innerHTML = '';
}
```

View file

@ -0,0 +1,20 @@
importance: 5
---
# clear
Create a function `clear(elem)` that removes everything from element.
```html run
<ol id="elem">
<li>Hello</li>
<li>World</li>
</ol>
<script>
function clear(elem) { /* your code */ }
clear(elem); // clears the list
</script>
```

View file

@ -0,0 +1,5 @@
The HTML in the task is incorrect. That's the matter. There may be no text inside the `<table>`, only table-specific tags.
The question can be easily solved by exploring the DOM in the browser tools. Then we'll see that the browser placed the text `"aaa"` *before* the table.
The HTML standard thoroughly specifies how to process bad HTML, and the behavior of the browser here is correct.

View file

@ -0,0 +1,23 @@
importance: 1
---
# Why "ааа" remains?
Run the example. Why `table.remove()` does not delete the text `"aaa"`?
```html height=100 run
<table id="table">
aaa
<tr>
<td>Test</td>
</tr>
</table>
<script>
alert(table); // the table, as it should be
table.remove();
// why there's still aaa in the document?
</script>
```

View file

@ -0,0 +1 @@
Please note using `textContent` to assign the `<li>` content.

View file

@ -0,0 +1,24 @@
<!DOCTYPE HTML>
<html>
<body>
<h1>Create a list</h1>
<script>
let ul = document.createElement('ul');
document.body.append(ul);
while (true) {
let data = prompt("Enter the text for the list item", "");
if (!data) {
break;
}
let li = document.createElement('li');
li.textContent = data;
ul.append(li);
}
</script>
</body>
</html>

View file

@ -0,0 +1,19 @@
importance: 4
---
# Create a list
Write an interface to create a list from user input.
For every list item:
1. Ask a user about its content using `prompt`.
2. Create the `<li>` with it and add it to `<ul>`.
3. Continue until the user cancels the input (by pressing `key:Esc` or CANCEL in prompt).
All elements should be created dynamically.
If a user types HTML-tags, they should be treated like a text.
[demo src="solution"]

View file

@ -0,0 +1,57 @@
<!DOCTYPE html>
<html>
<body>
<div id="container"></div>
<script>
let data = {
"Fish": {
"trout": {},
"salmon": {}
},
"Tree": {
"Huge": {
"sequoia": {},
"oak": {}
},
"Flowering": {
"redbud": {},
"magnolia": {}
}
}
};
function createTree(container, obj) {
container.append(createTreeDom(obj));
}
function createTreeDom(obj) {
// if there's no children, then the call returns undefined
// and the <ul> won't be created
if (!Object.keys(obj).length) return;
let ul = document.createElement('ul');
for (let key in obj) {
let li = document.createElement('li');
li.innerHTML = key;
let childrenUl = createTreeDom(obj[key]);
if (childrenUl) {
li.append(childrenUl);
}
ul.append(li);
}
return ul;
}
let container = document.getElementById('container');
createTree(container, data);
</script>
</body>
</html>

View file

@ -0,0 +1,45 @@
<!DOCTYPE html>
<html>
<body>
<div id="container"></div>
<script>
let data = {
"Fish": {
"trout": {},
"salmon": {}
},
"Tree": {
"Huge": {
"sequoia": {},
"oak": {}
},
"Flowering": {
"redbud": {},
"magnolia": {}
}
}
};
function createTree(container, obj) {
container.innerHTML = createTreeText(obj);
}
function createTreeText(obj) { // отдельная рекурсивная функция
let li = '';
for (let key in obj) {
li += '<li>' + key + createTreeText(obj[key]) + '</li>';
}
if (li) {
let ul = '<ul>' + li + '</ul>'
}
return ul || '';
}
let container = document.getElementById('container');
createTree(container, data);
</script>
</body>
</html>

View file

@ -0,0 +1,4 @@
The easiest way to walk the object is to use recursion.
1. [The solution with innerHTML](sandbox:innerhtml).
2. [The solution with DOM](sandbox:build-tree-dom).

View file

@ -0,0 +1,68 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<div id="tree"></div>
<!-- The result should be:
<div id="tree">
<ul>
<li>Fish
<ul>
<li>trout</li>
<li>salmon</li>
</ul>
</li>
<li>Trees
<ul>
<li>Huge
<ul>
<li>sequoia</li>
<li>oak</li>
</ul>
</li>
<li>Flowering
<ul>
<li>redbud</li>
<li>magnolia</li>
</ul>
</li>
</ul>
</li>
</ul>
</div>
-->
<script>
let data = {
"Fish": {
"trout": {},
"salmon": {}
},
"Tree": {
"Huge": {
"sequoia": {},
"oak": {}
},
"Flowering": {
"redbud": {},
"magnolia": {}
}
}
};
function createTree(container, data) {
/* your code */
}
createTree(document.getElementById('tree'), data);
</script>
</body>
</html>

View file

@ -0,0 +1,51 @@
importance: 5
---
# Create a tree from the object
Write a function `createTree` that creates a nested `ul/li` list from the nested object.
For instance:
```js
let data = {
"Fish": {
"trout": {},
"salmon": {}
},
"Tree": {
"Huge": {
"sequoia": {},
"oak": {}
},
"Flowering": {
"redbud": {},
"magnolia": {}
}
}
};
```
The syntax:
```js
let container = document.getElementById('container');
*!*
createTree(container, data); // creates the tree in the container
*/!*
```
The result (tree) should look like this:
[iframe border=1 src="build-tree-dom"]
Choose one of two ways of solving this task:
1. Create the HTML for the tree and then assign to `container.innerHTML`.
2. Create tree nodes and append with DOM methods.
Would be great if you could do both.
P.S. The tree should not have "extra" elements like empty `<ul></ul>` for the leaves.

View file

@ -0,0 +1 @@
To append text to each `<li>` we can alter the text node `data`.

View file

@ -0,0 +1,57 @@
<!DOCTYPE HTML>
<html>
<body>
<ul>
<li>Animals
<ul>
<li>Mammals
<ul>
<li>Cows</li>
<li>Donkeys</li>
<li>Dogs</li>
<li>Tigers</li>
</ul>
</li>
<li>Other
<ul>
<li>Snakes</li>
<li>Birds</li>
<li>Lizards</li>
</ul>
</li>
</ul>
</li>
<li>Fishes
<ul>
<li>Aquarium
<ul>
<li>Guppy</li>
<li>Angelfish</li>
</ul>
</li>
<li>Sea
<ul>
<li>Sea trout</li>
</ul>
</li>
</ul>
</li>
</ul>
<script>
let lis = document.getElementsByTagName('li');
for (let li of lis) {
// получить количество детей
let childCount = li.getElementsByTagName('li').length;
if (!childCount) continue;
// добавить кол-во детей к текстовому узлу
li.firstChild.data += ' [' + childCount + ']';
}
</script>
</body>
</html>

View file

@ -0,0 +1,47 @@
<!DOCTYPE HTML>
<html>
<body>
<ul>
<li>Animals
<ul>
<li>Mammals
<ul>
<li>Cows</li>
<li>Donkeys</li>
<li>Dogs</li>
<li>Tigers</li>
</ul>
</li>
<li>Other
<ul>
<li>Snakes</li>
<li>Birds</li>
<li>Lizards</li>
</ul>
</li>
</ul>
</li>
<li>Fishes
<ul>
<li>Aquarium
<ul>
<li>Guppy</li>
<li>Angelfish</li>
</ul>
</li>
<li>Sea
<ul>
<li>Sea trout</li>
</ul>
</li>
</ul>
</li>
</ul>
<script>
// ... your code ...
</script>
</body>
</html>

View file

@ -0,0 +1,13 @@
importance: 5
---
# Дерево
There's a tree organized as nested `ul/li`.
Write the code that adds to each `<li>` the number of its descendants. Skip leaves (nodes without children).
The result:
[iframe border=1 src="solution"]

View file

@ -0,0 +1,9 @@
We'll create the table as a string: `"<table>...</table>"`, and then assign it to `innerHTML`.
The algorithm:
1. Create the table header with `<th>` and weekday names.
1. Create the date object `d = new Date(year, month-1)`. That's the first day of `month` (taking into account that months in JavaScript start from `0`, not `1`).
2. First few cells till the first day of the month `d.getDay()` may be empty. Let's fill them in with `<td></td>`.
3. Increase the day in `d`: `d.setDate(d.getDate()+1)`. If `d.getMonth()` is not yet the next month, then add the new cell `<td>` to the calendar. If that's a Sunday, then add a newline <code>"&lt;/tr&gt;&lt;tr&gt;"</code>.
4. If the month has finished, but the table row is not yet full, add empty `<td>` into it, to make it square.

View file

@ -0,0 +1,79 @@
<!DOCTYPE HTML>
<html>
<head>
<style>
table {
border-collapse: collapse;
}
td,
th {
border: 1px solid black;
padding: 3px;
text-align: center;
}
th {
font-weight: bold;
background-color: #E6E6E6;
}
</style>
</head>
<body>
<div id="calendar"></div>
<script>
function createCalendar(elem, year, month) {
let mon = month - 1; // months in JS are 0..11, not 1..12
let d = new Date(year, mon);
let table = '<table><tr><th>MO</th><th>TU</th><th>WE</th><th>TH</th><th>FR</th><th>SA</th><th>SU</th></tr><tr>';
// spaces for the first row
// from Monday till the first day of the month
// * * * 1 2 3 4
for (let i = 0; i < getDay(d); i++) {
table += '<td></td>';
}
// <td> with actual dates
while (d.getMonth() == mon) {
table += '<td>' + d.getDate() + '</td>';
if (getDay(d) % 7 == 6) { // sunday, last day of week - newline
table += '</tr><tr>';
}
d.setDate(d.getDate() + 1);
}
// add spaces after last days of month for the last row
// 29 30 31 * * * *
if (getDay(d) != 0) {
for (let i = getDay(d); i < 7; i++) {
table += '<td></td>';
}
}
// close the table
table += '</tr></table>';
elem.innerHTML = table;
}
function getDay(date) { // get day number from 0 (monday) to 6 (sunday)
let day = date.getDay();
if (day == 0) day = 7; // make Sunday (0) the last day
return day - 1;
}
createCalendar(calendar, 2012, 9);
</script>
</body>
</html>

View file

@ -0,0 +1,38 @@
<!DOCTYPE HTML>
<html>
<head>
<style>
table {
border-collapse: collapse;
}
td,
th {
border: 1px solid black;
padding: 3px;
text-align: center;
}
th {
font-weight: bold;
background-color: #E6E6E6;
}
</style>
</head>
<body>
<div id="calendar"></div>
<script>
function createCalendar(elem, year, month) {
// ...your code that generates the calndar in elem...
}
createCalendar(calendar, 2012, 9);
</script>
</body>
</html>

View file

@ -0,0 +1,17 @@
importance: 4
---
# Create a calendar
Write a function `createCalendar(elem, year, month)`.
The call should create a calendar for the given year/month and put it inside `elem`.
The calendar should be a table, where a week is `<tr>`, and a day is `<td>`. The table top should be `<th>` with weekday names: the first day should be Monday, and so on till Sunday.
For instance, `createCalendar(cal, 2012, 9)` should generate in <code>&lt;div id='cal'&gt;&lt;/div&gt;</code> the following calendar:
[iframe height=210 src="solution"]
P.S. For this task it's enough to generate the calendar, should not yet be clickable.

View file

@ -0,0 +1,431 @@
# Modifying DOM
Modifying DOM is the key to create "live" pages.
Here we'll see how to create new elements "on the fly" and modify the existing page content.
There are many methods for that. First we'll see a simple example and then explain them.
[cut]
## Example: show a message
For the start, let's see how to add a message on the page, that looks nicer than `alert`.
Here's how it will look:
```html autorun height="80"
<style>
.alert {
padding: 15px;
border: 1px solid #d6e9c6;
border-radius: 4px;
color: #3c763d;
background-color: #dff0d8;
}
</style>
*!*
<div class="alert">
<strong>Hi there!</strong> You've read an important message.
</div>
*/!*
```
Now let's create the same `div` with Javascript.
## Creating an element
To create DOM nodes, there are two methods:
`document.createElement(tag)`
: Creates a new element with the given tag:
```js
let div = document.createElement('div');
```
`document.createTextNode(text)`
: Creates a new *text node* with the given text:
```js
let textNode = document.createTextNode('Here I am');
```
### Creating the message
In our case we want to make a `div`, add classes and the message into it:
```js
let div = document.createElement('div');
div.className = "alert alert-success";
div.innerHTML = "<strong>Hi there!</strong> You've read an important message.";
```
After that, we have a ready DOM element. Right now it's in the variable `div`, but not yet seen, because not inserted into the page.
## Insertion methods
To make the `div` show up, we need to insert it somewhere into `document`.
Let's say we want to insert it into `parentElem`, like `let parentElem=document.body`.
There exist following methods to insert a node:
`parentElem.appendChild(elem)`
: Appends `elem` as the last child of `parentElem`.
The following example adds a new `<li>` to the end of `<ol>`:
```html run height=100
<ol id="list">
<li>0</li>
<li>1</li>
<li>2</li>
</ol>
<script>
let newLi = document.createElement('li');
newLi.innerHTML = 'Hello, world!';
list.appendChild(newLi);
</script>
```
`parentElem.insertBefore(elem, nextSibling)`
: Inserts `elem` before `nextSibling` into `parentElem`.
The following code inserts a new list item before the second `<li>`:
```html run height=100
<ol id="list">
<li>0</li>
<li>1</li>
<li>2</li>
</ol>
<script>
let newLi = document.createElement('li');
newLi.innerHTML = 'Hello, world!';
*!*
list.insertBefore(newLi, list.children[1]);
*/!*
</script>
```
To insert as the first element, we can do like this:
```js
list.insertBefore(newLi, list.firstChild);
```
`parentElem.replaceChild(elem, oldChild)`
: Replaces `oldChild` with `elem` among children of `parentElem`.
All these methods return the inserted node. In other words, `parentElem.appendChild(elem)` returns `elem`. But usually the returned value is not used, we just run the method.
For our example, it would be like this:
```html run height="80"
<style>
.alert {
padding: 15px;
border: 1px solid #d6e9c6;
border-radius: 4px;
color: #3c763d;
background-color: #dff0d8;
}
</style>
<script>
*!*
let div = document.createElement('div');
div.className = "alert alert-success";
div.innerHTML = "<strong>Hi there!</strong> You've read an important message.";
document.body.appendChild(div);
*/!*
</script>
```
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.
For instance, how to insert *html* if we have it as a string? Or, given a node, how to insert something not into it, but *before* it?
Of course, all that is solvable, but not in an elegant way.
So there exist two other sets of insertion methods to handle all cases easily.
### prepend/append/before/after
This set of methods provides more flexible insertions:
- `node.append(...nodes or strings)` -- append nodes or strings at the end of `node`,
- `node.prepend(...nodes or strings)` -- insert nodes or strings into the beginning of `node`,
- `node.before(...nodes or strings)` - insert nodes or strings before the `node`,
- `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.
Let's say we have a list, like this:
```html autorun
<ol id="ol">
<li>0</li>
<li>1</li>
<li>2</li>
</ol>
<script>
ol.before('before');
ol.after('after');
let prepend = document.createElement('li');
prepend.innerHTML = 'prepend';
ol.prepend(prepend);
let append = document.createElement('li');
append.innerHTML = 'append';
ol.append(append);
</script>
```
Here's where the insertions will go:
![](before-prepend-append-after.png)
So the final list will be:
```html
before
<ol id="ol">
<li>prepend</li>
<li>0</li>
<li>1</li>
<li>2</li>
<li>append</li>
</ol>
after
```
These methods can insert a list of nodes and text pieces. But please note: all text is inserted *as text*.
For instance, here a string and an element are inserted:
```html run
<div id="div"></div>
<script>
div.before('<p>Hello</p>', document.createElement('hr'));
</script>
```
The final HTML would be:
```html run
*!*
&lt;p&gt;Hello&lt;/p&gt;
*/!*
<hr>
<div id="div"></div>
```
In other words, strings are inserted exactly "as text", in a safe way, like `elem.textContent` does it.
So, these methods allow to insert DOM nodes or text pieces at given places.
But what if we want to insert HTML "as html", with all tags and stuff working, like `elem.innerHTML` does it?
### insertAdjacentHTML/Text/Element
There's a versatile method `elem.insertAdjacentHTML(where, html)`.
The first parameter is a string, specifying where to insert, must be one of the following:
- `"beforebegin"` -- insert `html` before `elem`,
- `"afterbegin"` -- insert `html` into `elem`, at the beginning,
- `"beforeend"` -- insert `html` into `elem`, at the end,
- `"afterend"` -- insert `html` after `elem`.
The second parameter `html` is a HTML string, inserted "as is".
For instance:
```html run
<div id="div"></div>
<script>
div.insertAdjacentHTML('beforebegin', '<p>Hello</p>');
</script>
```
...Would lead to:
```html run
<p>Hello</p>
<div id="div"></div>
```
That's how we can append an arbitrary HTML to our page.
Here's the picture of insertion variants:
![](insert-adjacent.png)
We definitely can notice similarities between this and the previous picture. The insertion points are actually the same, but here we can insert HTML.
The method has two brothers:
- `elem.insertAdjacentText(where, text)` -- the same syntax, but a string of `text` in inserted "as text" instead of HTML,
- `elem.insertAdjacentElement(where, elem)` -- the same syntax, but inserts an element.
They exist mainly to make the syntax "uniform". In practice, most of time only `insertAdjacentHTML` is used, because for elements and text we have methods `append/prepend/before/after` -- they are just shorter to write.
So here's an alternative variant of showing a message:
```html run
<style>
.alert {
padding: 15px;
border: 1px solid #d6e9c6;
border-radius: 4px;
color: #3c763d;
background-color: #dff0d8;
}
</style>
<script>
document.body.insertAdjacentHTML("afterbegin", `<div class="alert alert-success">
<strong>Hi there!</strong> You've read an important message.
</div>`);
</script>
```
## Cloning nodes: cloneNode
How to insert one more similar message?
We could do a message-generating function and put the code there. But the alternative way would be to *clone* the existing `div` and modify the text inside it.
Sometimes when we have a big element, that may be faster and simpler.
The call `elem.cloneNode(true)` creates a "deep" clone of the element -- with all attributes and subelements. If we call it with `false`, then there would be no child elements.
An example of copying the message:
```html run height="120"
<style>
.alert {
padding: 15px;
border: 1px solid #d6e9c6;
border-radius: 4px;
color: #3c763d;
background-color: #dff0d8;
}
</style>
<div class="alert" id="div">
<strong>Hi there!</strong> You've read an important message.
</div>
<script>
*!*
let div2 = div.cloneNode(true); // clone the message
div2.querySelector('strong').innerHTML = 'Bye there!'; // change the clone
div.after(div2); // show the clone after the existing div
*/!*
</script>
```
## Removal methods
To remove nodes, there are following methods:
`parentElem.removeChild(node)`
: Removes `elem` from `parentElem` (assuming it's a child).
`node.remove()`
: Removes the `node` from its place.
We can easily see that the second method is much shorter. The first one exists for historical reasons.
````smart
If we want to *move* an element to another place -- there's no need to remove it from the old one.
**All insertion methods automatically remove the node from the old place.**
For instance, let's swap elements:
```html run height=50
<div id="first">First</div>
<div id="second">Second</div>
<script>
// no need to call remove
second.after(first); // after second insert first
</script>
```
````
Let's make our message to disappear after a second:
```html run untrusted
<style>
.alert {
padding: 15px;
border: 1px solid #d6e9c6;
border-radius: 4px;
color: #3c763d;
background-color: #dff0d8;
}
</style>
<script>
let div = document.createElement('div');
div.className = "alert alert-success";
div.innerHTML = "<strong>Hi there!</strong> You've read an important message.";
document.body.append(div);
*!*
setTimeout(div => div.remove(), 1000);
// or setTimeout(div => document.body.removeChild(div), 1000);
*/!*
</script>
```
## Summary
Methods to create new nodes:
- `document.createElement(tag)` -- creates an element with the given tag,
- `document.createTextNode(value)` -- creates a text node (rarely used),
- `elem.cloneNode(deep)` -- clones the element, if `deep==true` then with all descendants.
Insertion and removal of nodes:
- From the parent:
- `parent.appendChild(elem)`
- `parent.insertBefore(elem, nextSibling)`
- `parent.removeChild(elem)`
- `parent.replaceChild(newElem, elem)`
all thes methods return `elem`.
- Given a node:
- `node.append(...nodes or strings)` -- insert into `node`, at the end,
- `node.prepend(...nodes or strings)` -- insert into `node`, at the beginning,
- `node.before(...nodes or strings)` - insert right before `node`,
- `node.after(...nodes or strings)` - insert right after `node`,
- `node.replaceWith(...nodes or strings)` - replace `node`.
- `node.remove()` - remove the `node`.
All these methods accept a list of DOM nodes or text strings. Text strings are inserted "as text".
- To insert HTML: `elem.insertAdjacentHTML(where, html)`, inserts depending on where:
- `"beforebegin"` -- insert `html` right before `elem`,
- `"afterbegin"` -- insert `html` into `elem`, at the beginning,
- `"beforeend"` -- insert `html` into `elem`, at the end,
- `"afterend"` -- insert `html` right after `elem`.
Also there are similar methods `elem.insertAdjacentText` and `elem.insertAdjacentElement`, they insert text strings and elements, but they are rarely used.

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

View file

@ -179,13 +179,13 @@ alert( document.body.previousSibling ); // HTMLHeadElement
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. 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 on element nodes that represent tags. 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 check out more navigation links that only take *element nodes* into account: So let's see more navigation links that only take *element nodes* into account:
![](dom-links-elements.png) ![](dom-links-elements.png)
The links look like those given above, just with `Element` word inside: The links are similar to those given above, just with `Element` word inside:
- `children` -- only those children that are element nodes. - `children` -- only those children that are element nodes.
- `firstElementChild`, `lastElementChild` -- first and last element children. - `firstElementChild`, `lastElementChild` -- first and last element children.
@ -230,26 +230,26 @@ Let's modify one of examples above: replace `childNodes` with `children`. Now it
</html> </html>
``` ```
## More links, tables [#dom-navigation-tables] ## More links: tables [#dom-navigation-tables]
Till now we described the basic navigation properties. Certain DOM element types may provide more, specific to their type, for better convenience. 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. Tables are a great example and important particular case of that.
`<table>` elements support these properties: **`<table>`** elements support these properties:
- `table.rows` -- the collection of `<tr>` elements of the table. - `table.rows` -- the collection of `<tr>` elements of the table.
- `table.caption/tHead/tFoot` -- references to elements `<caption>`, `<thead>`, `<tfoot>`. - `table.caption/tHead/tFoot` -- references to elements `<caption>`, `<thead>`, `<tfoot>`.
- `table.tBodies` -- the collection of `<tbody>` elements (can be many according to the standard). - `table.tBodies` -- the collection of `<tbody>` elements (can be many according to the standard).
`<thead>`, `<tfoot>`, `<tbody>` elements provide: **`<thead>`, `<tfoot>`, `<tbody>`** elements provide:
- `tbody.rows` -- the collection of `<tr>` inside them. - `tbody.rows` -- the collection of `<tr>` inside them.
`<tr>`: **`<tr>`:**
- `tr.cells` -- the collection of `<td>` and `<th>` cells inside the given `<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.sectionRowIndex` -- the number of the given `<tr>` inside the enclosing `<thead>/<tbody>`.
- `tr.rowIndex` -- the number of the `<tr>` in the table. - `tr.rowIndex` -- the number of the `<tr>` in the table.
`<td>` and `<th>`: **`<td>` and `<th>`:**
- `td.cellIndex` -- the number of the cell inside the enclosing `<tr>`. - `td.cellIndex` -- the number of the cell inside the enclosing `<tr>`.
An example of usage: An example of usage:
@ -276,9 +276,11 @@ There are also additional navigation properties for HTML forms. We'll look at th
# Summary # Summary
The main navigation sets are: Given a DOM node, we can go to its immediate neighbours using navigation properties.
- `parentNode`, `childNodes`, `firstChild`, `lastChild`, `previousSibling`, `nextSibling` -- for all nodes. There are two main sets of them:
- `parentElement`, `children`, `firstElementChild`, `lastElementChild`, `previousElementSibling`, `nextElementSibling` -- if we take only element nodes into account.
- 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. Some types of DOM elements, e.g. tables, provide additional properties and collections to access their content.

View file

@ -351,3 +351,6 @@ Besides that:
- There is `elem.matches(css)` to check if `elem` matches the given CSS selector. - There is `elem.matches(css)` to check if `elem` matches the given CSS selector.
- There is `elem.closest(css)` to look for a nearest ancestor that matches the given CSS-selector. The `elem` itself is also checked. - There is `elem.closest(css)` to look for a nearest ancestor that matches the given CSS-selector. The `elem` itself is also checked.
And let's mention one more method here to check for the child-parent relationship:
- `elemA.contains(elemB)` returns true if `elemB` is inside `elemA`. Or when it's the same element.

View file

@ -18,18 +18,23 @@ The root object is [EventTarget](https://dom.spec.whatwg.org/#eventtarget), then
![](dom-class-hierarchy.png) ![](dom-class-hierarchy.png)
The main classes are: The classes are:
- [EventTarget](https://dom.spec.whatwg.org/#eventtarget) is an "abstract" class. Objects of that class are never created. It serves as a base, because all DOM nodes support so-called "events", we'll study them later. - [EventTarget](https://dom.spec.whatwg.org/#eventtarget) is the root "abstract" class. Objects of that class are never created. It serves as a base, because 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 like `parentNode`, `nextSibling`, `childNodes` and so on. That class is abstract as well. Objects of bare `Node` 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 classes like `Comment` for comment nodes. - [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 like `parentNode`, `nextSibling`, `childNodes` and so on. Objects of bare `Node` are never created. But there are concrete node classes that inherit from it, namely: `Text` for text nodes, `Element` for element nodes and few more exotic ones like `Comment` for comment nodes.
- [Element](http://dom.spec.whatwg.org/#interface-element) is for DOM elements: not only for HTML, but also XML and SVG documents. It provides element-level navigation like `nextElementSibling`, `children` and searching methods like `getElementsByTagName`, `querySelector`. - [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 `SVGElement`, `XMLElement` and `HTMLElement`.
- [HTMLElement](https://html.spec.whatwg.org/multipage/dom.html#htmlelement) is finally the class for HTML elements. It is inherited by various HTML elements: - [HTMLElement](https://html.spec.whatwg.org/multipage/dom.html#htmlelement) is finally the class for HTML elements. It is inherited by various HTML elements:
- For `<input>` -- `HTMLInputElement` - For `<input>` -- `HTMLInputElement`
- For `<body>` -- `HTMLBodyElement` - For `<body>` -- `HTMLBodyElement`
- For `<a>` -- `HTMLAnchorElement`... and so on, that have their own specific properties. - For `<a>` -- `HTMLAnchorElement`... and so on, that have their own specific properties.
For instance, DOM element that corresponds to `<input>` is of the [HTMLInputElement]((https://html.spec.whatwg.org/multipage/forms.html#htmlinputelement) class. The class provides input-specific properties and methods and inherits more common stuff from `HTMLElement`. In turn, it provides HTML element functionality and inherits more generic methods from `Element` and so on. So, the full set of properties and methods of a given node comes as the result of the inheritance.
For instance, `<input>` element is of the [HTMLInputElement](https://html.spec.whatwg.org/multipage/forms.html#htmlinputelement) class:
- `HTMLInputElement` provides input-specific properties and inherits the common HTML element properties from `HTMLElement`.
- Then it has common element properties, because `HTMLElement` inherits from `Element`.
- Then it has common DOM node properties from `Node` and so on.
To see the DOM node class, we can check its constructor name or just `toString` it: To see the DOM node class, we can check its constructor name or just `toString` it:
@ -48,7 +53,7 @@ alert( document.body instanceof Node ); // true
alert( document.body instanceof EventTarget ); // true alert( document.body instanceof EventTarget ); // true
``` ```
As we can see, DOM nodes are regular Javascript objects. They use prototype-style classes for inheritance. That's easy to see by outputting an element with `console.dir(elem)`. There you can see `Node.prototype`, `Element.prototype` and so on. As we can see, DOM nodes are regular Javascript objects. They use prototype-style classes for inheritance. That's easy to see by outputting an element with `console.dir(elem)`. There you can see `HTMLElement.prototype`, `Element.prototype` and so on.
```smart header="`console.dir(elem)` versus `console.log(elem)`" ```smart header="`console.dir(elem)` versus `console.log(elem)`"
Most browsers support two commands: `console.log` and `console.dir`. For Javascript objects these commands usually do the same. Most browsers support two commands: `console.log` and `console.dir`. For Javascript objects these commands usually do the same.
@ -71,18 +76,18 @@ Here's an excerpt from it, with comments:
// The colon means that it inherits from HTMLElement // The colon means that it inherits from HTMLElement
interface HTMLInputElement: HTMLElement { interface HTMLInputElement: HTMLElement {
// such elements have string properties // string properties
// accept, alt, autocomplete, value // accept, alt, autocomplete, value
attribute DOMString accept; attribute DOMString accept;
attribute DOMString alt; attribute DOMString alt;
attribute DOMString autocomplete; attribute DOMString autocomplete;
attribute DOMString value; attribute DOMString value;
// and the boolean property autofocus // and the boolean property: autofocus
attribute boolean autofocus; attribute boolean autofocus;
... ...
// and also the method select(), // and also the method select(),
// that returns no value ("void") // "void" means that that returns no value
void select(); void select();
... ...
} }
@ -97,7 +102,7 @@ It has a numeric value:
- `elem.nodeType == 1` for element nodes, - `elem.nodeType == 1` for element nodes,
- `elem.nodeType == 3` for text nodes, - `elem.nodeType == 3` for text nodes,
- `elem.nodeType == 9` for the document object, - `elem.nodeType == 9` for the document object,
- there are few other values in [the docs](https://dom.spec.whatwg.org/#node). - there are few other values in [the specification](https://dom.spec.whatwg.org/#node).
For instance: For instance:

View file

@ -1,4 +1,4 @@
# DOM: attributes and properties [todo] # DOM: attributes and properties
The browser "reads" HTML text and generates DOM objects from it. For element nodes most standard HTML attributes automatically become properties of DOM objects. The browser "reads" HTML text and generates DOM objects from it. For element nodes most standard HTML attributes automatically become properties of DOM objects.

Binary file not shown.