- Создание элементов:
createElement - Добавление элемента:
appendChild,insertBefore - Удаление узлов:
removeChild - Пример: показ сообщения
- Текстовые узлы, тонкости использования
- Итого
Изменение DOM — ключ к созданию «живых» страниц.
В этой главе мы рассмотрим как создавать новые элементы «на лету» и заполнять их данными.
Создание элементов: createElement
Для создания элементов используются следующие методы документа:
document.createElement(tag)- Создает новый элемент с указанным тегом:
var div = document.createElement('div'); document.createTextNode(text)- Создает новый текстовый узел с данным текстом:
var textElem = document.createTextNode('Тут был я');
Новому элементу тут же можно поставить свойства:
var newDiv = document.createElement('div');
newDiv.className = 'myclass';
newDiv.id = 'myid';
newDiv.innerHTML = 'Привет, мир!';
Новый элемент можно также склонировать из существующего:
newElem = elem.cloneNode(true)- Клонирует элемент
elem, вместе с атрибутами, включая вложенные в него. newElem = elem.cloneNode(false)- Клонирует элемент
elem, вместе с атрибутами, но без подэлементов.
Добавление элемента: appendChild, insertBefore
Чтобы DOM-узел был показан на странице, его необходимо вставить в документ.
Для этого у любого элемента есть метод appendChild:
parentElem.appendChild(elem)- Добавляет
elemв список дочерних элементовparentElem. Новый узел добавляется в конец списка.Следующий пример добавляет новый элемент в уже существующий
div:
<div> ... </div> <script> var parentElem = document.body.children[0]; var newDiv = document.createElement('div'); newDiv.innerHTML = 'Привет, мир!'; parentElem.appendChild(newDiv); </script> parentElem.insertBefore(elem, nextSibling)- Вставляет
elemв список дочернихparentElem, перед элементомnextSibling.Сделать новый
divпервым дочерним можно так:
<div> ... </div> <script> var parentElem = document.body.children[0]; var newDiv = document.createElement('div'); newDiv.innerHTML = 'Привет, мир!'; *!* parentElem.insertBefore(newDiv, parentElem.firstChild); */!* </script>Вместо
nextSiblingможет бытьnull, тогдаinsertBeforeработает какappendChild.parentElem.insertBefore(elem, null); // то же что: parentElem.appendChild(elem)
Все методы вставки возвращают вставленный узел, например parent.appendChild(elem) возвращает elem.
Удаление узлов: removeChild
Для удаления узла есть два метода:
parentElem.removeChild(elem)- Удаляет
elemиз списка детейparentElem. parentElem.replaceChild(elem, currentElem)- Среди детей
parentElemзаменяетcurrentElemнаelem.
Оба этих метода возвращают удаленный узел. Они просто вынимают его из списка, никто не мешает вставить его обратно в DOM в будущем.
Если вы хотите переместить элемент на новое место — не нужно его удалять со старого.
Методы appendChild/insertBefore автоматически удаляют вставляемый элемент со старого места.
Например, поменяем элементы местами:
<div>First div</div> <div>Last div</div> <script> var first = document.body.children[0]; var last = document.body.children[1]; // нет необходимости в предварительном removeChild(last) document.body.insertBefore(last, first); // поменять местами </script>
Пример: показ сообщения
В качестве реального примера рассмотрим добавление сообщения на страницу. Чтобы показывалось посередине экрана и было красивее, чем обычный alert.
HTML-код для сообщения (без JS):
<style>
.message {
width: 300px;
height: 130px;
border: 1px solid gray;
text-align: center;
}
.message h1 {
color: red;
background: azure;
font-size: 16px;
height: 30px;
line-height: 30px;
margin: 0;
}
.message .content {
height: 50px;
padding: 10px;
}
</style>
<div class="message">
<h1>Заголовок</h1>
<div class="content">Текст Сообщения</div>
<input class="ok" type="button" value="OK"/>
</div>
Как видно - сообщение вложено в div фиксированного размера message и состоит из заголовка h1, тела content и кнопки OK, которая нужна, чтобы сообщение закрыть.
Кроме того, добавлено немного стилей, чтобы как-то смотрелось.
Создание сообщения
Для создания сложных структур DOM, как правило, используют либо готовый «шаблонный узел» и метод cloneNode, либо свойство innerHTML.
Следующая функция создает сообщение с указанным телом и заголовком.
function createMessage(title, body) {
// (1)
var container = document.createElement('div');
// (2)
container.innerHTML = '<div class="message"> \
<h1>' + title + '</h1> \
<div class="content">' + body + '</div> \
<input class="ok" type="button" value="OK"> \
</div>';
// (3)
return container.firstChild;
}
Как видно, она поступает довольно хитро. Чтобы создать элемент по текстовому шаблону, она сначала создает временный элемент container (1), а потом записывает (2) сообщение как innerHTML временного элемента. Теперь готовый элемент можно получить как container.firstChild и вернуть в (3).
Добавление
Полученный элемент можно добавить в DOM:
var messageElem = createMessage('Привет, Мир!', 'Я - элемент DOM!')
document.body.appendChild(messageElem);
Окончательный результат:
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<link type="text/css" rel="stylesheet" href="alert.css" />
</head>
<body>
Сообщение добавлено из JavaScript.
<script>
function createMessage(title, body) {
var container = document.createElement('div');
container.innerHTML = '<div class="message"> \
<h1>' + title + '</h1> \
<div class="content">' + body + '</div> \
<input class="ok" type="button" value="OK"> \
</div>';
return container.firstChild;
}
var messageElem = createMessage('Привет, Мир!', 'Я - элемент DOM!')
document.body.appendChild(messageElem);
</script>
</body>
</html>
Показать в отдельном окне
Конечно, нам хотелось бы пойти дальше. Расположить сообщение не где-нибудь, а посередине окна. Все это вполне возможно, а как — читайте в следующих занятиях.
Текстовые узлы, тонкости использования
Как правило, при создании узлов и заполнении их используется innerHTML. Но текстовые узлы тоже имеют интересную область применения.
У них есть две особенности. Начнем с небольшого вопроса.
Есть пустой узел DOM elem.
Одинаковый ли результат дадут эти скрипты?
Первый:
elem.appendChild(document.createTextNode(text))Второй:
elem.innerHTML = text
Если нет — дайте пример значения text, для которого результат разный.
Результат выполнения может быть разный.
Запустите следующие примеры, чтобы увидеть разницу:
createTextNodeсоздает текст'<b>текст</b>':
<div></div> <script> var text = '<b>текст</b>'; var elem = document.body.children[0]; elem.appendChild(document.createTextNode(text)); </script>
innerHTMLприсваивает HTML<b>текст</b>:
<div></div> <script> var text = '<b>текст</b>'; var elem = document.body.children[0]; elem.innerHTML = text; </script>
Ответили на вопрос выше? Даже если нет, то, поглядев в решение, вы легко увидите разницу.
Итак, отличий два:
- При создании текстового узла
createTextNode('<b>...</b>')любые специальные символы и теги в строке будут интерпретированы как текст. АinnerHTMLвставит их как HTML. - Во всех современных браузерах (кроме IE<8) создание и вставка текстового узла работает гораздо быстрее, чем присвоение HTML.
Итого
Методы для создания узлов:
document.createElement(tag)— создает элементdocument.createTextNode(value)— создает текстовый узелelem.cloneNode(deep)— клонирует элемент, еслиdeep == true, то со всеми потомками.
Вставка и удаление узлов:
parent.appendChild(elem)parent.insertBefore(elem, nextSibling)parent.removeChild(elem)parent.replaceChild(elem, currentElem)
Все эти методы возвращают elem.
Запомнить порядок аргументов очень просто: новый(вставляемый) элемент — всегда первый.
Методы для изменения DOM также описаны в спецификации DOM Level 1.
Напишите функцию, которая удаляет элемент из DOM.
Синтаксис должен быть таким: remove(elem), то есть, в отличие от parentNode.removeChild(elem) — без родительского элемента.
<div>Это</div>
<div>Все</div>
<div>Элементы DOM</div>
<script>
var elem = document.body.children[0];
function remove(elem) { /* ваш код */ }
*!*
remove(elem); // <-- функция должна удалить элемент
*/!*
</script>
Родителя parentNode можно получить из elem.
Нужно учесть два момента.
- Родителя может не быть (элемент уже удален или еще не вставлен).
- Для совместимости со стандартным методом нужно вернуть удаленный элемент.
Вот так выглядит решение:
function remove(elem) {
return elem.parentNode ? elem.parentNode.removeChild(elem) : elem;
}
Напишите функцию insertAfter(elem, refElem), которая добавит elem после refElem.
<div>Это</div>
<div>Элементы</div>
<script>
var elem = document.createElement('div');
elem.innerHTML = '<b>Новый</b>'
function insertAfter(elem, refElem) { /* ваш код */ }
var body = document.body;
// вставить elem после первого элемента
insertAfter(elem, body.firstChild); // <--- должно работать
// вставить elem за последним элементом
insertAfter(elem, body.lastChild); // <--- должно работать
</script>
Для того, чтобы добавить элемент после refElem, мы можем вставить его перед refElem.nextSibling.
Но что если nextSibling нет? Это означает, что refElem является последним потомком своего родители и можем использовать appendChild.
Код:
function insertAfter(elem, refElem) {
var parent = refElem.parentNode;
var next = refElem.nextSibling;
if (next) {
return parent.insertBefore(elem, next);
} else {
return parent.appendChild(elem);
}
}
Но код может быть гораздо короче, если использовать фишку со вторым аргументом null метода insertBefore:
function insertAfter(elem, refElem) {
return refElem.parentNode.insertBefore(elem, refElem.nextSibling);
}
Если нет nextSibling, то второй аргумент insertBefore становится null и тогда insertBefore(elem,null) работает как appendChild.
В решении нет проверки на существование refElem.parentNode, поскольку вставка после элемента без родителя — уже ошибка, пусть она возникнет в функции, это нормально.
Напишите функцию removeChildren, которая удаляет всех потомков элемента.
<table>
<tr>
<td>Это</td><td>Все</td><td>Элементы DOM</td>
</tr>
</table>
<script>
function removeChildren(elem) { /* ваш код */ }
removeChildren(document.body.children[0]); // очищает таблицу
</script>
Для начала рассмотрим забавный пример того, как делать не надо:
function removeChildren(elem) {
for(var k=0; k<elem.childNodes.length;k++) {
elem.removeChild(elem.childNodes[k]);
}
}
Если вы попробуете это на практике, то увидите, то это не сработает.
Не сработает потому, что childNodes всегда начинается 0 и автоматически смещается, когда первый потомок удален(т.е. тот, что был вторым, станет первым), поэтому k пропустит половину узлов.
Правильное решение:
function removeChildren(elem) {
while(elem.lastChild) {
elem.removeChild(elem.lastChild);
}
}
Прямая попытка использовать innerHTML была бы неправильной:
function removeChildren(elem) {
elem.innerHTML = '';
}
Дело в том, что в IE<9 свойство innerHTML на большинстве табличных элементов (кроме ячеек TH/TD) не работает. Будет ошибка.
Можно, к примеру, завернуть innerHTML в try/catch:
function removeChildren(elem) {
try {
elem.innerHTML = '';
} catch(e) {
while(elem.firstChild) {
elem.removeChild(elem.firstChild);
}
}
}
Напишите интерфейс для создания списка.
Для каждого пункта:
- Запрашивайте содержимое пункта у пользователя с помощью
prompt. - Создавайте пункт и добавляйте его к
UL. - Процесс прерывается, когда пользователь нажимает ESC.
Все элементы должны создаваться динамически.
Если посетитель вводит теги — в списке они показываются как обычный текст.
Работающий пример тут: tutorial/browser/dom/createList.html
P.S. prompt возвращает null, если пользователь нажал ESC.
Решение говорит само за себя:
<!DOCTYPE HTML>
<html>
<body>
<h1>Creation of the list</h1>
<script>
var ul = document.createElement('ul')
document.body.appendChild(ul)
while (true) {
var data = prompt("Enter the contents for the item", "")
if (data === null) {
break
}
var li = document.createElement('li')
li.appendChild(document.createTextNode(data))
ul.appendChild(li)
}
</script>
</body>
</html>
Показать в отдельном окне
Делайте проверку на null в цикле. prompt возвращает это значение только если был нажат ESC.
Контент в LI добавляйте с помощью document.createTextNode, чтобы правильно работали <, > и т.д.
Напишите функцию , которая создаёт вложенный список UL/LI (дерево) из объекта.
Например:
var data = {
"Рыбы":{
"Форель":{},
"Щука":{}
},
"Деревья":{
"Хвойные":{
"Лиственница":{},
"Ель":{}
},
"Цветковые":{
"Берёза":{},
"Тополь":{}
}
}
};
Синтаксис:
var container = document.getElementById('container');
*!*
createTree(container, data); // создаёт
*/!*
Результат (дерево):
Выберите один из двух способов решения этой задачи:
- Создать строку, а затем присвоить через
container.innerHTML. - Создавать узлы через методы DOM.
Если получится — сделайте оба.
Исходный документ: tutorial/browser/dom/build-tree-src.html.
Решения через рекурсию.
Напишите функцию, которая умеет генерировать календарь для заданной пары (месяц, год).
Календарь должен быть таблицей, где каждый день — это TD. У таблицы должен быть заголовок с названиями дней недели, каждый день — TH.
Синтаксис: createCalendar(id, year, month).
Такой вызов должен генерировать текст для календаря месяца month в году year, а затем помещать его внутрь элемента с указанным id.
Например: createCalendar("cal", 2012, 9) сгенерирует в <div id='cal'></div> следующий календарь:
Начальный документ со стилями: tutorial/date/calendar_src.html
P.S. Достаточно сгенерировать календарь, кликабельным его делать не нужно.
Для решения задачи сгенерируем таблицу в виде строки: "<table>...</table>", а затем присвоим в innerHTML.
Алгоритм:
- Создать объект даты
d = new Date(year, month-1). Это первый день месяцаmonth(с учетом того, что месяцы в JS начинаются от 0, а не от 1). - Ячейки первого ряда пустые от начала и до дня недели
d.getDay(), с которого начинается месяц. Создадим их. - Увеличиваем день в
dна единицу:d.setDate(d.getDate()+1), и добавляем в календарь очередную ячейку, пока не достигли следующего месяца. При этом последний день недели означает вставку перевода строки"</tr><tr>". - При необходимости, если календарь окончился не на воскресенье - добавить пустые
TDв таблицу, чтобы было все ровно.
Код решения находится здесь: tutorial/date/calendar_rus.html
Комментарии
- Приветствуются комментарии, содержащие дополнения и вопросы по статье, и ответы на них.
- Если ваш комментарий касается задачи -- откройте её в отдельном окне и напишите там.
- Комментарии без смысла, с рекламой или не о статье вообще - удаляются.