Дизайн сайтовЮзабилитиВерстка сайтовВходящий маркетингКонверсия сайтовПоисковые системыКонтекстная рекламаИнформационные технологииНовости E-Planet
20 февраля 2016

Советы и техники для манипуляций с DOM элементами

Комплексные веб-приложения со сложной разметкой стали вполне обычным явлением в наше время. Библиотеки наподобие jQuery просты в использовании и основаны на кросс-браузерных решениях. Это эффективное подспорье в манипуляциях с HTML на лету . Поэтому нет ничего удивительного в том, что многие разработчики отдают предпочтение подобным библиотекам, а не нативному DOM API, который обладает рядом исторически сложившихся проблем.

В этой статье мы продемонстрируем некоторые свойства DOM, которые могут быть использованы для манипуляции HTML: в основном речь будет идти об уровнях вложенности и взаимодействии элементов одного уровня.

Подсчет дочерних элементов

У нас есть два способа посчитать количество пунктов списка в подобной разметке:

<body>
    <ul id="myList">
       <li>Example one</li>
       <li>Example two</li>
       <li>Example three</li>
       <li>Example four</li>
       <li>Example five</li>
       <li>Example Six</li>
  </ul>
</body>

Мы можем использовать myList.children.length или myList.children.length (пример на CodePen:

var myList = document.getElementById('myList');
console.log(myList.children.length); // 6
console.log(С); // 6

В обоих случаях мы получаем одинаковый результат, но с помощью разных методик. В первом варианте мы используем свойство children. Это свойство возвращает массив HTML-элементов, находящихся внутри элемента с id= myList, а свойство length позволяет узнать количество элементов в этом массиве.

Во втором варианте мы используем свойство childElementCount, которое является более изящным и простым в поддержке способом реализации нашей задачи. Интересно, что у результата childElementCount также есть свойство length, но обратите внимание на возвращаемое значение:

var myList = document.getElementById('myList');
console.log(myList.childNodes.length); // 13

Мы получаем 13, так как свойство возвращает массив всех элементов, включая пустое пространство, созданное переносом строки при оформлении кода, запомните это.

Проверка существования дочерних элементов

Мы можем использовать hasChildNodes() для того чтобы проверить существование дочерних элементов. Этот метод возвращает true, если дочерние элементы существуют и false в противном случае.

var myList = document.getElementById('myList');
console.log(myList.hasChildNodes()); // true

Мы помним, что наш элемент с идентификатором myList содержит несколько li, но что если мы немного изменим нашу разметку:

<body>
  <ul id="myList">
  </ul>  
</body>

Давайте попробуем снова:

console.log(myList.hasChildNodes()); // true

Метод вернул True, но наш контейнер не содержит дочерних элементов, он содержит пустое пространство из-за форматирования кода. Для того чтобы метод вернул false, код должен выглядеть примерно так:

<body>
  <ul id="myList"> </ul>  
</body>

Поэтому, зная особенности поведения метода, не стоит забывать о проверке типа узла с помощью свойства nodeType.

Добавление и удаление дочерних элементов

Самый привычный и часто используемый способ - использование createElement() и appendChild():

var myEl = document.createElement('div');
document.body.appendChild(myEl);

В этом примере мы создаем элемент div и добавляем его в body. Довольно просто, и вполне вероятно, что вы использовали эту технику ранее. Для создания нового элемента в конкретном месте, мы также используем appendChild(), но что он делает?

Предположим у нас есть следующая разметка:

<div id="c">
  <ul id="myList">
    <li>Example one</li>
    <li>Example two</li>
    <li>Example three</li>
    <li>Example four</li>
    <li>Example five</li>
    <li>Example Six</li>
  </ul>
  <p>Example text</p>
</div>

Мы можем изменить положение списка, используя следующий код:

var myList = document.getElementById('myList'),
    container = document.getElementById('c');
container.appendChild(myList)

В результате мы получим:

<div id="c">
  <p>Example text</p>
  <ul id="myList">
    <li>Example one</li>
    <li>Example two</li>
    <li>Example three</li>
    <li>Example four</li>
    <li>Example five</li>
    <li>Example Six</li>
  </ul>
</div>

Обратите внимание, что весь список был удален из его первоначального местоположения и перемещен в конец контейнера. То есть, appendChild(), который вы привыкли использовать в связке с createElement(), также прекрасно подходит для перемещения существующих узлов.

Также мы можем удалить элементы с помощью DOM, используя removeChild():

var myList = document.getElementById('myList'),
    container = document.getElementById('c');
container.removeChild(myList);

В результате мы получим:

<div id="c">
  <p>Example text</p>
</div>

Метод removeChild() возвращает удаленный элемент, поэтому вы можете сохранить его и использовать в будущем при необходимости. Например, так.

Перемещение дочернего элемента

Мы можем заменить существующего ребенка другим существующим или новым дочерним элементом:

<p id="par">Example Text</p>

JavaScript:

var myPar = document.getElementById('par'),
    myDiv = document.createElement('div');
myDiv.className = 'example';
myDiv.appendChild(document.createTextNode('New element text'));
document.body.replaceChild(myDiv, myPar);

Этот код создает элемент с классом Example и заменяет ним существующий параграф. В результате мы получаем такую разметку:

<div class="example">New element text</div>

Как вы видите, replaceChild() получает два аргумента: новый элемент и элемент, который необходимо заменить. Также, с помощью этого метода можно переместить существующий элемент на место другого:

<p id="par1">Example text 1</p>
<p id="par2">Example text 2</p>
<p id="par3">Example text 3</p>

Чтобы поменять местами первый и третий параграф, нам необходимо:

var myPar1 = document.getElementById('par1'),
    myPar3 = document.getElementById('par3');
document.body.replaceChild(myPar1, myPar3);

Выделение конкретного дочернего элемента

Есть несколько способов выделить интересующий нас дочерний элемент. Как говорилось ранее, мы можем получить их массив, используя childNodes, и работать с ним. Но существует еще несколько вариантов.

firstElementChild and lastElementChild, как следует из названия, возвращают первый и последний элемент переданного им родителя.

<body>
  <ul id="myList">
    <li>Example one</li>
    <li>Example two</li>
    <li>Example three</li>
    <li>Example four</li>
    <li>Example five</li>
    <li>Example Six</li>
  </ul>
</body>
var myList = document.getElementById('myList');
console.log(myList.firstElementChild.innerHTML); // "Example one"
console.log(myList.lastElementChild.innerHTML); // "Example six"

Также, мы можем использовать свойства previousElementSibling и nextElementSibling если мы хотим получить второй или предпоследний элемент:

var myList = document.getElementById('myList');
console.log(myList.firstElementChild.nextElementSibling.innerHTML); // "Example two"
console.log(myList.lastElementChild.previousElementSibling.innerHTML); // "Example five"

Свойства firstChild, lastChild, previousSibling, и nextSibling работают точно так же, но они возвращают не только теги, а и все объекты, находящиеся внутри контейнера, в том числе пробелы, возникшие из-за форматирования кода.

Добавление контента

Большинство разработчиков решают эту задачу добавлением нового элемента. Однако в нашем распоряжении есть несколько свойств, которые позволяют нам добиться более тщательного контроля над тем, что мы делаем.

Во-первых, метод insertBefore(), который значительно эффективней, чем replaceChild(). Этот метод позволяет работать как с новыми, так и с существующим элементами.

<div id="c">
  <ul id="myList">
    <li>Example one</li>
    <li>Example two</li>
    <li>Example three</li>
    <li>Example four</li>
    <li>Example five</li>
    <li>Example Six</li>
  </ul>
  <p id="par">Example Paragraph</p>
</div>
var myList = document.getElementById('myList'),
    container = document.getElementBy('c'),
    myPar = document.getElementById('par');
container.insertBefore(myPar, myList);

В результате параграф будет располагаться перед списком.

<div id="c">
  <p id="par">Example Paragraph</p>
  <ul id="myList">
    <li>Example one</li>
    <li>Example two</li>
    <li>Example three</li>
    <li>Example four</li>
    <li>Example five</li>
    <li>Example Six</li>
  </ul>
</div>

Как и replaceChild(), insertBefore() получает два аргумента: элемент, который будет добавлен, и элемент, перед которым должен быть добавлен контент.

Это был довольно простой способ. Теперь давайте попробуем использовать кое-что помощнее, а именно метод insertAdjacentHTML().var myEl = document.getElementById(’el’);

Этот код вставляет строку контента перед определенным элементом (в нашем случае перед списком). Второй аргумент - это строка в формате HTML, которая должна быть вставлена (это может быть даже простой текст без тегов). Первый элемент, хотя и является строкой, имеет конкретные значения:

  • "beforebegin" - вставить строку перед элементом;
  • "afterbegin" - вставить строку в элемент, перед его первым ребенком;
  • "beforeend"- вставить строку в элемент после его последнего ребенка;
  • "afterend" - вставить строку после элемента.

Поддержка браузерами

Во всех спорах об использовании DOM, поддержка браузерами - весомый аргумент. Почти все свойства, которые мы использовали в нашей статье, поддерживаются всеми браузерами, включая старые версии IE. Исключение составляют children, childElementCount, firstElementChild, lastElementChild, previousElementSibling и nextElementSibling, которые не поддерживаются IE младше девятого, а также Node.remove(), который вообще не работает в IE.

По материалам www.sitepoint.com