Получение доступа к узлам DOM для UX-аналитики

Для получения доступа и управлением содержанием веб-страницы используется JavaScript. С помощью JS можно выцепить из элементов HTML нужную информацию и модифицировать для проведения A/B теста или прототипирования. Не смотря на то, что статья про работу с узлами HTML, технически она про работу в целом с DOM.

DOM это интерфейс (API) для HTML и XML-страниц, обеспечивающий структуру и необходимые методы для работы с элементами, и написан на C++. Каждый элемент на странице это узел, содержание узла тоже является дочерним узлом. Можно выделить узлы-элементы, это тэги вроде body, head, html, и текстовые узлы с контентом внутри этих тэгов, у которых не может быть потомков. Отдельно в DOM находятся и комментарии к коду, и точка входа в DOM.

JS позволяет играться с самим DOM в целом. Для просмотра родителей элемента можно использовать следующий способ:

var allContent = document.querySelector('div');
allContent.parentElement.parentElement

Выполнение будет идти от элемента в переменной вверх по иерархии. Для просмотра дочерних элементов используется allContent.children.

Бывает полезно поиграться с allContent.childNodes. В DOM узлом является текст, все пробелы, даже переносы учитываются в childNodes. Текстовая нода и элементная это разные типы узлов, как и указано на картинке выше.

Также имеется возможность обращаться к конкретному дочернему элементу allContent.children[0]. Узнать количество дочерних элементов можно с помощью allContent.childElementCount.

Получение доступа по имени элемента

● Начнем с объекта document, у которого есть свойства и методы для доступа к элементам. Самый простой и распространенный способ получения элемента из любой части документа это getElementById, который возвращает элемент по его id. Например, нам нужно найти элемент с уникальным id «userData» и сохранить HTML контент в переменную.

var dataCollection = document.getElementById( "masthead" ).innerHTML;

Отлично работает при условии, что все id в документе уникальны и не повторяются. Если есть много элементов с одинаковым id, то поведение непредсказуемо. Также DOM позволяет менять стили style с помощью getElementById. Разумеется, скрипт для управления элементами DOM должен размещаться после создания этих элементов. Это можно сделать за счет размещения скрипта <script> после <body>, использования onload или слушателя событий.

document.getElementById("masthead").style.color = "#347632";
document.getElementById("masthead").style.backgroundColor ="#456714";
document.getElementById("bio").textContent = "Прекрасный человек";

Из важных нюансов также можно отметить, что имена свойств, которые пишутся через дефис (border-right-padding), в JS и DOM пишутся слитно и с заглавной буквы каждое слово, borderRightPadding. Еще нюанс: у вас есть переменная myMug, и она хранит ссылку на объект с ID. Если уничтожить объект, то он останется висеть в DOM, но на странице его видно не будет. Чтобы его уничтожить полностью, нужно преобразовать его в null. Сборщик мусора не тронет объект, пока на него ссылается переменная.


getElementsByName возвращает коллекцию элементов с классом, причем не важно, один класс у элемента или много. Работает только с элементами, для которых явно предусмотрен атрибут name (это может быть form, input, a, select, textarea и т.п.). Не сработает с div, p. Как и с любой коллекцией, можно использовать свойство length для получения длины списка объектов и метод item() для получения самого списка. Используется редко, но если надо вбить данные в поля и нажать «submit», то допустим к использованию.

var elems = document.getElementsByName("description");
document.getElementsByName('login')[0].value = 'login';
document.getElementsByName('password')[0].value = 'password';
document.getElementsByName('goOff')[0].form.submit()

getElementsByClassName() делает тоже самое, что и getElementById, но по значению атрибута class, и возвращает коллекцию элементов HTMLCollection.

<div class="article">Статья</div>
<div class="long article">Длинная статья</div>

<script>
  var articles = document.getElementsByClassName('article');
  alert( articles.length ); // 2, найдёт оба элемента
</script>

Вернуть в виде строки все классы элемента можно с помощью elem.className, но есть проблема: если мы хотим оставить только часть класса, то придется класс переопределять. А для этого надо знать, какой класс был до изменений. Либо replace строки.

Поэтому лучше использовать classList: elem.classList.add('true'); для добавления класса.

elem.classList.remove('true'); удалит класс.

elem.classList.contains('true'); проверит наличие класса и вернет true или false.

Вы уже представили, как придется писать сложные конструкции if/else для добавления/удаления классов, но в JS еще есть elem.classList.toggle('true');, который добавляет/убирает классы в зависимости от их наличия у тэга.

querySelectorAll() куда интереснее и выручает в сложных ситуациях. Позволяет получить доступ к узлам DOM по CSS-селекторам.  Возвращает NodeList. Если вы захотите удивить коллег, то напишите querySelectorAll.forEach, будет работать в некоторых случаях. В примере ниже мы не только получили все input, но и преобразовали в нормальный массив. Работает для ≥ 5.1. Для быстрой работы в консоли я использую сокращенную запись $$("li.group"), но такая запись работает не во всех браузерах.

var listDomeArray = document.querySelectorAll('input');
listDomeArray.forEach(function() {
}); 
Array.prototype.forEach.call(domList, function() {
});

В CSS можно написать несколько селекторов, в JS доступен только один селектор. document.querySelector('body');. при вызове такого метода получим в распоряжение весь body. В дальнейшем его можно присвоить переменной var dropPage = document.querySelector('body'); dropPage.baseURI, у которой в JS есть много свойств и методов.

Вот пример добавления всплывающей подсказки: получаем атрибут var elem = document.querySelector('div'); elem.getAttribute('class'); и устанавливаем elem.setAttribute('title', 'всплывающая подсказка')

И еще один пример, который очень важен для A/B-тестов, изменение свойств CSS объекта на странице.

style.var elem = document.querySelector('h1');
elem.style.color = 'red';

Таким кодом мы поменяем цвет h1 заголовка на красный, позиция меняется с помощью всяких top, right, position. Если же задача состоит в определении размера элемента на странице, то поможет такой способ: var allContent = document.querySelector('body'); allContent.offsetWidth. В данном случае будет получена ширина контента, и полученное значение может отличаться от результата команды allContent.clientWidth т.к. это ширина без полосы прокрутки. Свойства, начинающиеся с offset, возвращают размеры видимого контента, отсутпы, borders и скроллбар. Client возвращает размеры без borders и ширины скроллбара. Scroll возвращает размер полного контента, но без скроллбара.Аналогично работает для высоты allContent.offsetHeight и allContent.clientHeight. На разных браузерах и операционных системах полученное значение может и будет отличаться. Для получения высоты всей страницы, даже с учетом контента, который за пределами области видимости, используется allContent.scrollHeight. Теперь вы знаете, как правильно мериться высотой лендинга.

document.getElementsByTagName() ищет все элементы с заданным тэгом внутри элемента, который мы укажем вместо document. Возвращает список узлов (коллекцию), который очень похож по поведению на массивы. Соответственно, и обращаться к ним нужно по индексу. Если указать в качестве передаваемого аргумента символ *, то метод вернем все элементы из HTML-документа.

var myTime = document.getElementsByTagName('*'); //выбираем все элементы на странице

Код выше извлечет ВСЕ элементы и заключит их в список узлов (коллекцию). Соответственно, для получения доступа к каждому узлу по очереди нужен цикл. В принципе, querySelectorAll() и getElementsByTagName взаимозаменяемы. Важно понимать, что получать все элементы на динамически обновляемых страницах не так страшно, как кажется, т.к. на выходе мы получаем не массив.

var dataMy = document.getElementsByTagName("div");
for( var i = 0; i &lt; dataMy.length; i++ ) {
// описание того, что надо сделать
}

Полученные похожие на массивы сущности это HTMLCollection или NodeList objects, про которые я говорил выше. Отличие от обычных массивов одно, наследуется Object.prototype вместо Array.prototype. Соответственно, можно забыть про forEach (), push (), map (), filter () и slice (). Преобразовать в нормальный массив можно с помощью:

var almostArray = {
0: 'myItem1',
1: 'myItem2',
2: 'myItem3',
length: 3
};
var fullyArray = Array.prototype.slice.call(almostArray);
fullyArray = [].slice.call(almostArray);
fullyArray.indexOf('myItem1');

Проверить, прошло ли преобразование и чем является наш объект, массивом или любым другим объектом, можно с помощью следующей конструкции: Array.isArray(dataS); //вернет true. Или более сложный вариант чуть ниже. Он не сработает, если массив был создан во фрейме. Проверка не пройдет корректно, т.к. будет унаследован другой прототип.

var whatItIs = [], itWillFlase = '';
alert(whatItIs instanceof Array);
alert(itWillFlase instanceof Array);

Если же задача в получении доступа сразу ко всем элементам с определенным классом, то можно использовать такое решение:

var elements = document.querySelectorAll('.user');
var handler = function(e) {
console.log("ваш код");
};
 
for (let btn of elements) {
btn.addEventListener('click', handler);
}

Доступ к значениям атрибутов

● Это все было очень полезно, но чаще всего недостаточно получить доступ к элементам. Значения атрибутов тоже важны и их нужно менять. Для этого используется метод GetAttribute(). Он прост в использовании, т.к. ему требуется только один аргумент: имя атрибута. Обратите внимание, в примере мы использовали "src", но могли использовать "alt" или "id". Если в элементе не будет найден указанный атрибут, то будет возвращены null или пустая строка.

var homeCont = document.getElementById("home");
alert( homeCont.getAttribute("src") );

● Теперь надо научиться всем этим делом управлять. Мы хотим поменять значение атрибута src. В первую очередь, это setAttribute(). Работает он также просто, достаточно указать атрибут, который нужно изменить и на какой атрибут мы его будем менять. А главное, работает быстро.

var item = document.getElementById("afisha");
item.setAttribute("class", "democlass");

Таким способом можно менять link у .css файлов, указав в качестве атрибута другой href. GetAttribute() и setAttribute() хороши тем, что поддерживают старые браузеры.

InnerHtml позволяет получить доступ к разметке и тексту внутри элемента, и менять их. Можно даже полностью удалить содержимое body, или можно получить код страницы, которая динамически меняется. Открывает хорошие возможности Для тестов. для примитивных задач, вроде вставки текста, я чаще использую node.textContent, меньше проблем безопасности.

var list = document.getElementById("header");
for(var i = 1; i &lt;= 5000; i++) {
list.innerHTML += `&lt;li&gt;item ${i}&lt;/li&gt;`; // update 5000 times
}


Про использование innerHTML будет еще много сказано в этой статьей, т.к. не входит в стандарт, но при этом работает почти везде и работает быстрее операций с DOM. Умеет забрать всю разметку и содержание внутри указанного элемента и очистить его.

swillovPage = document.getElementsByTagName("body")[0];
swillovPage.innerHTML = swillovPage.innerHTML;
swillovPage.innerHTML = ''

Если вставить внутрь innerHTML некорректный код, например elem.innerHTML += '</div>', то браузер не сможет это распарсить и результат будет непредсказуемым. И для простых задач, вроде замены значения title, я бы рекомендовал использовать document.title = 'New title';, а не писать огромные скрипты.


Добавление новых элементов

createElement() позволяет создать новый узел элемента, функция принимает только один аргумент: элемент, который требуется создать. Сразу после применения метода новый элемент не появится на странице, но JS уже будет о нем знать. Многие подумают, не дубль ли это innerHtml? Нет, потому что это гораздо более подходящий инструмент для добавления элементов в HTML, и гораздо более шустрый. innerHTML удаляет все дочерние элементы, разбирает полученную строку и результат добавляет как дочерние элементы. createElement() работает без всех этих вычислений.

var newDiv = document.createElement("div");

Задать пустому элементу контент можно с помощью

littleOne.innerHTML = '<h1>КОТЫ!</h1>';

Класс задается при помощи littleOne.classList.add('ac-gn-item'). И для добавления элемента с классом и контентом на страницу используется var content = document.querySelector('body'); content.appendChild(littleOne);. Элемент будет добавлен в конец страницы, что очень редко является ожидаемым поведением. Для добавления элемента в определенное место на странице используется метод insertBefore с двумя параметрами: что вставляем и куда. content.insertBefore(littleOne, content.children[1]).

Заменить элемент на другой довольно легко:

var newContent = document.createElement('span');
newContent.innerHTML = '<i>Возникла ошибка</i>';
newContent.style.borderBottom = '2px dotted white';
content.replaceChild(littleOne,newContent);

Если же надо удалить, то content.removeChild.(littleOne); поможет решить задачу. Про это подробнее поговорим в конце статьи.

registerElement. Синтаксис чуть ниже, в котором tag-name это имя тэга, например «mug-shops» (дефис обязателен). И options отвечает за новый элемент, который наследуется от HTMLElement. Иначе не будет стандартных методов и свойств. По факту, происходит регистрация нового кастомного элемента и возврат его конструктора.

var <em>constructor</em> = document.registerElement(<em>tag-name</em>, <em>options</em>);

● Давайте рассмотрим createTextNode(), с помощью которого можно создать новый элемент, в частности текст. Его качественное отличие от innerHTML в том, что используя innerHTML нам необходимо проходить весь путь построения DOM. А вот между createElement() и createTextNode() большой разницы в скорости работы замечено не было.

var itsText = document.createTextNode ("нужный стринг");

Вот мы создали новую строку текста и новый элемент, но где же он теперь находится? Для добавления созданного элемента в документ используется метод appendChild. У него есть только один аргумент, имя узла, который мы хотим добавить в DOM. Важно указать, какой из уже существующих элементов будет родительским, т.к. будет добавлен узел в конец списка дочерних узлов. Помимо appendChild, можно использовать insertBefore. Начнем с appendChild().

var myNewText = document.createElement('div');
myNewText.textContent = "Это новый элемент.";
document.body.appendChild(myNewText);

или

document.domain = 'google.ru';
var output = document.createElement('p');
document.body.appendChild(output);

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

 

Клонирование и удаление элементов

● Элементы можно клонировать с помощью elem.cloneNode(true) и удалять с помощью removeChild.

var post = document.getElementById("_Q5");
post.parentNode.removeChild(post);

Теперь вы сможете более гибко получать, изменять, и добавлять данные при подготовке тестирования и проверке гипотез. Всего немного JavaScript, и проверка гипотез становится куда быстрее и затрагивает все меньше участников команды. И готовьтесь к проблемам со старыми IE, куда уж без этого.

«Взаимодействуя с данным сайтом, вы, как пользователь, автоматически даете согласие согласие на обработку персональных данных»

Этот сайт использует Akismet для борьбы со спамом. Узнайте как обрабатываются ваши данные комментариев.