hx-ws
Передача HTML-потоком и отправка данных через WebSocket
Расширение hx-ws открывает соединения WebSocket, заменяет входящий HTML и отправляет данные формы в формате JSON.
Если вы использовали ws в htmx 2.0, см. примечания по миграции.
Установка
<script src="https://cdn.jsdelivr.net/npm/htmx.org@4.0.0/dist/htmx.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/htmx.org@4.0.0/dist/ext/hx-ws.min.js"></script>
Использование
Обновление элемента
Откройте постоянное соединение WebSocket:
<div hx-ws:connect="/chat"> ... </div>
Браузер получает это сообщение WebSocket:
<p>New message</p>
Результат:
<div hx-ws:connect="/chat"> <p>New message</p> <!-- Swapped in --> </div>
htmx использует те же правила, что и для ответа text/html:
Выбор способа замены
Используйте hx-swap и hx-target, чтобы выбрать, как и где выполнять замену при обновлении:
<div hx-ws:connect="/chat"
hx-target="#messages"
hx-swap="beforeend">
<div id="messages">
<p>Old message</p>
<!-- Content goes here -->
</div>
</div> Сервер отправляет:
<p>New message</p>
Результат:
<div hx-ws:connect="/chat"
hx-target="#messages"
hx-swap="beforeend">
<div id="messages">
<p>Old message</p>
<p>New message</p> <!-- Appended -->
</div>
</div> Также можно использовать:
-
hx-select, чтобы выбрать содержимое для замены -
hx-select-oob, чтобы выбрать дополнительные элементы для замены
Обновление элементов
Начните с элементов страницы, которые нужно обновить:
<div hx-ws:connect="/chat"></div> <div id="feed"> <p>Old</p> </div> <div id="status">Offline</div>
Мы отправим элемент hx-swap-oob и элемент <hx-partial> (новинка в версии 4.0):
<!-- Match by ID --> <div id="status" hx-swap-oob="true">Online</div> <!-- Append --> <hx-partial hx-target="#feed" hx-swap="beforeend"> <p>New</p> </hx-partial>
Страница станет такой:
<div hx-ws:connect="/chat"></div> <div id="feed"> <p>Old</p> <p>New</p> </div> <div id="status">Online</div>
Почему не использовалась обычная замена?
После извлечения дополнительных замен htmx обычная замена может оказаться пустой:
(empty)
Элементы hx-swap-oob и <hx-partial> извлекаются до обычной замены. По умолчанию swapEmpty:false оставляет соединение без изменений.
Сервер также может объединять эти обновления с обычным HTML:
<p>New chat content</p> <!-- Also replace #status --> <hx-partial hx-target="#status">Busy</hx-partial>
Для первого элемента используются целевой элемент и способ замены соединения. Частичные обновления #status.
Чтобы отключить замену соединения, задайте hx-swap="none":
<div hx-ws:connect="/chat" hx-swap="none"> ... </div>
Замены hx-swap-oob и <hx-partial> по-прежнему выполняются.
Отправка сообщения
Добавьте hx-ws:send к полю ввода внутри соединения:
<div hx-ws:connect="/chat" hx-target="#messages"> <div id="messages"></div> <input hx-ws:send type="button" name="message" value="Hello"> </div>
Исходящее сообщение:
{
"headers": {
"HX-Request": "true",
"HX-Request-Type": "partial",
"HX-Source": "input",
"HX-Target": "div#messages",
"HX-Current-URL": "https://example.com/chat"
},
"message": "Hello"
} headers зарезервирован для метаданных. Значения формы и hx-vals используют другие ключи верхнего уровня.
Чтобы отправить массив, повторите поле формы:
<form hx-ws:send> <input name="tag" value="urgent"> <input name="tag" value="public"> <button>Send</button> </form>
Исходящее сообщение:
{
"headers": { /* ... */ },
"tag": ["urgent", "public"]
} hx-vals переопределяет значения формы, не преобразуя их типы:
<form hx-ws:send hx-vals="count:2"> <input name="count" value="1"> <button>Send</button> </form>
{ "headers": { /* ... */ }, "count": 2 } Переопределение входящей замены
Используйте JSON, чтобы переопределить способ замены соединения:
{
"content": "<p class=\"message\">New message</p>",
"target": "#messages",
"swap": "beforeend settle:10ms",
"select": ".message"
} -
content: HTML-код для замены -
target: место замены -
swap: сериализованная спецификацияhx-swap -
select: что выбрать изcontent
HTTP-заголовки HX-Re* заменяют значения, уже выбранные для запроса.
Сообщение WebSocket может поступить без запроса, поэтому его поля JSON могут задавать эти значения сразу:
| Поле JSON | Заголовок ответа HTTP | Значение элемента по умолчанию |
|---|---|---|
target |
HX-Retarget |
hx-target |
swap |
HX-Reswap |
hx-swap |
select |
HX-Reselect |
hx-select |
content использует те же атрибуты hx-target, hx-swap и hx-select, что и обычный HTML. Элементы hx-swap-oob и <hx-partial> внутри него по-прежнему создают независимые замены.
Поля JSON переопределяют соответствующие атрибуты:
TARGET JSON target --> hx-target --> connection element SWAP JSON swap --> hx-swap --> defaultSwap SELECT JSON select --> hx-select --> all content
hx-select-oob остается настройкой элемента. Вместо этого сервер может использовать hx-swap-oob или <hx-partial> внутри content.
Обработка пользовательских сообщений
JSON без content не заменяется:
{
"type": "notification",
"text": "New message"
} Обработайте его с помощью htmx:ws:before:message:incoming:
document.addEventListener('htmx:ws:before:message:incoming', async event => {
let message = await event.detail.message.json()
if (message.type === 'notification') showNotification(message)
}) Отмените событие, чтобы самостоятельно обработать пользовательские или двоичные данные:
document.addEventListener('htmx:ws:before:message:incoming', async event => {
event.preventDefault()
handleCustomMessage(await event.detail.message.text())
}) Изначально message.data содержит исходную строку, Blob или ArrayBuffer.
Преобразуйте его текущее значение:
await message.text() await message.json()
Отмените событие, чтобы пропустить встроенную обработку. Двоичные сообщения автоматически не заменяются.
Постоянные соединения
Соединения WebSocket остаются открытыми для входящих и исходящих сообщений.
Открытие соединений
Используйте hx-trigger, чтобы открыть соединение позже, чем load:
<button id="connect">Connect</button>
<div hx-ws:connect="/chat"
hx-trigger="click from:#connect">
</div> Поддерживаются все модификаторы hx-trigger.
Открытие при отправке
Укажите оба атрибута на одном элементе, чтобы при отправке открыть его соединение:
<button hx-ws:connect="/actions"
hx-ws:send
hx-trigger="click"
name="action"
value="refresh">
Refresh
</button> При нажатии кнопки открывается /actions и через это соединение отправляется action=refresh.
Отправка во время повторного подключения
Пользователь может инициировать отправку сообщений, пока соединение устанавливается повторно:
<div hx-ws:connect="/actions"> <button hx-ws:send name="action" value="save">Save</button> <button hx-ws:send name="action" value="refresh">Refresh</button> </div>
Если пользователь нажмет Сохранить, а затем Обновить, htmx отправит оба сообщения после открытия соединения:
// First
{ "headers": { /* ... */ }, "action": "save" }
// Then
{ "headers": { /* ... */ }, "action": "refresh" } Использование общих соединений
Поместите несколько элементов hx-ws:send внутрь одного hx-ws:connect:
<div hx-ws:connect="/actions"> <button hx-ws:send name="action" value="save">Save</button> <button hx-ws:send name="action" value="delete">Delete</button> </div> <div id="save-result"></div> <div id="delete-result"></div>
Обе кнопки используют соединение, принадлежащее <div>. Для входящих сообщений используется <div>, а не кнопка, отправившая сообщение.
Маршрутизация входящих сообщений
Укажите target во входящем сообщении JSON, чтобы направить замену:
{
"content": "<p>Saved</p>",
"target": "#save-result"
} Используйте hx-swap-oob или <hx-partial>, если одно сообщение обновляет несколько целевых элементов.
Закрытие соединений
Закройте соединение с кодом 1000, чтобы отключить повторное подключение:
socket.close(1000, 'done')
Для кодов из ws.reconnectCodes выполняется повторное подключение.
Настройка соединений
Настроить hx-ws можно в трех местах:
-
<meta name="htmx-config">задает глобальные значения по умолчанию в HTML.<meta name="htmx-config" content="ws.reconnectDelay:1s ws.reconnectMaxAttempts:5"> -
htmx.config.wsзадает глобальные значения по умолчанию в JavaScript.htmx.config.ws.reconnectDelay = '1s' htmx.config.ws.reconnectMaxAttempts = 5
-
hx-configпереопределяет значения по умолчанию для одного соединения.<div hx-ws:connect="/ws" hx-config="ws.reconnectMaxAttempts:2"> </div>
Эти значения считываются при создании соединения.
Атрибуты
hx-ws:connect
Открывает соединение WebSocket:
<div hx-ws:connect="/chat"></div>
Для входящего HTML используются следующие наследуемые атрибуты замены:
-
hx-target: по умолчанию — элемент соединения -
hx-swap: по умолчанию —htmx.config.defaultSwap -
hx-select: выбирает содержимое для замены соединения -
hx-select-oob: выбирает дополнительные элементы для замены
Значения по умолчанию:
-
swapEmpty:false; чтобы переопределить его, укажите явно вhx-swap -
hx-trigger="load"; используйтеhx-trigger, чтобы изменить его ws.reconnect:truews.pauseOnBackground:true
Каждый элемент hx-ws:connect владеет своим соединением. Разные элементы открывают разные соединения, даже если используют один и тот же URL.
hx-ws:send
Отправляет данные формы и hx-vals в формате JSON.
<div hx-ws:connect="/chat">
<form id="chat-form" hx-ws:send hx-target="#messages">
<input name="message">
<button>Send</button>
</form>
<div id="messages"></div>
</div> hx-ws:send использует ближайший hx-ws:connect, в том числе указанный на том же элементе. Его значение игнорируется.
Значение hx-trigger по умолчанию:
-
changeдля текстовых полей ввода,<textarea>и<select> -
submitдля<form> -
clickдля кнопок и других элементов
События
Данные события доступны в event.detail.
События подключения и закрытия:
event.detail.connection = {
url,
config,
socket, // WebSocket or null
queue, // outgoing messages waiting to send
attempt, // reconnect count
cancelled
} События входящих сообщений:
event.detail = {
connection,
message: {
data, // current string, Blob, or ArrayBuffer
text(),
json()
},
waitUntil(), // before processing only
cancelled // before processing only
} События исходящих сообщений предоставляют:
event.detail = {
connection,
message: {
headers, // htmx metadata
values, // form values and hx-vals
data // replacement payload before send; sent payload afterward
},
waitUntil(), // before sending only
cancelled // before sending only
} События входящих сообщений отправляются элементом, владеющим соединением. События исходящих сообщений отправляются элементом, инициировавшим отправку.
htmx:ws:before:connection
Срабатывает перед первоначальным подключением и каждым повторным подключением.
document.addEventListener('htmx:ws:before:connection', event => {
event.detail.connection.config.protocols = 'graphql-transport-ws'
}) В любом случае событие можно отменить:
- вызовите
event.preventDefault() - установите
event.detail.connection.cancelledвtrue
htmx:ws:after:connection
Срабатывает после открытия соединения.
document.addEventListener('htmx:ws:after:connection', event => {
event.detail.connection.socket.binaryType = 'arraybuffer'
}) htmx:ws:before:message:outgoing
Срабатывает перед отправкой исходящего сообщения.
document.addEventListener('htmx:ws:before:message:outgoing', event => {
let message = event.detail.message
message.headers.Authorization = `Bearer ${token}`
if (!isValid(message.values)) event.preventDefault()
}) -
message.headers: изменяемые метаданные htmx -
message.values: изменяемые значения формы иhx-vals -
message.data: необязательные данные для замены
detail.waitUntil(promise) откладывает сериализацию и отправку до завершения асинхронной работы.
Обычно {...values, headers} сериализуется в JSON. Установите message.data, чтобы вместо этого отправить строку, Blob, ArrayBuffer или представление типизированного массива:
document.addEventListener('htmx:ws:before:message:outgoing', event => {
let message = event.detail.message
message.data = encodeMessagePack({
...message.values,
headers: message.headers
})
}) Включите message.headers в заменяющие данные, если серверу нужны метаданные запроса htmx.
htmx:ws:after:message:outgoing
Срабатывает после отправки исходящего сообщения.
document.addEventListener('htmx:ws:after:message:outgoing', event => {
console.log('Outgoing:', event.detail.message.data)
}) message.data — это значение, переданное в WebSocket.send().
htmx:ws:before:message:incoming
Срабатывает перед обработкой входящего сообщения.
document.addEventListener('htmx:ws:before:message:incoming', event => {
let { message, waitUntil } = event.detail
waitUntil(message.json().then(data => {
if (!isValid(data)) event.detail.cancelled = true
}))
}) detail.waitUntil(promise) откладывает встроенную обработку до завершения асинхронной работы.
Установите message.data, чтобы заменить данные перед встроенной обработкой. Также применяются изменения, внесенные через waitUntil().
Синхронную обработку можно отменить любым из способов:
- вызовите
event.preventDefault() - установите
event.detail.cancelledвtrue
htmx:ws:after:message:incoming
Срабатывает после обработки входящего сообщения расширением.
document.addEventListener('htmx:ws:after:message:incoming', event => {
console.log('Incoming:', event.detail.message.data)
}) htmx:ws:close
Срабатывает при закрытии соединения.
document.addEventListener('htmx:ws:close', event => {
console.log('Closed:', event.detail.reason, event.detail.code)
}) -
reason:closed,removedилиcancelled -
code: код закрытия WebSocket илиnull
Код из ws.reconnectCodes запускает повторное подключение, если ws.reconnect:true.
htmx:ws:error
Срабатывает при ошибках подключения и отправки.
document.addEventListener('htmx:ws:error', event => {
console.error('WebSocket error:', event.detail.error)
}) -
connection: соединение, в котором произошла ошибка, если оно доступно -
url: запрошенный URL, если соединения нет -
error: значение ошибки
Настройки
ws.reconnect
Определяет, выполняется ли автоматическое повторное подключение при разрешенных кодах закрытия.
<meta name="htmx-config" content="ws.reconnect:false">
По умолчанию — true.
ws.reconnectCodes
Выберите коды закрытия WebSocket, при которых выполняется повторное подключение:
<meta name="htmx-config"
content='{"ws":{"reconnectCodes":[1001,1005,1006,1011,1012,1013,1014]}}'> По умолчанию:
-
1001: уход -
1005: код состояния не получен -
1006: ненормальное закрытие -
1011: внутренняя ошибка -
1012: перезапуск службы -
1013: повторите попытку позже -
1014: ошибка шлюза
См. описания кодов закрытия на MDN и реестр кодов закрытия IANA.
ws.reconnectDelay
Задайте время ожидания перед первой попыткой повторного подключения.
<meta name="htmx-config" content="ws.reconnectDelay:1s">
По умолчанию — 500 миллисекунд. После каждой неудачной попытки задержка удваивается; значения можно указывать в миллисекундах или в виде временных строк, например 500ms, 1s и 2m.
ws.reconnectMaxDelay
Ограничивает время ожидания между попытками повторного подключения.
<meta name="htmx-config" content="ws.reconnectMaxDelay:30s">
По умолчанию — 60000 миллисекунд. Используйте миллисекунды или временную строку.
ws.reconnectMaxAttempts
Ограничивает количество попыток повторного подключения после закрытия соединения.
<meta name="htmx-config" content="ws.reconnectMaxAttempts:5">
По умолчанию — Infinity.
ws.reconnectJitter
Распределяет попытки повторного подключения во времени, чтобы множество клиентов не повторяли их одновременно.
<meta name="htmx-config" content="ws.reconnectJitter:0">
По умолчанию — 0.3: задержка каждой попытки случайным образом изменяется в пределах ±30%. Используйте 0 для точных задержек.
ws.maxOutgoingMessagesQueueSize
Ограничивает количество исходящих сообщений, ожидающих повторного подключения.
<meta name="htmx-config" content="ws.maxOutgoingMessagesQueueSize:20">
По умолчанию — 100. Для последующих сообщений генерируется событие htmx:ws:error, и они не отправляются. Используйте 0, чтобы отключить очередь.
ws.pauseOnBackground
Закрывает соединения, пока страница скрыта, и повторно подключается, когда она становится видимой.
<meta name="htmx-config" content="ws.pauseOnBackground:false">
По умолчанию — true.
ws.protocols
Задайте подпротоколы WebSocket для рукопожатия.
<meta name="htmx-config" content="ws.protocols:graphql-transport-ws">
По умолчанию подпротокол не задается. Используйте конфигурацию JSON, чтобы задать несколько подпротоколов.
Миграция
htmx 2.0
В htmx 2.0 каждый входящий элемент считается неявным hx-swap-oob:
<!-- interpreted as hx-swap-oob="true" by default --> <div id="notifications"> New message </div>
В htmx 4.0 на соединении используются hx-target и hx-swap:
<div hx-ws:connect="/notifications"
hx-target="#notifications"
hx-swap="outerHTML">
</div>
<div id="notifications"></div> Входящее сообщение содержит обычный HTML:
<div id="notifications"> New message </div>
В htmx 4.0 для каждой дополнительной замены требуется явный синтаксис:
-
hx-swap-oobили<hx-partial>для дополнительных замен - JSON для выбора целевого элемента и способа замены соединения
Исходящие сообщения
В htmx 2 к значениям формы добавлялся HEADERS:
{
"message": "Hello",
"HEADERS": {
"HX-Request": "true"
}
} В htmx 4 для метаданных зарезервирован headers, а значения размещаются на верхнем уровне:
{
"headers": {
"HX-Request": "true"
},
"message": "Hello"
} Атрибуты и API
Названия изменились следующим образом:
| htmx 2.x | htmx 4.x |
|---|---|
ws-connect |
hx-ws:connect |
ws-send |
hx-ws:send |
htmx.config.wsReconnectDelay |
htmx.config.ws.reconnectDelay |
createWebSocket |
Удален |
wsBinaryType |
Удален |
socketWrapper |
Удален |
ws-connect и ws-send по-прежнему работают, но выводится предупреждение.
События
События изменились следующим образом:
От бета-версии к RC1
В RC1 изменены форматы сообщений, события и поведение повторного подключения по сравнению с бета-версией.
Исходящие сообщения
Формат передачи изменен с {headers, body: values} на {...values, headers}. headers зарезервирован, поэтому поле формы или запись hx-vals с таким именем не отправляется.
Данные события изменены с {headers, body} на {message: {headers, values, data}, waitUntil, cancelled}. Установите message.data, чтобы заменить данные. Используйте waitUntil(promise), чтобы завершить асинхронную работу до сериализации, постановки в очередь или отправки.
Входящие сообщения
Сопоставление сообщений через HX-Request-ID и request_id удалено.
Данные события изменены с {message: {text, json, cancelled}} на {message: {data, text(), json()}, waitUntil, cancelled}. Поля преобразования теперь являются методами. Установите message.data, чтобы заменить входящие данные. Отмените обработку с помощью event.preventDefault() или event.detail.cancelled = true.
События
| Бета-версия | RC1 |
|---|---|
htmx:before:ws:connection |
htmx:ws:before:connection |
htmx:after:ws:connection |
htmx:ws:after:connection |
htmx:before:ws:request |
htmx:ws:before:message:outgoing |
htmx:after:ws:request |
htmx:ws:after:message:outgoing |
htmx:before:ws:message |
htmx:ws:before:message:incoming |
htmx:after:ws:message |
htmx:ws:after:message:incoming |
htmx:ws:error предоставляет connection, если ошибка связана с соединением. Если соединения нет, предоставляется url.
Повторное подключение
В бета-версии повторное подключение выполнялось после каждого закрытия. В RC1 оно выполняется, только если ws.reconnect имеет значение true, код закрытия входит в ws.reconnectCodes, а подключенный элемент все еще существует.
По умолчанию код 1000 прекращает попытки повторного подключения. Сообщения, созданные во время открытия или повторного подключения, ставятся в очередь и отправляются по порядку.
Владение соединением
Каждый элемент hx-ws:connect владеет своим соединением. Разные элементы больше не используют общее соединение на основе URL.
Значение hx-ws:send больше не открывает соединение и игнорируется. Поместите hx-ws:send внутрь hx-ws:connect или укажите оба атрибута на одном элементе.
Другие изменения
| Бета-версия | RC1 | Совместимость |
|---|---|---|
htmx.config.websockets |
htmx.config.ws |
Удалено |
ws.pendingRequestTTL |
Удалено | Удалено |
ws.reconnectJitter:true/false |
ws.reconnectJitter:0.3/0 |
Удалено |
payload |
content |
Работает, но выводится предупреждение |
Примечания
-
hx-ws:connectпринимает:- URL относительно корня:
/ws - URL относительно текущего пути:
events - URL относительно протокола:
//api.example.com/ws - URL HTTP(S):
https://example.com/ws - URL WebSocket:
wss://example.com/ws
URL HTTP(S) преобразуются в соответствующие URL WebSocket.
- URL относительно корня:
-
Каждое соединение обрабатывает входящие и исходящие сообщения по порядку. Обработка входящих сообщений ожидает завершения каждой замены.
-
Для всех замен через WebSocket используется
htmx.swap(). -
Используйте
hx-ws-connectиhx-ws-sendтам, где двоеточия не поддерживаются, например в JSX.
См. также
Licensed under the Zero-Clause BSD License.
https://four.htmx.org/extensions/hx-ws/