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 предоставляет несколько удобных способов взаимодействия с множественными таргетами:
Пример динамического изменения списка:
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")
}
}
Использование множественных таргетов в данном примере обеспечивает синхронизацию между кнопками и панелями, упрощая поддержку кода.
this.has{Target}Target и
this.{target}Targets позволяют безопасно проверять наличие
элементов и предотвращать ошибки.Множественные таргеты и коллекции в Stimulus создают мощный инструмент для работы с динамическими группами элементов, упрощают управление событиями и поддерживают чистую структуру контроллеров, обеспечивая высокую читаемость и масштабируемость фронтенд-кода.