This commit is contained in:
Ilya Kantor 2017-02-23 19:03:14 +03:00
parent 20784e7f26
commit 7019d1470d
48 changed files with 1019 additions and 464 deletions

View file

@ -130,7 +130,7 @@ The example above can be split into two scripts to work:
- We can use a `<script>` tag to add JavaScript code to the page. - We can use a `<script>` tag to add JavaScript code to the page.
- The `type` and `language` attributes are not required. - The `type` and `language` attributes are not required.
- Scripts in an external file can be inserted on the page via `<script src="path"></script>`. - A script in an external file can be inserted with `<script src="path/to/script.js"></script>`.
There is much more about browser scripts and their interaction with the web-page. But let's keep in mind that this part of the tutorial is devoted to Javascript language. So we shouldn't distract ourselves from it. We'll be using a browser as a way to run Javascript, very convenient for online reading, but yet one of many. There is much more about browser scripts and their interaction with the web-page. But let's keep in mind that this part of the tutorial is devoted to Javascript language. So we shouldn't distract ourselves from it. We'll be using a browser as a way to run Javascript, very convenient for online reading, but yet one of many.

View file

@ -218,7 +218,7 @@ let age = prompt('Your age?', 18);
switch (age) { switch (age) {
case 18: case 18:
alert("Won't work"); // the result of prompt is a string, not a number число alert("Won't work"); // the result of prompt is a string, not a number
case "18": case "18":
alert("This works!""); alert("This works!"");

View file

@ -1,50 +1,48 @@
# Ответ
Вы могли заметить следующие недостатки, сверху-вниз: You could note the following:
```js no-beautify ```js no-beautify
function pow(x,n) // <- отсутствует пробел между аргументами function pow(x,n) // <- no space between arguments
{ // <- фигурная скобка на отдельной строке { // <- figure bracket on a separate line
var result=1; // <- нет пробелов вокруг знака = let result=1; // <- no spaces to the both sides of =
for(var i=0;i<n;i++) {result*=x;} // <- нет пробелов for(let i=0;i<n;i++) {result*=x;} // <- no spaces
// содержимое скобок { ... } лучше вынести на отдельную строку // the contents of { ... } should be on a new line
return result; return result;
} }
x=prompt("x?",'') // <- не объявлена переменная, нет пробелов, ; let x=prompt("x?",''), n=prompt("n?",'') // <-- technically possible,
n=prompt("n?",'') // but better make it 2 lines, also there's no spaces and ;
if (n<0) // <- нет пробелов, стоит добавить вертикальную отбивку if (n<0) // <- no spaces inside (n < 0), and should be extra line above it
{ // <- фигурная скобка на отдельной строке { // <- figure bracket on a separate line
// ниже - слишком длинная строка, нет пробелов // below - a long line, may be worth to split into 2 lines
alert('Степень '+n+'не поддерживается, введите целую степень, большую 0'); alert(`Power ${n} is not supported, please enter an integer number greater than zero`);
} }
else // <- можно на одной строке } else { else // <- could write it on a single line like "} else {"
{ {
alert(pow(x,n)) // нет точки с запятой alert(pow(x,n)) // no spaces and ;
} }
``` ```
Исправленный вариант: The fixed variant:
```js ```js
function pow(x, n) { function pow(x, n) {
var result = 1; let result = 1;
for (var i = 0; i < n; i++) { for (let i = 0; i < n; i++) {
result *= x; result *= x;
} }
return result; return result;
} }
var x = prompt("x?", ""); let x = prompt("x?", "");
var n = prompt("n?", ""); let n = prompt("n?", "");
if (n < 0) { if (n < 0) {
alert('Степень ' + n + alert(`Power ${n} is not supported,
'не поддерживается, введите целую степень, большую 0'); please enter an integer number greater than zero`);
} else { } else {
alert( pow(x, n) ); alert( pow(x, n) );
} }
``` ```

View file

@ -2,23 +2,22 @@ importance: 4
--- ---
# Ошибки в стиле # Bad style
Какие недостатки вы видите в стиле этого примера? What's wrong with the code style below?
```js no-beautify ```js no-beautify
function pow(x,n) function pow(x,n)
{ {
var result=1; let result=1;
for(var i=0;i<n;i++) {result*=x;} for(let i=0;i<n;i++) {result*=x;}
return result; return result;
} }
x=prompt("x?",'') let x=prompt("x?",''), n=prompt("n?",'')
n=prompt("n?",'')
if (n<=0) if (n<=0)
{ {
alert('Степень '+n+'не поддерживается, введите целую степень, большую 0'); alert(`Power ${n} is not supported, please enter an integer number greater than zero`);
} }
else else
{ {
@ -26,3 +25,4 @@ else
} }
``` ```
Fix it.

View file

@ -1,10 +0,0 @@
function pow(x, n) {
if (n < 0) return NaN;
if (Math.round(n) != n) return NaN;
var result = 1;
for (var i = 0; i < n; i++) {
result *= x;
}
return result;
}

View file

@ -1,8 +0,0 @@
/* исправьте этот код */
function pow(x, n) {
var result = 1;
for (var i = 0; i < n; i++) {
result *= x;
}
return result;
}

View file

@ -1,26 +0,0 @@
describe("pow", function() {
describe("возводит x в степень n", function() {
function makeTest(x) {
var expected = x * x * x;
it("при возведении " + x + " в степень 3 результат: " + expected, function() {
assert.equal(pow(x, 3), expected);
});
}
for (var x = 1; x <= 5; x++) {
makeTest(x);
}
});
it("при возведении в отрицательную степень результат NaN", function() {
assert(isNaN(pow(2, -1)), "pow(2, -1) не NaN");
});
it("при возведении в дробную степень результат NaN", function() {
assert(isNaN(pow(2, 1.5)), "pow(2, -1.5) не NaN");
});
});

View file

@ -1,17 +0,0 @@
```js
function pow(x, n) {
*!*
if (n < 0) return NaN;
if (Math.round(n) != n) return NaN;
*/!*
var result = 1;
for (var i = 0; i < n; i++) {
result *= x;
}
return result;
}
```

View file

@ -1,19 +0,0 @@
importance: 5
---
# Сделать pow по спецификации
Исправьте код функции `pow`, чтобы тесты проходили.
Для этого ниже в задаче вы найдёте ссылку на песочницу.
Она содержит HTML с тестами. Обратите внимание, что HTML-страница в ней короче той, что обсуждалась в статье <info:testing>. Это потому что библиотеки Chai, Mocha и Sinon объединены в один файл:
```html
<script src="https://js.cx/test/libs.js"></script>
```
Этот файл содержит код библиотек, стили, настройки для них и запуск `mocha.run` по окончании загрузки страницы. Если нет элемента с `id="mocha"`, то результаты выводятся в `<body>`.
Сборка сделана исключительно для более компактного представления задач, без рекомендаций использовать именно её в проектах.

View file

@ -1,38 +0,0 @@
Новый тест может быть, к примеру, таким:
```js
it("любое число в степени 0 равно 1", function() {
assert.equal(pow(123, 0), 1);
});
```
Конечно, желательно проверить на нескольких числах.
Поэтому лучше будет создать блок `describe`, аналогичный тому, что мы делали для произвольных чисел:
```js
describe("любое число, кроме нуля, в степени 0 равно 1", function() {
function makeTest(x) {
it("при возведении " + x + " в степень 0 результат: 1", function() {
assert.equal(pow(x, 0), 1);
});
}
for (var x = -5; x <= 5; x += 2) {
makeTest(x);
}
});
```
И не забудем добавить отдельный тест для нуля:
```js no-beautify
...
it("ноль в нулевой степени даёт NaN", function() {
assert( isNaN(pow(0, 0)), "0 в степени 0 не NaN");
});
...
```

View file

@ -1,27 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="https://js.cx/test/libs.js"></script>
<script src="test.js"></script>
</head>
<body>
<script>
function pow(x, n) {
if (n < 0) return NaN;
if (Math.round(n) != n) return NaN;
if (n == 0 && x == 0) return NaN;
var result = 1;
for (var i = 0; i < n; i++) {
result *= x;
}
return result;
}
</script>
</body>
</html>

View file

@ -1,44 +0,0 @@
describe("pow", function() {
describe("возводит x в степень n", function() {
function makeTest(x) {
var expected = x * x * x;
it("при возведении " + x + " в степень 3 результат: " + expected, function() {
assert.equal(pow(x, 3), expected);
});
}
for (var x = 1; x <= 5; x++) {
makeTest(x);
}
});
it("при возведении в отрицательную степень результат NaN", function() {
assert(isNaN(pow(2, -1)), "pow(2, -1) не NaN");
});
it("при возведении в дробную степень результат NaN", function() {
assert(isNaN(pow(2, 1.5)), "pow(2, -1.5) не NaN");
});
describe("любое число, кроме нуля, в степени 0 равно 1", function() {
function makeTest(x) {
it("при возведении " + x + " в степень 0 результат: 1", function() {
assert.equal(pow(x, 0), 1);
});
}
for (var x = -5; x <= 5; x += 2) {
makeTest(x);
}
});
it("ноль в нулевой степени даёт NaN", function() {
assert(isNaN(pow(0, 0)), "0 в степени 0 не NaN");
});
});

View file

@ -1,26 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="https://js.cx/test/libs.js"></script>
<script src="test.js"></script>
</head>
<body>
<script>
function pow(x, n) {
if (n < 0) return NaN;
if (Math.round(n) != n) return NaN;
var result = 1;
for (var i = 0; i < n; i++) {
result *= x;
}
return result;
}
</script>
</body>
</html>

View file

@ -1,26 +0,0 @@
describe("pow", function() {
describe("возводит x в степень n", function() {
function makeTest(x) {
var expected = x * x * x;
it("при возведении " + x + " в степень 3 результат: " + expected, function() {
assert.equal(pow(x, 3), expected);
});
}
for (var x = 1; x <= 5; x++) {
makeTest(x);
}
});
it("при возведении в отрицательную степень результат NaN", function() {
assert(isNaN(pow(2, -1)), "pow(2, -1) не NaN");
});
it("при возведении в дробную степень результат NaN", function() {
assert(isNaN(pow(2, 1.5)), "pow(2, -1.5) не NaN");
});
});

View file

@ -1,9 +0,0 @@
importance: 5
---
# Добавьте тест к задаче
Добавьте к [предыдущей задаче](/task/pow-nan-spec) тесты, которые будут проверять, что любое число, кроме нуля, в нулевой степени равно `1`, а ноль в нулевой степени даёт `NaN` (это математически корректно, результат 0<sup>0</sup> не определён).
При необходимости, исправьте реализацию, чтобы тесты проходили без ошибок.

View file

@ -4,28 +4,47 @@ What we have here is actually 3 tests, but layed out as a single function with 3
Sometimes it's easier to write this way, but if an error occurs, it's much less obvious what went wrong. Sometimes it's easier to write this way, but if an error occurs, it's much less obvious what went wrong.
If an error happens inside a complex execution flow, then we'll have to figure out what was the data at that point. If an error happens inside a complex execution flow, then we'll have to figure out the data at that point. We'll actually have to *debug the test*.
TODO It would be much better to break the test into multiple `it` blocks with clearly written inputs and outputs.
Если в сложном тесте произошла ошибка где-то посередине потока вычислений, то придётся выяснять, какие конкретно были входные и выходные данные на этот момент, то есть по сути -- отлаживать код самого теста.
Гораздо лучше будет разбить тест на несколько блоков `it`, с чётко прописанными входными и выходными данными.
Like this:
```js ```js
describe("Возводит x в степень n", function() { describe("Raises x to power n", function() {
it("5 в степени 1 равно 5", function() { it("5 in the power of 1 equals 5", function() {
assert.equal(pow(5, 1), 5); assert.equal(pow(5, 1), 5);
}); });
it("5 в степени 2 равно 25", function() { it("5 in the power of 2 equals 25", function() {
assert.equal(pow(5, 2), 25); assert.equal(pow(5, 2), 25);
}); });
it("5 в степени 3 равно 125", function() { it("5 in the power of 3 equals 125", function() {
assert.equal(pow(5, 3), 125); assert.equal(pow(5, 3), 125);
}); });
}); });
``` ```
Можно использовать цикл для генерации блоков `it`, в этом случае важно, чтобы сам код такого цикла был достаточно простым. Иногда проще записать несколько блоков `it` вручную, как сделано выше, чем "городить огород" из синтаксических конструкций. We replaced the single `it` with `describe` and a group of `it` blocks. Now if something fails we would see clearly what the data was.
Also we can isolate a single test and run it in standalone mode by writing `it.only` instead of `it`:
```js
describe("Raises x to power n", function() {
it("5 in the power of 1 equals 5", function() {
assert.equal(pow(5, 1), 5);
});
*!*
// Mocha will run only this block
it.only("5 in the power of 2 equals 25", function() {
assert.equal(pow(5, 2), 25);
});
*/!*
it("5 in the power of 3 equals 125", function() {
assert.equal(pow(5, 3), 125);
});
});
```

View file

@ -0,0 +1,6 @@
There are many possible solutions here.
For instance:
- `one.insertAdjacentHTML('afterend', '<li>2</li><li>3</li>')`

View file

@ -0,0 +1,14 @@
importance: 5
---
# Insert the HTML in the list
Write the code to insert `<li>2</li><li>3</li>` between two `<li>` here:
```html
<ul id="ul">
<li id="one">1</li>
<li id="two">4</li>
</ul>
```

View file

@ -0,0 +1,19 @@
The solution is short, yet may look a bit tricky, so here I provide it with extensive comments:
```js
let sortedRows = Array.from(table.rows)
.slice(1)
.sort((rowA, rowB) => rowA.cells[0].innerHTML > rowB.cells[0].innerHTML ? 1 : -1);
table.tBodies[0].append(...sortedRows);
```
1. Get all `<tr>`, like `table.querySelectorAll('tr')`, then make an array from them, cause we need array methods.
2. The first TR (`table.rows[0]`) is actually a table header, so we take the rest by `.slice(1)`.
3. Then sort them comparing by the content of the first `<td>` (the name field).
4. Now insert nodes in the right order by `.append(...sortedRows)`.
Tables always have an implicit <tbody> element, so we need to take it and insert into it: a simple `table.append(...)` would fail.
Please note: we don't have to remove them, just "re-insert", they leave the old place automatically.

View file

@ -0,0 +1,37 @@
<!DOCTYPE html>
<html>
<body>
<table id="table">
<tr>
<th>Name</th>
<th>Surname</th>
<th>Age</th>
</tr>
<tr>
<td>John</td>
<td>Smith</td>
<td>10</td>
</tr>
<tr>
<td>Pete</td>
<td>Brown</td>
<td>15</td>
</tr>
<tr>
<td>Ann</td>
<td>Lee</td>
<td>5</td>
</tr>
</table>
<script>
let sortedRows = Array.from(table.rows)
.slice(1)
.sort((rowA, rowB) => rowA.cells[0].innerHTML > rowB.cells[0].innerHTML ? 1 : -1);
table.tBodies[0].append(...sortedRows);
</script>
</body>
</html>

View file

@ -0,0 +1,33 @@
<!DOCTYPE html>
<html>
<body>
<table id="table">
<tr>
<th>Name</th>
<th>Surname</th>
<th>Age</th>
</tr>
<tr>
<td>John</td>
<td>Smith</td>
<td>10</td>
</tr>
<tr>
<td>Pete</td>
<td>Brown</td>
<td>15</td>
</tr>
<tr>
<td>Ann</td>
<td>Lee</td>
<td>5</td>
</tr>
</table>
<script>
// ... your code ...
</script>
</body>
</html>

View file

@ -0,0 +1,39 @@
importance: 5
---
# Sort the table
There's a table:
<table>
<tr>
<th>Name</th>
<th>Surname</th>
<th>Age</th>
</tr>
<tr>
<td>John</td>
<td>Smith</td>
<td>10</td>
</tr>
<tr>
<td>Pete</td>
<td>Brown</td>
<td>15</td>
</tr>
<tr>
<td>Ann</td>
<td>Lee</td>
<td>5</td>
</tr>
<tr>
<td>...</td>
<td>...</td>
<td>...</td>
</tr>
</table>
There may be more rows in it.
Write the code to sort it by the `"name"` column.

View file

@ -49,7 +49,6 @@
// добавить кол-во детей к текстовому узлу // добавить кол-во детей к текстовому узлу
li.firstChild.data += ' [' + childCount + ']'; li.firstChild.data += ' [' + childCount + ']';
} }
</script> </script>

View file

@ -2,7 +2,7 @@ importance: 5
--- ---
# Дерево # Show descendants in a tree
There's a tree organized as nested `ul/li`. There's a tree organized as nested `ul/li`.

View file

@ -394,6 +394,54 @@ Let's make our message to disappear after a second:
</script> </script>
``` ```
## A word about "document.write"
There's one more, quite ancient method of adding something to a web-page: `document.write`.
The syntax:
```html run
<p>Somewhere in the page...</p>
*!*
<script>
document.write('<b>Hello from JS</b>');
</script>
*/!*
<p>The end</p>
```
The call to `document.write(html)` writes the `html` into page "right here and now". The `html` string can be dynamically generated, so it's kind of flexible.
The method comes from times when there were no DOM, no standards... Really old times. It still lives, because there are scripts using it.
In modern scripts we can rarely see it, because of the important limitation.
**The call to `document.write` only works while the page is loading.**
If we call it after it, the existing document will be erased.
For instance:
```html run
<p>After one second the contents of this page will be replaced...</p>
*!*
<script>
// document.write after 1 second
setTimeout(() => document.write('<b>...By this.</b>'), 1000);
</script>
*/!*
```
The method `document.write` works at the "reading HTML" phase. It appends something to the page and the browser consumes it along with the rest of HTML.
So it's kind of unusable at "after loaded" stage, unlike other DOM methods we covered above.
That was the downside.
The upside -- it works blazingly fast, because it writes directly into the text, without interfering with complex DOM structures.
So if we need to add a lot of text into HTML dynamically, and we're at page loading phase, and the speed matters, it may help. But in practice that's a really rare use case. Mostly we can see this method in scripts just because they are old.
## Summary ## Summary
Methods to create new nodes: Methods to create new nodes:
@ -429,3 +477,8 @@ Insertion and removal of nodes:
- `"afterend"` -- insert `html` right after `elem`. - `"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. Also there are similar methods `elem.insertAdjacentText` and `elem.insertAdjacentElement`, they insert text strings and elements, but they are rarely used.
- To append HTML to the page before it has finished loading:
- `document.write(html)`
After the page is loaded such call erases the document. Mostly seen in old scripts.

View file

@ -0,0 +1,41 @@
Есть два варианта.
1. Можно использовать свойство `elem.style.cssText` и присвоить стиль в текстовом виде. При этом все присвоенные ранее свойства `elem.style` будут удалены.
2. Можно назначить подсвойства `elem.style` одно за другим. Этот способ более безопасен, т.к. меняет только явно присваемые свойства.
Мы выберем второй путь.
**Описание CSS-свойств:**
```css
.button {
-moz-border-radius: 8px;
-webkit-border-radius: 8px;
border-radius: 8px;
border: 2px groove green;
display: block;
height: 30px;
line-height: 30px;
width: 100px;
text-decoration: none;
text-align: center;
color: red;
font-weight: bold;
}
```
`*-border-radius`
: Добавляет скругленные углы. Свойство присваивается в вариантах для Firefox `-moz-...`, Chrome/Safari `-webkit-...` и стандартное CSS3-свойство для тех, кто его поддерживает (Opera).
`display`
: По умолчанию, у `A` это свойство имеет значение `display: inline`.
`height`, `line-height`
: Устанавливает высоту и делает текст вертикально центрированным путем установки `line-height` в значение, равное высоте. Такой способ центрирования текста работает, если он состоит из одной строки.
`text-align`
: Центрирует текст горизонтально.
`color`, `font-weight`
: Делает текст красным и жирным.

View file

@ -0,0 +1,40 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<div>
Кнопка:
<!-- создайте элемент и расположите его тут -->
</div>
<script>
var a = document.createElement('a');
a.className = 'button';
a.appendChild(document.createTextNode('Нажми меня'));
a.href = '/';
var s = a.style
s.MozBorderRadius = s.WebkitBorderRadius = s.borderRadius = '8px';
s.border = '2px groove green';
s.display = 'block';
s.height = '30px';
s.lineHeight = '30px';
s.width = '100px';
s.textDecoration = 'none';
s.textAlign = 'center';
s.color = 'red';
s.fontWeight = 'bold';
var div = document.body.children[0];
div.appendChild(a);
</script>
</body>
</html>

View file

@ -0,0 +1,22 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<div>
Кнопка:
<!-- создайте элемент и расположите его тут -->
</div>
<script>
// .. ваш код
</script>
</body>
</html>

View file

@ -0,0 +1,33 @@
importance: 3
---
# Скругленая кнопка со стилями из JavaScript
Создайте кнопку в виде элемента `<a>` с заданным стилем, используя JavaScript.
В примере ниже такая кнопка создана при помощи HTML/CSS. В вашем решении кнопка должна создаваться, настраиваться и добавляться в документ при помощи *только JavaScript*, без тегов `<style>` и `<a>`.
```html autorun height="50"
<style>
.button {
-moz-border-radius: 8px;
-webkit-border-radius: 8px;
border-radius: 8px;
border: 2px groove green;
display: block;
height: 30px;
line-height: 30px;
width: 100px;
text-decoration: none;
text-align: center;
color: red;
font-weight: bold;
}
</style>
<a class="button" href="/">Нажми меня</a>
```
**Проверьте себя: вспомните, что означает каждое свойство. В чём состоит эффект его появления здесь?**

View file

@ -0,0 +1,14 @@
.notification {
position: fixed;
z-index: 1000;
padding: 5px;
border: 1px solid black;
font: normal 20px Georgia;
background: white;
text-align: center;
}
.welcome {
background: red;
color: yellow;
}

View file

@ -0,0 +1,65 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="index.css">
</head>
<body>
<h2>Уведомление</h2>
<p>
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Dolorum aspernatur quam ex eaque inventore quod voluptatem adipisci omnis nemo nulla fugit iste numquam ducimus cumque minima porro ea quidem maxime necessitatibus beatae labore soluta voluptatum
magnam consequatur sit laboriosam velit excepturi laborum sequi eos placeat et quia deleniti? Corrupti velit impedit autem et obcaecati fuga debitis nemo ratione iste veniam amet dicta hic ipsam unde cupiditate incidunt aut iure ipsum officiis soluta
temporibus. Tempore dicta ullam delectus numquam consectetur quisquam explicabo culpa excepturi placeat quo sequi molestias reprehenderit hic at nemo cumque voluptates quidem repellendus maiores unde earum molestiae ad.
</p>
<script>
/**
* Показывает уведомление, пропадающее через 1.5 сек
*
* @param options.top {number} вертикальный отступ, в px
* @param options.right {number} правый отступ, в px
* @param options.cssText {string} строка стиля
* @param options.className {string} CSS-класс
* @param options.html {string} HTML-текст для показа
*/
function showNotification(options) {
var notification = document.createElement('div');
notification.className = "notification";
if (options.cssText) {
notification.style.cssText = options.cssText;
}
notification.style.top = (options.top || 0) + 'px'; // can use cssText
notification.style.right = (options.right || 0) + 'px'; // can use cssText
if (options.className) {
notification.classList.add(options.className);
}
notification.innerHTML = options.html;
document.body.appendChild(notification); // over cover
setTimeout(function() {
document.body.removeChild(notification);
}, 1500);
}
// тест работоспособности
var i = 0;
setInterval(function() {
showNotification({
top: 10,
right: 10,
html: 'Привет ' + ++i,
className: "welcome"
});
}, 2000);
</script>
</body>
</html>

View file

@ -0,0 +1,14 @@
.notification {
position: fixed;
z-index: 1000;
padding: 5px;
border: 1px solid black;
font: normal 20px Georgia;
background: white;
text-align: center;
}
.welcome {
background: red;
color: yellow;
}

View file

@ -0,0 +1,50 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="index.css">
</head>
<body>
<h2>Уведомление</h2>
<p>
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Dolorum aspernatur quam ex eaque inventore quod voluptatem adipisci omnis nemo nulla fugit iste numquam ducimus cumque minima porro ea quidem maxime necessitatibus beatae labore soluta voluptatum
magnam consequatur sit laboriosam velit excepturi laborum sequi eos placeat et quia deleniti? Corrupti velit impedit autem et obcaecati fuga debitis nemo ratione iste veniam amet dicta hic ipsam unde cupiditate incidunt aut iure ipsum officiis soluta
temporibus. Tempore dicta ullam delectus numquam consectetur quisquam explicabo culpa excepturi placeat quo sequi molestias reprehenderit hic at nemo cumque voluptates quidem repellendus maiores unde earum molestiae ad.
</p>
<p>В CSS есть готовый класс notification, который можно ставить уведомлению.</p>
<script>
/**
* Показывает уведомление, пропадающее через 1.5 сек
*
* @param options.top {number} вертикальный отступ, в px
* @param options.right {number} правый отступ, в px
* @param options.cssText {string} строка стиля
* @param options.className {string} CSS-класс
* @param options.html {string} HTML-текст для показа
*/
function showNotification(options) {
// ваш код
}
// тест работоспособности
var i = 0;
setInterval(function() {
showNotification({
top: 10,
right: 10,
html: 'Привет ' + ++i,
className: "welcome"
});
}, 2000);
</script>
</body>
</html>

View file

@ -0,0 +1,41 @@
importance: 5
---
# Создать уведомление
Напишите функцию `showNotification(options)`, которая показывает уведомление, пропадающее через 1.5 сек.
Описание функции:
```js
/**
* Показывает уведомление, пропадающее через 1.5 сек
*
* @param options.top {number} вертикальный отступ, в px
* @param options.right {number} правый отступ, в px
* @param options.cssText {string} строка стиля
* @param options.className {string} CSS-класс
* @param options.html {string} HTML-текст для показа
*/
function showNotification(options) {
// ваш код
}
```
Пример использования:
```js
// покажет элемент с текстом "Привет" и классом welcome справа-сверху окна
showNotification({
top: 10,
right: 10,
html: "Привет",
className: "welcome"
});
```
[demo src="solution"]
Элемент уведомления должен иметь CSS-класс `notification`, к которому добавляется класс из `options.className`, если есть. Исходный документ содержит готовые стили.

View file

@ -0,0 +1,295 @@
# Styles and classes
Before we get to Javascript ways of dealing with styles and classes -- here's an important rule.
There are generally two ways to style an element, both in HTML and Javascript:
1. Create a class in CSS and add it: `<div class="...">`
2. Write properties directly into `style`: `<div style="...">`.
[cut]
CSS is always the preferred way, both in HTML and Javascript. We should only use `style` if classes "can't handle it".
For instance, `style` is acceptable if we calculated coordinates for an element dynamically and want to set them from Javascript, like here:
```js
let top = /* complex calculations */;
let left = /* complex calculations */;
elem.style.left = left; // e.g '123px'
elem.style.top = top; // e.g '456px'
```
For other cases, like making the text red, adding a background icon -- describe that in CSS and then apply the class. That's more flexible and easier to support.
## className and classList
Changing a class is one of the most often actions in scripts.
In the ancient time, there was a limitation in Javascript: a reserved word like `"class"` could not be an object property. That limitation does not exist now, but at that time it was impossible to use `elem.class`.
So instead of `elem.class` we have `elem.className` property. It's the string with all classes, the same value as in the `"class"` attribute.
For instance:
```html run
<body class="main page">
<script>
alert(document.body.className); // main page
</script>
</body>
```
Adding/removing a class is a widespread operation. Using a string for such purpose is cumbersome, so there's another property for that: `elem.classList`.
The `elem.classList` is a special object with methods to `add/remove/toggle` classes.
For instance:
```html run
<body class="main page">
<script>
document.body.classList.add('article');
alert(document.body.className); // main page article
</script>
</body>
```
So we can operate both on the full class string using `className` or on individual classes using `classList`. What we choose depends on our needs.
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.contains("class")` -- returns `true/false`, checks for the given class.
Besides that, `classList` is iterable, so we can list all classes like this:
```html run
<body class="main page">
<script>
for(let name of document.body.classList) {
alert(name); // main, and then page
}
</script>
</body>
```
## Element style
The property `elem.style` is an object that corresponds to what's written in the `"style"` attribute. Setting `elem.style.width="100px"` works as if we had in the attribute `style="width:100px"`.
For multi-word property the camelCase is used:
```js no-beautify
background-color => elem.style.backgroundColor
z-index => elem.style.zIndex
border-left-width => elem.style.borderLeftWidth
```
For instance:
```js run
document.body.style.backgroundColor = prompt('background color?', 'green');
```
````smart header="Prefixed properties"
Browser-prefixed properties like `-moz-border-radius`, `-webkit-border-radius` also follow the same rule, for instance:
```js
button.style.MozBorderRadius = '5px';
button.style.WebkitBorderRadius = '5px';
```
That is: a dash `"-"` becomes an uppercase.
````
## Resetting the style
To "reset" the style property, we should assign an empty line to it. For instance, if we set a `width` and now want to remove it, then `elem.style.width=""`.
For instance, to hide an element, we can set `elem.style.display = "none"`.
And to show it back, we should not set another `display` like `elem.style.display = "block"`. To return the "default" `display`: `elem.style.display = ""`.
```js run
// if we run this code, the <body> would "blink"
document.body.style.display = "none";
setTimeout(() => document.body.style.display = "", 1000);
```
If we set `display` to an empty string, then the browser applies CSS classes and its built-in styles normally, as if there were no such `style` property.
````smart header="Full rewrite with `style.cssText`"
Normally, `style.*` assign individual style properties. We can't set the full style like `div.style="color: red; width: 100px"`, because `div.style` is an object.
To set the full style as a string, there's a special property `style.cssText`:
```html run
<div id="div">Button</div>
<script>
// we can set special style flags like "important" here
div.style.cssText=`color: red !important;
background-color: yellow;
width: 100px;
text-align: center;
`;
alert(div.style.cssText);
</script>
```
We rarely use it, because such a setting removes all existing styles: not adds, but rather replaces them. But still can be done for new elements when we know we don't delete something important.
The same can be accomplished by setting an attribute: `div.setAttribute('style', "color: red...")`.
````
## Mind the units
CSS units must exist in values. We should not set `elem.style.top` to `10`, but rather to `10px`. Otherwise it wouldn't work.
For instance:
```html run height=100
<body>
<script>
*!*
// won't work!
document.body.style.margin = 20;
alert(document.body.style.margin); // '' (empty string)
*/!*
// now the right way
document.body.style.margin = '20px';
alert(document.body.style.margin); // 20px
alert(document.body.style.marginTop); // 20px
alert(document.body.style.marginLeft); // 20px
</script>
</body>
```
Please note how the browser "unpacks" the property `style.margin` and infers `style.marginLeft` and `style.marginTop` (and other partial margins) from it.
## Computed styles: getComputedStyle
Modifying a style is easy. But how to *read* it?
For instance, we want to know the size, margins, the color of an element. How to do it?
**The `style` property contains only the style in the `"style"` attribute, without any CSS cascade.**
So we can't read anything that comes from CSS classes.
For instance, here `style` won't see the margin:
```html run height=60 no-beautify
<head>
<style> body { color: red; margin: 5px } </style>
</head>
<body>
The red text
<script>
*!*
alert(document.body.style.color); // empty
alert(document.body.style.marginTop); // empty
*/!*
</script>
</body>
```
...But what if we need, say, increase the margin by 20px? We want the current value for that.
There's another method for that: `getComputedStyle`.
The syntax is:
```js
getComputedStyle(element[, pseudo])
```
element
: Element to read the value for.
pseudo
: A pseudo-element if required, for instance `::before`. An empty string or no argument mean the element itself.
The result is an object with style properties, like `elem.style`, but now with respect to all CSS classes.
For instance:
```html run height=100
<head>
<style> body { color: red; margin: 5px } </style>
</head>
<body>
<script>
let computedStyle = getComputedStyle(document.body);
// now can read the margin and the color from it
alert( computedStyle.marginTop ); // 5px
alert( computedStyle.color ); // rgb(255, 0, 0)
</script>
</body>
```
```smart header="Computed and resolved values"
There are two concepts in [CSS](https://drafts.csswg.org/cssom/#resolved-values):
1. A *computed* style value is the one after all CSS rules and CSS inheritance is applied. If can look like `width: auto` or `font-size: 125%`.
2. A *resolved* style value is the one finally applied to the element. The browser takes the computed value and makes all units fixed and absolute, for instance: `width: 212px` or `font-size: 16px`. In some browsers values can have a floating point.
Long time ago `getComputedStyle` was created to get computed values, but it turned out that resolved values are much more convenient.
So nowadays `getComputedStyle` actually returns the final, resolved value.
```
````warn header="`getComputedStyle` requires the full property name"
We should always ask for the exact property that we want, like `paddingLeft` or `marginTop` or `borderTopWidth`. Otherwise the correct result is not guaranteed.
For instance, if properties `paddingLeft/paddingTop` come from the different CSS classes, then what should we get for `getComputedStyle(elem).padding`?
Some browsers (Chrome) show `10px` in the document below, and some of them (Firefox) -- do not:
```html run
<style>
body {
margin: 10px;
}
</style>
<script>
let style = getComputedStyle(document.body);
alert(style.margin); // empty string in Firefox
</script>
```
````
```smart header="\"Visited\" links styles are hidden!"
Visited links may be colored using `:visited` CSS pseudoclass.
But `getComputedStyle` does not give access to that color, because otherwise an arbitrary page could find out whether the user visited a link by creating it on the page and checking the styles.
Javascript we may not see the styles applied by `:visited`. And also, there's a limitation in CSS that forbids to apply geometry-changing styles in `:visited`. That's to guarantee that there's no side way for an evil page to see if a link was visited and hence to break the privacy.
```
## Summary
To manage classes, there are two DOM properties:
- `className` -- the string value, good to manage the whole set of classes.
- `classList` -- the object with methods `add/remove/toggle/contains`, good for individual classes.
To change the styles:
- The `style` property is an object with camelCased styles. Reading and writing to it has the same meaning as modifying individual properties in the `"style"` attribute. To see how to apply `important` and other rare stuff -- there's a list of methods at [MDN](mdn:api/CSSStyleDeclaration).
- The `style.cssText` property corresponds to the whole `"style"` attribute, the full string of styles.
To read the resolved styles (after all CSS is applied and final values are calculated):
- The `getComputedStyles(elem[, pseudo])` returns the style-like object with them. Read-only.

View file

@ -0,0 +1,18 @@
function getIEComputedStyle(elem, prop) {
var value = elem.currentStyle[prop] || 0;
// we use 'left' property as a place holder so backup values
var leftCopy = elem.style.left;
var runtimeLeftCopy = elem.runtimeStyle.left;
// assign to runtimeStyle and get pixel value
elem.runtimeStyle.left = elem.currentStyle.left;
elem.style.left = (prop === "fontSize") ? "1em" : value;
value = elem.style.pixelLeft + "px";
// restore values for left
elem.style.left = leftCopy;
elem.runtimeStyle.left = runtimeLeftCopy;
return value;
}

View file

@ -0,0 +1,30 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<script src="getiecomputedstyle.js"></script>
</head>
<body>
<style>
#margin-test {
margin: 1%;
border: 1px solid black;
}
</style>
<div id="margin-test">Тестовый элемент с margin 1%</div>
<script>
var elem = document.getElementById('margin-test');
if (!window.getComputedStyle) { // старые IE
document.write(getIEComputedStyle(elem, 'marginTop'));
} else {
document.write('Пример работает только в IE8-');
}
</script>
</body>
</html>

View file

@ -1,18 +1,19 @@
Сделаем цикл по узлам `<li>`: Let's make a loop over `<li>`:
```js ```js
var lis = document.getElementsByTagName('li'); for (let li of document.querySelector('li')) {
for (i = 0; i < lis.length; i++) {
... ...
} }
``` ```
В цикле для каждого `lis[i]` можно получить текст, используя свойство `firstChild`. Ведь первым в `<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:
Также можно получить количество потомков, используя `lis[i].getElementsByTagName('li')`. ```js
for (let li of document.querySelector('li')) {
let title = li.firstChild.data;
Напишите код с этой подсказкой. // title is the text in <li> before any other nodes
}
Если уж не выйдет -- тогда откройте решение. ```
Then we can get the number of descendants `li.getElementsByTagName('li')`.

View file

@ -1,44 +1,38 @@
<!DOCTYPE HTML> <!DOCTYPE HTML>
<html> <html>
<head>
<meta charset="utf-8">
</head>
<body> <body>
<ul> <ul>
<li>Животные <li>Animals
<ul> <ul>
<li>Млекопитающие <li>Mammals
<ul> <ul>
<li>Коровы</li> <li>Cows</li>
<li>Ослы</li> <li>Donkeys</li>
<li>Собаки</li> <li>Dogs</li>
<li>Тигры</li> <li>Tigers</li>
</ul> </ul>
</li> </li>
<li>Другие <li>Other
<ul> <ul>
<li>Змеи</li> <li>Snakes</li>
<li>Птицы</li> <li>Birds</li>
<li>Ящерицы</li> <li>Lizards</li>
</ul> </ul>
</li> </li>
</ul> </ul>
</li> </li>
<li>Рыбы <li>Fishes
<ul> <ul>
<li>Аквариумные <li>Aquarium
<ul> <ul>
<li>Гуппи</li> <li>Guppy</li>
<li>Скалярии</li> <li>Angelfish</li>
</ul> </ul>
</li> </li>
<li>Морские <li>Sea
<ul> <ul>
<li>Морская форель</li> <li>Sea trout</li>
</ul> </ul>
</li> </li>
</ul> </ul>
@ -46,21 +40,18 @@
</ul> </ul>
<script> <script>
var lis = document.getElementsByTagName('li'); for (let li of document.querySelector('li')) {
// get the title from the text node
let title = li.firstChild.data;
for (i = 0; i < lis.length; i++) { title = title.trim(); // remove extra spaces from ends
// получить название из текстового узла
var title = lis[i].firstChild.data;
title = title.trim(); // убрать лишние пробелы с концов // get the descendants count
let count = li.getElementsByTagName('li').length;
// получить количество детей alert(title + ': ' + count);
var childCount = lis[i].getElementsByTagName('li').length;
alert(title + ': ' + childCount);
} }
</script> </script>
</body> </body>
</html>
</html>

View file

@ -1,44 +1,38 @@
<!DOCTYPE HTML> <!DOCTYPE HTML>
<html> <html>
<head>
<meta charset="utf-8">
</head>
<body> <body>
<ul> <ul>
<li>Животные <li>Animals
<ul> <ul>
<li>Млекопитающие <li>Mammals
<ul> <ul>
<li>Коровы</li> <li>Cows</li>
<li>Ослы</li> <li>Donkeys</li>
<li>Собаки</li> <li>Dogs</li>
<li>Тигры</li> <li>Tigers</li>
</ul> </ul>
</li> </li>
<li>Другие <li>Other
<ul> <ul>
<li>Змеи</li> <li>Snakes</li>
<li>Птицы</li> <li>Birds</li>
<li>Ящерицы</li> <li>Lizards</li>
</ul> </ul>
</li> </li>
</ul> </ul>
</li> </li>
<li>Рыбы <li>Fishes
<ul> <ul>
<li>Аквариумные <li>Aquarium
<ul> <ul>
<li>Гуппи</li> <li>Guppy</li>
<li>Скалярии</li> <li>Angelfish</li>
</ul> </ul>
</li> </li>
<li>Морские <li>Sea
<ul> <ul>
<li>Морская форель</li> <li>Sea trout</li>
</ul> </ul>
</li> </li>
</ul> </ul>
@ -46,9 +40,8 @@
</ul> </ul>
<script> <script>
// .. ваш код .. // ... your code...
</script> </script>
</body> </body>
</html>
</html>

View file

@ -2,14 +2,13 @@ importance: 5
--- ---
# Дерево # Count descendants
Есть дерево из тегов `<ul>/<li>`. There's a tree structured as nested `ul/li`.
Напишите код, который для каждого элемента `<li>` выведет: Write the code that for each `<li>` shows:
1. Текст непосредственно в нём (без подразделов). 1. What's the text inside it (without the subtree)
2. Количество вложенных в него элементов `<li>` -- всех, с учётом вложенных. 2. The number of nested `<li>` -- all descendants, including the deeply nested ones.
[demo src="solution"] [demo src="solution"]

View file

@ -215,27 +215,20 @@ In other words, the method `closest` goes up from the element and checks each of
For instance: For instance:
```html run ```html run
<ul> <h1>Contents</h1>
<li class="chapter">Chapter I
<ul> <ul class="book">
<li class="subchapter">Chapter <span class="num">1.1</span></li> <li class="chapter">Chapter 1</li>
<li class="subchapter">Chapter <span class="num">1.2</span></li> <li class="chapter">Chapter 1</li>
</ul>
</li>
</ul> </ul>
<script> <script>
let numSpan = document.querySelector('.num'); let chapter = document.querySelector('.chapter'); // LI
// nearest li alert(chapter.closest('.book')); // UL above our this LI
alert(numSpan.closest('li').className) // subchapter
// nearest chapter alert(chapter.closest('h1')); // null
alert(numSpan.closest('.chapter').className) // chapter // because h1 is not an ancestor
// nearest span
// (the numSpan is the span itself, so it's the result)
alert(numSpan.closest('span') === numSpan) // true
</script> </script>
``` ```

View file

@ -11,7 +11,7 @@ Write the code to select the element with `data-widget-name` attribute from the
<html> <html>
<body> <body>
<div data-widget-name="menu">Выберите жанр</div> <div data-widget-name="menu">Choose the genre</div>
<script> <script>
/* your code */ /* your code */

View file

@ -18,7 +18,7 @@ for (let link of links) {
if (href.startsWith('http://internal.com')) continue; // internal if (href.startsWith('http://internal.com')) continue; // internal
link.classList.add('external'); link.style.color = 'yellow';
} }
``` ```
@ -32,5 +32,5 @@ Please note: we use `link.getAttribute('href')`. Not `link.href`, because we nee
let selector = 'a[href*="://"]:not([href^="http://internal.com"])'; let selector = 'a[href*="://"]:not([href^="http://internal.com"])';
let links = document.querySelectorAll(selector); let links = document.querySelectorAll(selector);
links.forEach(link => link.classList.add('external')); links.forEach(link => link.style.color = 'yellow');
``` ```

View file

@ -1,14 +1,5 @@
<!DOCTYPE HTML> <!DOCTYPE HTML>
<html> <html>
<head>
<meta charset="utf-8">
<style>
.external {
background-color: yellow;
}
</style>
</head>
<body> <body>
<a name="list">the list</a> <a name="list">the list</a>
@ -25,7 +16,7 @@
let selector = 'a[href*="://"]:not([href^="http://internal.com"])'; let selector = 'a[href*="://"]:not([href^="http://internal.com"])';
let links = document.querySelectorAll(selector); let links = document.querySelectorAll(selector);
links.forEach(link => link.classList.add('external')) links.forEach(link => link.style.color = 'yellow');
</script> </script>
</body> </body>

View file

@ -2,21 +2,17 @@ importance: 3
--- ---
# Add the class to external links # Make external links yellow
Make all external links yellow by adding them the class `"external"`. Make all external links yellow by altering their `style` property.
A link is external if: A link is external if:
- It's `href` has `://` in it - It's `href` has `://` in it
- But doesn't start with `http://internal.com`. - But doesn't start with `http://internal.com`.
```html run Example:
<style>
.external {
background-color: yellow
}
</style>
```html run
<a name="list">the list</a> <a name="list">the list</a>
<ul> <ul>
<li><a href="http://google.com">http://google.com</a></li> <li><a href="http://google.com">http://google.com</a></li>
@ -26,6 +22,12 @@ A link is external if:
<li><a href="http://nodejs.org">http://nodejs.org</a></li> <li><a href="http://nodejs.org">http://nodejs.org</a></li>
<li><a href="http://internal.com/test">http://internal.com/test</a></li> <li><a href="http://internal.com/test">http://internal.com/test</a></li>
</ul> </ul>
<script>
// setting style for a single link
let link = document.querySelector('a');
link.style.color = 'yellow';
</script>
``` ```
The result should be: The result should be:

View file

@ -1,4 +1,4 @@
# DOM: attributes and properties # 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.
@ -204,7 +204,18 @@ DOM properties are not always strings. For instance, `input.checked` property (f
</script> </script>
``` ```
Javascript enforces the right type for a DOM property. There are more advanced examples. As we've already seen, `style` property is an object:
```html run
<div id="div" style="color:red;font-size:120%">Hello</div>
<script>
alert(div.getAttribute('style')); // color:red;font-size:120%
alert(div.style); // [object CSSStyleDeclaration]
alert(div.style.color); // red
</script>
```
**But even if a DOM property type is a string, it may differ from the attribute.** **But even if a DOM property type is a string, it may differ from the attribute.**
@ -226,50 +237,6 @@ Here we can see that:
Let's note again: if we need the value exactly as written in the HTML, we need to use `getAttribute`. Let's note again: if we need the value exactly as written in the HTML, we need to use `getAttribute`.
## className and classList
The `"class"` attribute is truly special, as there are two properties that correspond to it:
1. `className` -- the string, same value as in the attribute. In the old times, there was a problem to use a reserved word such as `"class"` as an object property (now it's ok). So `className` was chosen instead.
2. `classList` -- a special object with methods to `add/remove/toggle` classes. That's for better convenience.
For instance:
```html run
<body class="main page">
<script>
// className: get the classes
alert( document.body.className ); // main page
// className: change the classes
document.body.className = "class1 class2";
// classList: add one more class
document.body.classList.add('class3');
alert(document.body.className); // class1 class2 class 3
</script>
</body>
```
So we can operate both on the full class string using `className` or on individual classes using `classList`. What we choose depends on our needs.
Methods of `classList`:
- `elem.classList.contains("class")` -- returns `true/false`, checks for the given class.
- `elem.classList.add/remove("class")` -- adds/removes the class.
- `elem.classList.toggle("class")` -- if the class exists, then removes it, otherwise adds it.
Besides that, `classList` is iterable, so we can list all classes like this:
```html run
<body class="main page">
<script>
for(let name of document.body.classList) {
alert(name); // main, then page
}
</script>
</body>
```
## Non-standard attributes, dataset ## Non-standard attributes, dataset
When writing HTML, we use a lot of standard attributes. But what about non-standard, custom ones? May they be useful? What for? When writing HTML, we use a lot of standard attributes. But what about non-standard, custom ones? May they be useful? What for?
@ -333,15 +300,7 @@ That's because an attribute is more convenient to manage. If we want to change t
div.setAttribute('order-state', 'canceled'); div.setAttribute('order-state', 'canceled');
``` ```
For classes we would have to clean the current state and add the new one. More cumbersome: ...But there may be a possible problem here. What if we use a non-standard attribute for our purposes and later the standard introduces it and makes it do something? The HTML language is alive, it grows, more attributes appear to suit the needs of developers. There may be unexpected side-effects.
```js
// two lines, and we need to know the old class to clean it
div.classList.remove('order-state-new');
div.classList.add('order-state-canceled');
```
...But there's a problem here. What if we use a non-standard attribute for our purposes and later the standard introduces it and makes it do something? The HTML language is alive, it grows, more attributes appear to suit the needs of developers. There may be unexpected side-effects.
To evade conflicts, there exist [data-*](https://html.spec.whatwg.org/#embedding-custom-non-visible-data-with-the-data-*-attributes) attributes. To evade conflicts, there exist [data-*](https://html.spec.whatwg.org/#embedding-custom-non-visible-data-with-the-data-*-attributes) attributes.
@ -432,7 +391,3 @@ Some use cases:
- We want to access a non-standard attribute. But if it starts with `data-`, then we should use `dataset`. - We want to access a non-standard attribute. But if it starts with `data-`, then we should use `dataset`.
- We want to read the value "as written" in HTML. The value of the DOM property may be different, for instance `href` property is always a full URL, and we may want to get the "original" value. - We want to read the value "as written" in HTML. The value of the DOM property may be different, for instance `href` property is always a full URL, and we may want to get the "original" value.
Also please note that for `class` attribute there are two DOM properties:
- `className` -- the string value.
- `classList` -- the object for easier management of individual classes.