Документация
htmx в нескольких словах
htmx — это библиотека, которая позволяет вам обращаться к современным функциям браузера непосредственно из HTML, а не с помощью JavaScript.
Чтобы понять htmx, давайте сначала рассмотрим тег ссылки:
<a href="/blog">Blog</a>
Этот тег ссылки говорит браузеру:
«Когда пользователь нажимает на эту ссылку, отправьте HTTP-запрос GET на ‘/blog’ и загрузите содержимое ответа в окно браузера».
С учетом этого, рассмотрим следующий фрагмент HTML:
<button hx-post="/clicked"
hx-trigger="click"
hx-target="#parent-div"
hx-swap="outerHTML"
>
Click Me!
</button>
Это говорит htmx:
«Когда пользователь нажимает на эту кнопку, отправьте HTTP-запрос POST на ‘/clicked’ и используйте содержимое ответа для замены элемента с id
parent-divв DOM».
htmx расширяет и обобщает основную идею HTML как гипертекста, открывая гораздо больше возможностей непосредственно в языке:
- Теперь любой элемент, а не только ссылки и формы, может отправлять HTTP-запрос
- Теперь любое событие, а не только щелчки или отправка форм, может запускать запросы
- Теперь любой HTTP-глагол, а не только
GETиPOST, может быть использован - Теперь любой элемент, а не только всё окно, может быть целью обновления запросом
Обратите внимание, что при использовании htmx на стороне сервера вы обычно отвечаете с помощью HTML, а не JSON. Это позволяет вам оставаться в рамках оригинальной модели веб-программирования, используя исходной модели веб-программирования, используя Гипертекст как движок состояния приложения, даже не нужно глубоко понимать эту концепцию.
Стоит упомянуть, что, если вы предпочитаете, вы можете использовать префикс data- при использовании htmx:
<a data-hx-post="/click">Click Me!</a>
Установка
Htmx — это независимая от зависимостей, ориентированная на браузер JavaScript-библиотека. Это означает, что ее использование так же просто, как добавление тега <script> в заголовок вашего документа. Вам не нужны сложные этапы сборки или системы.
Если вы мигрируете с intercooler.js на htmx, ознакомьтесь с руководством по миграции.
Через CDN (например, unpkg.com)
Самый быстрый способ начать работу с htmx — загрузить его через CDN. Вы можете просто добавить это в тег заголовка и начать работу:
<script src="https://unpkg.com/htmx.org@1.9.10" integrity="sha384-D1Kt99CQMDuVetoL1lrYwg5t+9QdHe7NLX/SoJYkXDFfX37iInKRy5xLSi8nO7UC" crossorigin="anonymous"></script>
Хотя подход CDN чрезвычайно прост, вы можете рассмотреть не использовать CDN в производстве.
Скачать копию
Следующий по простоте способ установки htmx — просто скопировать его в свой проект.
Загрузите htmx.min.js с unpkg.com и добавьте его в соответствующий каталог в вашем проекте и включите его там, где это необходимо, с помощью тега <script>.
<script src="/path/to/htmx.min.js"></script>
Вы также можете добавлять расширения таким образом, загрузив их из каталога ext/.
npm
Для систем сборки типа npm вы можете установить htmx с помощью npm:
npm install htmx.org
После установки вам нужно будет использовать соответствующие инструменты для использования node_modules/htmx.org/dist/htmx.js (или .min.js). Например, вы можете собрать htmx с некоторыми расширениями и проектно-специфическим кодом.
Webpack
Если вы используете webpack для управления JavaScript:
- Установите
htmxчерез ваш любимый менеджер пакетов (например, npm или yarn) - Добавьте импорт в свой
index.js
import 'htmx.org';
Если вы хотите использовать глобальную переменную htmx (рекомендуется), вам необходимо внедрить ее в область видимости window:
- Создайте пользовательский файл JS
- Импортируйте этот файл в свой
index.js(ниже импорта из шага 2)
import 'path/to/my_custom.js';
- Затем добавьте этот код в файл:
window.htmx = require('htmx.org');
- Наконец, перестройте свой бандл
AJAX
Ядро htmx — это набор атрибутов, которые позволяют отправлять AJAX-запросы непосредственно из HTML:
| Атрибут | Описание |
|---|---|
| hx-get | Отправляет запрос GET на указанный URL |
| hx-post | Отправляет запрос POST на указанный URL |
| hx-put | Отправляет запрос PUT на указанный URL |
| hx-patch | Отправляет запрос PATCH на указанный URL |
| hx-delete | Отправляет запрос DELETE на указанный URL |
Каждый из этих атрибутов принимает URL для отправки AJAX-запроса. Элемент отправит запрос указанного типа на указанный URL, когда элемент будет запущен:
<div hx-put="/messages">
Put To Messages
</div>
Это говорит браузеру:
Когда пользователь нажимает на этот div, отправьте PUT-запрос на URL /messages и загрузите ответ в div
Триггеры запросов
По умолчанию AJAX-запросы запускаются по «естественному» событию элемента:
-
input,textareaиselectзапускаются при событииchange -
formзапускается при событииsubmit - все остальное запускается по событию
click
Если вы хотите другое поведение, вы можете использовать атрибут hx-trigger, чтобы указать, какое событие вызовет запрос.
Вот пример, который отправляет POST-запрос на /mouse_entered при наведении курсора мыши:
<div hx-post="/mouse_entered" hx-trigger="mouseenter">
[Here Mouse, Mouse!]
</div>
Модификаторы триггеров
Триггер также может иметь несколько дополнительных модификаторов, которые изменяют его поведение. Например, если вы хотите, чтобы запрос выполнялся только один раз, вы можете использовать модификатор once для триггера:
<div hx-post="/mouse_entered" hx-trigger="mouseenter once">
[Here Mouse, Mouse!]
</div>
Другие модификаторы, которые вы можете использовать для триггеров:
-
changed— отправлять запрос только если значение элемента изменилось -
delay:<time interval>— подождать указанное время (например,1s) перед отправкой запроса. Если событие снова запускается, отсчет времени сбрасывается. -
throttle:<time interval>— подождать указанное время (например,1s) перед отправкой запроса. В отличие отdelay, если новое событие произойдёт до истечения срока, событие будет отброшено, поэтому запрос будет выполнен в конце временного интервала. -
from:<CSS Selector>— прослушивать событие на другом элементе. Это можно использовать для таких вещей, как сочетания клавиш.
Вы можете использовать эти атрибуты для реализации многих распространенных шаблонов пользовательского интерфейса, таких как Активный поиск:
<input type="text" name="q"
hx-get="/trigger_delay"
hx-trigger="keyup changed delay:500ms"
hx-target="#search-results"
placeholder="Search..."
>
<div id="search-results"></div>
Этот элемент ввода отправит запрос через 500 миллисекунд после события нажатия клавиши, если значение ввода изменилось, и вставит результаты в div с id search-results.
Несколько триггеров можно указать в атрибуте hx-trigger, разделенных запятыми.
Фильтры триггеров
Вы также можете применять фильтры триггеров, используя квадратные скобки после имени события, заключив в них выражение JavaScript, которое будет вычислено. Если выражение вычисляется как true, событие будет запущен, в противном случае — нет.
Вот пример, который запускает событие только при нажатии Ctrl:
<div hx-get="/clicked" hx-trigger="click[ctrlKey]">
Control Click Me
</div>
Свойства, такие как ctrlKey, будут сначала разрешены относительно запускающего события, а затем относительно глобальной области видимости. Символ this будет установлен на текущий элемент.
Специальные события
htmx предоставляет несколько специальных событий для использования в hx-trigger:
-
load— срабатывает один раз при первой загрузке элемента -
revealed— срабатывает один раз, когда элемент впервые попадает в область видимости -
intersect— срабатывает один раз, когда элемент впервые пересекает область видимости. Это поддерживает два дополнительных параметра:-
root:<selector>— CSS-селектор корневого элемента для пересечения -
threshold:<float>— число с плавающей точкой от 0,0 до 1,0, указывающее, какая часть пересечения должна запустить событие
-
Вы также можете использовать пользовательские события для запуска запросов, если у вас есть сложный случай использования.
Опрос
Если вы хотите, чтобы элемент опрашивал указанный URL, а не ждал события, вы можете использовать синтаксис every с атрибутом hx-trigger:
<div hx-get="/news" hx-trigger="every 2s"></div>
Это говорит htmx
Каждые 2 секунды отправляйте GET-запрос на /news и загружайте ответ в div
Если вы хотите остановить опрос по ответу сервера, вы можете ответить с помощью HTTP-кода ответа 286, и элемент отменит опрос.
Опрос загрузки
Другой метод, который можно использовать для реализации опроса в htmx, — это «опрос загрузки», где элемент указывает триггер load вместе с задержкой и заменяет себя ответом:
<div hx-get="/messages"
hx-trigger="load delay:1s"
hx-swap="outerHTML"
>
</div>
Если конечная точка /messages продолжает возвращать div, настроенный таким образом, она будет продолжать «опрашивать» URL каждые секунду.
Опрос загрузки может быть полезен в ситуациях, когда у опроса есть конечная точка, в которой опрос завершается, например, когда вы показываете пользователю индикатор прогресса.
Индикаторы запросов
Когда отправляется AJAX-запрос, часто бывает полезно сообщить пользователю, что что-то происходит, так как браузер не предоставляет обратной связи. Это можно сделать в htmx с помощью класса htmx-indicator.
Класс htmx-indicator определён так, что непрозрачность любого элемента с этим классом по умолчанию равна 0, делая его невидимым, но присутствующим в DOM.
Когда htmx отправляет запрос, он помещает класс htmx-request на элемент (либо запрашивающий элемент, либо другой элемент, если указано). Класс htmx-request заставит дочерний элемент с классом htmx-indicator перейти к непрозрачности 1, показав индикатор.
<button hx-get="/click">
Click Me!
<img class="htmx-indicator" src="/spinner.gif">
</button>
Здесь у нас есть кнопка. При нажатии на неё класс htmx-request будет добавлен к ней, что отобразит элемент вращающегося GIF. (Мне сейчас нравятся SVG-спиннеры).
Хотя класс htmx-indicator использует непрозрачность для скрытия и отображения индикатора прогресса, если вы предпочитаете другой механизм, вы можете создать свою собственную CSS-анимацию:
.htmx-indicator{
display:none;
}
.htmx-request .htmx-indicator{
display:inline;
}
.htmx-request.htmx-indicator{
display:inline;
}
Если вы хотите добавить класс htmx-request к другому элементу, вы можете использовать атрибут hx-indicator с CSS-селектором:
<div>
<button hx-get="/click" hx-indicator="#indicator">
Click Me!
</button>
<img id="indicator" class="htmx-indicator" src="/spinner.gif"/>
</div>
Здесь мы явно указываем индикатор по id. Обратите внимание, что мы могли бы поместить класс на родительский элемент div и получить тот же эффект.
Вы также можете добавить атрибут disabled к элементам на время запроса, используя атрибут hx-disabled-elt.
Цели
Если вы хотите, чтобы ответ был загружен в другой элемент, кроме того, который сделал запрос, вы можете использовать атрибут hx-target, который принимает CSS-селектор. Обращаясь к нашему примеру Live Search:
<input type="text" name="q"
hx-get="/trigger_delay"
hx-trigger="keyup delay:500ms changed"
hx-target="#search-results"
placeholder="Search..."
>
<div id="search-results"></div>
Вы можете увидеть, что результаты поиска будут загружены в div#search-results, а не в тег input.
Расширенные CSS-селекторы
hx-target, и большинство атрибутов, принимающих CSS-селектор, поддерживают расширенный синтаксис CSS:
- Вы можете использовать ключевое слово
this, которое указывает, что элемент, на котором находится атрибутhx-target, является целевым - Синтаксис
closest <CSS selector>найдет ближайший родительский элемент или сам элемент, соответствующий заданному CSS-селектору. (Например,closest trбудет нацелен на ближайшую строку таблицы к элементу) - Синтаксис
next <CSS selector>найдет следующий элемент в DOM, соответствующий заданному CSS-селектору. - Синтаксис
previous <CSS selector>найдет предыдущий элемент в DOM, соответствующий заданному CSS-селектору. -
find <CSS selector>найдет первый дочерний элемент-потомок, соответствующий заданному CSS-селектору. (Например,find trбудет нацелен на первую дочернюю строку, которая является потомком элемента)
Кроме того, CSS-селектор может быть заключен в символы < и />, имитируя синтаксис литералов запроса гиперскрипта.
Относительные цели, такие как эта, могут быть полезны для создания гибких пользовательских интерфейсов без перегрузки DOM множеством атрибутов id.
Замена
htmx предлагает несколько способов замены HTML, возвращенного в DOM. По умолчанию содержимое заменяет innerHTML целевого элемента. Вы можете изменить это, используя атрибут hx-swap со следующими значениями:
| Имя | Описание |
|---|---|
innerHTML |
по умолчанию, помещает содержимое внутрь целевого элемента |
outerHTML |
заменяет весь целевой элемент возвращаемым содержимым |
afterbegin |
представляет содержимое перед первым дочерним элементом внутри целевого |
beforebegin |
представляет содержимое перед целевым элементом в родительском элементе целевого |
beforeend |
присоединяет содержимое после последнего дочернего элемента внутри целевого |
afterend |
присоединяет содержимое после целевого элемента в родительском элементе целевого |
delete |
удаляет целевой элемент независимо от ответа |
none |
не добавляет содержимое из ответа (обмен вне зоны доступа и заголовки ответа все равно будут обработаны) |
Замены морфинга
В дополнение к стандартным механизмам обмена выше, htmx также поддерживает морфинговые замены через расширения. Морфинговые замены пытаются слить новое содержимое в существующий DOM, а не просто заменить его. Они часто лучше сохраняют такие вещи, как фокус, состояние видео и т. д., изменяя существующие узлы на месте во время операции обмена, ценой большей загрузки процессора.
Доступны следующие расширения для замен типа морфинга:
- Idiomorph - алгоритм морфинга, созданный разработчиками htmx.
- Morphdom Swap - основанный на morphdom, исходной библиотеке морфинга DOM.
- Alpine-morph - основанный на плагине alpine morph, хорошо работает с alpine.js
Переходы между представлениями
Новый экспериментальный API переходов между представлениями предоставляет разработчикам способ создания анимационного перехода между различными состояниями DOM. Он все еще находится в активной разработке и недоступен во всех браузерах, но htmx предоставляет способ работы с этим новым API, который обращается к механизму без переходов, если API недоступен в данном браузере.
Вы можете поэкспериментировать с этим новым API, используя следующие подходы:
- Установите переменную конфигурации
htmx.config.globalViewTransitionsвtrue, чтобы использовать переходы для всех замен - Используйте опцию
transition:trueв атрибутеhx-swap - Если замена элемента будет переходить из-за одной из вышеуказанных конфигураций, вы можете перехватить событие
htmx:beforeTransitionи вызватьpreventDefault()на нем, чтобы отменить переход.
Переходы между представлениями можно настроить с помощью CSS, как указано в документации Chrome для этой функции.
Пример перехода между представлениями можно посмотреть на странице Примеры анимаций.
Параметры замены
Атрибут hx-swap поддерживает множество вариантов настройки поведения замены htmx. Например, по умолчанию htmx будет заменять в заголовке тега title, найденного где-либо в новом содержимом. Вы можете отключить это поведение, установив модификатор ignoreTitle в true:
<button hx-post="/like" hx-swap="outerHTML ignoreTitle:true">Like</button>
Доступные модификаторы для hx-swap:
| Параметр | Описание |
|---|---|
transition |
true или false, использовать ли API переходов между представлениями для этой замены |
swap |
Задержка замены (например, 100ms) между моментом удаления старого содержимого и вставкой нового |
settle |
Задержка завершения (например, 100ms) между моментом вставки нового содержимого и его завершением |
ignoreTitle |
Если установлено в true, любой заголовок, найденный в новом содержимом, будет проигнорирован и не обновит заголовок документа |
scroll |
top или bottom, прокрутить целевой элемент к верху или низу |
show |
top или bottom, прокрутить целевой элемент к верху или низу в область видимости |
Все модификаторы замены указываются после указания стиля замены и разделены двоеточием.
Для получения дополнительной информации об этих параметрах см. документацию по атрибуту hx-swap.
Синхронизация
Часто вам нужно координировать запросы между двумя элементами. Например, вы можете захотеть, чтобы запрос от одного элемента заменил запрос другого элемента или подождать, пока запрос другого элемента не завершится.
htmx предлагает атрибут hx-sync, чтобы помочь вам в этом.
Рассмотрим состояние гонки между отправкой формы и запросом валидации отдельного input в этом HTML:
<form hx-post="/store">
<input id="title" name="title" type="text"
hx-post="/validate"
hx-trigger="change"
>
<button type="submit">Submit</button>
</form>
Без использования hx-sync, заполнение input и немедленная отправка формы вызывают два параллельных запроса к /validate и /store.
Использование hx-sync="closest form:abort" для input отслеживает запросы формы и прерывает запрос input, если присутствует или начинается запрос формы, пока запрос input находится в полете:
<form hx-post="/store">
<input id="title" name="title" type="text"
hx-post="/validate"
hx-trigger="change"
hx-sync="closest form:abort"
>
<button type="submit">Submit</button>
</form>
Это решает синхронизацию между двумя элементами декларативным способом.
htmx также поддерживает программный способ отмены запросов: вы можете отправить событие htmx:abort элементу, чтобы отменить все запросы в процессе:
<button id="request-button" hx-post="/example">
Issue Request
</button>
<button onclick="htmx.trigger('#request-button', 'htmx:abort')">
Cancel Request
</button>
Дополнительные примеры и подробности можно найти на странице атрибута hx-sync.
CSS-переходы
htmx упрощает использование CSS-переходов без JavaScript. Рассмотрим этот HTML-контент:
<div id="div1">Original Content</div>
Представьте, что это содержимое заменяется htmx посредством ajax-запроса с этим новым содержимым:
<div id="div1" class="red">New Content</div>
Обратите внимание на два момента:
- У div одинаковый id в исходном и в новом содержимом
- Класс
redбыл добавлен к новому содержимому
В данной ситуации мы можем написать CSS-переход от старого состояния к новому:
.red {
color: red;
transition: all ease-in 1s ;
}
Когда htmx подменяет это новое содержимое, он сделает это таким образом, что CSS-переход будет применяться к новому содержимому, обеспечивая плавный переход к новому состоянию.
Таким образом, для использования CSS-переходов для элемента вам нужно лишь поддерживать его id неизменным при запросах!
Для получения дополнительной информации и живых демонстраций см. Примеры анимаций.
Детали
Чтобы понять, как CSS-переходы фактически работают в htmx, необходимо понять лежащую в основе модель обмена и ожидания, используемую htmx.
Когда новое содержимое получено от сервера, перед заменой содержимого, существующее содержимое страницы проверяется на наличие элементов, соответствующих атрибуту id. Если для элемента в новом содержимом найден совпадение, атрибуты старого содержимого копируются в новый элемент перед заменой. Новое содержимое затем подменяется, но с старыми значениями атрибутов. Наконец, новые значения атрибутов подменяются после задержки «ожидания» (по умолчанию 20 мс). Немного необычно, но именно это позволяет CSS-переходам работать без JavaScript от разработчика.
Замены вне зоны доступа
Если вы хотите заменить содержимое из ответа непосредственно в DOM, используя атрибут id , вы можете использовать атрибут hx-swap-oob в ответе html:
<div id="message" hx-swap-oob="true">Swap me directly!</div>
Additional Content
В этом ответе div#message будет непосредственно подставлен в соответствующий элемент DOM, а дополнительный контент будет подставлен в целевой элемент обычным способом.
Вы можете использовать этот метод для «прикрепления» обновлений к другим запросам.
Выбор контента для замены
Если вы хотите выбрать подмножество HTML-ответа для замены в целевом элементе, вы можете использовать атрибут hx-select, который принимает CSS-селектор и выбирает соответствующие элементы из ответа.
Вы также можете выбрать фрагменты контента для внеочередной замены, используя атрибут hx-select-oob, который принимает список идентификаторов элементов для выбора и замены.
Сохранение контента во время замены
Если есть контент, который вы хотите сохранить при каждой замене (например, видеоплеер, который должен продолжать воспроизведение, даже если произошла замена), вы можете использовать атрибут hx-preserve для элементов, которые нужно сохранить.
Параметры
По умолчанию элемент, вызывающий запрос, будет включать своё значение, если оно есть. Если элемент — это форма, будут включены значения всех полей ввода внутри неё.
Как и в HTML-формах, атрибут name элемента ввода используется как имя параметра в запросе, отправляемом htmx.
Кроме того, если элемент вызывает не-GET запрос, будут включены значения всех полей ввода ближайшей содержащей формы.
Если вы хотите включить значения других элементов, вы можете использовать атрибут hx-include с CSS-селектором всех элементов, значения которых вы хотите включить в запрос.
Если вы хотите отфильтровать некоторые параметры, вы можете использовать атрибут hx-params.
Наконец, если вы хотите программно изменить параметры, вы можете использовать событие htmx:configRequest.
Загрузка файлов
Если вы хотите загрузить файлы через запрос htmx, вы можете установить атрибут hx-encoding в multipart/form-data. Это позволит использовать объект FormData для отправки запроса, который должным образом включит файл в запрос.
Обратите внимание, что в зависимости от используемой технологии на стороне сервера, вам может потребоваться по-другому обрабатывать запросы с таким типом тела содержимого.
Обратите внимание, что htmx периодически генерирует событие htmx:xhr:progress на основе стандартного события progress во время загрузки, которое вы можете подключить, чтобы отобразить прогресс загрузки.
См. раздел с примерами для более сложных схем форм, включая индикаторы прогресса и обработку ошибок.
Дополнительные значения
Вы можете включить дополнительные значения в запрос, используя атрибуты hx-vals (пар имя-выражение в формате JSON) и hx-vars (запятой-разделенные пары имя-выражение, динамически вычисляемые).
Подтверждение запросов
Часто вам потребуется подтвердить действие перед отправкой запроса. htmx поддерживает атрибут hx-confirm, который позволяет подтвердить действие с помощью простого диалога JavaScript:
<button hx-delete="/account" hx-confirm="Are you sure you wish to delete your account?">
Delete My Account
</button>
Используя события, вы можете реализовать более сложные диалоги подтверждения. Пример подтверждения демонстрирует, как использовать библиотеку sweetalert2 для подтверждения действий htmx.
Наследование атрибутов
Большинство атрибутов htmx наследуются: они применяются к элементу, где они определены, а также к его дочерним элементам. Это позволяет «поднять» атрибуты вверх по дереву DOM, чтобы избежать дублирования кода. Рассмотрим следующий пример htmx:
<button hx-delete="/account" hx-confirm="Are you sure?">
Delete My Account
</button>
<button hx-put="/account" hx-confirm="Are you sure?">
Update My Account
</button>
Здесь у нас есть дублирующийся атрибут hx-confirm. Мы можем поднять этот атрибут на родительский элемент:
<div hx-confirm="Are you sure?">
<button hx-delete="/account">
Delete My Account
</button>
<button hx-put="/account">
Update My Account
</button>
</div>
Этот атрибут hx-confirm теперь будет применяться ко всем элементам, поддерживающим htmx, внутри него.
Иногда вы хотите отменить это наследование. Представьте, что у нас есть кнопка отмены для этой группы, но мы не хотим, чтобы она подтверждалась. Мы можем добавить директиву unset к ней, как показано ниже:
<div hx-confirm="Are you sure?">
<button hx-delete="/account">
Delete My Account
</button>
<button hx-put="/account">
Update My Account
</button>
<button hx-confirm="unset" hx-get="/">
Cancel
</button>
</div>
Тогда для двух верхних кнопок будет отображаться диалог подтверждения, а для нижней кнопки отмены — нет.
Автоматическое наследование можно отключить, используя атрибут hx-disinherit.
Ускорение
Htmx поддерживает «ускорение» обычных HTML-якорей и форм с помощью атрибута hx-boost. Этот атрибут преобразует все теги и формы в AJAX-запросы, которые по умолчанию обращаются к телу страницы.
Вот пример:
<div hx-boost="true">
<a href="/blog">Blog</a>
</div>
Тег в этом div вызовет AJAX- запрос GET к /blog и подставит ответ в тег body.
Постепенное улучшение
Особенность hx-boost заключается в том, что она обеспечивает плавную работу в случае отсутствия JavaScript: ссылки и формы продолжают работать, просто не используя AJAX-запросы. Это известно как постепенное улучшение, и это позволяет более широкой аудитории использовать функциональность вашего сайта.
Другие схемы htmx также можно адаптировать для достижения постепенного улучшения, но для этого потребуется больше обдумывания.
Рассмотрим пример активного поиска. В текущем виде он не обеспечивает плавное снижение функциональности: пользователи без JavaScript не смогут использовать эту функцию. Это сделано для простоты, чтобы пример был как можно короче.
Однако вы можете обернуть улучшенный htmx-элемент ввода в элемент формы:
<form action="/search" method="POST">
<input class="form-control" type="search"
name="search" placeholder="Begin typing to search users..."
hx-post="/search"
hx-trigger="keyup changed delay:500ms, search"
hx-target="#search-results"
hx-indicator=".htmx-indicator">
</form>
С этим на месте, клиенты с JavaScript по-прежнему получат удобный UX активного поиска, а клиенты без JavaScript смогут нажать клавишу Enter и все равно выполнить поиск. Более того, вы также можете добавить кнопку «Поиск». В таком случае вам нужно будет обновить форму с атрибутом hx-post, который отражает атрибут action, или, возможно, использовать hx-boost на нём.
Вам необходимо проверить заголовок HX-Request на стороне сервера, чтобы отличить htmx-запрос от обычного, чтобы точно определить, что нужно отобразить клиенту.
Аналогичным образом можно адаптировать другие шаблоны для достижения потребностей вашего приложения в постепенном улучшении.
Как вы можете видеть, это требует больше обдумывания и больше работы. Это также исключает определённую функциональность. Эти компромиссы должны быть приняты вами, разработчиком, с учётом целей вашего проекта и аудитории.
Универсальный доступ — понятие, тесно связанное с постепенным улучшением. Использование методов постепенного улучшения, таких как hx-boost, сделает ваше приложение htmx более доступным для широкой аудитории.
Приложения, основанные на htmx, очень похожи на обычные, не основанные на AJAX веб-приложения, потому что htmx ориентирован на HTML.
Поэтому рекомендации по доступности обычного HTML остаются актуальными. Например:
- Используйте семантический HTML по возможности (т.е. правильные теги для правильных вещей)
- Убедитесь, что состояние фокуса чётко видно
- Связывайте текстовые метки со всеми полями формы
- Максимизируйте читаемость вашего приложения с помощью подходящих шрифтов, контрастности и т.д.
Веб-сокеты и SSE
Htmx имеет экспериментальную поддержку декларативного использования как веб-сокетов, так и событий, отправляемых сервером.
Примечание: В htmx 2.0 эти функции будут перенесены в расширения. Эти новые расширения уже доступны в htmx 1.7+ и, если вы пишете новый код, вам рекомендуется использовать расширения вместо них. Все новые функции для SSE и веб-сокетов будут реализованы в расширениях.
Пожалуйста, посетите страницы расширения SSE и расширения WebSocket для получения дополнительной информации о новых расширениях.
Веб-сокеты
Если вы хотите установить соединение WebSocket в htmx, используйте атрибут hx-ws:
<div hx-ws="connect:wss:/chatroom">
<div id="chat_room">
...
</div>
<form hx-ws="send:submit">
<input name="chat_message">
</form>
</div>
Объявление connect устанавливает соединение, а объявление send сообщает форме отправлять значения в сокет по submit.
Более подробную информацию можно найти на странице страница атрибута hx-ws
События, отправляемые сервером
События, отправляемые сервером — это способ, которым серверы могут отправлять события в браузеры. Он предоставляет более высокий уровень механизма взаимодействия между сервером и браузером, чем веб-сокеты.
Если вы хотите, чтобы элемент реагировал на событие, отправляемое сервером, через htmx, вам нужно сделать две вещи:
-
Определите источник SSE. Для этого добавьте атрибут hx-sse на родительский элемент с объявлением
connect:<url>, которое указывает URL, из которого будут приниматься события, отправляемые сервером. -
Определите элементы, которые являются потомками этого элемента, которые активируются событиями, отправляемыми сервером, используя синтаксис
hx-trigger="sse:<event_name>".
Вот пример:
<body hx-sse="connect:/news_updates">
<div hx-trigger="sse:new_news" hx-get="/news"></div>
</body>
В зависимости от вашей реализации, это может быть более эффективным, чем пример с опросом, так как сервер уведомит div, если есть новые новости, а не постоянные запросы, которые вызывает опрос.
Поддержка истории
Htmx предоставляет простой механизм взаимодействия с API истории браузера:
Если вы хотите, чтобы определённый элемент поместил URL своего запроса в адресную строку браузера и добавил текущее состояние страницы в историю браузера, добавьте атрибут hx-push-url:
<a hx-get="/blog" hx-push-url="true">Blog</a>
Когда пользователь нажимает на эту ссылку, htmx сделает моментальную фотографию текущего DOM и сохранит её перед отправкой запроса на /blog. Затем он выполнит замену и добавит новую позицию в стек истории.
Когда пользователь нажимает кнопку «Назад», htmx извлечёт старое содержимое из хранилища и вернёт его в целевой элемент, имитируя «возврат» к предыдущему состоянию. Если местоположение не найдено в кэше, htmx выполнит AJAX-запрос к указанному URL-адресу, с заголовком HX-History-Restore-Request установленным в значение «true», и ожидает возврата HTML-кода для всей страницы. В качестве альтернативы, если переменная конфигурации htmx.config.refreshOnHistoryMiss установлена в значение «true», будет выполнен жёсткий перезапрос браузера.
ПРИМЕЧАНИЕ: Если вы добавляете URL в историю, вы обязаны иметь возможность перейти по этому URL и получить всю страницу обратно! Пользователь может скопировать и вставить URL в электронное письмо или новую вкладку. Кроме того, htmx потребуется вся страница при восстановлении истории, если страница не находится в кэше истории.
Указание элемента снимка истории
По умолчанию htmx будет использовать body для получения и восстановления снимка истории. Обычно это правильное решение, но если вам нужно использовать более узкий элемент для снимков, вы можете использовать атрибут hx-history-elt для указания другого.
Внимательно: этот элемент должен присутствовать на всех страницах, иначе восстановление из истории не будет работать надёжно.
Отключение снимков истории
Снимок истории можно отключить для URL, установив атрибут hx-history в значение false на любом элементе в текущем документе или любом HTML-фрагменте, загруженном в текущий документ htmx. Это можно использовать для предотвращения попадания конфиденциальных данных в кэш localStorage, что важно для компьютеров общего пользования/общедоступных компьютеров. Навигация по истории будет работать как ожидается, но при восстановлении URL будет запрошен у сервера, а не из локального кэша истории.
Запросы и ответы
Htmx ожидает, что ответы на AJAX-запросы будут в формате HTML, обычно фрагментами HTML (хотя и весь HTML-документ, соответствующий тегу hx-select, может быть полезен).
Иногда вам может потребоваться ничего не делать при замене, но, возможно, сработать событие на стороне клиента (см. ниже). В такой ситуации вы можете вернуть ответ с кодом 204 - No Content, и htmx проигнорирует содержимое ответа.
В случае ошибки ответа от сервера (например, 404 или 501), htmx сработает событие htmx:responseError, которое вы можете обработать.
В случае ошибки подключения сработает событие htmx:sendError.
CORS
При использовании htmx в контексте кросс-оригинальной ситуации не забудьте настроить свой веб-сервер для установки заголовков Access-Control, чтобы заголовки htmx были видны на стороне клиента.
- Access-Control-Allow-Headers (для заголовков запроса)
- Access-Control-Expose-Headers (для заголовков ответа)
См. все заголовки запросов и ответов, которые реализует htmx.
Заголовки запроса
htmx включает в запросы ряд полезных заголовков:
| Заголовок | Описание |
|---|---|
HX-Boosted |
указывает, что запрос происходит через элемент с использованием hx-boost |
HX-Current-URL |
текущий URL-адрес браузера |
HX-History-Restore-Request |
«true», если запрос предназначен для восстановления истории после промаха в локальном кэше истории |
HX-Prompt |
ответ пользователя на hx-prompt |
HX-Request |
всегда «true» |
HX-Target |
id целевого элемента, если он существует |
HX-Trigger-Name |
name элемента, вызвавшего событие, если он существует |
HX-Trigger |
id элемента, вызвавшего событие, если он существует |
Заголовки ответа
htmx поддерживает некоторые специфичные для htmx заголовки ответа:
-
HX-Location- позволяет выполнять переадресацию на стороне клиента, которая не выполняет полный перезагрузку страницы -
HX-Push-Url- добавляет новый URL в стек истории -
HX-Redirect- можно использовать для переадресации на стороне клиента на новое местоположение -
HX-Refresh- если установлено в «true», клиент выполнит полную перезагрузку страницы -
HX-Replace-Url- заменяет текущий URL в адресной строке -
HX-Reswap- позволяет указать, как будет заменено содержимое ответа. См. hx-swap для возможных значений -
HX-Retarget- CSS-селектор, который обновляет целевой элемент для обновления содержимого на другой элемент страницы -
HX-Reselect- CSS-селектор, который позволяет выбрать ту часть ответа, которая будет использоваться для замены. Переопределяет существующийhx-selectна элементе, вызвавшем событие -
HX-Trigger- позволяет вызывать события на стороне клиента -
HX-Trigger-After-Settle- позволяет вызывать события на стороне клиента после шага settle -
HX-Trigger-After-Swap- позволяет вызывать события на стороне клиента после шага swap
Для получения дополнительной информации о заголовках HX-Trigger, см. HX-Trigger Заголовки ответа.
Отправка формы через htmx имеет преимущество, что больше не нужно использовать Post/Redirect/Get Pattern. После успешной обработки POST-запроса на сервере не нужно возвращать HTTP 302 (Redirect). Вы можете напрямую вернуть новый фрагмент HTML.
Порядок выполнения запроса
Порядок операций при запросе htmx:
- Элемент срабатывает и начинает запрос
- Значения собираются для запроса
- Класс
htmx-requestприменяется к соответствующим элементам - Запрос затем отправляется асинхронно через AJAX
- При получении ответа целевой элемент помечается классом
htmx-swapping - Применяется необязательная задержка при замене (см. атрибут hx-swap)
- Выполняется фактическая замена содержимого
- Класс
htmx-swappingудаляется с целевого элемента - Класс
htmx-addedдобавляется к каждому новому фрагменту содержимого - Класс
htmx-settlingприменяется к целевому элементу - Выполняется задержка settle (по умолчанию: 20 мс)
- DOM стабилизируется
- Класс
htmx-settlingудаляется с целевого элемента - Класс
htmx-addedудаляется с каждого нового фрагмента содержимого
- Класс
- При получении ответа целевой элемент помечается классом
Вы можете использовать классы htmx-swapping и htmx-settling для создания CSS-переходов между страницами.
Валидация
Htmx интегрируется с API валидации HTML5 и не будет отправлять запрос для формы, если валидируемый элемент имеет неверные данные. Это относится как к AJAX-запросам, так и к отправке по WebSocket.
Htmx генерирует события, связанные с валидацией, которые можно использовать для подключения пользовательской логики валидации и обработки ошибок:
-
htmx:validation:validate- вызывается перед вызовом методаcheckValidity()элемента. Может использоваться для добавления пользовательской логики валидации -
htmx:validation:failed- вызывается, когдаcheckValidity()возвращает false, указывая на неверный ввод -
htmx:validation:halted- вызывается, когда запрос не отправляется из-за ошибок валидации. Конкретные ошибки можно найти в объектеevent.detail.errors
Неформатные элементы по умолчанию не проверяются перед отправкой запросов, но вы можете включить валидацию, установив атрибут hx-validate в «true».
Пример валидации
Вот пример поля ввода, который использует атрибут hx-on для перехвата события htmx:validation:validate и требует, чтобы в поле был указан foo.
<form id="example-form" hx-post="/test">
<input name="example"
onkeyup="this.setCustomValidity('') // reset the validation on keyup"
hx-on:htmx:validation:validate="if(this.value != 'foo') {
this.setCustomValidity('Please enter the value foo') // set the validation error
htmx.find('#foo-form').reportValidity() // report the issue
}">
</form>
Обратите внимание, что все валидации на стороне клиента должны быть повторены на стороне сервера, так как их всегда можно обойти.
Анимации
Htmx позволяет использовать CSS-переходы во многих ситуациях, используя только HTML и CSS.
Дополнительную информацию о доступных вариантах можно найти в руководстве по анимациям.
Расширения
Htmx имеет механизм расширений, который позволяет настраивать поведение библиотек. Расширения определяются в JavaScript и затем используются с помощью атрибута hx-ext:
<div hx-ext="debug">
<button hx-post="/example">This button used the debug extension</button>
<button hx-post="/example" hx-ext="ignore:debug">This button does not</button>
</div>
Если вы заинтересованы в добавлении собственного расширения в htmx, пожалуйста, см. документацию по расширениям.
Включенные расширения
Htmx включает некоторые расширения, протестированные на основе кода htmx. Вот несколько из них:
| Расширение | Описание |
|---|---|
json-enc |
использовать кодирование JSON в теле запросов, а не по умолчанию x-www-form-urlencoded
|
morphdom-swap |
расширение для использования библиотеки morphdom в качестве механизма обмена в htmx. |
alpine-morph |
расширение для использования плагина Alpine.js morph в качестве механизма обмена в htmx. |
client-side-templates |
поддержка обработки шаблонов на стороне клиента ответов JSON |
path-deps |
расширение для выражения зависимостей на основе пути, аналогично intercoolerjs |
class-tools |
расширение для управления временным добавлением и удалением классов на HTML-элементах |
multi-swap |
позволяет обменивать несколько элементов с различными методами обмена |
response-targets |
позволяет обменивать элементы для ответов с кодами HTTP, превышающими 200
|
См. страницу расширений для получения полного списка.
События и Логирование
Htmx имеет обширную систему событий, которая одновременно является системой логирования.
Если вы хотите зарегистрироваться на определённое событие htmx, вы можете использовать
document.body.addEventListener('htmx:load', function(evt) {
myJavascriptLib.init(evt.detail.elt);
});
или, если вам больше нравится, вы можете использовать следующий вспомогательный инструмент htmx:
htmx.on("htmx:load", function(evt) {
myJavascriptLib.init(evt.detail.elt);
});
Событие htmx:load срабатывает каждый раз, когда элемент загружается в DOM с помощью htmx и фактически эквивалентен обычному событию load.
Некоторые распространённые варианты использования событий htmx:
Инициализация сторонней библиотеки с помощью событий
Использование события htmx:load для инициализации содержимого настолько распространено, что htmx предоставляет вспомогательную функцию:
htmx.onLoad(function(target) {
myJavascriptLib.init(target);
});
Это делает то же самое, что и первый пример, но немного чище.
Настройка запроса с помощью событий
Вы можете обработать событие htmx:configRequest, чтобы изменить AJAX-запрос перед его отправкой:
document.body.addEventListener('htmx:configRequest', function(evt) {
evt.detail.parameters['auth_token'] = getAuthToken(); // add a new parameter into the request
evt.detail.headers['Authentication-Token'] = getAuthToken(); // add a new header into the request
});
Здесь мы добавляем параметр и заголовок к запросу перед отправкой.
Изменение поведения обмена с помощью событий
Вы можете обработать событие htmx:beforeSwap, чтобы изменить поведение обмена htmx:
document.body.addEventListener('htmx:beforeSwap', function(evt) {
if(evt.detail.xhr.status === 404){
// alert the user when a 404 occurs (maybe use a nicer mechanism than alert())
alert("Error: Could Not Find Resource");
} else if(evt.detail.xhr.status === 422){
// allow 422 responses to swap as we are using this as a signal that
// a form was submitted with bad data and want to rerender with the
// errors
//
// set isError to false to avoid error logging in console
evt.detail.shouldSwap = true;
evt.detail.isError = false;
} else if(evt.detail.xhr.status === 418){
// if the response code 418 (I'm a teapot) is returned, retarget the
// content of the response to the element with the id `teapot`
evt.detail.shouldSwap = true;
evt.detail.target = htmx.find("#teapot");
}
});
Здесь мы обрабатываем несколько кодов ошибок 400-го уровня, которые обычно не выполняют обмен в htmx.
Именование событий
Обратите внимание, что все события запускаются с двумя различными именами
- CamelCase
- KebabCase
Например, вы можете прослушивать htmx:afterSwap или htmx:after-swap. Это способствует взаимодействию с другими библиотеками. Например, Alpine.js требует kebab case.
Логирование
Если вы установите логирование в htmx.logger, каждое событие будет записываться в журнал. Это может быть очень полезно для отладки:
htmx.logger = function(elt, event, data) {
if(console) {
console.log(event, elt, data);
}
}
Отладка
Декларативное и событийно-управляемое программирование с htmx (или любым другим декларативным языком) может быть замечательным и высокопродуктивным занятием, но одним из недостатков по сравнению с императивными подходами является то, что отладка может быть сложнее.
Например, понять, почему что-то *не* происходит, может быть сложно, если вы не знаете приёмов.
Итак, вот приёмы:
Первый инструмент отладки, который вы можете использовать, — это метод htmx.logAll(). Он записывает в журнал каждое событие, которое вызывает htmx, и позволит вам увидеть, что именно делает библиотека.
htmx.logAll();
Конечно, это не скажет вам, почему htmx *не* делает чего-то. Вы также можете не знать, какие события генерирует элемент DOM, чтобы использовать его в качестве триггера. Для решения этой проблемы вы можете использовать метод monitorEvents() в консоли браузера:
monitorEvents(htmx.find("#theElement"));
Это выведет все события, происходящие на элементе с id theElement, в консоль и позволит вам увидеть, что с ним происходит.
Обратите внимание, что это *только* работает из консоли, вы не можете встроить его в тег сценария на вашей странице.
Наконец, в крайнем случае, вы можете просто отладить htmx.js, загрузив неминифицированную версию. Это примерно 2500 строк JavaScript, так что это не непреодолимый объём кода. Скорее всего, вам захочется установить точку останова в методах issueAjaxRequest() и handleAjaxResponse(), чтобы увидеть, что происходит.
И всегда обращайтесь на Discord, если вам нужна помощь.
Создание демоверсий
Иногда, для демонстрации ошибки или уточнения использования, полезно использовать сайт с примерами JavaScript, например, jsfiddle. Чтобы упростить создание демоверсий, htmx предоставляет сайт с кодом демоверсии, который установит:
- htmx
- hyperscript
- библиотеку для имитации запросов
Просто добавьте следующий тег скрипта в свою демоверсию/jsfiddle/что-то ещё:
<script src="https://demo.htmx.org"></script>
Этот вспомогательный инструмент позволяет добавлять имитированные ответы, добавляя теги template, с атрибутом url, указывающим URL. Ответом на этот URL будет внутреннее содержимое шаблона, что упрощает создание имитированных ответов. Вы можете добавить задержку ответа с помощью атрибута delay, который должен быть целым числом, указывающим количество миллисекунд задержки
Вы можете встраивать простые выражения в шаблон с помощью синтаксиса ${}.
Обратите внимание, что это должно использоваться только для демонстраций и никак не гарантируется, что оно будет работать в течение длительного времени, так как оно всегда будет использовать последние версии htmx и hyperscript!
Пример демо
Вот пример работы кода:
<!-- load demo environment -->
<script src="https://demo.htmx.org"></script>
<!-- post to /foo -->
<button hx-post="/foo" hx-target="#result">
Count Up
</button>
<output id="result"></output>
<!-- respond to /foo with some dynamic content in a template tag -->
<script>
globalInt = 0;
</script>
<template url="/foo" delay="500"> <!-- note the url and delay attributes -->
${globalInt++}
</template>
Скрипты
Хотя htmx поощряет гипермедийный подход к созданию веб-приложений, он не исключает использование скриптов и предлагает несколько механизмов для интеграции скриптов в ваше веб-приложение. Использование скриптов было явно включено в описание архитектуры REST-веба в разделе Code-On-Demand. По возможности, мы рекомендуем гипермедийный подход к написанию скриптов в вашем веб-приложении на базе htmx:
- Уважать HATEOAS
- Использовать события для коммуникации между компонентами
- Использовать острова для изоляции компонентов, не использующих гипермедиа, от остальной части приложения
- Рассмотреть встроенные скрипты
Основной точкой интеграции между htmx и решениями для написания скриптов являются события, которые отправляет и на которые может реагировать htmx. Смотрите пример SortableJS в разделе Сторонние JavaScript-библиотеки для хорошего шаблона интеграции JavaScript-библиотеки с htmx через события.
Решения для написания скриптов, которые хорошо сочетаются с htmx:
- VanillaJS — простое использование встроенных возможностей JavaScript для подключения обработчиков событий для реагирования на события, отправляемые htmx, может работать очень хорошо для написания скриптов. Это очень лёгкий и всё более популярный подход.
- AlpineJS — Alpine.js предоставляет богатый набор инструментов для создания сложных скриптов на стороне клиента, включая поддержку реактивного программирования, при этом оставаясь очень лёгким. Alpine рекомендует подход «встроенных скриптов», который, по нашему мнению, хорошо сочетается с htmx.
- jQuery — несмотря на свой возраст и репутацию в некоторых кругах, jQuery хорошо сочетается с htmx, особенно в старых кодовых базах, где уже много jQuery.
- hyperscript — Hyperscript — это экспериментальный язык сценариев front-end, созданный той же командой, что и htmx. Он разработан для успешной вставки в HTML, для ответа на события и создания событий и отлично сочетается с htmx.
У нас есть целая глава под названием «Клиентские скрипты» в нашей книге, которая рассматривает интеграцию скриптов в ваше приложение на основе htmx.
Атрибуты hx-on*
HTML позволяет встраивать встроенные скрипты с помощью onevent свойств, таких как onClick.
<button onclick="alert('You clicked me!')">
Click Me!
</button>
Эта функция позволяет расположить логику скриптов совместно с HTML-элементами, к которым эта логика применяется, обеспечивая хорошее местоположение поведения (LoB). К сожалению, HTML позволяет использовать атрибуты on* только для фиксированного количества конкретных событий DOM (например, onclick) и не предоставляет обобщённый механизм для реагирования на произвольные события на элементах.
Для решения этой проблемы htmx предлагает атрибуты hx-on*. Эти атрибуты позволяют реагировать на любое событие таким образом, что сохраняется LoB стандартных свойств on*.
Если мы хотим реагировать на событие click с помощью атрибута hx-on, мы напишем это:
<button hx-on:click="alert('You clicked me!')">
Click Me!
</button>
Таким образом, строка hx-on, за которой следует двоеточие (или дефис), затем имя события.
Для события click, конечно же, мы рекомендуем придерживаться стандартного атрибута onclick. Однако, рассмотрим кнопку htmx, которая хочет добавить параметр к запросу с использованием события htmx:config-request. Это не будет возможно с помощью стандартного свойства on*, но можно сделать это с помощью атрибута hx-on:htmx:config-request:
<button hx-post="/example"
hx-on:htmx:config-request="event.detail.parameters.example = 'Hello Scripting!'">
Post Me!
</button>
Здесь параметр example добавляется к запросу POST перед его отправкой со значением ‘Hello Scripting!’.
Атрибуты hx-on* — это очень простой механизм для обобщённого встроенного скриптирования. Это не замена более развитым решениям для фронтенд-скриптинга, таким как AlpineJS или hyperscript. Однако, он может дополнять подход на основе VanillaJS к скриптингу в вашем приложении htmx.
Обратите внимание, что атрибуты HTML нечувствительны к регистру. Это означает, что, к сожалению, события, зависящие от регистра/кемел-кейса, не могут быть обработаны. Если вам нужно поддерживать события кемел-кейса, мы рекомендуем использовать более функциональное решение, такое как AlpineJS или hyperscript. htmx отправляет все свои события как в camelCase, так и в kebab-case по этой самой причине.
JavaScript сторонних разработчиков
Htmx довольно хорошо интегрируется с сторонними библиотеками. Если библиотека запускает события на DOM, вы можете использовать эти события для запуска запросов от htmx.
Хороший пример — демонстрация SortableJS: SortableJS demo
<form class="sortable" hx-post="/items" hx-trigger="end">
<div class="htmx-indicator">Updating...</div>
<div><input type='hidden' name='item' value='1'/>Item 1</div>
<div><input type='hidden' name='item' value='2'/>Item 2</div>
<div><input type='hidden' name='item' value='2'/>Item 3</div>
</form>
С Sortable, как и с большинством JavaScript-библиотек, вам нужно инициализировать контент в какой-то момент.
В jQuery вы можете сделать это так:
$(document).ready(function() {
var sortables = document.body.querySelectorAll(".sortable");
for (var i = 0; i < sortables.length; i++) {
var sortable = sortables[i];
new Sortable(sortable, {
animation: 150,
ghostClass: 'blue-background-class'
});
}
});
В htmx, вместо этого, вы бы использовали функцию htmx.onLoad, и вы бы выбирали только из загруженного контента, а не из всего документа:
htmx.onLoad(function(content) {
var sortables = content.querySelectorAll(".sortable");
for (var i = 0; i < sortables.length; i++) {
var sortable = sortables[i];
new Sortable(sortable, {
animation: 150,
ghostClass: 'blue-background-class'
});
}
})
Это гарантирует, что при добавлении нового контента в DOM с помощью htmx, элементы sortable должным образом инициализируются.
Если JavaScript добавляет контент в DOM, содержащий атрибуты htmx, вам нужно убедиться, что этот контент инициализирован с помощью функции htmx.process().
Например, если вы хотите получить данные и поместить их в div с использованием API fetch, и в этом HTML есть атрибуты htmx, вам нужно добавить вызов htmx.process() следующим образом:
let myDiv = document.getElementById('my-div')
fetch('http://example.com/movies.json')
.then(response => response.text())
.then(data => { myDiv.innerHTML = data; htmx.process(myDiv); } );
Некоторые сторонние библиотеки создают контент из элементов шаблонов HTML. Например, Alpine JS использует атрибут x-if в шаблонах для добавления контента условно. Такие шаблоны изначально не являются частью DOM, и, если они содержат атрибуты htmx, после загрузки им потребуется вызов htmx.process(). Следующий пример использует функцию Alpine’s $watch для поиска изменения значения, которое приведет к условному контенту:
<div x-data="{show_new: false}"
x-init="$watch('show_new', value => {
if (show_new) {
htmx.process(document.querySelector('#new_content'))
}
})">
<button @click = "show_new = !show_new">Toggle New Content</button>
<template x-if="show_new">
<div id="new_content">
<a hx-get="/server/newstuff" href="#">New Clickable</a>
</div>
</template>
</div>
Кэширование
htmx работает со стандартными механизмами кэширования HTTP HTTP caching.
Если ваш сервер добавляет HTTP-заголовок ответа Last-Modified в ответ для данного URL, браузер автоматически добавит заголовок запроса If-Modified-Since к последующим запросам к этому же URL. Обратите внимание, что если ваш сервер может отображать различный контент для одного и того же URL в зависимости от других заголовков, вам необходимо использовать заголовок ответа Vary. Например, если ваш сервер отображает весь HTML, когда заголовок HX-Request отсутствует или false, и отображает фрагмент этого HTML, когда HX-Request: true, вам нужно добавить Vary: HX-Request. Это заставляет кеш основываться на комбинации URL ответа и заголовка запроса HX-Request, а не только на URL ответа.
Если вы не можете (или не хотите) использовать заголовок Vary, вы можете альтернативно установить параметр конфигурации getCacheBusterParam в true. Если эта переменная конфигурации установлена, htmx будет включать параметр кэширования в запросах GET, что предотвратит браузерам кеширование htmx- и не-htmx-ответов в одном и том же слоте кеша.
htmx также работает с ETag, как ожидается. Обратите внимание, что если ваш сервер может отображать различный контент для одного и того же URL (например, в зависимости от значения заголовка HX-Request), серверу нужно генерировать разный ETag для каждого контента.
Безопасность
htmx позволяет определять логику непосредственно в вашем DOM. Это имеет ряд преимуществ, самым большим из которых является Локальность поведения, которая упрощает понимание и поддержку вашей системы.
Однако, проблема с этим подходом заключается в безопасности: поскольку htmx повышает выразительность HTML, если злоумышленник может ввести HTML в ваше приложение, он может использовать эту выразительность htmx для злонамеренных целей.
Правило 1: Экранируйте весь пользовательский контент
Первым правилом разработки веб-приложений на основе HTML всегда было: не доверяйте вводимым пользователем данным. Вы должны экранировать весь контент сторонних, недоверенных источников, который вставляется на ваш сайт. Это для предотвращения, среди прочих проблем, атак XSS.
Подробная документация по XSS и способам предотвращения её доступна на отличном сайте OWASP, включая Cheat Sheet по предотвращению XSS.
Хорошая новость заключается в том, что это очень старая и хорошо понятая тема, и подавляющее большинство серверных языков шаблонизации поддерживают автоматическую экранизацию контента для предотвращения подобных проблем.
Тем не менее, иногда люди выбирают более опасный способ вставки HTML, часто с помощью механизма raw() в языке шаблонов. Это может делаться по уважительным причинам, но если вставляемый контент поступает от сторонней стороны, то его необходимо очистить, включая удаление атрибутов, начинающихся с hx- и data-hx, а также инлайновых тегов <script> и т. д.
Если вы вставляете необработанный HTML и сами занимаетесь экранированием, лучшей практикой является составление белого списка разрешённых атрибутов и тегов, а не чёрный список запрещённых.
Инструменты безопасности htmx
Конечно, ошибки случаются, и разработчики не совершенны, поэтому для обеспечения безопасности веб-приложения желательно использовать многослойный подход, и htmx предоставляет инструменты для повышения безопасности вашего приложения.
Давайте рассмотрим их.
hx-disable
Первый инструмент, который htmx предоставляет для повышения безопасности вашего приложения, — атрибут hx-disable. Этот атрибут предотвратит обработку всех атрибутов htmx в данном элементе и во всех элементах внутри него. Так, например, если вы включаете необработанный HTML-контент в шаблоне (снова, это не рекомендуется!), то вы можете поместить div вокруг этого контента с атрибутом hx-disable на нём:
<div hx-disable>
<%= raw(user_content) %>
</div>
И htmx не будет обрабатывать атрибуты и функции, связанные с htmx, найденные в этом контенте. Этот атрибут нельзя отключить путём вставки дополнительного контента: если атрибут hx-disable найден где-либо в родительской иерархии элемента, он не будет обработан htmx.
hx-history
Другой аспект безопасности — кэш истории htmx. Возможно, у вас есть страницы со чувствительными данными, которые вы не хотите хранить в кэше пользователя. Вы можете исключить конкретную страницу из кэша истории, включив атрибут hx-history на странице и задав его значение false.
Параметры конфигурации
htmx также предоставляет параметры конфигурации, относящиеся к безопасности:
-
htmx.config.selfRequestsOnly— если установлено значениеtrue, будут разрешены только запросы к тому же домену, что и текущий документ -
htmx.config.allowScriptTags— htmx будет обрабатывать теги<script>в загружаемом им новом контенте. Если вы хотите отключить это поведение, вы можете установить эту переменную конфигурации вfalse -
htmx.config.historyCacheSize— может быть установлено в0для предотвращения хранения любого HTML в кэшеlocalStorage -
htmx.config.allowEval— может быть установлено вfalseдля отключения всех функций htmx, которые зависят от eval:- фильтры событий
-
hx-on:атрибуты -
hx-valsс префиксомjs: -
hx-headersс префиксомjs:
Обратите внимание, что все функции, удалённые путём отключения eval(), могут быть повторно реализованы с помощью собственного пользовательского JavaScript и модели событий htmx.
События
Если вы хотите разрешить запросы к некоторым доменам за пределами текущего хоста, но не оставлять всё полностью открытым, вы можете использовать событие htmx:validateUrl. Это событие будет содержать URL запроса в слоте detail.url, а также свойство sameHost.
Вы можете проверить эти значения и, если запрос недействителен, вызвать preventDefault() в событии, чтобы предотвратить отправку запроса.
document.body.addEventListener('htmx:validateUrl', function (evt) {
// only allow requests to the current server as well as myserver.com
if (!evt.detail.sameHost && evt.detail.url.hostname !== "myserver.com") {
evt.preventDefault();
}
});
Параметры CSP
Браузеры также предоставляют инструменты для дополнительной защиты вашего веб-приложения. Самым мощным инструментом является Политика безопасности контента (CSP). С помощью CSP вы можете, например, указать браузеру не выполнять запросы к хостам, отличным от исходного, не оценивать инлайновые теги script и т. д.
Вот пример CSP в теге meta:
<meta http-equiv="Content-Security-Policy" content="default-src 'self';">
Это говорит браузеру «Разрешить подключения только к исходному домену». Это было бы избыточно с htmx.config.selfRequestsOnly, но многослойный подход к безопасности оправдан и, на самом деле, идеален при работе с безопасностью приложений.
Полное обсуждение CSP выходит за рамки этого документа, но статья MDN предоставляет хорошую отправную точку для изучения этой темы.
Настройка htmx
Htmx имеет некоторые параметры конфигурации, к которым можно получить доступ программно или декларативно. Они перечислены ниже:
| Переменная конфигурации | Информация |
|---|---|
htmx.config.historyEnabled |
по умолчанию true, в основном полезна для тестирования |
htmx.config.historyCacheSize |
по умолчанию 10 |
htmx.config.refreshOnHistoryMiss |
по умолчанию false, если установлено значение true, htmx вызовет полную перезагрузку страницы при пропуске истории вместо использования запроса AJAX |
htmx.config.defaultSwapStyle |
по умолчанию innerHTML
|
htmx.config.defaultSwapDelay |
по умолчанию 0 |
htmx.config.defaultSettleDelay |
по умолчанию 20 |
htmx.config.includeIndicatorStyles |
по умолчанию true (определяет, загружаются ли стили индикатора) |
htmx.config.indicatorClass |
по умолчанию htmx-indicator
|
htmx.config.requestClass |
по умолчанию htmx-request
|
htmx.config.addedClass |
по умолчанию htmx-added
|
htmx.config.settlingClass |
по умолчанию htmx-settling
|
htmx.config.swappingClass |
по умолчанию htmx-swapping
|
htmx.config.allowEval |
по умолчанию true, может быть использовано для отключения использования htmx eval для определённых функций (например, фильтров триггеров) |
htmx.config.allowScriptTags |
по умолчанию true, определяет, будет ли htmx обрабатывать теги script, найденные в новом содержимом |
htmx.config.inlineScriptNonce |
по умолчанию '', что означает, что nonce не будет добавлен к встроенным скриптам |
htmx.config.useTemplateFragments |
по умолчанию false, теги шаблонов HTML для парсинга содержимого с сервера (несовместимо с IE11!) |
htmx.config.wsReconnectDelay |
по умолчанию full-jitter
|
htmx.config.disableSelector |
по умолчанию [disable-htmx], [data-disable-htmx], htmx не будет обрабатывать элементы с этим атрибутом или родительским элементом |
htmx.config.timeout |
по умолчанию 0 миллисекунд |
htmx.config.defaultFocusScroll |
если фокусированный элемент должен быть прокручен в область видимости, по умолчанию false и может быть переопределён с помощью модификатора обмена focus-scroll. |
htmx.config.getCacheBusterParam |
по умолчанию false, если установлено значение true, htmx включит параметр cache-busting в запросах GET, чтобы избежать кэширования частичных ответов браузером |
htmx.config.globalViewTransitions |
если установлено значение true, htmx будет использовать API переходов между представлениями View Transition при обмене новым содержимым. |
htmx.config.methodsThatUseUrlParams |
по умолчанию ["get"], htmx отформатирует запросы с этим методом, закодировав их параметры в URL, а не в теле запроса |
htmx.config.selfRequestsOnly |
по умолчанию false, если установлено значение true, разрешит только запросы AJAX к тому же домену, что и текущий документ |
htmx.config.ignoreTitle |
по умолчанию false, если установлено значение true, htmx не будет обновлять заголовок документа, когда будет найден тег title в новом содержимом |
htmx.config.triggerSpecsCache |
по умолчанию null, кэш для хранения спецификаций обработанных триггеров, улучшая производительность парсинга за счёт большего использования памяти. Можно определить простой объект для использования кэша, который никогда не очищается, или реализовать собственную систему с помощью объекта прокси
|
Вы можете установить их непосредственно в javascript или использовать тег meta.
<meta name="htmx-config" content='{"defaultSwapStyle":"outerHTML"}'>
Заключение
И на этом всё!
Приятного использования htmx! Вы можете добиться довольно многого, не написав много кода!
Licensed under the Zero-Clause BSD License.
https://htmx.org/docs/