This commit is contained in:
Ilya Kantor 2017-02-13 23:38:52 +03:00
parent 8979e9f0ff
commit 5ef1b92e8b
11 changed files with 527 additions and 672 deletions

View file

@ -1,19 +0,0 @@
```html run height=100
<body>
<div id="widget" data-widget-name="menu">Выберите жанр</div>
<script>
var div = document.getElementById('widget');
var widgetName = div.getAttribute('data-widget-name');
// или так, кроме IE10-
var widgetName = div.dataset.widgetName;
alert( widgetName ); // "menu"
</script>
</body>
```

View file

@ -1,14 +0,0 @@
<!DOCTYLE HTML>
<html>
<body>
<div id="widget" data-widget-name="menu">Выберите жанр</div>
<script>
// ваш код
</script>
</body>
</html>

View file

@ -1,25 +0,0 @@
importance: 5
---
# Получите пользовательский атрибут
1. Получите `div` в переменную.
2. Получите значение атрибута `"data-widget-name"` в переменную.
3. Выведите его.
Документ:
```html
<body>
<div id="widget" data-widget-name="menu">Выберите жанр</div>
<script>
/* ... */
</script>
</body>
```
[edit src="solution" task]

View file

@ -1,52 +0,0 @@
Сначала можно найти ссылки, например, при помощи `document.querySelectorAll('a')`, а затем выбрать из них нужные.
Затем определимся -- что использовать для проверки адреса ссылки: свойство `href` или атрибут `getAttribute('href')`?
Различие между ними заключается в том, что свойство будет содержать полный путь ссылки, а атрибут -- значение, указанное в HTML.
Если открыть страницу локально, на диске, то для `<a href="/tutorial">` значения будут такими:
- `a.getAttribute('href') == "/tutorial"`.
- `a.href == "file:///tutorial"` (возможно, в пути будет также буква диска).
Здесь нужен именно атрибут, хотя бы потому, что в свойстве все ссылки уже с хостом и протоколом, а нам надо понять, был ли протокол в `href` или нет.
Правила определения:
- Ссылки без `href` и без протокола `://` являются заведомо внутренними.
- Там, где протокол есть -- проверяем, начинается ли адрес с `http://internal.com`.
Итого, код может быть таким:
```js
var links = document.querySelectorAll('a');
for (var i = 0; i < links.length; i++) {
var a = links[i];
var href = a.getAttribute('href');
if (!href) continue; // нет атрибута
if (href.indexOf('://') == -1) continue; // без протокола
if (href.indexOf('http://internal.com') === 0) continue; // внутренняя
a.classList.add('external');
}
```
...Но, как это часто бывает, знание CSS может упростить задачу. Удобнее и эффективнее здесь -- указать проверки для `href` прямо в CSS-селекторе:
```js
// ищем все ссылки, у которых в href есть протокол,
// но адрес начинается не с http://internal.com
var css = 'a[href*="://"]:not([href^="http://internal.com"])';
var links = document.querySelectorAll(css);
for (var i = 0; i < links.length; i++) {
links[i].classList.add('external');
}
```

View file

@ -1,545 +0,0 @@
# Attributes and DOM 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.
For instance, if the tag is `<body id="page">`, then the DOM object will have `body.id = "page"`.
But the mapping is not one-to-one! Sometimes they are different, we'll see that soon. We should understand attributes and DOM properties relationship to feel comfortable with DOM.
[cut]
## Custom DOM properties
We've already seen built-in DOM properties. But technically no one limits us.
DOM nodes are regular Javascript objects. We can alter them if we'd like, create custom properties and methods.
For instance, let's create a new property in `document.body`:
```js run
document.body.myData = {
name: 'Caesar',
title: 'Imperator'
};
alert(document.body.myData.title); // Imperator
```
We can add a method as well:
```js run
document.body.sayHi = function() {
alert(this.nodeName);
};
document.body.sayHi(); // BODY (the value of "this" in the method is document.body)
```
We can also modify built-in prototypes like `Element.prototype` and add new methods to all element nodes.
So, DOM properties:
- Can have any value.
- Are case-sensitive (`elem.nodeType`, not `elem.NoDeTyPe`).
- Work, because DOM nodes are objects.
## HTML attributes
In HTML language, tags may have attributes. When the browser reads HTML text and creates DOM objects for tags, it recognizes *known* attributes and creates DOM properties from them.
So when an element has `id` or another standard attribute, the corresponding property gets created. But that doesn't happen if the attribute is non-standard.
For instance:
```html run
<body id="test" something="non-standard">
<script>
alert(document.body.id); // test
*!*
alert(document.body.something); // undefined
*/!*
</script>
</body>
```
Please note that a standard attribute for one element can be unknown for another one. All standard attributes are described in the specification for the corresponding class.
For instance, `type` is standard for `<input>` ([HTMLInputElement](https://html.spec.whatwg.org/#htmlinputelement)), but not for `<body>` ([HTMLBodyElement](https://html.spec.whatwg.org/#htmlbodyelement)):
```html run
<body id="body" type="...">
<input id="input" type="text">
<script>
alert(input.type); // text
*!*
alert(body.type); // undefined, DOM property not created
*/!*
</script>
</body>
```
So, if an attribute is non-standard, there won't be DOM-property for it. Is there a way to access such attributes?
Sure. All attributes are accessible using following methods:
- `elem.hasAttribute(name)` -- checks for existance.
- `elem.getAttribute(name)` -- gets the value.
- `elem.setAttribute(name, value)` -- sets the value.
- `elem.removeAttribute(name)` -- removes the attribute.
Also one can read all attributes using `elem.attributes`. It's a collection of [Attr](https://dom.spec.whatwg.org/#attr) objects, each one with `name` and `value`.
These methods operate exactly with what's written in HTML.
Here's an example:
```html run
<body type="test" something="non-standard">
<script>
alert(document.body.getAttribute('type')); // test
*!*
alert(document.body.getAttribute('something')); // non-standard
*/!*
</script>
</body>
```
HTML attributes have following special features:
- They are always strings.
- Their name is case-insensitive (that's HTML: `id` is same as `ID`).
Here's the extended demo of working with attributes:
```html run
<body>
<div id="elem" about="Elephant"></div>
<script>
alert( elem.getAttribute('About') ); // (1) 'Elephant', reading
elem.setAttribute('Test', 123); // (2), writing
alert( elem.outerHTML ); // (3), see it's there
for (let attr of elem.attributes) { // (4) list all
alert( attr.name + " = " + attr.value );
}
</script>
</body>
```
Please note:
1. `getAttribute('About')` -- the first letter is uppercase here, and in HTML it's all lowercase. But that doesn't matter: attribute names are case-insensitive.
2. We can assign anything to an attribute, but that becomes a string. So here we have `"123"` as the value.
3. All attributes including ones that we set are seen in `innerHTML`.
4. The `attributes` collection is iterable and has all attributes with `name` and `value`.
## Attribute synchronization
All standard DOM properties are synchronized with
Все стандартные свойства DOM синхронизируются с атрибутами, однако не всегда такая синхронизация происходит 1-в-1, поэтому иногда нам нужно значение именно из HTML, то есть атрибут.
Рассмотрим несколько примеров.
### Ссылка "как есть" из атрибута href
Синхронизация не гарантирует одинакового значения в атрибуте и свойстве.
Для примера, посмотрим, что произойдет с атрибутом `"href"` при изменении свойства:
```html height=30 run
<a id="a" href="#"></a>
<script>
*!*
a.href = '/';
*/!*
alert( 'атрибут:' + a.getAttribute('href') ); // '/'
alert( 'свойство:' + a.href ); // *!*полный URL*/!*
</script>
```
Это происходит потому, что атрибут может быть любым, а свойство `href`, <a href="http://www.w3.org/TR/REC-html40/struct/links.html#adef-href">в соответствии со спецификацией W3C</a>, должно быть полной ссылкой.
Стало быть, если мы хотим именно то, что в HTML, то нужно обращаться через атрибут.
````smart header="Есть и другие подобные атрибуты"
Кстати, есть и другие атрибуты, которые не копируются в точности. Например, DOM-свойство `input.checked` имеет логическое значение `true/false`, а HTML-атрибут `checked` -- любое строковое, важно лишь его наличие.
Работа с `checked` через атрибут и свойство:
```html run
<input id="input" type="checkbox" checked>
<script>
*!*
// работа с checked через атрибут
*/!*
alert( input.getAttribute('checked') ); // пустая строка
input.removeAttribute('checked'); // снять галочку
*!*
// работа с checked через свойство
*/!*
alert( input.checked ); // false <-- может быть только true/false
input.checked = true; // поставить галочку
</script>
```
````
### Исходное значение value
Изменение некоторых свойств обновляет атрибут. Но это скорее исключение, чем правило.
**Чаще синхронизация -- односторонняя: свойство зависит от атрибута, но не наоборот.**
Например, при изменении свойства `input.value` атрибут `input.getAttribute('value')` не меняется:
```html height=30 run
<body>
<input id="input" type="text" value="markup">
<script>
*!*
input.value = 'new'; // поменяли свойство
alert( input.getAttribute('value') ); // 'markup', не изменилось!
*/!*
</script>
</body>
```
То есть, изменение DOM-свойства `value` на атрибут не влияет, он остаётся таким же.
А вот изменение атрибута обновляет свойство:
```html height=30 run
<body>
<input id="input" type="text" value="markup">
<script>
*!*
input.setAttribute('value', 'new'); // поменяли атрибут
alert( input.value ); // 'new', input.value изменилось!
*/!*
</script>
</body>
```
Эту особенность можно красиво использовать.
Получается, что атрибут `input.getAttribute('value')` хранит оригинальное (исходное) значение даже после того, как пользователь заполнил поле и свойство изменилось.
Например, можно взять изначальное значение из атрибута и сравнить со свойством, чтобы узнать, изменилось ли значение. А при необходимости и перезаписать свойство атрибутом, отменив изменения.
## Классы в виде строки: className
Атрибуту `"class"` соответствует свойство `className`.
Так как слово `"class"` является зарезервированным словом в Javascript, то при проектировании DOM решили, что соответствующее свойство будет называться `className`.
Например:
```html run
<body class="main page">
<script>
// прочитать класс элемента
alert( document.body.className ); // main page
// поменять класс элемента
document.body.className = "class1 class2";
</script>
</body>
```
Кстати, есть и другие атрибуты, которые называются иначе, чем свойство. Например, атрибуту `for` (`<label for="...">`) соответствует свойство с названием `htmlFor`.
## Классы в виде объекта: classList
Атрибут `class` -- уникален. Ему соответствует аж целых два свойства!
Работать с классами как со строкой неудобно. Поэтому, кроме `className`, в современных браузерах есть свойство `classList`.
**Свойство `classList` -- это объект для работы с классами.**
Оно поддерживается в IE начиная с IE10, но его можно эмулировать в IE8+, подключив мини-библиотеку [classList.js](https://github.com/eligrey/classList.js).
Методы `classList`:
- `elem.classList.contains("class")` -- возвращает `true/false`, в зависимости от того, есть ли у элемента класс `class`.
- `elem.classList.add/remove("class")` -- добавляет/удаляет класс `class`
- `elem.classList.toggle("class")` -- если класса `class` нет, добавляет его, если есть -- удаляет.
Кроме того, можно перебрать классы через `for`, так как `classList` -- это псевдо-массив.
Например:
```html run
<body class="main page">
<script>
var classList = document.body.classList;
classList.remove('page'); // удалить класс
classList.add('post'); // добавить класс
for (var i = 0; i < classList.length; i++) { // перечислить классы
alert( classList[i] ); // main, затем post
}
alert( classList.contains('post') ); // проверить наличие класса
alert( document.body.className ); // main post, тоже работает
</script>
</body>
```
## Нестандартные атрибуты
У каждого элемента есть некоторый набор стандартных свойств, например для `<a>` это будут `href`, `name`, `title`, а для `<img>` это будут `src`, `alt`, и так далее.
Точный набор свойств описан в стандарте, обычно мы более-менее представляем, если пользуемся HTML, какие свойства могут быть, а какие -- нет.
Для нестандартных атрибутов DOM-свойство не создаётся.
Например:
```html run
<div id="elem" href="http://ya.ru" about="Elephant"></div>
<script>
alert( elem.id ); // elem
*!*
alert( elem.about ); // undefined
*/!*
</script>
```
Свойство является стандартным, только если оно описано в стандарте именно для этого элемента.
То есть, если назначить элементу `<img>` атрибут `href`, то свойство `img.href` от этого не появится. Как, впрочем, и если назначить ссылке `<a>` атрибут `alt`:
```html run
<img id="img" href="test">
<a id="link" alt="test"></a>
<script>
alert( img.href ); // undefined
alert( link.alt ); // undefined
</script>
```
Нестандартные атрибуты иногда используют для CSS.
В примере ниже для показа "состояния заказа" используется атрибут `order-state`:
```html run
<style>
.order[order-state="new"] {
color: green;
}
.order[order-state="pending"] {
color: blue;
}
.order[order-state="canceled"] {
color: red;
}
</style>
<div class="order" order-state="new">
Новый заказ.
</div>
<div class="order" order-state="pending">
Ожидающий заказ.
</div>
<div class="order" order-state="canceled">
Заказ отменён.
</div>
```
Почему именно атрибут? Разве нельзя было сделать классы `.order-state-new`, `.order-state-pending`, `order-state-canceled`?
Конечно можно, но манипулировать атрибутом из JavaScript гораздо проще.
Например, если нужно отменить заказ, неважно в каком он состоянии сейчас -- это сделает код:
```js
div.setAttribute('order-state', 'canceled');
```
Для классов -- нужно знать, какой класс у заказа сейчас. И тогда мы можем снять старый класс, и поставить новый:
```js
div.classList.remove('order-state-new');
div.classList.add('order-state-canceled');
```
...То есть, требуется больше исходной информации и надо написать больше букв. Это менее удобно.
Проще говоря, значение атрибута -- произвольная строка, значение класса -- это "есть" или "нет", поэтому естественно, что атрибуты "мощнее" и бывают удобнее классов как в JS так и в CSS.
## Свойство dataset, data-атрибуты
С помощью нестандартных атрибутов можно привязать к элементу данные, которые будут доступны в JavaScript.
Как правило, это делается при помощи атрибутов с названиями, начинающимися на `data-`, например:
```html run
<div id="elem" *!*data-about*/!*="Elephant" *!*data-user-location*/!*="street">
По улице прошёлся слон. Весьма красив и толст был он.
</div>
<script>
alert( elem.getAttribute('data-about') ); // Elephant
alert( elem.getAttribute('data-user-location') ); // street
</script>
```
[Стандарт HTML5](http://www.w3.org/TR/2010/WD-html5-20101019/elements.html#embedding-custom-non-visible-data-with-the-data-attributes) специально разрешает атрибуты `data-*` и резервирует их для пользовательских данных.
При этом во всех браузерах, кроме IE10-, к таким атрибутам можно обратиться не только как к атрибутам, но и как к свойствам, при помощи специального свойства `dataset`:
```html run
<div id="elem" data-about="Elephant" data-user-location="street">
По улице прошёлся слон. Весьма красив и толст был он.
</div>
<script>
*!*
alert( elem.dataset.about ); // Elephant
alert( elem.dataset.userLocation ); // street
*/!*
</script>
```
Обратим внимание -- название `data-user-location` трансформировалось в `dataset.userLocation`. Дефис превращается в большую букву.
## Полифилл для атрибута hidden
Для старых браузеров современные атрибуты иногда нуждаются в полифилле. Как правило, такой полифилл включает в себя не только JavaScript, но и CSS.
Например, свойство/атрибут hidden не поддерживается в IE11.
Этот атрибут должен прятать элемент, действие весьма простое, для его поддержки в HTML достаточно такого CSS:
```html run height="80" no-beautify
<style>
*!*
[hidden] { display: none }
*/!*
</style>
<div>Текст</div>
<div hidden>С атрибутом hidden</div>
<div id="last">Со свойством hidden</div>
<script>
last.hidden = true;
</script>
```
Если запустить в IE11- пример выше, то `<div hidden>` будет скрыт, а вот последний `div`, которому поставили свойство `hidden` в JavaScript -- по-прежнему виден.
Это потому что CSS "не видит" присвоенное свойство, нужно синхронизировать его в атрибут.
Вот так -- уже работает:
```html run height="80" no-beautify
<style>
*!*
[hidden] { display: none }
*/!*
</style>
<script>
*!*
if (document.documentElement.hidden === undefined) {
Object.defineProperty(Element.prototype, "hidden", {
set: function(value) {
this.setAttribute('hidden', value);
},
get: function() {
return this.getAttribute('hidden');
}
});
}
*/!*
</script>
<div>Текст</div>
<div hidden>С атрибутом hidden</div>
<div id="last">Со свойством hidden</div>
<script>
last.hidden = true;
</script>
```
## "Особенности" IE8
Если вам нужна поддержка этих версий IE -- есть пара нюансов.
1. Во-первых, версии IE8- синхронизируют все свойства и атрибуты, а не только стандартные:
```js run
document.body.setAttribute('my', 123);
alert( document.body.my ); // 123 в IE8-
```
При этом даже тип данных не меняется. Атрибут не становится строкой, как ему положено.
2. Ещё одна некорректность IE8-: для изменения класса нужно использовать именно свойство `className`, вызов `setAttribute('class', ...)` не сработает.
Вывод из этого довольно прост -- чтобы не иметь проблем в IE8, нужно использовать всегда только свойства, кроме тех ситуаций, когда нужны именно атрибуты. Впрочем, это в любом случае хорошая практика.
## Итого
- Атрибуты -- это то, что написано в HTML.
- Свойство -- это то, что находится в свойстве DOM-объекта.
Таблица сравнений для атрибутов и свойств:
<table>
<thead>
<tr>
<th>Свойства</th>
<th>Атрибуты</th>
</tr>
</thead>
<tbody>
<tr>
<td>Любое значение</td>
<td>Строка</td>
</tr>
<tr>
<td>Названия регистрозависимы</td>
<td>Не чувствительны к регистру</td>
</tr>
<tr>
<td>Не видны в <code>innerHTML</code></td>
<td>Видны в <code>innerHTML</code></td>
</tr>
</tbody>
</table>
Синхронизация между атрибутами и свойствами:
- Стандартные свойства и атрибуты синхронизируются: установка атрибута автоматически ставит свойство DOM. Некоторые свойства синхронизируются в обе стороны.
- Бывает так, что свойство не совсем соответствует атрибуту. Например, "логические" свойства вроде `checked`, `selected` всегда имеют значение `true/false`, а в атрибут можно записать произвольную строку.Выше мы видели другие примеры на эту тему, например `href`.
Нестандартные атрибуты:
- Нестандартный атрибут (если забыть глюки старых IE) никогда не попадёт в свойство, так что для кросс-браузерного доступа к нему нужно обязательно использовать `getAttribute`.
- Атрибуты, название которых начинается с `data-`, можно прочитать через `dataset`. Эта возможность не поддерживается IE10-.
Для того, чтобы избежать проблем со старыми IE, а также для более короткого и понятного кода старайтесь везде использовать свойства, а атрибуты -- только там, где это *действительно* нужно.
А *действительно* нужны атрибуты очень редко - лишь в следующих трёх случаях:
1. Когда нужно кросс-браузерно получить нестандартный HTML-атрибут.
2. Когда нужно получить "оригинальное значение" стандартного HTML-атрибута, например, `<input value="...">`.
3. Когда нужно получить список всех атрибутов, включая пользовательские. Для этого используется коллекция `attributes`.
Если вы хотите использовать собственные атрибуты в HTML, то помните, что атрибуты с именем, начинающимся на `data-` валидны в HTML5 и современные браузеры поддерживают доступ к ним через свойство `dataset`.

View file

@ -0,0 +1,20 @@
```html run height=100
<!DOCTYLE HTML>
<html>
<body>
<div data-widget-name="menu">Choose the genre</div>
<script>
// getting it
let elem = document.querySelector('[data-widget-name]');
// reading the value
alert(elem.dataset.widgetName);
// or
alert(elem.getAttribute('data-widget-name'));
</script>
</body>
</html>
```

View file

@ -0,0 +1,21 @@
importance: 5
---
# Get the attribute
Write the code to select the element with `data-widget-name` attribute from the document and to read its value.
```html run
<!DOCTYLE HTML>
<html>
<body>
<div data-widget-name="menu">Выберите жанр</div>
<script>
/* your code */
</script>
</body>
</html>
```

View file

@ -0,0 +1,36 @@
First, we need to find all external references.
There are two ways.
The first is to find all links using `document.querySelectorAll('a')` and then filter out what we need:
```js
let links = document.querySelectorAll('a');
for (let link of links) {
*!*
let href = link.getAttribute('href');
*/!*
if (!href) continue; // no attribute
if (!href.includes('://')) continue; // no protocol
if (href.startsWith('http://internal.com')) continue; // internal
link.classList.add('external');
}
```
Please note: we use `link.getAttribute('href')`. Not `link.href`, because we need the value from HTML.
...Another, simpler way would be to add the checks to CSS selector:
```js
// look for all links that have :// in href
// but href doesn't start with http://internal.com
let selector = 'a[href*="://"]:not([href^="http://internal.com"])';
let links = document.querySelectorAll(selector);
links.forEach(link => link.classList.add('external'));
```

View file

@ -9,11 +9,9 @@
}
</style>
</head>
<body>
<a name="list">список</a>
<a name="list">the list</a>
<ul>
<li><a href="http://google.com">http://google.com</a></li>
<li><a href="/tutorial">/tutorial.html</a></li>
@ -23,16 +21,12 @@
<li><a href="http://internal.com/test">http://internal.com/test</a></li>
</ul>
<script>
var css = 'a[href*="://"]:not([href^="http://internal.com"])';
var links = document.querySelectorAll(css);
let selector = 'a[href*="://"]:not([href^="http://internal.com"])';
let links = document.querySelectorAll(selector);
for (var i = 0; i < links.length; i++) {
links[i].classList.add('external');
}
links.forEach(link => link.classList.add('external'))
</script>
</body>
</html>

View file

@ -2,11 +2,13 @@ importance: 3
---
# Поставьте класс ссылкам
# Add the class to external links
Сделайте желтыми внешние ссылки, добавив им класс `external`.
Make all external links yellow by adding them the class `"external"`.
Все ссылки без `href`, без протокола и начинающиеся с `http://internal.com` считаются внутренними.
A link is external if:
- It's `href` has `://` in it
- But doesn't start with `http://internal.com`.
```html run
<style>
@ -15,7 +17,7 @@ importance: 3
}
</style>
<a name="list">список</a>
<a name="list">the list</a>
<ul>
<li><a href="http://google.com">http://google.com</a></li>
<li><a href="/tutorial">/tutorial.html</a></li>
@ -26,7 +28,6 @@ importance: 3
</ul>
```
Результат:
The result should be:
[iframe border=1 height=180 src="solution"]

View file

@ -0,0 +1,438 @@
# DOM: attributes and properties [todo]
The browser "reads" HTML text and generates DOM objects from it. For element nodes most standard HTML attributes automatically become properties of DOM objects.
For instance, if the tag is `<body id="page">`, then the DOM object will have `body.id="page"`.
But the mapping is not one-to-one! In this chapter we'll see that DOM properties and attributes are linked, but they are still different things.
[cut]
## DOM properties
We've already seen built-in DOM properties. But technically no one limits us -- we can add own own.
DOM nodes are regular Javascript objects. We can alter them.
For instance, let's create a new property in `document.body`:
```js run
document.body.myData = {
name: 'Caesar',
title: 'Imperator'
};
alert(document.body.myData.title); // Imperator
```
We can add a method as well:
```js run
document.body.sayHi = function() {
alert(this.tagName);
};
document.body.sayHi(); // BODY (the value of "this" in the method is document.body)
```
We can also modify built-in prototypes like `Element.prototype` and add new methods to all elements:
```js run
Element.prototype.sayHi = function() {
alert(`Hello, I'm ${this.tagName}`);
};
document.documentElement.sayHi(); // Hello, I'm HTML
document.body.sayHi(); // Hello, I'm BODY
```
So, DOM properties:
- Can have any value.
- Are case-sensitive (`elem.nodeType`, not `elem.NoDeTyPe`).
- Work, because DOM nodes are objects.
## HTML attributes
In HTML language, tags may have attributes. When the browser reads HTML text and creates DOM objects for tags, it recognizes *standard* attributes and creates DOM properties from them.
So when an element has `id` or another standard attribute, the corresponding property gets created. But that doesn't happen if the attribute is non-standard.
For instance:
```html run
<body id="test" something="non-standard">
<script>
alert(document.body.id); // test
*!*
// non-standard attribute does not yield a property
alert(document.body.something); // undefined
*/!*
</script>
</body>
```
Please note that a standard attribute for one element can be unknown for another one.
Standard attributes are described in the specification for the corresponding class. For instance, `"type"` is standard for `<input>` ([HTMLInputElement](https://html.spec.whatwg.org/#htmlinputelement)), but not for `<body>` ([HTMLBodyElement](https://html.spec.whatwg.org/#htmlbodyelement)):
```html run
<body id="body" type="...">
<input id="input" type="text">
<script>
alert(input.type); // text
*!*
alert(body.type); // undefined: DOM property not created, because it's non-standard
*/!*
</script>
</body>
```
So, if an attribute is non-standard, there won't be DOM-property for it. Is there a way to access such attributes?
Sure. All attributes are accessible using following methods:
- `elem.hasAttribute(name)` -- checks for existance.
- `elem.getAttribute(name)` -- gets the value.
- `elem.setAttribute(name, value)` -- sets the value.
- `elem.removeAttribute(name)` -- removes the attribute.
Also one can read all attributes using `elem.attributes`. It's a collection of [Attr](https://dom.spec.whatwg.org/#attr) objects, each one with `name` and `value`.
These methods operate exactly with what's written in HTML.
Here's a demo of reading a non-standard property:
```html run
<body something="non-standard">
<script>
*!*
alert(document.body.getAttribute('something')); // non-standard
*/!*
</script>
</body>
```
HTML attributes have following features:
- Their name is case-insensitive (that's HTML: `id` is same as `ID`).
- They are always strings.
Here's an extended demo of working with attributes:
```html run
<body>
<div id="elem" about="Elephant"></div>
<script>
alert( elem.getAttribute('About') ); // (1) 'Elephant', reading
elem.setAttribute('Test', 123); // (2), writing
alert( elem.outerHTML ); // (3), see it's there
for (let attr of elem.attributes) { // (4) list all
alert( attr.name + " = " + attr.value );
}
</script>
</body>
```
Please note:
1. `getAttribute('About')` -- the first letter is uppercase here, and in HTML it's all lowercase. But that doesn't matter: attribute names are case-insensitive.
2. We can assign anything to an attribute, but that becomes a string. So here we have `"123"` as the value.
3. All attributes including ones that we set are seen in `innerHTML`.
4. The `attributes` collection is iterable and has all attributes with `name` and `value`.
## Property-attribute sync
When a standard attribute changes, the corresponding property is auto-updated, and (with some exceptions) vise-versa.
In the example below `id` is modified as an attribute, and we can see the property change too. And then the same backwards:
```html run
<input>
<script>
let input = document.querySelector('input');
// attribute => property
input.setAttribute('id', 'id');
alert(input.id); // id (updated)
// property => attribute
input.id = 'newId';
alert(input.getAttribute('id')); // newId (updated)
</script>
```
But there are exclusions, for instance `input.value` synchronizes only from attribute to property:
```html run
<input>
<script>
let input = document.querySelector('input');
// attribute => property
input.setAttribute('value', 'text');
alert(input.value); // text
*!*
// NOT property => attribute
input.value = 'newValue';
alert(input.getAttribute('value')); // text (not updated!)
*/!*
</script>
```
In the example above:
- Changing the attribute `value` updates the property.
- But the property change does not affect the attribute.
Speaking about `value`, that actually can come in handy, because the user may modify `value` as he wants, and so do we. Then, if we want to recover the "original" value from HTML, it's in the attribute.
## DOM properties are typed
DOM properties are not always strings. For instance, `input.checked` property (for checkboxes) and other similar properties are boolean:
```html run
<input id="input" type="checkbox" checked> checkbox
<script>
alert(input.getAttribute('checked')); // empty string
alert(input.checked); // true
</script>
```
Javascript enforces the right type for a DOM property.
**But even if a DOM property type is a string, it may differ from the attribute.**
For instance, the `href` DOM property is always a full URL (by the standard), even if the attribute has a relative URL or just a `#hash` part.
Here we can see that:
```html height=30 run
<a id="a" href="#hello">link</a>
<script>
// attribute
alert(a.getAttribute('href')); // #hello
// property
alert(a.href ); // full URL like http://site.com/page#hello
</script>
```
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
When writing HTML, we use a lot of standard attributes. But what about non-standard, custom ones? May they be useful? What for?
Sometimes they are used to pass custom data from HTML to Javascript, or "mark" elements like this:
```html run
<!-- marking to show the "name" here -->
<div *!*show-info="name"*/!*></div>
<script>
// the code finds an element with the mark and shows what's requested
let info = {
name: "Pete"
};
let div = document.querySelector('[show-info]');
// insert info.name into the div
div.innerHTML = info[div.getAttribute('show-info')]; // Pete
</script>
```
Also, they can be used to style the element.
For instance, here for the order state the attribute `order-state` is used:
```html run
<style>
.order[order-state="new"] {
color: green;
}
.order[order-state="pending"] {
color: blue;
}
.order[order-state="canceled"] {
color: red;
}
</style>
<div class="order" order-state="new">
A new order.
</div>
<div class="order" order-state="pending">
A pending order.
</div>
<div class="order" order-state="canceled">
A canceled order.
</div>
```
Why the attribute was chosen in the example above, not classes like `.order-state-new`, `.order-state-pending`, `order-state-canceled`?
That's because an attribute is more convenient to manage. If we want to change the order state, we can modify it like this:
```js
div.setAttribute('order-state', 'canceled');
```
For classes we would have to clean the current state and add the new one. More cumbersome:
```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.
**All attributes starting with "data-" are reserved for programmers' use. They are available in `dataset` property.**
So, if an `elem` has an attribute named `"data-about"`, it's available as `elem.dataset.about`.
Like this:
```html run
<body data-about="Elephants">
<script>
alert(document.body.dataset.about); // Elephants
</script>
```
Multiword attributes like `data-order-state` become camel-cased: `dataset.orderState`.
Here's a rewritten "order state" example:
```html run
<style>
.order[data-order-state="new"] {
color: green;
}
.order[data-order-state="pending"] {
color: blue;
}
.order[data-order-state="canceled"] {
color: red;
}
</style>
<div id="order" class="order" data-order-state="new">
A new order.
</div>
<script>
alert(order.dataset.orderState); // new
order.dataset.orderState = "pending";
</script>
```
Using `data-*` attributes for custom purposes is a valid, safe way.
Please note that we can not only read, but modify data-attributes. Then CSS updates the view accordingly: in the example above the last line changes the color to blue.
## Summary
- Attributes -- is what's written in HTML.
- Properties -- is what's in DOM objects.
<table>
<thead>
<tr>
<th></th>
<th>Properties</th>
<th>Attributes</th>
</tr>
</thead>
<tbody>
<tr>
<td>Type</td>
<td>Any value, standard properties have types described in the spec</td>
<td>A string</td>
</tr>
<tr>
<td>Name</td>
<td>Name is case-sensitive</td>
<td>Name is case-insensitive</td>
</tr>
</tbody>
</table>
Methods to work with attributes are:
- `elem.hasAttribute(name)` -- to check for existance.
- `elem.getAttribute(name)` -- to get the value.
- `elem.setAttribute(name, value)` -- to set the value.
- `elem.removeAttribute(name)` -- to remove the attribute.
- `elem.attributes` is a collection of all attributes.
We use those in cases when DOM properties do not suit us and we need exactly attributes for some reasons.
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 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.