Множественные таргеты и коллекции

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


Определение множественных таргетов

Таргет в Stimulus — это DOM-элемент, на который контроллер «смотрит» и с которым взаимодействует. Обычные таргеты используются для связи с одним элементом:

<div data-controller="example">
  <input data-example-target="input">
</div>

Однако часто требуется работать с набором элементов одновременно. Для этого применяется множественный таргет. Стандартный синтаксис остаётся прежним, отличие в том, что одно имя таргета может быть присвоено нескольким элементам:

<div data-controller="list">
  <li data-list-target="item">Элемент 1</li>
  <li data-list-target="item">Элемент 2</li>
  <li data-list-target="item">Элемент 3</li>
</div>

В контроллере доступ к этим элементам осуществляется через коллекцию:

import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  static targets = ["item"]

  connect() {
    console.log(this.itemTargets) // [li, li, li]
  }
}

Ключевой момент: Stimulus автоматически создаёт массив this.itemTargets, содержащий все элементы с указанным таргетом. При этом имя свойства (itemTargets) формируется добавлением Targets к имени таргета.


Методы для работы с коллекцией

Stimulus предоставляет несколько удобных способов взаимодействия с множественными таргетами:

  • this.itemTargets.forEach(callback) — перебор всех элементов коллекции.
  • this.itemTargets.find(callback) — поиск первого элемента, удовлетворяющего условию.
  • this.itemTargets.filter(callback) — фильтрация элементов по критерию.
  • this.hasItemTarget — проверка наличия хотя бы одного элемента с данным таргетом.

Пример динамического изменения списка:

highlightItems() {
  this.itemTargets.forEach((element, index) => {
    element.style.backgroundColor = index % 2 === 0 ? 'lightblue' : 'lightgreen'
  })
}

Динамическое добавление и удаление элементов

Stimulus отслеживает только те элементы, которые существуют в DOM на момент загрузки контроллера. Чтобы работать с динамически добавляемыми элементами, необходимо использовать MutationObserver или повторно инициировать работу с таргетами.

Пример:

addItem(content) {
  const li = document.createElement("li")
  li.textContent = content
  li.dataset.listTarget = "item"
  this.element.appendChild(li)
  // обновление коллекции
  this.itemTargets.push(li)
}

Однако предпочтительный способ — использовать MutationObserver для автоматического обновления:

connect() {
  this.observer = new MutationObserver(() => this.refreshTargets())
  this.observer.observe(this.element, { childList: true })
}

refreshTargets() {
  this.itemTargets = Array.from(this.element.querySelectorAll('[data-list-target="item"]'))
}

События и множественные таргеты

Для групп элементов удобно назначать обработчики событий:

<li data-list-target="item" data-action="click->list#select">Элемент 1</li>

Контроллер:

SELECT(event) {
  const clickedItem = event.currentTarget
  this.itemTargets.forEach(item => item.classList.remove("selected"))
  clickedItem.classList.add("selected")
}

Этот подход позволяет сохранять логику работы с отдельным элементом, одновременно управляя всей коллекцией.


Связь между множественными таргетами

Множественные таргеты часто работают в паре с другими таргетами. Например, при создании табов:

<div data-controller="tabs">
  <button data-tabs-target="tab" data-action="click->tabs#show">Tab 1</button>
  <button data-tabs-target="tab" data-action="click->tabs#show">Tab 2</button>

  <div data-tabs-target="panel">Content 1</div>
  <div data-tabs-target="panel">Content 2</div>
</div>

Контроллер:

import { Controller } FROM "@hotwired/stimulus"

export default class extends Controller {
  static targets = ["tab", "panel"]

  show(event) {
    const index = this.tabTargets.indexOf(event.currentTarget)
    this.tabTargets.forEach(tab => tab.classList.remove("active"))
    this.panelTargets.forEach(panel => panel.classList.remove("active"))

    this.tabTargets[index].classList.add("active")
    this.panelTargets[index].classList.add("active")
  }
}

Использование множественных таргетов в данном примере обеспечивает синхронизацию между кнопками и панелями, упрощая поддержку кода.


Полезные советы

  • Для больших коллекций рекомендуется не манипулировать DOM напрямую на каждой итерации, а использовать DocumentFragment или классы.
  • Имена таргетов должны быть уникальными в рамках одного контроллера, иначе коллекции могут конфликтовать.
  • Методы this.has{Target}Target и this.{target}Targets позволяют безопасно проверять наличие элементов и предотвращать ошибки.

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