14 серпня 2023 р.

Пошук: getElement*, querySelector*

Властивості навігації по DOM чудові, коли елементи розташовані близько один до одного. А якщо ні? Як отримати довільний елемент сторінки?

Для цього в DOM існують додаткові методи пошуку.

document.getElementById або просто id

Якщо елемент має атрибут id, ми можемо отримати його викликом document.getElementById(id), незалежно від того, де він знаходиться.

Наприклад:

<div id="elem">
  <div id="elem-content">Елемент</div>
</div>

<script>
  // отримати елемент
  let elem = document.getElementById('elem');

  // зробити його фон червоним
  elem.style.background = 'red';
</script>

Крім того, існує глобальна змінна з назвою id, яка посилається на елемент:

<div id="elem">
  <div id="elem-content">Елемент</div>
</div>

<script>
  // elem -- це посилання на елемент DOM з id="elem"
  elem.style.background = 'red';

  // id="elem-content" містить дефіс всередині, тому не може бути ім’ям змінної
  // ...але ми можемо отримати доступ до нього за допомогою квадратних дужок: window['elem-content']
</script>

…Але це лише якщо ми не оголосили змінну JavaScript з таким же ім’ям, інакше вона матиме пріоритет:

<div id="elem"></div>

<script>
  let elem = 5; // тепер elem дорівнює 5, а не посилається на <div id="elem">

  alert(elem); // 5
</script>
Будь ласка, не використовуйте id-іменовані глобальні змінні для доступу до елементів

Ця поведінка описана у специфікації, тож це свого роду стандарт. Але він підтримується в основному для сумісності.

Браузер намагається нам допомогти, змішуючи простори імен JS і DOM. Це зручно для простих скриптів, які знаходяться прямо в HTML, але загалом це не дуже добре. Можуть виникнути конфлікти імен. Крім того, коли хтось читає JS-код і не бачить HTML, незрозуміло, звідки приходить змінна.

У цьому посібнику ми будемо використовувати id для прямого посилання на елемент для стислості, коли очевидно, звідки цей елемент походить.

У реальному житті краще використовувати document.getElementById.

Значення id має бути унікальним

Значення id має бути унікальним. У документі повинен бути лише один елемент із заданим id.

Якщо є кілька елементів з однаковим id, то поведінка методів, які його використовують є непередбачуваною. Наприклад, document.getElementById може повертати будь-який з таких елементів випадковим чином. Тому, будь ласка, дотримуйтеся правила та залишайте id унікальним.

Лише document.getElementById, а не anyElem.getElementById

Метод getElementById може бути викликаний лише на об’єкті document. Він шукає вказаний id у всьому документі.

querySelectorAll

Найбільш гнучким методом є elem.querySelectorAll(css), який повертає всі елементи всередині elem, що відповідають заданому CSS-селектору.

Тут ми шукаємо всі елементи <li>, які є останніми дочірніми елементами:

<ul>
  <li>Цей</li>
  <li>тест</li>
</ul>
<ul>
  <li>повністю</li>
  <li>пройдено</li>
</ul>
<script>
  let elements = document.querySelectorAll('ul > li:last-child');

  for (let elem of elements) {
    alert(elem.innerHTML); // "тест", "пройдено"
  }
</script>

Цей метод дійсно потужний, оскільки можна використовувати будь-який CSS-селектор.

Також можна використовувати псевдокласи

Псевдокласи в CSS-селекторі, такі як :hover і :active, також підтримуються. Наприклад, document.querySelectorAll(':hover') поверне колекцію елементів, що знаходяться під курсором миші (у порядку вкладення: від зовнішнього <html> до найбільш вкладеного).

querySelector

Виклик elem.querySelector(css) повертає перший елемент, що відповідає даному CSS-селектору.

Іншими словами, результат такий самий, як і elem.querySelectorAll(css)[0], але він спочатку знайде всі елементи, а потім візьме перший, тоді як elem.querySelector знайде лише перший та зупиниться. Цей метод не лише прискорює виконання, а й дозволяє скоротити кількість написаного коду.

matches

Попередні методи виконували пошук по DOM.

Метод elem.matches(css) нічого не шукає, він просто перевіряє, чи відповідає elem заданому CSS-селектору. Він повертає true або false.

Цей метод стає в нагоді, коли ми перебираємо елементи (наприклад, у масиві чи чомусь подібному) і намагаємося відфільтрувати ті, які нас цікавлять.

Наприклад:

<a href="http://example.com/file.zip">...</a>
<a href="http://ya.ru">...</a>

<script>
  // може бути будь-якою колекцією замість document.body.children
  for (let elem of document.body.children) {
    if (elem.matches('a[href$="zip"]')) {
      alert("Посилання на архів: " + elem.href );
    }
  }
</script>

closest

Предками елемента є: батько, батько батька, його батько і так далі. Предки разом утворюють ланцюг батьків від елемента до вершини.

Метод elem.closest(css) шукає найближчого предка, який відповідає CSS-селектору. Сам elem також включається в пошук.

Іншими словами, метод closest йде вгору від елемента і перевіряє кожного з батьківських елементів. Якщо він відповідає заданому селектору, пошук припиняється, і повертається знайдений предок.

Наприклад:

<h1>Зміст</h1>

<div class="contents">
  <ul class="book">
    <li class="chapter">Розділ 1</li>
    <li class="chapter">Розділ 2</li>
  </ul>
</div>

<script>
  let chapter = document.querySelector('.chapter'); // LI

  alert(chapter.closest('.book')); // UL
  alert(chapter.closest('.contents')); // DIV

  alert(chapter.closest('h1')); // null (тому що h1 -- не предок)
</script>

getElementsBy*

Існують також інші методи пошуку елементів за тегом, класом тощо.

Сьогодні вони здебільшого історичні, оскільки querySelector є потужнішим і коротшим для написання.

Тому тут ми розглянемо їх переважно для повноти, тоді як ви все ще можете знайти їх у старих скриптах.

  • elem.getElementsByTagName(tag) шукає елементи з заданим тегом і повертає колекцію цих елементів. Параметр tag також може бути зірочкою "*" для “будь-яких тегів”.
  • elem.getElementsByClassName(className) повертає елементи, які мають вказаний CSS-клас.
  • document.getElementsByName(name) повертає елементи з заданим атрибутом name для всього документа. Використовується дуже рідко.

Наприклад:

// отримує всі елементи div у документі
let divs = document.getElementsByTagName('div');

Знайдімо всі теги input в таблиці:

<table id="table">
  <tr>
    <td>Ваш вік:</td>

    <td>
      <label>
        <input type="radio" name="age" value="young" checked> молодше 18
      </label>
      <label>
        <input type="radio" name="age" value="mature"> від 18 до 50
      </label>
      <label>
        <input type="radio" name="age" value="senior"> старше 60
      </label>
    </td>
  </tr>
</table>

<script>
  let inputs = table.getElementsByTagName('input');

  for (let input of inputs) {
    alert( input.value + ': ' + input.checked );
  }
</script>
Не забуваємо про літеру "s"!

У новачків у розробці іноді трапляються випадки, коли вони забувають про літеру "s". Тобто вони намагаються викликати метод getElementByTagName замість getElementsByTagName.

Літера "s" відсутня в назві методу getElementById, оскільки він повертає один елемент. А getElementsByTagName повертає колекцію елементів, тому всередині є "s".

Повертає колекцію, а не елемент!

Іншою поширеною помилкою новачків є ось таке написання:

// не працює
document.getElementsByTagName('input').value = 5;

Це не спрацює, тому що код приймає колекцію вхідних даних і призначає значення їй, а не елементам всередині неї.

Ми повинні або перебрати колекцію, або отримати елемент за його індексом, а потім призначити, як тут:

// має працювати (якщо є input)
document.getElementsByTagName('input')[0].value = 5;

Шукаємо елементи .article:

<form name="my-form">
  <div class="article">Стаття</div>
  <div class="long article">Довга стаття</div>
</form>

<script>
  // шукаємо за ім’ям атрибуту
  let form = document.getElementsByName('my-form')[0];

  // шукаємо за класом всередині form
  let articles = form.getElementsByClassName('article');
  alert(articles.length); // 2, знаходимо два елементи з класом "article"
</script>

Живі колекції

Усі методи "getElementsBy*" повертають живу колекцію. Такі колекції завжди відображають поточний стан документа і “автооновлюються” при його зміні.

У нижченаведеному прикладі є два скрипта.

  1. Перший створює посилання на колекцію <div>. На даний момент його довжина дорівнює 1.
  2. Другий скрипт запускається після того, як браузер зустріне ще один <div>, тому його довжина дорівнює 2.
<div>Перший div</div>

<script>
  let divs = document.getElementsByTagName('div');
  alert(divs.length); // 1
</script>

<div>Другий div</div>

<script>
  alert(divs.length); // 2
</script>

На відміну від цього, querySelectorAll повертає статичну колекцію. Це схоже на фіксований масив елементів.

Якщо ми використаємо його у вищенаведеному прикладі, тоді обидва сценарії виведуть 1:

<div>Перший div</div>

<script>
  let divs = document.querySelectorAll('div');
  alert(divs.length); // 1
</script>

<div>Другий div</div>

<script>
  alert(divs.length); // 1
</script>

Тепер ми легко бачимо різницю. Статична колекція не збільшилася після появи нового div у документі.

Підсумки

Існує 6 основних методів пошуку елементів у DOM:

Метод Шукає, використовуючи... Чи може викликатися на елементі? Чи повертає живу колекцію?
querySelector CSS-селектор -
querySelectorAll CSS-селектор -
getElementById id - -
getElementsByName ім’я -
getElementsByTagName тег або '*'
getElementsByClassName клас

Найчастіше використовуються querySelector і querySelectorAll, але getElement(s)By* може бути корисним час від часу або зустрічатися в старих скриптах.

Крім того:

  • Існує метод elem.matches(css) , який перевіряє, чи відповідає elem заданому CSS-селектору.
  • Існує метод elem.closest(css) , який шукає найближчого предка, який відповідає заданому CSS-селектору. Сам elem також перевіряється.

І згадаймо тут ще один метод перевірки взаємовідносин нащадок-предок, оскільки він іноді стає в нагоді:

  • elemA.contains(elemB) повертає true, якщо elemB знаходиться всередині elemA (нащадок elemA) або коли elemA==elemB.

Завдання

важливість: 4

Ось документ із таблицею та формою.

Як знайти?…

  1. Таблиця з id="age-table".
  2. Усі елементи label всередині цієї таблиці (їх має бути 3).
  3. Перший td у цій таблиці (зі словом “Age”).
  4. form з name="search".
  5. Перший input у цій формі.
  6. Останній input у цій формі.

Відкрийте сторінку table.html в окремому вікні та скористайтеся для цього інструментами браузера.

Є багато способів зробити це.

Ось деякі з них:

// 1. Таблиця з `id="age-table"`.
let table = document.getElementById('age-table')

// 2. Всі елементи label всередині цієї таблиці
table.getElementsByTagName('label')
// або
document.querySelectorAll('#age-table label')

// 3. Перший td в цій таблиці (зі словом "Age")
table.rows[0].cells[0]
// або
table.getElementsByTagName('td')[0]
// або
table.querySelector('td')

// 4. форма з іменем "search"
// припускаємо, що в документі є лише один елемент з name="search".
let form = document.getElementsByName('search')[0]
// або безпосередньо форма
document.querySelector('form[name="search"]')

// 5. Перший input у цій формі.
form.getElementsByTagName('input')[0]
// або
form.querySelector('input')

// 6. Останній input у цій формі
let inputs = form.querySelectorAll('input') // знайти всі input
inputs[inputs.length-1] // взяти останній
Навчальна карта