Глава 08. Уловки мастеров Htmx


Расширенный Htmx

В этой главе мы собираемся более подробно изучить набор инструментов htmx. Мы уже многого добились благодаря тому, что узнали на данный момент. Тем не менее, когда вы разрабатываете приложения, управляемые гипермедиа, могут возникнуть моменты, когда вам потребуется использовать дополнительные параметры и методы.

Мы рассмотрим более сложные атрибуты в htmx, а также подробно остановимся на атрибутах, которые мы уже использовали.

Кроме того, мы рассмотрим функциональность, которую htmx предлагает помимо простых атрибутов HTML: как htmx расширяет стандартные HTTP-запросы и ответы, как htmx работает с событиями (и создает их), и как подходить к ситуациям, когда нет простой единой цели для страница, которую необходимо обновить.

Наконец, мы рассмотрим практические аспекты разработки htmx: как эффективно отлаживать приложения на основе htmx, соображения безопасности, которые необходимо учитывать при работе с htmx, и как настроить поведение htmx.

Благодаря функциям и методам, описанным в этой главе, вы сможете создавать чрезвычайно сложные пользовательские интерфейсы, используя только Htmx и, возможно, немного клиентских сценариев, дружественных к гипермедиа.

Htmx-атрибуты

На данный момент в нашем приложении мы использовали около пятнадцати различных атрибутов htmx. Наиболее важными из них были:

hx-get, hx-postи т. д.

Чтобы указать запрос AJAX, элемент должен сделать

hx-trigger

Чтобы указать событие, которое запускает запрос

hx-swap

Чтобы указать, как заменить возвращенное содержимое HTML в DOM.

hx-target

Чтобы указать, где в DOM заменить возвращаемое содержимое HTML.

Два из этих атрибутов hx-swapи hx-triggerподдерживают ряд полезных опций для создания более продвинутых приложений, управляемых гипермедиа.

hx-своп

Начнем с атрибута hx-swap. Это часто не включается в элементы, которые отправляют запросы на основе htmx, поскольку его поведение по умолчанию — ,  innerHTMLкоторое заменяет внутренний HTML-код элемента — имеет тенденцию охватывать большинство случаев использования.

Ранее мы видели ситуации, когда нам хотелось переопределить поведение по умолчанию и использовать outerHTML, например, . А в главе 2 мы обсуждали некоторые другие варианты обмена, помимо этих двух: beforebegin, afterendи т. д.

В главе 5 мы также рассмотрели swapмодификатор задержки для hx-swap, который позволил нам постепенно затухать некоторый контент перед его удалением из DOM.

В дополнение к этому hx-swapпредлагает дополнительный контроль со следующими модификаторами:

settle

Как и swap, это позволяет вам применять определенную задержку между моментом замены контента в DOM и моментом «установления» его атрибутов, то есть обновления их старых значений (если таковые имеются) до новых значений. Это может дать вам детальный контроль над переходами CSS.

show

Позволяет указать элемент, который должен отображаться — то есть при необходимости прокручиваться в область просмотра браузера — после завершения запроса.

scroll

Позволяет указать прокручиваемый элемент (то есть элемент с полосами прокрутки), который должен быть прокручен вверх или вниз после завершения запроса.

focus-scroll

Позволяет указать, что htmx должен прокручиваться до элемента, находящегося в фокусе, после завершения запроса. По умолчанию для этого модификатора установлено значение «false».

Так, например, если бы у нас была кнопка, выдающая запрос GET, и мы хотели бы прокрутить элемент до верхней части bodyпосле завершения запроса, мы бы написали следующий HTML:

<button hx-get="/contacts" hx-target="#content-div"
        hx-swap="innerHTML show:body:top"> (1)
  Get Contacts
</button>
  1. Это указывает htmx отображать верхнюю часть тела после того, как произойдет замена.

Более подробную информацию и примеры можно найти в сети в hx-swap документации .

hx-триггер

Например hx-swap, , hx-triggerчасто можно опустить при использовании htmx, поскольку поведение по умолчанию обычно соответствует вашему желанию. Напомним, что триггерные события по умолчанию определяются типом элемента:

  • Запросы на элементы input, textarea& selectзапускаются событием change.

  • Запросы к formэлементам запускаются по submitсобытию.

  • Запросы ко всем остальным элементам инициируются событием click.

Однако бывают случаи, когда вам нужна более подробная спецификация триггера. Классическим примером является пример активного поиска, который мы реализовали в Contact.app:

    <input id="search" type="search" name="q" value="{{ request.args.get('q') or '' }}"
           hx-get="/contacts"
           hx-trigger="search, keyup delay:200ms changed"/> (1)
  1. Подробная спецификация триггера.

В этом примере использованы два модификатора, доступные для hx-triggerатрибута:

delay

Позволяет указать задержку ожидания перед отправкой запроса. Если событие происходит снова, первое событие отбрасывается и таймер сбрасывается. Это позволяет «отклонять» запросы.

changed

Позволяет указать, что запрос должен быть выдан только при valueизменении свойства данного элемента.

hx-triggerимеет несколько дополнительных модификаторов. Это имеет смысл, поскольку события довольно сложны, и мы хотим иметь возможность воспользоваться всей мощью, которую они предлагают. Подробнее о событиях мы поговорим ниже.

Вот другие модификаторы, доступные на hx-trigger:

once

Данное событие вызовет запрос только один раз.

throttle

Позволяет регулировать события, выдавая их только один раз в определенный интервал. Это отличается от delayтого, что первое событие сработает немедленно, но любые последующие события не сработают, пока не истечет период времени регулирования.

from

Селектор CSS, который позволяет вам выбрать другой элемент для прослушивания событий. Мы увидим пример этого использования позже в этой главе.

target

Селектор CSS, который позволяет фильтровать события, выбирая только те, которые происходят непосредственно с данным элементом. В DOM события «переходят» к своим родительским элементам, поэтому clickсобытие на кнопке также вызывает clickсобытие на родительском элементе div, вплоть до самого bodyэлемента. Иногда вам нужно указать событие непосредственно для данного элемента, и этот атрибут позволяет вам это сделать.

consume

Если для этой опции установлено значение true, событие-триггер будет отменено и не распространится на родительские элементы.

queue

Эта опция позволяет вам указать, как события помещаются в очередь в htmx. По умолчанию, когда htmx получает триггерное событие, он выдает запрос и запускает очередь событий. Если запрос все еще находится в работе, когда получено другое событие, оно будет помещено в очередь и, когда запрос завершится, инициирует новый запрос. По умолчанию он сохраняет только последнее полученное событие, но вы можете изменить это поведение, используя этот параметр: например, вы можете установить его noneи игнорировать все триггерные события, которые происходят во время запроса.

Триггерные фильтры

Атрибут hx-triggerтакже позволяет указать фильтр для событий, используя квадратные скобки, заключающие выражение JavaScript после имени события.

Допустим, у вас сложная ситуация, когда контакты можно восстановить только в определенных ситуациях. У вас есть функция JavaScript, contactRetrievalEnabled()которая возвращает логическое значение, trueесли контакты можно получить, и falseв противном случае. Как вы могли бы использовать эту функцию, чтобы разместить шлюз на кнопке, которая отправляет запрос /contacts?

Чтобы сделать это с помощью фильтра событий в htmx, вы должны написать следующий HTML:

<script>
  function contactRetrievalEnabled() {
      // code to test if contact retrieval is enabled
      ...
  }
</script>
<button hx-get="/contacts" hx-trigger="click[contactRetrievalEnabled()]"> (1)
  Get Contacts
</button>
  1. Запрос выдается по клику только при contactRetrievalEnabled()возврате true.

Кнопка не выдает запрос, если contactRetrievalEnabled()возвращает false, что позволяет вам динамически контролировать время выполнения запроса. Существуют распространенные ситуации, требующие триггера событий, когда вы хотите отправить запрос только при определенных обстоятельствах:

  • если определенный элемент имеет фокус

  • если данная форма действительна

  • если набор входных данных имеет определенные значения

Используя фильтры событий, вы можете использовать любую логику для фильтрации запросов по htmx.

Синтетические события

В дополнение к этим модификаторам hx-triggerпредлагает несколько «синтетических» событий, то есть событий, которые не являются частью обычного DOM API. Мы уже видели loadэто revealedв наших примерах отложенной загрузки и бесконечной прокрутки, но htmx также предоставляет intersectсобытие, которое срабатывает, когда элемент пересекает свой родительский элемент.

Это синтетическое событие использует современный API Intersection Observer, о котором вы можете прочитать подробнее на MDN .

Пересечение дает вам детальный контроль над тем, когда именно должен быть запущен запрос. Например, вы можете установить пороговое значение и указать, что запрос будет выдаваться только тогда, когда элемент виден на 50 %.

Атрибут hx-trigger, безусловно, самый сложный в HTML. Более подробную информацию и примеры можно найти в его документации .

Другие атрибуты

Htmx предлагает множество других, менее часто используемых атрибутов для тонкой настройки поведения вашего приложения, управляемого гипермедиа.

Вот некоторые из наиболее полезных:

hx-push-url

«Проталкивает» URL-адрес запроса (или какое-либо другое значение) в панель навигации.

hx-заповедник

Сохраняет часть DOM между запросами; исходное содержимое будет сохранено независимо от того, что будет возвращено.

hx-синхронизация

Синхронизированные запросы между двумя или более элементами.

hx-отключить

Отключает поведение htmx для этого элемента и всех дочерних элементов. Мы вернемся к этому, когда будем обсуждать тему безопасности.

Давайте посмотрим на hx-sync, который позволяет нам синхронизировать запросы AJAX между двумя или более элементами. Рассмотрим простой случай, когда у нас есть две кнопки, которые нацелены на один и тот же элемент на экране:

<button hx-get="/contacts" hx-target="body"> (1)
  Get Contacts
</button>
<button hx-get="/settings" hx-target="body"> (1)
  Get Settings
</button>

Это нормально и будет работать, но что, если пользователь нажмет кнопку «Получить контакты», а затем на ответ на запрос потребуется некоторое время? И тем временем пользователь нажимает кнопку «Получить настройки»? В этом случае у нас будет одновременно выполняться два запроса.

Если /settingsзапрос завершился первым и отобразил информацию о настройках пользователя, они могут быть очень удивлены, если начнут вносить изменения, а затем внезапно запрос /contactsзавершится и вместо этого все тело будет заменено контактами!

Чтобы справиться с этой ситуацией, мы могли бы рассмотреть возможность использования , hx-indicatorчтобы предупредить пользователя о том, что что-то происходит, что снизит вероятность того, что он нажмет вторую кнопку. Но если мы действительно хотим гарантировать, что между этими двумя кнопками одновременно выполняется только один запрос, правильно будет использовать атрибут hx-sync. Давайте заключим обе кнопки в divи устраним избыточную hx-targetспецификацию, подняв атрибут до этого значения div. Затем мы можем использовать hx-syncэтот div для координации запросов между двумя кнопками.

Вот наш обновленный код:

.Синхронизация двух кнопок

<div hx-target="body"  (1)
     hx-sync="this">  (2)
    <button hx-get="/contacts"> (1)
      Get Contacts
    </button>
    <button hx-get="/settings"> (1)
      Get Settings
    </button>
</div>
  1. Поднимите повторяющиеся hx-targetатрибуты родительскому элементу div.

  2. Синхронизируйте с родительским файлом div.

Размещая hx-syncатрибут со divзначением this, мы говорим: «Синхронизируйте все запросы htmx, которые происходят внутри этого divэлемента, друг с другом». Это означает, что если у одной кнопки уже есть выполняющийся запрос, другие кнопки внутри divне будут выдавать запросы до тех пор, пока он не завершится.

Атрибут hx-syncподдерживает несколько различных стратегий, которые позволяют, например, заменять существующий запрос в процессе выполнения или ставить запросы в очередь с определенной стратегией организации очереди. Вы можете найти полную документацию, а также примеры на странице htmx.org для hx-sync.

Как видите, htmx предлагает множество функций, основанных на атрибутах, для более продвинутых приложений, управляемых гипермедиа. Полную ссылку на все атрибуты htmx можно найти на сайте htmx .

События

До сих пор мы работали с событиями JavaScript в htmx в основном через hx-triggerатрибут. Этот атрибут оказался мощным механизмом управления нашим приложением с использованием декларативного, дружественного к HTML синтаксиса.

Однако с событиями мы можем сделать гораздо больше. События играют решающую роль как в расширении HTML как гипермедиа, так и, как мы увидим, в сценариях, дружественных к гипермедиа. События — это «клей», который объединяет DOM, HTML, htmx и сценарии. Вы можете думать о DOM как о сложной «шине событий» для приложений.

Мы не можем не подчеркнуть: для создания продвинутых приложений, управляемых гипермедиа, стоит приложить усилия для более глубокого изучения событий .

События, генерируемые Htmx

Помимо упрощения реакции на события, htmx также генерирует множество полезных событий. Вы можете использовать эти события, чтобы добавить больше функциональности вашему приложению либо через HTML-код, либо с помощью сценариев.

Вот некоторые из наиболее часто используемых событий, запускаемых htmx:

htmx:load

Срабатывает, когда новый контент загружается в DOM с помощью htmx.

htmx:configRequest

Срабатывает перед выдачей запроса, что позволяет программно настроить запрос или полностью отменить его.

htmx:afterRequest

Запускается после ответа на запрос.

htmx:abort

Пользовательское событие, которое можно отправить элементу на базе htmx, чтобы прервать открытый запрос.

Использование события htmx:configRequest

Давайте рассмотрим пример того, как работать с событиями, создаваемыми htmx. Мы будем использовать это htmx:configRequestсобытие для настройки HTTP-запроса.

Рассмотрим следующий сценарий: ваша серверная команда решила, что они хотят, чтобы вы включали сгенерированный сервером токен для дополнительной безопасности в каждый запрос. Токен будет храниться в localStorageбраузере, в слоте special-token.

Токен устанавливается с помощью JavaScript (пока не беспокойтесь о деталях), когда пользователь впервые входит в систему:

    let response = await fetch("/token"); (1)
    localStorage['special-token'] = await response.text();
  1. Получите значение токена, а затем установите его в localStorage.

Серверная команда хочет, чтобы вы включали этот специальный токен в каждый запрос, сделанный htmx, в качестве заголовка X-SPECIAL-TOKEN. Как вы могли этого добиться? Один из способов — перехватить htmx:configRequestсобытие и обновить detail.headers объект этим токеном из localStorage.

В VanillaJS это будет выглядеть примерно так, если разместить его в <script>теге <head>нашего HTML-документа:

document.body.addEventListener("htmx:configRequest", function(configEvent){
    configEvent.detail.headers['X-SPECIAL-TOKEN'] = localStorage['special-token']; (1)
})
  1. Получите значение из локального хранилища и поместите его в заголовок.

Как видите, мы добавляем новое значение в headersсвойство подробностей события. После выполнения обработчика событий это headersсвойство считывается htmx и используется для создания заголовков запроса AJAX, который он делает.

Свойство detailсобытия htmx:configRequestсодержит множество полезных свойств, которые вы можете обновить, чтобы изменить «форму» запроса, в том числе:

detail.parameters

Позволяет добавлять или удалять параметры запроса

detail.target

Позволяет обновить цель запроса

detail.verb

Позволяет обновлять HTTP-глагол запроса (например GET)

Так, например, если команда серверной стороны решила, что токен должен быть включен в качестве параметра, а не в качестве заголовка запроса, вы можете обновить свой код, чтобы он выглядел так:

document.body.addEventListener("htmx:configRequest", function(configEvent){
    configEvent.detail.parameters['token'] = localStorage['special-token']; (1)
})
  1. Получите значение из локального хранилища и установите его в параметр.

Как видите, это дает вам большую гибкость при обновлении запроса AJAX, который делает htmx.

Полную документацию по htmx:configRequestмероприятию (и другим мероприятиям, которые могут вас заинтересовать) можно найти на сайте htmx .

Отмена запроса с помощью htmx:abort

Мы можем прослушивать любое из множества полезных событий из htmx и реагировать на эти события, используя hx-trigger. Что еще мы можем делать с событиями?

Оказывается, сам htmx прослушивает одно специальное событие: htmx:abort. Когда htmx получает это событие для элемента, к которому находится запрос, он прерывает запрос.

Рассмотрим ситуацию, когда у нас есть потенциально длительный запрос к /contacts, и мы хотим предложить пользователям возможность отменить запрос. Нам нужна кнопка, которая отправляет запрос, конечно же, управляемая htmx, а затем еще одна кнопка, которая будет отправлять событие htmx:abortпервой.

Вот как может выглядеть код:

<button id="contacts-btn" hx-get="/contacts" hx-target="body"> (1)
  Get Contacts
</button>
<button onclick="document.getElementById('contacts-btn').dispatchEvent(new Event('htmx:abort'))"> (2)
  Cancel
</button>
  1. Обычный HTML- GETзапрос к/contacts

  2. JavaScript для поиска кнопки и отправки ей htxm:abortсобытия

Итак, теперь, если пользователь нажимает кнопку «Получить контакты» и запрос занимает некоторое время, он может нажать кнопку «Отменить» и завершить запрос. Конечно, в более сложном пользовательском интерфейсе вы можете захотеть отключить кнопку «Отмена», если только не выполняется HTTP-запрос, но это было бы сложно реализовать на чистом JavaScript.

К счастью, это не так уж плохо реализовать в гиперскрипте, поэтому давайте посмотрим, как это будет выглядеть:

<button id="contacts-btn" hx-get="/contacts" hx-target="body">
  Get Contacts
</button>
<button _="on click send htmx:abort to #contacts-btn
           on htmx:beforeRequest from #contacts-btn remove @disabled from me
           on htmx:afterRequest from #contacts-btn add @disabled to me">
  Cancel
</button>

Теперь у нас есть кнопка «Отмена», которая отключается только тогда, когда запрос от contacts-btnкнопки находится в полете. И чтобы это произошло, мы используем преимущества событий, генерируемых и обрабатываемых с помощью htmx, а также дружественного к событиям синтаксиса гиперскрипта. Ловко!

События, генерируемые сервером

В следующем разделе мы поговорим подробнее о различных способах, с помощью которых htmx улучшает регулярные HTTP-запросы и ответы, но, поскольку это связано с событиями, мы собираемся обсудить один заголовок HTTP-ответа, который поддерживает htmx: HX-Trigger. Ранее мы обсуждали, как HTTP-запросы и ответы поддерживают заголовки — пары имя-значение, содержащие метаданные о данном запросе или ответе. Мы воспользовались HX-Triggerзаголовком запроса, который включает идентификатор элемента, вызвавшего данный запрос.

В дополнение к этому заголовку запроса , htmx также поддерживает заголовок ответа, также называемый HX-Trigger. Этот заголовок ответа позволяет инициировать событие для элемента, отправившего запрос AJAX. Это оказывается мощным способом несвязанной координации элементов в DOM.

Чтобы увидеть, как это может работать, давайте рассмотрим следующую ситуацию: у нас есть кнопка, которая получает новые контакты из некоторой удаленной системы на сервере. Мы проигнорируем детали реализации на стороне сервера, но мы знаем, что если мы введем POSTпуть /sync, это вызовет синхронизацию с системой.

Теперь эта синхронизация может привести, а может и не привести к созданию новых контактов. В случае создания новых контактов мы хотим обновить нашу таблицу контактов. В случае, если контакты не созданы, мы не хотим обновлять таблицу.

Чтобы реализовать это, мы могли бы условно добавить HX-Triggerзаголовок ответа со значением contacts-updated:

@app.route('/sync', methods=["POST"])
def sync_with_server():
    contacts_updated = RemoteServer.sync() (1)
    resp = make_response(render_template('sync.html'))
    if contacts_updated (2)
      resp.headers['HX-Trigger'] = 'contacts-updated'
    return resp
  1. Обращение к удаленной системе, которая синхронизировала с ней нашу базу контактов

  2. Если какие-либо контакты были обновлены, мы условно запускаем contacts-updatedсобытие на клиенте.

Это значение вызовет contacts-updatedсобытие на кнопке, которая отправила запрос AJAX к /sync. Затем мы можем воспользоваться модификатором from:атрибута hx-trigger для прослушивания этого события. С помощью этого шаблона мы можем эффективно запускать запросы htmx со стороны сервера.

Вот как может выглядеть код на стороне клиента:

   <button hx-post="/integrations/1"> (1)
     Pull Contacts From Integration
   </button>

      ...

    <table hx-get="/contacts/table" hx-trigger="contacts-updated from:body"> (2)
      ...
    </table>
  1. Ответ на этот запрос может условно вызвать contacts-updatedсобытие.

  2. Эта таблица прослушивает событие и обновляется, когда оно происходит.

Таблица прослушивает событие contacts-updatedи делает это для bodyэлемента. Он прослушивает элемент, body поскольку событие всплывает из кнопки, и это позволяет нам не связывать кнопку и таблицу вместе: мы можем перемещать кнопку и таблицу по своему усмотрению, и с помощью событий будет продолжаться желаемое поведение. работать нормально. Кроме того, мы можем захотеть, чтобы другие элементы или запросы инициировали contacts-updatedсобытие, поэтому это обеспечивает общий механизм обновления таблицы контактов в нашем приложении.

HTTP-запросы и ответы

Мы только что увидели расширенную функцию HTTP-ответов, поддерживаемую htmx, HX-Triggerзаголовок ответа, но htmx поддерживает еще несколько заголовков как для запросов, так и для ответов. В главе 4 мы обсуждали заголовки, присутствующие в HTTP-запросах. Вот некоторые из наиболее важных заголовков, которые вы можете использовать для изменения поведения htmx с ответами HTTP:

HX-Location

Вызывает перенаправление на стороне клиента в новое место.

HX-Push-Url

Помещает новый URL-адрес в адресную строку

HX-Refresh

Обновляет текущую страницу

HX-Retarget

Позволяет указать новую цель для замены содержимого ответа на стороне клиента.

Ссылку на все запросы и заголовки ответов вы можете найти в документации htmx .

Коды ответа HTTP

Еще более важным, чем заголовки ответа, с точки зрения информации, передаваемой клиенту, является код ответа HTTP . Мы обсуждали коды ответов HTTP в главе 3. В целом htmx обрабатывает различные коды ответов так, как и следовало ожидать: он меняет содержимое для всех кодов ответа 200-го уровня и ничего не делает для других. Однако есть два «специальных» 200-уровневых кода ответа:

  • 204 No Content- Когда htmx получает этот код ответа, он не будет заменять какой-либо контент в DOM (даже если ответ имеет тело).

  • 286- Когда htmx получит этот код ответа на опрос, он остановит опрос.

Вы можете переопределить поведение htmx в отношении кодов ответа, как вы уже догадались, отвечая на событие! Событие htmx:beforeSwapпозволяет вам изменить поведение htmx по отношению к различным кодам состояния.

Предположим, что вместо того, чтобы ничего не делать при 404возникновении ошибки, вы хотите предупредить пользователя о том, что произошла ошибка. Для этого вам нужно вызвать метод JavaScript showNotFoundError(). Давайте добавим немного кода, чтобы использовать htmx:beforeSwap это событие:

document.body.addEventListener('htmx:beforeSwap', function(evt) { (1)
    if(evt.detail.xhr.status === 404){ (2)
        showNotFoundError();
    }
});
  1. Подключитесь к htmx:beforeSwapсобытию.

  2. Если код ответа — 404, покажите пользователю диалоговое окно.

Вы также можете использовать это htmx:beforeSwapсобытие, чтобы настроить, следует ли заменять ответ в DOM и на какой элемент должен быть нацелен ответ. Это дает вам некоторую гибкость в выборе того, как вы хотите использовать коды HTTP-ответов в своем приложении. Полную документацию о htmx:beforeSwapмероприятии можно найти на сайте htmx.org .

Обновление другого контента

Выше мы увидели, как использовать событие, инициируемое сервером, через HX-Triggerзаголовок ответа HTTP, чтобы обновить часть DOM на основе ответа на другую часть DOM. Этот метод решает общую проблему, возникающую в приложениях, управляемых гипермедиа: «Как мне обновить другой контент?» Ведь в обычных HTTP-запросах есть только одна «цель» — весь экран, и, аналогично, в запросах на основе htmx есть только одна цель: либо явная, либо неявная цель элемента.

Если вы хотите обновить другой контент в формате Htmx, у вас есть несколько вариантов:

Расширение вашего выбора

Первый и самый простой вариант — «расширить цель». То есть вместо того, чтобы просто заменять небольшую часть экрана, расширьте цель вашего htmx-запроса, пока она не станет достаточно большой, чтобы вместить все элементы, которые необходимо обновить на экране. Это имеет огромное преимущество: простота и надежность. Обратной стороной является то, что он может не обеспечить желаемый пользовательский интерфейс и может не подходить для определенного макета шаблона на стороне сервера. В любом случае, мы всегда рекомендуем сначала подумать хотя бы об этом подходе.

Внеполосные свопы

Второй вариант, немного более сложный, — воспользоваться поддержкой внеполосного контента в htmx. Когда htmx получает ответ, он проверяет его на наличие содержимого верхнего уровня, включающего этот hx-swap-oobатрибут. Этот контент будет удален из ответа, поэтому он не будет заменен в DOM обычным способом. Вместо этого он будет заменен на контент, которому он соответствует по идентификатору.

Давайте посмотрим на пример. Рассмотрим ситуацию, которая у нас была ранее, когда таблицу контактов необходимо обновить, если интеграция удаляет какие-либо новые контакты. Ранее мы решали эту проблему, используя события и событие, инициируемое сервером, через HX-Triggerзаголовок ответа.

На этот раз мы будем использовать hx-swap-oobатрибут в ответе на POSTto /integrations/1. Новое содержимое таблицы контактов будет добавлено к ответу.

   <button hx-post="/integrations/1"> (1)
     Pull Contacts From Integration
   </button>

      ...

    <table id="contacts-table"> (2)
      ...
    </table>
  1. Кнопка по-прежнему выдает POSTсообщение /integrations/1.

  2. Таблица больше не ожидает событий, но теперь у нее есть идентификатор.

Далее ответ на POSTto /integrations/1будет включать в себя содержимое, которое необходимо заменить на кнопку, в соответствии с обычным механизмом htmx. Но он также будет включать новую, обновленную версию таблицы контактов, которая будет помечена как hx-swap-oob="true". Этот контент будет удален из ответа и не будет вставлен в кнопку. Вместо этого она заменяется в DOM вместо существующей таблицы, поскольку имеет соответствующий идентификатор.

HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
...

Pull Contacts From Integration (1)

<table id="contacts-table" hx-swap-oob="true"> (2)
  ...
</table>
  1. Этот контент будет помещен в кнопку.

  2. Этот контент будет удален из ответа и заменен по идентификатору.

Используя этот метод объединения, вы можете обновлять контент в любом месте страницы. Атрибут hx-swap-oobподдерживает другие дополнительные функции, все из которых документированы .

В зависимости от того, как именно работает ваша технология шаблонов на стороне сервера и какой уровень интерактивности требуется вашему приложению, внеполосная замена может стать мощным механизмом обновления контента.

События

Наконец, самый сложный механизм обновления контента — это тот, который мы видели в разделе «События»: использование событий, инициируемых сервером, для обновления элементов. Этот подход может быть очень чистым, но также требует более глубоких концептуальных знаний HTML и событий, а также приверженности подходу, управляемому событиями. Хотя нам нравится такой стиль разработки, он подходит не всем. Обычно мы рекомендуем этот шаблон только в том случае, если философия htmx-гипермедиа, управляемой событиями, действительно вам подходит.

Однако, если это действительно говорит с вами, мы говорим: действуйте. Используя этот подход, мы создали несколько очень сложных и гибких пользовательских интерфейсов, и он нам очень нравится.

Быть прагматичным

Все эти подходы к проблеме «Обновление другого контента» будут работать, и часто будут работать хорошо. Однако может наступить момент, когда будет проще использовать другой подход для вашего пользовательского интерфейса, например реактивный. Как бы нам ни нравился подход гипермедиа, реальность такова, что существуют некоторые шаблоны UX, которые просто невозможно легко реализовать с его помощью. Каноническим примером такого рода шаблона, о котором мы упоминали ранее, является что-то вроде живой онлайн-таблицы: это просто слишком сложный пользовательский интерфейс со слишком большим количеством взаимозависимостей, чтобы его можно было эффективно реализовать посредством обмена гипермедиа с сервером.

В подобных случаях и каждый раз, когда вы чувствуете, что решение на основе htmx оказывается более сложным, чем другой подход, мы рекомендуем вам рассмотреть другую технологию. Будьте прагматичны и используйте правильный инструмент для работы. Вы всегда можете использовать htmx для тех частей вашего приложения, которые не так сложны и не требуют всей сложности реактивной среды, и сэкономить этот бюджет сложности для тех частей, которые требуют этого.

Мы рекомендуем вам изучить множество различных веб-технологий, обращая внимание на сильные и слабые стороны каждой из них. Это даст вам глубокий арсенал инструментов, к которому можно будет обратиться в случае возникновения проблем. Наш опыт показывает, что благодаря htmx гипермедиа становится инструментом, к которому вы можете часто обращаться.

Отладка

Нам не стыдно признаться: мы большие поклонники мероприятий. Они лежат в основе практически любого интересного пользовательского интерфейса и особенно полезны в DOM, когда они доступны для общего использования в HTML. Они позволяют создавать хорошо изолированное программное обеспечение, часто сохраняя при этом локальность поведения, которая нам так нравится.

Однако события не идеальны. Одна из областей, где с событиями может быть особенно сложно справиться, — это отладка : часто требуется знать, почему событие не происходит. Но где можно установить точку останова для того, чего не происходит? Ответ на данный момент таков: вы не можете.

Есть два метода, которые могут помочь в этом отношении: один предоставляется htmx, другой — Chrome, браузером Google.

Регистрация событий Htmx

Первый метод, предоставляемый самим htmx, — это вызов htmx.logAll()метода. Когда вы это сделаете, htmx будет регистрировать все внутренние события, которые происходят в ходе его работы, загрузки контента, реагирования на события и т. д.

Это может быть сложно, но разумная фильтрация поможет вам сосредоточиться на проблеме. Вот как (немного) выглядят журналы при нажатии на ссылку «docs» на https://htmx.org с logAll()включенным параметром:

htmx:configRequest
<a href="/docs/">
Object { parameters: {}, unfilteredParameters: {}, headers: {…}, target: body, verb: "get", errors: [], withCredentials: false, timeout: 0, path: "/docs/", triggeringEvent: a
, … }
htmx.js:439:29
htmx:beforeRequest
<a href="/docs/">
Object { xhr: XMLHttpRequest, target: body, requestConfig: {…}, etc: {}, pathInfo: {…}, elt: a
 }
htmx.js:439:29
htmx:beforeSend
<a class="htmx-request" href="/docs/">
Object { xhr: XMLHttpRequest, target: body, requestConfig: {…}, etc: {}, pathInfo: {…}, elt: a.htmx-request
 }
htmx.js:439:29
htmx:xhr:loadstart
<a class="htmx-request" href="/docs/">
Object { lengthComputable: false, loaded: 0, total: 0, elt: a.htmx-request
 }
htmx.js:439:29
htmx:xhr:progress
<a class="htmx-request" href="/docs/">
Object { lengthComputable: true, loaded: 4096, total: 19915, elt: a.htmx-request
 }
htmx.js:439:29
htmx:xhr:progress
<a class="htmx-request" href="/docs/">
Object { lengthComputable: true, loaded: 19915, total: 19915, elt: a.htmx-request
 }
htmx.js:439:29
htmx:beforeOnLoad
<a class="htmx-request" href="/docs/">
Object { xhr: XMLHttpRequest, target: body, requestConfig: {…}, etc: {}, pathInfo: {…}, elt: a.htmx-request
 }
htmx.js:439:29
htmx:beforeSwap
<body hx-ext="class-tools, preload">

Не совсем приятно для глаз, не так ли?

Но если вы глубоко вздохнете и прищуритесь, то увидите, что все не так уж и плохо: серия htmx-событий, некоторые из которых мы уже видели раньше (есть htmx:configRequest!), записываются в консоль вместе с элементом они срабатывают.

После небольшого чтения и фильтрации вы сможете разобраться в потоке событий, и это может помочь вам отладить проблемы, связанные с htmx.

Мониторинг событий в Chrome

Предыдущий метод полезен, если проблема возникает где-то внутри htmx, но что, если htmx вообще не запускается? Такое случается иногда, например, когда вы случайно где-то неправильно ввели название события.

В подобных случаях вам понадобится воспользоваться инструментом, доступным в самом браузере. К счастью, браузер Chrome от Google предоставляет очень полезную функцию, monitorEvents()которая позволяет отслеживать все события, происходящие на элементе.

Эта функция доступна только в консоли, поэтому вы не можете использовать ее в коде на своей странице. Но если вы работаете с htmx в Chrome и вам интересно, почему событие не запускается для элемента, вы можете открыть консоль разработчика и ввести следующее:

monitorEvents(document.getElementById("some-element"));

Затем это выведет на консоль все события, которые запускаются на элементе с идентификатором . some-elementЭто может быть очень полезно для понимания того, на какие именно события вы хотите реагировать с помощью htmx, или для устранения неполадок, почему ожидаемое событие не происходит.

Использование этих двух методов поможет вам (мы надеемся, что нечасто) устранять проблемы, связанные с событиями, при разработке с использованием htmx.

Вопросы безопасности

В целом, htmx и гипермедиа, как правило, более безопасны, чем тяжелые подходы JavaScript к созданию веб-приложений. Это связано с тем, что, перенося большую часть обработки на серверную часть, подход с использованием гипермедиа имеет тенденцию не предоставлять конечным пользователям большую часть поверхности вашей системы для манипуляций и махинаций.

Однако даже при использовании гипермедиа существуют ситуации, требующие осторожности при разработке. Особую озабоченность вызывают ситуации, когда пользовательский контент показывается другим пользователям: умный пользователь может попытаться вставить код htmx, который обманным путем заставит других пользователей щелкнуть контент, который инициирует действия, которые они не хотят предпринимать.

В общем, весь пользовательский контент должен экранироваться на стороне сервера, и большинство серверных платформ рендеринга предоставляют функциональные возможности для обработки этой ситуации. Но всегда есть риск, что что-то ускользнет.

Чтобы помочь вам лучше спать по ночам, htmx предоставляет атрибут hx-disable. Когда этот атрибут помещается в элемент, все атрибуты htmx внутри этого элемента будут игнорироваться.

Политики безопасности контента и Htmx

Политика безопасности контента (CSP) — это технология браузера, которая позволяет обнаруживать и предотвращать определенные типы атак, основанных на внедрении контента. Полное обсуждение CSP выходит за рамки этой книги, но за дополнительной информацией мы отсылаем вас к статье Mozilla Developer Network по этой теме.

Распространенной функцией, которую можно отключить с помощью CSP, является eval()функция JavaScript, которая позволяет оценивать произвольный код JavaScript из строки. Это оказалось проблемой безопасности, и многие команды решили, что не стоит рисковать, оставляя его включенным в своих веб-приложениях.

Htmx не использует интенсивно eval(), поэтому CSP с этим ограничением подойдет. Единственная функция, на которую все-таки полагается, eval()— это фильтры событий, описанные выше. Если вы решите отключить его eval()для своего веб-приложения, вы не сможете использовать синтаксис фильтрации событий.

Настройка

Для htmx доступно большое количество вариантов конфигурации. Вот некоторые примеры того, что вы можете настроить:

  • Стиль подкачки по умолчанию

  • Задержка замены по умолчанию

  • Тайм-аут запросов AJAX по умолчанию

Полный список опций конфигурации можно найти в разделе config основной htmx-документации .

Htmx обычно настраивается с помощью metaтега, расположенного в заголовке страницы. Имя метатега должно быть htmx-config, а атрибут содержимого должен содержать переопределения конфигурации в формате JSON. Вот пример:

<meta name="htmx-config" content='{"defaultSwapStyle":"outerHTML"}'>

В этом случае мы переопределяем стиль подкачки по умолчанию с обычного innerHTMLна outerHTML. Это может быть полезно, если вы используете его outerHTMLчаще innerHTMLи хотите избежать необходимости явно устанавливать это значение подкачки во всем приложении.

Примечания к HTML: семантический HTML

Сказание людям «использовать семантический HTML» вместо «читать спецификацию» привело к тому, что многие люди догадывались о значении тегов: «Мне это кажется довольно семантическим!» — вместо того, чтобы заниматься спец.

Я думаю, что когда меня просят написать осмысленный HTML, это лучше освещает путь к пониманию того, что речь идет не о том, что текст значит для людей, а об использовании тегов для целей, указанных в спецификациях, для удовлетворения потребностей программного обеспечения, такого как браузеры, вспомогательные технологии. и поисковые системы.

— https://t-ravis.com/post/doc/semantic_the_8_letter_s-word/

Мы рекомендуем говорить и писать о совместимом HTML. (Мы всегда можем покататься на велосипеде дальше). Используйте элементы в полной мере, предусмотренные спецификацией HTML, и позвольте программному обеспечению извлечь из них любое значение, которое они могут.


Last updated