Stimulus использует концепцию таргетов для прямого
связывания элементов DOM с контроллером. Таргеты представляют собой
заранее определённые HTML-элементы, доступ к которым осуществляется
через свойства контроллера. Каждый таргет объявляется с помощью атрибута
data-<controller>-target, где
<controller> соответствует имени контроллера, а
значение — имени таргета.
Пример базового объявления:
<div data-controller="example">
<input data-example-target="input">
<button data-action="click->example#submit">Отправить</button>
</div>
В контроллере example_controller.js доступ к таргету
осуществляется через свойство this.<name>Target:
import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
static targets = ["input"]
submit() {
console.log(this.inputTarget.value)
}
}
Если на странице присутствует несколько элементов одного типа, можно
объявить таргет как массив, используя
this.<name>Targets. Stimulus автоматически собирает
все соответствующие элементы в массив.
<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"]
highlightAll() {
this.itemTargets.forEach(el => el.classList.add("highlight"))
}
}
Метод highlightAll применяет класс ко всем таргетам
сразу.
В сложных интерфейсах таргеты могут быть вложены или распределены по различным контейнерам. Stimulus сохраняет точные ссылки на таргеты относительно контроллера, даже если структура глубоко вложена.
<div data-controller="gallery">
<div class="album" data-gallery-target="album">
<img src="photo1.jpg" data-gallery-target="photo">
<img src="photo2.jpg" data-gallery-target="photo">
</div>
<div class="album" data-gallery-target="album">
<img src="photo3.jpg" data-gallery-target="photo">
<img src="photo4.jpg" data-gallery-target="photo">
</div>
</div>
Контроллер:
import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
static targets = ["album", "photo"]
logStructure() {
console.log(this.albumTargets.length) // 2
console.log(this.photoTargets.length) // 4
}
highlightAlbum(index) {
this.albumTargets[index].classList.add("active")
}
highlightPhotosInAlbum(albumIndex) {
const photos = this.albumTargets[albumIndex].querySelectorAll("[data-gallery-target='photo']")
photos.forEach(photo => photo.classList.add("highlight"))
}
}
В этом примере использование вложенной структуры позволяет
фокусироваться на конкретной группе элементов, не
затрагивая остальные. Метод querySelectorAll внутри таргета
помогает работать с подмассивами элементов.
Stimulus автоматически не отслеживает новые
элементы, добавленные после инициализации контроллера. Для
работы с динамически созданными элементами используется
обновление ссылок на таргеты через метод
this.initialize() или отдельный кастомный метод.
addPhoto(newPhotoElement) {
this.element.querySelector(".album").appendChild(newPhotoElement)
this.photoTargets.push(newPhotoElement)
}
Альтернативно, можно использовать MutationObserver для автоматического отслеживания изменений DOM:
connect() {
this.observer = new MutationObserver(() => this.refreshTargets())
this.observer.observe(this.element, { childList: true, subtree: true })
}
refreshTargets() {
this.photoTargets = Array.from(this.element.querySelectorAll("[data-gallery-target='photo']"))
}
Для упрощения работы со сложными интерфейсами часто применяют логические группы таргетов через множественные имена:
<div data-controller="form">
<input data-form-target="field username">
<input data-form-target="field email">
</div>
В контроллере можно обращаться по каждому имени или использовать общий массив для всех полей:
static targets = ["field"]
validateAll() {
this.fieldTargets.forEach(input => {
if (!input.value) input.classList.add("error")
})
}
querySelector там, где можно
объявить таргет на нужном уровне.Targets, чтобы не создавать
вручную массивы через DOM-методы.Эти подходы позволяют управлять сложными DOM-структурами эффективно и предсказуемо, сохраняя контроль над состоянием интерфейса и снижая вероятность ошибок при взаимодействии с множественными элементами на странице.