Мастер-классы по Javascript Екатеринбург Ростов-на-Дону Москва Узнать больше...
Содержание (скрыть) Содержание (показать)

Добавление и удаление узлов

  1. Создание элементов: createElement
  2. Добавление элемента: appendChild, insertBefore
  3. Удаление узлов: removeChild
  4. Пример: показ сообщения
    1. Создание сообщения
    2. Добавление
  5. Текстовые узлы, тонкости использования
  6. Итого

Изменение 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>
    

Ответили на вопрос выше? Даже если нет, то, поглядев в решение, вы легко увидите разницу.

Итак, отличий два:

  1. При создании текстового узла createTextNode('<b>...</b>') любые специальные символы и теги в строке будут интерпретированы как текст. А innerHTML вставит их как HTML.
  2. Во всех современных браузерах (кроме 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.

Нужно учесть два момента.

  1. Родителя может не быть (элемент уже удален или еще не вставлен).
  2. Для совместимости со стандартным методом нужно вернуть удаленный элемент.

Вот так выглядит решение:

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 пропустит половину узлов.

Решение через DOM
Решение через DOM

Правильное решение:

function removeChildren(elem) {
   while(elem.lastChild) {
       elem.removeChild(elem.lastChild);
   }
}

Неправильное решение (innerHTML)
Неправильное решение (innerHTML)

Прямая попытка использовать innerHTML была бы неправильной:

function removeChildren(elem) {
  elem.innerHTML = '';
}

Дело в том, что в IE<9 свойство innerHTML на большинстве табличных элементов (кроме ячеек TH/TD) не работает. Будет ошибка.

Верное решение (innerHTML)
Верное решение (innerHTML)

Можно, к примеру, завернуть innerHTML в try/catch:

function removeChildren(elem) {
  try {
    elem.innerHTML = '';
  } catch(e) {
    while(elem.firstChild) { 
      elem.removeChild(elem.firstChild);
    }
  }
}

Напишите интерфейс для создания списка.

Для каждого пункта:

  1. Запрашивайте содержимое пункта у пользователя с помощью prompt.
  2. Создавайте пункт и добавляйте его к UL.
  3. Процесс прерывается, когда пользователь нажимает 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); // создаёт 
*/!*

Результат (дерево):

Выберите один из двух способов решения этой задачи:

  1. Создать строку, а затем присвоить через container.innerHTML.
  2. Создавать узлы через методы DOM.

Если получится — сделайте оба.

Исходный документ: tutorial/browser/dom/build-tree-src.html.

Решение
Решение

Решения через рекурсию.

  1. tutorial/browser/dom/build-tree.html.
  2. tutorial/browser/dom/build-tree-dom.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.

Алгоритм:

  1. Создать объект даты d = new Date(year, month-1). Это первый день месяца month (с учетом того, что месяцы в JS начинаются от 0, а не от 1).
  2. Ячейки первого ряда пустые от начала и до дня недели d.getDay(), с которого начинается месяц. Создадим их.
  3. Увеличиваем день в d на единицу: d.setDate(d.getDate()+1), и добавляем в календарь очередную ячейку, пока не достигли следующего месяца. При этом последний день недели означает вставку перевода строки "</tr><tr>".
  4. При необходимости, если календарь окончился не на воскресенье - добавить пустые TD в таблицу, чтобы было все ровно.

Код решения находится здесь: tutorial/date/calendar_rus.html


Комментарии

  1. Приветствуются комментарии, содержащие дополнения и вопросы по статье, и ответы на них.
  2. Если ваш комментарий касается задачи -- откройте её в отдельном окне и напишите там.
  3. Комментарии без смысла, с рекламой или не о статье вообще - удаляются.
Наверх

Содержание

Реклама

Нашли опечатку?

Нашли опечатку на сайте? Что-то кажется странным?
Выделите соответствующий текст и нажмите Ctrl+Enter!

Последние Комментарии

Помоги другим!

Помоги другим узнать о хорошей статье!