Что такое htmx
Введение
HTMX — это небольшая (14k min. gzipped), свободная от зависимостей библиотека JavaScript, позволяющая создавать передовые пользовательские интерфейсы с лёгкостью и силой гипертекста (разметки). Она предоставляет доступ к AJAX, CSS Transitions, WebSockets и Server Sent Events напрямую в HTML с помощью атрибутов. Это стало переломным моментом для разработчиков, поскольку позволяет им добиться интерактивности (которую даёт JavaScript), но непосредственно из HTML разметки.
Установка HTMX
Вы можете установить HTMX тремя способами:
Установка через CDN
Вы можете загрузить HTMX на свой сайт/приложение, включив CDN в заголовок HTML-файла.
<script
src="https://unpkg.com/htmx.org@1.9.6"
integrity="sha384-FhXw7b6AlE/jyjlZH5iHa/tTe9EpJ1Y55RjcgPbjeWMskSxZt1v9qkxLJWNJaGni"
crossorigin="anonymous"
></script>Примечание: Хотя это возможно, но не рекомендуется использовать CDN в продакшене.
Загрузка копии на сайт или в приложение
Вы можете загрузить копию исходного файла HTMX на свой сайт/приложение.
Загрузите htmx.min.js с сайта unpkg.com, поместите его в нужную директорию вашего проекта, а затем включите его в тег <script> при необходимости:
<script src="/path/to/htmx.min.js"></script>Установка HTMX через npm
Вы можете установить HTMX через npm с помощью следующей команды:
npm install htmx.orgПосле установки вам нужно будет использовать соответствующий инструментарий для использования node_modules/htmx.org/dist/htmx.js или node_modules/htmx.org/dist/htmx.min.js. Например, вы можете использовать HTMX в пакете с определёнными расширениями и специфическим для проекта кодом.
Установка HTMX в Webpack
Если вы используете Webpack для управления JavaScript, во-первых, установите htmx через менеджер пакетов(npm, yarn, pnpm), а затем импортируйте в index.js
import "htmx.org";Если вы хотите использовать глобальную переменную htmx, вы можете ввести её в область видимости window следующим образом:
Создайте уникальный JS-файл.
Импортируйте этот файл в файл
index.js.import "path/to/my_custom.js";Затем добавьте в файл приведённый ниже код
window.htmx = require("htmx.org");Наконец, пересоберите пакет приложения (чтобы отразить изменения).
Ajax-запросы с помощью HTMX
HTMX предоставляет атрибуты, позволяющие выполнять AJAX-запросы непосредственно из HTML:
| HTMX атрибут | Описание атрибута |
|---|---|
| hx-post | выполняет POST запрос по заданному url |
| hx-get | выполняет GET запрос по заданному url |
| hx-put | выполняет PUT запрос по заданному url |
| hx-patch | выполняет PATCH запрос по заданному url |
| hx-delete | выполняет DELETE запрос по заданному url |
<div hx-post="/messages">Put To Messages</div>В приведённом выше коде атрибут hx-post выполняет POST запрос к API по адресу /messages.
Триггер запроса
В HTMX естественное событие элемента инициирует AJAX-запросы. Например, событие onchange инициируют input, select и textarea; событие onsubmit инициирует form; а событие onclick запускает всё остальное.
Однако HTMX предлагает уникальный атрибут hx-trigger для ситуаций, когда вы хотите изменить событие, инициирующее запрос:
<div hx-post="http://localhost:3000/submit" hx-trigger="mouseenter">
Submit
</div>В приведённом выше коде только при наведении мыши пользователя на div будет выполнен GET-запрос к указанному URL.
Модификаторы триггеров
Поведение триггера можно также изменить с помощью нескольких других модификаций. Например, используйте модификатор once для триггера, если вы хотите, чтобы запрос выполнялся только один раз:
<div hx-post="/mouse_entered" hx-trigger="mouseenter once">
Submit
</div>Другие модификаторы триггера, которые вы можете использовать:
changed— отправка запроса только в том случае, если значение элемента изменилось.delay:<временной интервал>— задаёт период задержки перед выполнением запроса. Отсчёт сбрасывается при повторном возникновении события.throttle:<временной интервал>— задаёт время задержки перед выполнением запроса. В отличие отdelay, если новое событие происходит до того, как будет достигнут лимит времени, событие удаляется, а запрос срабатывает по окончании временного интервала.from:<селектор CSS>— прослушивает событие другого элемента.
Посмотреть другие модификаторы, поддерживаемые HTMX, можно здесь
Фильтры триггеров
Они применяются с помощью заключения выражения JavaScript в квадратные скобки [] после имени события. Если выражение равно true, событие будет вызвано, в противном случае — нет.
<div hx-get="/clicked" hx-trigger="click[ctrlKey]">
Click the force
</div>В htmx есть несколько специальных событий, которые можно использовать с hx-trigger:
load— срабатывает один раз, когда элемент загружается в первый раз.revealed— срабатывает, когда элемент впервые прокручивается в область просмотраintersect— срабатывает, когда элемент впервые пересекает область просмотра. Позволяет использовать два дополнительных варианта:root:<selector>— CSS-селектор корневого элемента для пересеченияthreshold:<float>— число с плавающей точкой между0,0и1,0, указывающее величину пересечения, при котором должно срабатывать событие.
Если у вас более сложный вариант использования, вы также можете использовать пользовательские события для запуска запросов.
Опрос
Вы можете использовать синтаксис every с временными рамками (в секундах) со свойством hx-trigger, чтобы заставить элемент опрашивать определённый URL:
<div hx-get="/get-star-wars-data" hx-trigger="every 2s"></div>Опрос загрузки
"Опрос загрузки" — это дополнительный метод опроса в HTMX. Он заключается в том, что элемент задаёт триггер загрузки и задержку, а затем заменяет себя ответом:
<div hx-get="/get-star-wars-data" hx-trigger="load delay:1s" hx-swap="starWarsDataHTML"></div>Индикаторы запроса
Поскольку браузер не предоставляет обратной связи при выполнении AJAX-запроса, часто бывает полезно уведомить пользователя, что что-то происходит. Это можно сделать с помощью HTMX, используя класс htmx-indicator.
Непрозрачность любого элемента с классом htmx-indicator по умолчанию равна 0. Это означает, что хотя элемент и присутствует в DOM, он невидим.
Класс htmx-request добавляется к элементу, когда HTMX отправляет запрос. Когда дочерний элемент с классом htmx-indicator подвергается воздействию класса htmx-request, он переходит к непрозрачности 1, отображая индикатор.
<button hx-get="/click">
Click Me!
<img class="htmx-indicator" src="/spinner.gif">
</button>Используя атрибут hx-indicator и селектор CSS, вы можете добавить класс htmx-request альтернативному элементу, если захотите:
<div>
<button hx-get="/click" hx-indicator="#indicator">
Click the force!
</button>
<img id="indicator" class="htmx-indicator" src="/spinner.gif" />
</div>Цели
Вы можете загрузить ответ в элемент, отличный от того, который инициировал запрос, с помощью атрибута hx-target, принимающего CSS-селектор (div, class и т. д.).
<input type="text" name="star-wars-input"
hx-get="/star-wars-characters"
hx-trigger="keyup delay:500ms changed"
hx-target="#search-results"
placeholder="Search..."
>
<div id="search-results"></div>В приведённом выше примере атрибут hx-target указывает идентификатор #search-result и при успешном запросе выводит значения запроса в div, содержащий этот идентификатор.
Замена
В HTMX можно заменить содержимое HTML, возвращаемое в DOM. innerHTML целевого элемента автоматически заменяется содержимым. Это можно изменить, применив любое из следующих значений к атрибуту hx-swap:
| Имя | Описание |
|---|---|
innerHTML | Содержимое помещается внутрь целевого элемента по умолчанию. |
outerHTML | Возвращаемое содержимое полностью заменяет целевой элемент. |
afterbegin | Содержимое добавляется перед первым дочерним элементом целевого элемента |
beforebegin | Добавляет содержимое родительского элемента цели перед целью. |
beforeend | Добавляет содержимое после последнего дочернего элемента цели. |
afterend | добавляет содержимое в родительский элемент цели после цели |
delete | Удаляет целевой элемент независимо от ответа |
none | Не добавляет содержимое ответа |
пример hx-swap в действии
<div hx-get="/example" hx-swap="afterend">
Get Some HTML & Append It
</div>В приведённом выше коде div выполнит запрос и добавит полученное содержимое за div.
Синхронизация
Когда вы выполняете запросы в двух элементах и хотите, чтобы один из них имел приоритет над другим или ждал, пока запрос другого элемента будет выполнен, в HTMX есть полезная функция hx-sync.
Например, давайте представим сценарий гонки между отправкой формы и запросом на проверку определённого поля ввода:
<form hx-post="/submit-character">
<input id="title" name="title" type="text"
hx-post="/validate-character"
hx-trigger="change"
>
<button type="submit">Submit</button>
</form>Когда ввод завершён и форма сразу отправлена, в /validate-character и /submit-character отправляются два одновременных запроса без использования hx-sync.
Когда к input применяется hx-sync="closest form:abort", форма будет следить за запросами, и запрос input будет прерван, если запрос формы будет обнаружен или начнётся во время обработки запроса поля input. Это автоматически решает ограничение синхронизации.
<form hx-post="/submit-character">
<input id="title" name="title" type="text"
hx-post="/validate-character"
hx-trigger="change"
hx-sync="closest form:abort"
>
<button type="submit">Submit</button>
</form>Кроме того, htmx предлагает программный метод отмены запросов: Чтобы остановить все выполняющиеся запросы, вы можете отправить элементу событие htmx:abort в функцию htmx.trigger:
<button id="request-button" hx-post="/submit-character">
Submit Character
</button>
<button onclick="htmx.trigger('#request-button', 'htmx:abort')">
Cancel Submission
</button>CSS Transitions
HTMX позволяет выполнять CSS Transitions (CSS переходы) без использования JavaScript. Продемонстрируем это на примере:
<div id="div1">The Empire</div>Затем мы выполняем ajax-запрос и меняем содержимое на приведённое ниже:
<div id="div1" class="red">
The Republic
</div>Из приведённого выше кода видно, что к новому содержимому был добавлен класс red. В этом сценарии мы можем создать CSS переход от предыдущего состояния (без класса red) к текущему (с классом red):
.red {
color: red;
transition: all ease-in 1s;
}Замены вне группы
Атрибут hx-swap-oob в ответе html может быть использован для замены содержимого ответа непосредственно в DOM с помощью атрибута id:
<div id="message" hx-swap-oob="true">Dark Sidious</div>
Anakin SkywalkerИз приведённого выше кода, дополнительное содержимое будет подставлено в целевой элемент обычным способом, но div#message будет подставлен прямо в соответствующий элемент DOM в этом ответе.
Параметры
Вы можете фильтровать аргументы, которые будут переданы при вызове AJAX, с помощью атрибута hx-params.
Вот возможные значения этого атрибута:
*— Включает все параметры по умолчаниюnone— Не включает<список-параметров>и не включает никаких параметров. Всё, кроме списка имён параметров<список-параметров>, должен быть разделён запятыми. Предоставьте список имён всех параметров, разделённых запятыми.
<div hx-get="/get-starwars-characters" hx-params="*">
Luke Skywalker
</div>В приведённом выше примере все параметры, которые содержал бы POST, содержатся в этом div, однако как это обычно бывает при GET, они будут URL закодированы и включены в URL.
Подтверждение запросов
HTMX предоставляет атрибут hx-confirm, позволяющий подтвердить действие с помощью простого JavaScript-диалога:
<button hx-delete="/delete-character" hx-confirm="Are you sure you wish to delete Obiwan Kenobi">
Delete Obiwan Kenobi
</button>Веб-сокеты и SSE
На данный момент в HTMX есть экспериментальная поддержка WebSocket и Server Sent Event. Однако, если вы хотите установить соединение WebSocket, вы можете использовать атрибут hx-ws
<div hx-ws="connect:wss:/darth-chatroom">
<div id="chat_room">
...
</div>
<form hx-ws="send:submit">
<input name="chat_message">
</form>
</div>Для отправки событий в браузеры с помощью SSE (Server sent Events) добавьте объявление connect <url> к родительскому элементу и атрибут hx-sse с URL.
После этого вы можете использовать атрибут hx-trigger="sse:<имя_события>" для определения элементов, являющихся потомками этого элемента и активируемых событиями, посылаемыми сервером.
<body hx-sse="connect:/darth-events">
<div hx-trigger="sse:new_news" hx-get="/news"></div>
</body>Поддержка истории браузера
С помощью атрибута hx-push-url на элементе можно вывести URL-адрес запроса в навигационную панель браузера и добавить текущее состояние страницы в историю:
<a hx-get="/star-wars-characters" hx-push-url="true">
Blog
</a>HTMX имитирует "возврат" к предыдущему состоянию, когда пользователь нажимает кнопку "назад", извлекая старое содержимое из хранилища и заменяя его на целевое. HTMX отправит ajax-вызов на указанный URL с заголовком HX-History-Restore-call, установленным в true, если местоположение не будет найдено в кэше, и будет ожидать возвращения HTML, необходимого для полной страницы. В качестве альтернативы он принудительно обновит страницу в браузере, если параметр htmx.config.refreshOnHistoryMiss установлен в true.
Валидация с HTMX
Поскольку HTMX работает с HTML5 Validation API, недействительный валидируемый ввод не позволит выполнить запрос формы. Это относится как к WebSocket, так и к AJAX-запросам.
События, связанные с валидацией в HTMX, включают:
htmx:validation:validate— вызывается перед вызовом методаcheckValidity()элементов. Может использоваться для добавления пользовательской логики валидации.htmx:validation:failed— вызывается, когда функцияcheckValidity()возвращаетfalse, указывая на недопустимый ввод.htmx:validation:halted— вызывается, когда запрос не выдаётся из-за ошибок валидации. Конкретные ошибки можно найти в объектеevent.detail.errors.
<form hx-post="/create-characters">
<input _="on htmx:validation:validate
if my.value != 'foo'
call me.setCustomValidity('Please enter the value foo')
else
call me.setCustomValidity('')"
name="example"
>
</form>Приведённый выше код является иллюстрацией ввода с использованием htmx:validation. В коде используется hyperscript для проверки события, чтобы убедиться, что вводимое значение имеет значение foo.
Анимация с HTMX
HTMX предназначен для добавления плавных анимаций и переходов на веб-страницы с помощью только CSS и HTML. Теперь вы можете использовать новый View Transitions API для создания анимации с HTMX. Базовый пример — анимация Fade Out On Swap. Если вам нужно затушить элемент, который будет удалён по завершении запроса, используйте класс htmx-swapping с некоторыми CSS и увеличьте фазу замены так, чтобы она была достаточно длинной для завершения анимации. Этого можно добиться следующим способом:
<style>
.fade-me-out.htmx-swapping {
opacity: 0;
transition: opacity 1s ease-out;
}
</style>
<button class="fade-me-out"
hx-delete="/fade_out_demo"
hx-swap="outerHTML swap:1s">
Fade Me Out
</button>Больше примеров анимации с HTMX можно посмотреть здесь
Расширения HTMX
HTMX предоставляет фреймворк расширений для создания и развёртывания расширений в приложениях на основе htmx. С помощью атрибута hx-ext вы можете использовать библиотеки расширений (после их подключения в JavaScript).
Использование htmx расширения включает в себя два этапа:
- Включите определение расширения, что добавит его в реестр расширений HTMX.
- Атрибут
hx-extиспользуется для указания расширения.
// Подключения библиотеки расширения
<script src="/path/to/ext/debug.js" defer></script>
// добавление расширения (debug) в htm-ext атрибут
<button hx-post="/example" hx-ext="debug">
This Button Uses The Force
</button>HTMX поставляется с набором расширений, удовлетворяющих основные потребности разработчиков. В каждом выпуске эти расширения тестируются на htmx. Посмотреть список расширений, включённых в HTMX, можно здесь
Логирование и события в HTMX
События
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.
Событие htmx:load срабатывает всякий раз, когда HTMX загружает элемент в DOM, и функционально оно не отличается от обычного события load.
Вы также можете инициализировать стороннюю библиотеку с помощью события htmx:load:
htmx.onLoad(function (target) {
myJavascriptLib.init(target);
});Также можно настроить запрос с помощью событий.
document.body.addEventListener("htmx:configRequest", function (evt) {
evt.detail.parameters["auth_token"] = getAuthToken(); // добавление нового параметра в запрос
evt.detail.headers["Authentication-Token"] = getAuthToken(); // добавление нового заголовка в запрос
});В приведённом выше коде используется событие htmx:configRequest для изменения AJAX-запроса перед его отправкой:
Вы можете настроить поведение HTMX при замене, обработав событие htmx:beforeSwap.
document.body.addEventListener("htmx:beforeSwap", function (evt) {
if (evt.detail.xhr.status === 404) {
// предупреждение пользователя о возникновении ошибки 404 (возможно,
// использование более удобного механизма, чем alert())
alert("Error: Could Not Find Resource");
} else if (evt.detail.xhr.status === 422) {
// Разрешаем подмену ответа 422, поскольку мы используем его как сигнал,
// что что форма была отправлена с плохими данными, и хотим выполнить
// рендеринг с ошибки
//
// установите isError в false, чтобы предотвратить вывода ошибок в консоль
evt.detail.shouldSwap = true;
evt.detail.isError = false;
} else if (evt.detail.xhr.status === 418) {
// Если возвращается код ответа 418 (I'm a teapot), пере нацельте
// содержимое ответа на элемент с id `teapot`.
evt.detail.shouldSwap = true;
evt.detail.target = htmx.find("#teapot");
}
});В приведённом выше коде мы обработали несколько кодов ошибок 400-го уровня, которые обычно не приводят к замене в htmx.
Посмотреть другие события, поддерживаемые HTMX, можно здесь.
Логирование
В HTMX предусмотрена переменная htmx.logger, которая при установке записывает в журнал каждое событие.
htmx.logger = function (elt, event, data) {
if (console) {
console.log(event, elt, data);
}
};Заключение
В этой статье мы ознакомились с библиотекой HTMX. Мы продемонстрировали, что с помощью HTMX можно добиться функциональности Ajax, без использования JavaScript, что позволит вам легко создавать динамические приложения. HTMX может стать не просто новой библиотекой. Она может произвести революцию в подходе к интерактивности в веб-разработке.