13 KiB
Добавление и удаление узлов
Изменение DOM -- ключ к созданию "живых" страниц.
В этой главе мы рассмотрим, как создавать новые элементы "на лету" и заполнять их данными.
[cut]
Пример: показ сообщения
В качестве примера рассмотрим добавление сообщения на страницу, чтобы оно было оформленно красивее чем обычный alert.
HTML-код для сообщения (с подключённой библиотекой стилей bootstrap):
<!--+ autorun height="100" -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.1.1/css/bootstrap.css">
<div class="alert alert-success">
<strong>Ура!</strong> Вы прочитали это важное сообщение.
</div>
Создание элемента
Для создания элементов используются следующие методы:
- `document.createElement(tag)`
- Создает новый элемент с указанным тегом:
var div = document.createElement('div'); - `document.createTextNode(text)`
- Создает новый *текстовый* узел с данным текстом:
var textElem = document.createTextNode('Тут был я');
Создание сообщения
В нашем случае мы хотим сделать DOM-элемент div, дать ему классы и заполнить текстом:
var div = document.createElement('div');
div.className = "alert alert-success";
div.innerHTML = "<strong>Ура!</strong> Вы прочитали это важное сообщение.";
После этого кода у нас есть готовый DOM-элемент. Пока что он присвоен в переменную div, но не виден, так как никак не связан со страницей.
Добавление элемента: appendChild, insertBefore
Чтобы DOM-узел был показан на странице, его необходимо вставить в document.
Для этого первым делом нужно решить, куда мы будем его вставлять. Предположим, что мы решили, что вставлять будем в некий элемент parentElem, например var parentElem = document.body.
Для вставки внутрь parentElem есть следующие методы:
- `parentElem.appendChild(elem)`
- Добавляет `elem` в конец дочерних элементов `parentElem`.
Следующий пример добавляет новый элемент в конец
<ol>:<!--+ run height=100 --> <ol id="list"> <li>0</li> <li>1</li> <li>2</li> </ol> <script> var newLi = document.createElement('li'); newLi.innerHTML = 'Привет, мир!'; list.appendChild(newLi); </script> - `parentElem.insertBefore(elem, nextSibling)`
- Вставляет `elem` в список дочерних `parentElem` перед элементом `nextSibling`.
Следующий код вставляет новый элемент перед вторым
<li>:<!--+ run height=100 --> <ol id="list"> <li>0</li> <li>1</li> <li>2</li> </ol> <script> var newLi = document.createElement('li'); newLi.innerHTML = 'Привет, мир!'; *!* list.insertBefore(newLi, list.children[1]); */!* </script>Для вставки элемента в начало достаточно указать, что вставлять будем перед первым потомком:
list.insertBefore(newLi, list.firstChild);У читателя, который посмотрит на этот код внимательно, наверняка возникнет вопрос: "А что, если
listвообще пустой, в этом случае ведьlist.firstChild = null, произойдёт ли вставка?"Ответ -- да, произойдёт.
Дело в том, что если вторым аргументом указать
null, тоinsertBeforeсработает какappendChild:parentElem.insertBefore(elem, null); // то же, что и: parentElem.appendChild(elem)Так что
insertBeforeуниверсален.
[smart] Все методы вставки возвращают вставленный узел.
Например, parentElem.appendChild(elem) возвращает elem.
[/smart]
Пример использования
Добавим сообщение в конец <body>:
<!--+ height=150 run autorun -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.1.1/css/bootstrap.css">
<body>
<h3>Моя страница</h3>
</body>
<script>
var div = document.createElement('div');
div.className = "alert alert-success";
div.innerHTML = "<strong>Ура!</strong> Вы прочитали это важное сообщение.";
*!*
document.body.appendChild(div);
*/!*
</script>
...А теперь -- в начало <body>:
<!--+ height=150 run autorun -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.1.1/css/bootstrap.css">
<body>
<h3>Моя страница</h3>
</body>
<script>
var div = document.createElement('div');
div.className = "alert alert-success";
div.innerHTML = "<strong>Ура!</strong> Вы прочитали это важное сообщение.";
*!*
document.body.insertBefore(div, document.body.firstChild);
*/!*
</script>
Клонирование узлов: cloneNode
А как бы вставить второе похожее сообщение?
Конечно, можно сделать функцию для генерации сообщений и поместить туда этот код, но в ряде случаев гораздо эффективнее -- клонировать существующий div, а потом изменить текст внутри. В частности, если элемент большой, то клонировать его будет гораздо быстрее, чем пересоздавать.
Вызов elem.cloneNode(true) создаст "глубокую" копию элемента -- вместе с атрибутами, включая подэлементы. Если же вызвать с аргумнтом false, то он копия будет без подэлементов, но это нужно гораздо реже.
Копия сообщения
Пример со вставкой копии сообщения:
<!--+ height=200 run autorun -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.1.1/css/bootstrap.css">
<body>
<h3>Моя страница</h3>
</body>
<script>
var div = document.createElement('div');
div.className = "alert alert-success";
div.innerHTML = "<strong>Ура!</strong> Вы прочитали это важное сообщение.";
document.body.insertBefore(div, document.body.firstChild);
*!*
// создать копию узла
var div2 = div.cloneNode(true);
// копию можно подправить
div2.querySelector('strong').innerHTML = 'Супер!';
// вставим её после текущего сообщения
div.parentNode.insertBefore( div2, div.nextSibling );
*/!*
</script>
Обратите внимание на последнюю строку, которая вставляет div2 после div:
div.parentNode.insertBefore( div2, div.nextSibling );
- Для вставки нам нужен будущий родитель. Мы, возможно, не знаем, где точно находится `div` (или не хотим зависеть от того, где он), но если нужно вставить рядом с `div`, то родителем определённо будет `div.parentNode`.
- Мы хотели бы вставить *после* `div`, но метода `insertAfter` нет, есть только `insertBefore`, поэтому вставляем *перед* его правым соседом `div.nextSibling`.
Удаление узлов: removeChild
Для удаления узла есть два метода:
- `parentElem.removeChild(elem)`
- Удаляет `elem` из списка детей `parentElem`.
- `parentElem.replaceChild(elem, currentElem)`
- Среди детей `parentElem` заменяет `currentElem` на `elem`.
Оба этих метода возвращают удаленный узел. Если нужно, его можно вставить в другое место DOM тут же или в будущем.
[smart] Если вы хотите переместить элемент на новое место -- не нужно его удалять со старого.
Все методы вставки автоматически удаляют вставляемый элемент со старого места.
Конечно же, это очень удобно.
Например, поменяем элементы местами:
<!--+ run height=150 -->
<div>Первый</div>
<div>Второй</div>
<script>
var first = document.body.children[0];
var last = document.body.children[1];
// нет необходимости в предварительном removeChild(last)
document.body.insertBefore(last, first); // поменять местами
</script>
[/smart]
[smart header="Метод remove"]
В современном стандарте есть также метод elem.remove(), который удаляет элемент напрямую, не требуя ссылки на родителя. Это зачастую удобнее, чем removeChild.
Он поддерживается во всех современных браузерах, кроме IE11-. Впрочем, легко подключить или даже сделать полифилл. [/smart]
Удаление сообщения
Сделаем так, что через секунду сообщение пропадёт:
<!--+ run -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.1.1/css/bootstrap.css">
<body>
<h3>Сообщение пропадёт через секунду</h3>
</body>
<script>
var div = document.createElement('div');
div.className = "alert alert-success";
div.innerHTML = "<strong>Ура!</strong> Вы прочитали это важное сообщение.";
document.body.appendChild(div);
*!*
setTimeout(function() {
div.parentNode.removeChild(div);
}, 1000);
*/!*
</script>
Текстовые узлы для вставки текста
При работе с сообщением мы использовали только узлы-элементы и innerHTML.
Но и текстовые узлы тоже имеют интересную область применения!
Если текст для сообщения нужно показать именно как текст, а не как HTML, то можно обернуть его в текстовый узел.
Например:
<!--+ run -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.1.1/css/bootstrap.css">
<script>
var div = document.createElement('div');
div.className = "alert alert-success";
document.body.appendChild(div);
*!*
var text = prompt("Введите текст для сообщения", "Жили были <a> и <b>!");
// вставится именно как текст, без HTML-обработки
div.appendChild( document.createTextNode(text) );
*/!*
</script>
В современных браузерах (кроме IE8-) в качестве альтернативы можно использовать присвоение textContent.
Итого
Методы для создания узлов:
- `document.createElement(tag)` -- создает элемент
- `document.createTextNode(value)` -- создает текстовый узел
- `elem.cloneNode(deep)` -- клонирует элемент, если `deep == true`, то со всеми потомками, если `false` -- без потомков.
Вставка и удаление узлов:
- `parent.appendChild(elem)`
- `parent.insertBefore(elem, nextSibling)`
- `parent.removeChild(elem)`
- `parent.replaceChild(elem, currentElem)`
Все эти методы возвращают elem.
Запомнить порядок аргументов очень просто: новый(вставляемый) элемент -- всегда первый.
Методы для изменения DOM также описаны в спецификации DOM Level 1.