Обновление списка через htmx
Поиск уже умеет работать через обычный GET-запрос и сохраняет условие в адресе. Теперь добавим htmx, чтобы обновлять область списка без полной перезагрузки страницы. Сервер продолжит возвращать HTML, а браузер будет заменять выбранный участок документа. Здесь полезно увидеть, что интерактивность не требует второго JSON API с повторением правил фильтрации: данные и модель страницы остаются на прежней серверной границе.
В состоянии lesson-08 зафиксирован htmx 2.0.11. В архив не вложены внешние зависимости. Скрипт fetch-htmx.sh содержит команду, которой читатель самостоятельно получает конкретный файл библиотеки в static/htmx.min.js. До её выполнения сайт приложения сохраняет обычные ссылки и формы. При подготовке курса зависимость не загружалась, сервер не запускался и результат замены DOM не выдаётся за наблюдённый замер.
Адрес, цель и способ замены
К поисковой форме добавлены hx-get, hx-target и hx-swap. Первый атрибут определяет запрос, второй выбирает #panel, третий задаёт замену outerHTML. Это значит, что полученный фрагмент заменит сам элемент main целиком, а не только его внутреннее содержимое. Ответ должен содержать новый элемент с тем же идентификатором. Если вернуть только строки таблицы, следующая операция может потерять свою цель или создать некорректную структуру документа.
Сохраняем обычные method="get" и action="/". При недоступной библиотеке браузер выполняет знакомую навигацию. Это не отдельная декоративная возможность: один и тот же запрос имеет полноценную серверную страницу. Такое устройство упрощает открытие адреса в новой вкладке и помогает отделить проблему сети или загрузки JavaScript от ошибки запроса к хранилищу. Работоспособность формы не должна зависеть от существования предыдущего состояния DOM.
<form method="get" action="/" hx-get="/" hx-target="#panel" hx-swap="outerHTML">
<label>Поиск по названию или артикулу <input name="q" value="{{.Q}}" maxlength="100">
</label>
<button>Найти</button>
</form>
Фрагмент показан из view.html текущего состояния. Полный шаблон определяет page и panel. Первая часть содержит документ, заголовок и подключение скрипта; вторая содержит всю меняющуюся область. При обычном просмотре page вызывает panel, а при htmx-запросе сервер выполняет panel самостоятельно. Благодаря этому обе формы выдачи используют одну разметку таблицы, одно сообщение пустого поиска и одинаковые значения формы.
Выбор ответа на сервере
Htmx добавляет заголовок HX-Request. Обработчик после чтения базы выбирает имя шаблона по этому признаку. Заголовок не подтверждает личность и не даёт прав на изменение оборудования. Его может отправить любой клиент. Он влияет только на форму представления. Такая граница существенна: решение о доступе нельзя привязать к тому, что обращение выглядит как «наше красивое приложение».
Ответ также содержит Vary: HX-Request. Если промежуточный кэш начнёт сохранять выдачу, ему нужно знать, что один адрес может возвращать полный документ либо фрагмент. Без этого признака пользователь иногда получит main вместо всей страницы или, наоборот, вложенный документ внутри интерактивной области. В текущем стенде дополнительно установлен Cache-Control: no-store, поэтому сохранение ответа не предлагается; Vary всё равно документирует различие представлений.
Особый заголовок восстановления истории обрабатывается как запрос полной страницы. Этот договор пригодится в следующем уроке, когда включим изменение URL. История браузера не обязана всегда содержать подходящий сохранённый фрагмент, поэтому сервер должен уметь заново сформировать полный документ по тому же адресу. Восстановление не должно требовать скрытой последовательности предыдущих кликов. Адрес остаётся достаточным описанием выбранного поиска.
Что изменяется в браузере
После самостоятельного подключения библиотеки отправка поиска должна заменить main, сохранив внешнюю оболочку документа. Новая таблица уже содержит результат серверного SELECT. Браузер не фильтрует исходный массив оборудования самостоятельно. Это полезно при постоянном хранилище: источник истины один, а DOM является последним отображённым ответом. Если другой пользователь изменил карточку, новый запрос прочитает актуальное состояние базы, а не старую клиентскую копию.
Внутри заменяемой области находятся и таблица, и поисковая форма. Поэтому ответ обновляет значение условия согласованно с выдачей. Если заменить только tbody, подпись страницы и кнопка продолжения могут остаться от предыдущего поиска. Точный выбор границы фрагмента зависит от интерфейса. В нашем небольшом приложении main является связным представлением результата; дополнительные независимые панели потребовали бы отдельного договора обновления.
На этом шаге атрибут hx-push-url ещё не включён. Обычный GET изменяет адрес, а интерактивная отправка обновляет область без автоматической записи каждого состояния в историю. Это промежуточный результат курса, а не финальный договор навигации. Следующая глава добавит явное изменение URL и рассмотрит ошибки ответа. Разделение позволяет сначала понять правильную форму фрагмента, а затем связать её с привычными действиями «назад» и «открыть ссылку».
Рассмотрим две ошибки реализации. Первая: сервер всегда возвращает полный page, хотя цель замены находится внутри main. Тогда в существующую область может попасть лишняя оболочка документа. Вторая: сервер всегда возвращает panel, в том числе при прямом открытии адреса. Тогда новая вкладка не получает заголовок, стили и скрипт. Проверять нужно обе формы обращения, потому что удачный интерактивный клик не подтверждает правильность самостоятельной страницы.
Обычные карточки создания пока продолжают работать полным POST-циклом. Мы не превращаем все действия в htmx одним общим флагом, поскольку у записи есть дополнительные требования: ошибка 422, успешное перенаправление и состояние после изменения. Каждое действие должно получить явный договор ответа. В следующем состоянии рассмотрим именно этот переход, сохранив уже изученные правила валидации и SQL-параметров.
Основная польза текущего шага — совпадение содержимого полной страницы и фрагмента. Стало меньше браузерной навигации, но не появилось второй версии учёта. Предметная логика остаётся на сервере, шаблон остаётся общим, а htmx отвечает за запрос и замену. Такая модель пригодна для небольших корпоративных интерфейсов, где большинство операций выражается формами и списками, а сложная независимая клиентская модель пока не требуется.
Событие отправки пока связано с обычным submit формы. Мы не добавляем запрос на каждую нажатую клавишу. Для активного поиска понадобятся задержка, отмена устаревшего обращения и ясный порядок применения ответов. Иначе медленный ответ старого условия может заменить более свежий список. Простая кнопка оставляет одно понятное намерение пользователя и позволяет изучить границу фрагмента отдельно от управления частотой запросов.
Исходники урока
Оглавление серии · Скачать исходники. Полное самостоятельное состояние находится в lesson-08. Учебные приложения, зависимости, база и проверки при подготовке не запускались; описанный результат ожидается по исходникам. Материалы пока находятся в локальной редакционной версии.
Первичные источники: документация 1.