Глава 04. Расширение HTML как гипермедиа
Last updated
Last updated
В предыдущей главе мы представили простое гипермедийное приложение в стиле Web 1.0 для управления контактами. Наше приложение поддерживало обычные CRUD-операции для контактов, а также простой механизм поиска контактов. Наше приложение было создано с использованием только форм и тегов привязки — традиционных элементов управления гипермедиа, используемых для взаимодействия с серверами. Приложение обменивается гипермедиа (HTML) с сервером через HTTP, отправляя GET
HTTP POST
-запросы и получая в ответ полные HTML-документы.
Это базовое веб-приложение, но оно также определенно является приложением, управляемым гипермедиа. Он надежен, использует собственные технологии Интернета и прост для понимания.
Так что же не нравится в приложении?
К сожалению, у нашего приложения есть несколько проблем, характерных для приложений в стиле Web 1.0:
С точки зрения пользовательского опыта: заметное обновление происходит при перемещении между страницами приложения или при создании, обновлении или удалении контакта. Это связано с тем, что каждое взаимодействие с пользователем (щелчок по ссылке или отправка формы) требует полного обновления страницы с обработкой совершенно нового HTML-документа после каждого действия.
С технической точки зрения все обновления выполняются методом POST
HTTP. И это несмотря на то, что существуют более логичные действия и типы HTTP-запросов, PUT
которые DELETE
имели бы больше смысла для некоторых реализованных нами операций. В конце концов, если бы мы хотели удалить ресурс, не было бы разумнее использовать DELETE
для этого HTTP-запрос? По иронии судьбы, поскольку мы использовали чистый HTML, мы не можем получить доступ ко всем выразительным возможностям HTTP, который был разработан специально для HTML.
Первый момент, в частности, заметен в приложениях в стиле Web 1.0, подобных нашему, и именно он ответственен за то, что они получили репутацию «неуклюжих» по сравнению с их более сложными собратьями одностраничных приложений на основе JavaScript.
Мы могли бы решить эту проблему, приняв структуру одностраничного приложения и обновив нашу серверную часть для предоставления ответов на основе JSON. Одностраничные приложения устраняют неуклюжесть приложений Web 1.0, обновляя веб-страницу без ее обновления: они могут изменять части объектной модели документа (DOM) существующей страницы без необходимости замены (и повторной визуализации) всей страницы.
ДОМ
DOM — это внутренняя модель, которую браузер создает при обработке HTML, формируя дерево «узлов» для тегов и другого контента в HTML. DOM предоставляет программный API JavaScript, который позволяет обновлять узлы на странице напрямую, без использования гипермедиа. Используя этот API, код JavaScript может вставлять новый контент, а также удалять или обновлять существующий контент, полностью выходя за рамки обычного механизма запросов браузера.
Существует несколько различных стилей SPA, но, как мы обсуждали в главе 1, наиболее распространенным сегодня подходом является привязка DOM к модели JavaScript, а затем позволить инфраструктуре SPA, такой как React или Vue, реактивно обновлять , модель JavaScript обновляется: вы вносите изменения в объект JavaScript, который хранится локально в памяти браузера, и веб-страница «волшебным образом» обновляет свое состояние, чтобы отразить изменение в модели.
В этом стиле приложения связь с сервером обычно осуществляется через API данных JSON, при этом приложение жертвует преимуществами гипермедиа, чтобы обеспечить лучший и более плавный пользовательский интерфейс.
Многие веб-разработчики сегодня даже не рассматривают подход к гипермедиа из-за ощущения «наследия» этих приложений в стиле Web 1.0.
Теперь вторая, более техническая проблема, которую мы упомянули, может показаться вам немного педантичной, и мы первые, кто признает, что разговоры о REST и о том, какое действие HTTP подходит для конкретной операции, могут стать очень утомительными. Но все же странно, что при использовании простого HTML невозможно использовать всю функциональность HTTP!
Это кажется неправильным, не так ли?
Прежде чем мы перейдем к тому, как htmx позволяет нам улучшить UX нашего приложения в стиле Web 1.0, давайте вернемся к тегу гиперссылки/привязки из главы 1. Напомним, гиперссылка — это так называемый элемент управления гипермедиа, механизм, который описывает своего рода взаимодействие с сервером путем кодирования информации об этом взаимодействии непосредственно и полностью внутри самого элемента управления.
Рассмотрим еще раз этот простой тег привязки, который при интерпретации браузером создает гиперссылку на веб-сайт этой книги:
Давайте подробно разберем, что происходит с этой ссылкой:
Браузер отобразит на экране текст «Hypermedia Systems», вероятно, с украшением, указывающим, что он кликабельен.
Затем, когда пользователь нажимает на текст…
Браузер загрузит тело HTML-ответа HTTP в окно браузера, заменив текущий документ.
Итак, у нас есть четыре аспекта такой простой гипермедийной ссылки, причем последние три аспекта обеспечивают механизм, который отличает гиперссылку от «обычного» текста и, таким образом, делает ее элементом управления гипермедиа.
Теперь давайте подумаем о том, как мы можем обобщить эти последние три аспекта гиперссылки.
Подумайте: что делает теги привязки (и формы) такими особенными?
Почему другие элементы также не могут отправлять HTTP-запросы?
Например, почему button
элементы не могут отправлять HTTP-запросы? Кажется произвольным обертывать тег формы вокруг кнопки, например, только для того, чтобы удаление контактов работало в нашем приложении.
Возможно: другие элементы также должны иметь возможность отправлять HTTP-запросы. Возможно, другие элементы смогут действовать как элементы управления гипермедиа самостоятельно.
Это наша первая возможность обобщить HTML как гипермедиа.
Возможность 1
HTML можно расширить, чтобы любой элемент мог отправлять запрос на сервер и действовать как элемент управления гипермедиа.
Далее давайте рассмотрим событие, которое запускает запрос к серверу по нашей ссылке: событие клика.
Что же такого особенного в щелчках (в случае привязок) или отправке (в случае форм) вещей? В конце концов, это всего лишь два из многих, многих событий, которые инициируются DOM. Такие события, как нажатие мыши, нажатие клавиши или размытие, — это все события, которые вы можете использовать для выдачи HTTP-запроса.
Почему другие события не могут также инициировать запросы?
Это дает нам вторую возможность расширить выразительность HTML:
Возможность 2
HTML можно расширить, чтобы любое событие, а не только щелчок, как в случае с гиперссылками, могло запускать HTTP-запросы.
Немного более технический подход приводит нас к проблеме, о которой мы говорили ранее: простой HTML дает нам доступ только к действиям GET
HTTP POST
.
HTTP означает протокол передачи гипертекста, однако формат HTML, для которого он был явно разработан, поддерживает только два из пяти типов запросов, ориентированных на разработчиков. Вам придется использовать JavaScript и выполнить запрос AJAX, чтобы получить остальные три DELETE
: PUT
и PATCH
.
Давайте вспомним, что представляют собой эти различные типы HTTP-запросов:
GET
соответствует «получению» представления ресурса из URL-адреса: это чистое чтение без изменения ресурса.
POST
отправляет объект (или данные) в данный ресурс, часто создавая или изменяя ресурс и вызывая изменение состояния.
PUT
отправляет объект (или данные) в данный ресурс для обновления или замены, что снова может привести к изменению состояния.
PATCH
аналогично, PUT
но подразумевает частичное обновление и изменение состояния, а не полную замену объекта.
DELETE
удаляет данный ресурс.
Эти операции во многом соответствуют операциям CRUD, которые мы обсуждали в главе 2. Предоставляя нам доступ только к двум из пяти, HTML ограничивает нашу способность в полной мере использовать преимущества HTTP.
Это дает нам третью возможность расширить выразительность HTML:
Возможность 3
HTML можно расширить, чтобы обеспечить доступ к отсутствующим трем методам HTTP PUT
: PATCH
и DELETE
.
В качестве последнего замечания рассмотрим последний аспект гиперссылки: она заменяет весь экран , когда пользователь нажимает на нее.
Оказывается, эта техническая деталь является основной причиной плохого взаимодействия с пользователем в приложениях Web 1.0. Полное обновление страницы может вызвать вспышку нестилизованного контента, когда контент «перепрыгивает» на экране при переходе от исходной формы к окончательной стилизованной форме. Он также разрушает состояние прокрутки пользователя, прокручивая страницу до верха, удаляет фокус с элемента, находящегося в фокусе, и т. д.
Но, если задуматься, не существует правила, согласно которому обмен гипермедиа должен заменять весь документ.
Это дает нам четвертую, последнюю и, возможно, самую важную возможность обобщить HTML:
Возможность 4
HTML можно расширить, чтобы разрешить ответы на запросы о замене элементов в текущем документе, а не требовать замены всего документа.
На самом деле это очень старая концепция гипермедиа. Тед Нельсон в своей книге «Литературные машины» 1980 года ввел термин « трансклюзия» , чтобы отразить эту идею: включение контента в существующий документ посредством ссылки на гипермедиа. Если бы HTML поддерживал этот стиль «динамического включения», то приложения, управляемые гипермедиа, могли бы функционировать скорее как одностраничные приложения, где только часть DOM обновляется в результате определенного взаимодействия с пользователем или сетевого запроса.
Эти четыре возможности дают нам возможность расширить возможности HTML далеко за пределы его текущих возможностей, но таким образом, чтобы это полностью соответствовало гипермедийной модели Интернета. Основы HTML, HTTP, браузера и т. д. кардинально не изменятся. Скорее, эти обобщения существующих функций, уже имеющихся в HTML, просто позволят нам добиться большего с помощью HTML.
С практической точки зрения «начало работы» htmx — это простая, независимая и автономная библиотека JavaScript, которую можно добавить в веб-приложение, просто включив ее через тег script
в свой head
элемент.
Благодаря этой простой модели установки вы можете воспользоваться такими инструментами, как общедоступные CDN, для установки библиотеки.
Мы также помечаем сценарий как crossorigin="anonymous"
таковой, чтобы в CDN не отправлялись учетные данные.
Если вы привыкли к современной разработке на JavaScript со сложными системами сборки и большим количеством зависимостей, для вас может стать приятным сюрпризом обнаружить, что это все, что нужно для установки htmx.
Это в духе раннего Интернета, когда вы могли просто включить тег сценария, и все «просто работало».
Если вы не хотите использовать CDN, вы можете загрузить htmx в свою локальную систему и настроить тег сценария так, чтобы он указывал на то, где вы храните свои статические ресурсы. Или у вас может быть система сборки, которая автоматически устанавливает зависимости. В этом случае вы можете использовать имя Node Package Manager (npm): для библиотеки htmx.org
и установить ее обычным способом, который поддерживает ваша система сборки.
После установки htmx вы можете сразу же начать его использовать.
И здесь мы подходим к интересной части htmx: htmx не требует от вас, пользователя htmx, написания какого-либо JavaScript.
Вместо этого вы будете использовать атрибуты , размещенные непосредственно в элементах вашего HTML, чтобы обеспечить более динамичное поведение. Htmx расширяет возможности HTML как гипермедиа и создан для того, чтобы сделать это расширение максимально естественным и совместимым с существующими концепциями HTML. Точно так же, как тег привязки использует href
атрибут для указания URL-адреса для получения, а формы используют action
атрибут для указания URL-адреса для отправки формы, htmx использует атрибуты HTML для указания URL-адреса, на который должен быть отправлен HTTP-запрос.
Давайте посмотрим на первую особенность htmx: возможность любого элемента веб-страницы отправлять HTTP-запросы. Это основная функциональность, предоставляемая htmx, и она состоит из пяти атрибутов, которые можно использовать для выдачи пяти различных типов HTTP-запросов, ориентированных на разработчика:
hx-get
- выдает HTTP- GET
запрос.
hx-post
- выдает HTTP- POST
запрос.
hx-put
- выдает HTTP- PUT
запрос.
hx-patch
- выдает HTTP- PATCH
запрос.
hx-delete
- выдает HTTP- DELETE
запрос.
Каждый из этих атрибутов, помещенный в элемент, сообщает библиотеке htmx: «Когда пользователь щелкает (или что-то еще) на этом элементе, выдает HTTP-запрос указанного типа».
Значения этих атрибутов аналогичны значениям как href
в якорях, так и action
в формах: вы указываете URL-адрес, на который хотите отправить данный тип HTTP-запроса. Обычно это делается через путь, относящийся к серверу.
Например, если бы мы хотели, чтобы кнопка отправляла запрос, GET
мы /contacts
бы написали следующий HTML:
Простая кнопка, отправляющая HTTP- GET
запрос на /contacts
.
Библиотека htmx увидит hx-get
атрибут этой кнопки и подключит некоторую логику JavaScript для выдачи HTTP- GET
запроса AJAX к /contacts
пути, когда пользователь нажимает на нее.
Очень прост для понимания и очень совместим с остальной частью HTML.
С помощью запроса, отправленного кнопкой выше, мы подходим, пожалуй, к самой важной вещи, которую нужно понять о htmx: он ожидает, что ответом на этот запрос AJAX будет HTML . Htmx — это расширение HTML. Собственный элемент управления гипермедиа, такой как тег привязки, обычно получает ответ HTML на созданный им HTTP-запрос. Аналогично, htmx ожидает, что сервер ответит на запросы, которые он делает с помощью HTML.
Это может удивить веб-разработчиков, которые привыкли отвечать на запрос AJAX с помощью JSON, который, безусловно, является наиболее распространенным форматом ответа для таких запросов. Но запросы AJAX — это всего лишь HTTP-запросы, и не существует правила, согласно которому они должны использовать JSON. Напомним еще раз, что AJAX означает асинхронный JavaScript и XML, поэтому JSON — это уже шаг в сторону от формата, изначально предусмотренного для этого API: XML.
Htmx просто идет в другом направлении и ожидает HTML.
Существует важное различие между ответами HTTP на «обычные» HTTP-запросы, управляемые привязкой или формой, и на запросы на базе htmx: в случае запросов, запускаемых htmx, ответы могут представлять собой частичные биты HTML.
Как вы увидите, при взаимодействии на основе HTML мы часто не заменяем весь документ. Скорее мы используем «включение» для включения контента в существующий документ. По этой причине зачастую нет необходимости или нежелательно переносить весь HTML-документ с сервера в браузер.
Этот факт можно использовать для экономии пропускной способности, а также времени загрузки ресурсов. С сервера клиенту передается меньше общего содержимого, и нет необходимости повторно обрабатывать тег head
с помощью таблиц стилей, тегов сценариев и т. д.
При нажатии кнопки «Получить контакты» частичный HTML-ответ может выглядеть примерно так:
Это просто неупорядоченный список контактов с некоторыми кликабельными элементами. Обратите внимание, что здесь нет ни открывающего html
тега, ни head
тега и т. д.: это необработанный HTML-список без каких-либо украшений вокруг него. Ответ в реальном приложении может содержать более сложный HTML-код, чем этот простой список, но даже если бы он был более сложным, это не обязательно должна быть целая страница HTML: это может быть просто «внутреннее» содержимое HTML-представления для этот ресурс.
Теперь этот простой ответ в виде списка идеально подходит для htmx. Htmx просто возьмет возвращенный контент и затем заменит его в DOM вместо какого-либо элемента на странице. (Подробнее о том, где именно он будет помещен в DOM, чуть позже.) Такая замена HTML-контента является быстрой и эффективной, поскольку она использует существующий встроенный анализатор HTML в браузере, а не требует значительного объема операций на стороне клиента. JavaScript для выполнения.
Этот небольшой ответ HTML показывает, как htmx остается в рамках парадигмы гипермедиа: точно так же, как «обычный» элемент управления гипермедиа в «обычном» веб-приложении, мы видим, что гипермедиа передается клиенту без сохранения состояния и единообразным образом.
Эта кнопка просто дает нам немного более сложный механизм создания веб-приложения с использованием гипермедиа.
Теперь, учитывая, что htmx выдал запрос и получил в ответ некоторый HTML-код, и что мы собираемся заменить этот контент на существующую страницу (вместо того, чтобы заменять всю страницу), возникает вопрос: где должен находиться этот новый контент? поставил?
Оказывается, поведение htmx по умолчанию заключается в том, чтобы просто поместить возвращаемый контент внутрь элемента, который инициировал запрос. В случае с нашей кнопкой это не очень хорошо: в итоге мы получим список контактов, неуклюже встроенный в элемент кнопки. Это будет выглядеть довольно глупо и явно не то, чего мы хотим.
К счастью, htmx предоставляет еще один атрибут, hx-target
который можно использовать для точного указания места в DOM, где следует разместить новый контент. Значением атрибута является селекторhx-target
каскадной таблицы стилей (CSS) , который позволяет указать элемент, в который будет помещен новый гипермедийный контент.
Давайте добавим div
тег, который окружает кнопку с идентификатором main
. Затем мы нацелимся на это div
с помощью ответа:
Элемент div
, который обертывает кнопку.
Атрибут hx-target
, указывающий цель ответа.
Мы добавили hx-target="#main"
к нашей кнопке #main
селектор CSS с надписью «Эта штука с идентификатором «main».
Используя селекторы CSS, htmx основывается на знакомых и стандартных концепциях HTML. Это сводит к минимуму дополнительную концептуальную нагрузку при работе с htmx.
Учитывая эту новую конфигурацию, как будет выглядеть HTML-код на клиенте после того, как пользователь нажмет эту кнопку и ответ будет получен и обработан?
Это будет выглядеть примерно так:
HTML-код ответа был заменен на div
, заменив кнопку, которая инициировала запрос. Включение! И это произошло «в фоновом режиме» через AJAX, без неуклюжего обновления страницы.
Теперь, возможно, мы не хотим загружать содержимое ответа сервера в div как дочерние элементы. Возможно, по какой-то причине мы хотим заменить весь div ответом. Чтобы справиться с этим, htmx предоставляет еще один атрибут, hx-swap
который позволяет вам точно указать , как содержимое должно быть заменено в DOM.
Атрибут hx-swap
поддерживает следующие значения:
innerHTML
— По умолчанию заменяется внутренний HTML-код целевого элемента.
outerHTML
- Замените весь целевой элемент ответом.
beforebegin
- Вставьте ответ перед целевым элементом.
afterbegin
- Вставьте ответ перед первым дочерним элементом целевого элемента.
beforeend
- Вставьте ответ после последнего дочернего элемента целевого элемента.
afterend
- Вставьте ответ после целевого элемента.
delete
- Удаляет целевой элемент независимо от ответа.
none
- Обмен производиться не будет.
Первые два значения innerHTML
и outerHTML
берутся из стандартных свойств DOM, которые позволяют заменять содержимое внутри элемента или вместо всего элемента соответственно.
Следующие четыре значения взяты из Element.insertAdjacentHTML()
DOM API, которые позволяют размещать элемент или элементы вокруг данного элемента различными способами.
Последние два значения delete
относятся none
к htmx. Первый вариант удалит целевой элемент из DOM, а второй вариант ничего не сделает (возможно, вы захотите работать только с заголовками ответов — продвинутый метод, который мы рассмотрим позже в книге).
Опять же, вы можете видеть, что htmx максимально приближен к существующим веб-стандартам, чтобы минимизировать концептуальную нагрузку, необходимую для его использования.
Итак, давайте рассмотрим случай, когда вместо замены innerHTML
содержимого основного элемента div, указанного выше, мы хотим заменить весь элемент div ответом HTML.
Для этого потребуется лишь небольшое изменение нашей кнопки, добавив новый hx-swap
атрибут:
Атрибут hx-swap
указывает, как заменять новый контент.
Теперь при получении ответа весь div будет заменен гипермедийным контентом:
Вы можете видеть, что после этого изменения целевой элемент div был полностью удален из DOM, а список, возвращенный в ответ, заменил его.
Далее в книге мы увидим дополнительные варианты использования hx-swap
, например, когда мы реализуем бесконечную прокрутку в нашем приложении для управления контактами.
Обратите внимание, что с помощью атрибутов hx-get
, hx-post
, и мы рассмотрели две из четырех возможностей улучшения, которые мы перечислили относительно простого HTML hx-put
:hx-patchhx-delete
Возможность 1: Теперь мы можем отправить HTTP-запрос с любым элементом (в данном случае мы используем кнопку).
Возможность 3: Мы можем отправить любой HTTP-запрос, который нам нужен, PUT
и PATCH
, DELETE
в частности.
И hx-target
мы hx-swap
устранили третий недостаток: требование замены всей страницы.
Возможность 4: Теперь мы можем заменить любой элемент на нашей странице с помощью включения, и мы можем сделать это любым способом.
Итак, используя всего семь относительно простых дополнительных атрибутов, мы устранили большинство недостатков HTML как гипермедиа, которые мы выявили ранее.
Что дальше? Вспомним еще одну отмеченную нами возможность: тот факт, что только click
событие (в привязке) или submit
событие (в форме) может инициировать HTTP-запрос. Давайте посмотрим, как мы можем устранить это ограничение.
До сих пор мы использовали кнопку для отправки запроса с помощью htmx. Вы, вероятно, интуитивно поняли, что кнопка выдает свой запрос, когда вы нажимаете на кнопку, поскольку, ну, это то, что вы делаете с кнопками: вы нажимаете на них.
И да, по умолчанию, когда hx-get
на кнопку помещается та или иная аннотация, управляющая запросами, из htmx, запрос будет выдан при нажатии кнопки.
Однако htmx обобщает понятие события, запускающего запрос, используя, как вы уже догадались, другой атрибут: hx-trigger
. Атрибут hx-trigger
позволяет указать одно или несколько событий, которые заставят элемент инициировать HTTP-запрос.
Часто вам не нужно использовать его hx-trigger
, поскольку событие запуска по умолчанию будет тем, что вы хотите. Событие, вызывающее срабатывание по умолчанию, зависит от типа элемента и должно быть достаточно интуитивно понятным:
Запросы на элементы input
, textarea
& select
запускаются событием change
.
Запросы к form
элементам запускаются по submit
событию.
Запросы ко всем остальным элементам инициируются событием click
.
Чтобы продемонстрировать, как это hx-trigger
работает, рассмотрим следующую ситуацию: мы хотим инициировать запрос на нашей кнопке, когда мышь входит в нее. Конечно, это не очень хороший UX-шаблон, но будьте терпеливы: мы просто используем этот пример.
Чтобы реагировать на нажатие мыши на кнопку, мы добавим к нашей кнопке следующий атрибут:
Отправьте запрос по…mouseenter
событию .
Теперь, когда этот hx-trigger
атрибут установлен, всякий раз, когда мышь нажимает на эту кнопку, будет инициироваться запрос. Глупо, но это работает.
Давайте попробуем что-нибудь более реалистичное и потенциально полезное: добавим поддержку сочетания клавиш для загрузки контактов Ctrl-L
(для «Загрузить»). Для этого нам нужно будет воспользоваться дополнительным синтаксисом, который hx-trigger
поддерживает атрибут: фильтрами событий и дополнительными аргументами.
Фильтры событий — это механизм определения того, должно ли данное событие инициировать запрос или нет. Они применяются к событию путем добавления после него квадратных скобок: someEvent[someFilter]
. Сам фильтр представляет собой выражение JavaScript, которое будет оцениваться при возникновении данного события. Если результат правдивый в смысле JavaScript, запрос инициируется. В противном случае запрос не будет запущен.
В случае сочетаний клавиш мы хотим перехватить событие keyup
в дополнение к событию щелчка:
Триггер с двумя событиями.
Обратите внимание, что у нас есть список событий, разделенных запятыми, которые могут активировать этот элемент, что позволяет нам реагировать более чем на одно потенциальное событие-триггер. Мы по-прежнему хотим отреагировать на click
событие и загрузить контакты, помимо обработки Ctrl-L
сочетания клавиш.
К сожалению, у нашего дополнения есть две проблемы keyup
: в его нынешнем виде оно будет инициировать запросы при любом событии нажатия клавиши. И, что еще хуже, он сработает только тогда, когда внутри этой кнопки произойдет нажатие клавиши. Пользователю нужно будет нажать на кнопку, чтобы сделать ее активной, а затем начать печатать.
Давайте исправим эти две проблемы. Чтобы исправить первый, мы будем использовать триггерный фильтр, чтобы проверить, что клавиши Control и клавиши «L» нажаты вместе:
keyup
теперь есть фильтр, поэтому необходимо нажать клавишу управления и L.
Триггерный фильтр в данном случае ctrlKey && key == 'l'
. Это можно прочитать как «Событие нажатия клавиши, где свойство ctrlKey имеет значение true, а свойство key равно l». Обратите внимание, что свойства ctrlKey
и key
разрешаются по событию, а не по глобальному пространству имен, поэтому вы можете легко фильтровать свойства данного события. Однако для фильтра вы можете использовать любое выражение: вызов глобальной функции JavaScript, например, вполне приемлем.
Итак, этот фильтр ограничивает события нажатия клавиш, которые запускают запрос, только Ctrl-L
нажатиями. Однако у нас все еще есть проблема: в нынешнем виде только keyup
события внутри кнопки запускают запрос.
Если вы не знакомы с моделью всплытия событий JavaScript: события обычно «всплывают» до родительских элементов. Таким образом, событие типа keyup
будет вызвано сначала на элементе, находящемся в фокусе, а затем на его родительском (включающем) элементе и так далее, пока не достигнет объекта верхнего уровня, document
который является корнем всех других элементов.
Чтобы поддерживать глобальное сочетание клавиш, которое работает независимо от того, какой элемент находится в фокусе, мы воспользуемся всплыванием событий и функцией, которую hx-trigger
поддерживает атрибут: возможность прослушивать события в других элементах . Синтаксисом для этого является from:
модификатор, который добавляется после имени события и позволяет вам указать конкретный элемент для прослушивания данного события при использовании селектора CSS.
В данном случае мы хотим прослушивать элемент body
, который является родительским для всех видимых элементов на странице.
Вот как hx-trigger
выглядит наш обновленный атрибут:
Прослушайте событие «keyup» в теге body
.
Теперь, помимо кликов, кнопка будет прослушивать keyup
события в теле страницы. Таким образом, он выдает запрос при нажатии на него, а также всякий раз, когда кто-то нажимает Ctrl-L
на тело страницы.
И теперь у нас есть хорошее сочетание клавиш для нашего приложения, управляемого гипермедиа.
Атрибут hx-trigger
поддерживает гораздо больше модификаторов и более сложен, чем другие атрибуты htmx. Это потому, что события, как правило, сложны и требуют большого количества деталей, чтобы все получилось правильно. Однако триггера по умолчанию часто бывает достаточно, и вам обычно не нужно использовать сложные hx-trigger
функции при использовании htmx.
Даже с более сложными спецификациями триггеров, такими как только что добавленное сочетание клавиш, общее ощущение htmx является скорее декларативным , чем императивным . Это позволяет приложениям на основе htmx «ощущаться» как стандартные приложения Web 1.0, в отличие от добавления значительного количества JavaScript.
И эй, проверьте это! Мы hx-trigger
рассмотрели последнюю возможность улучшения HTML, которую мы обрисовали в начале этой главы:
Возможность 2. Мы можем использовать любое событие для запуска HTTP-запроса.
В общей сложности это восемь, посчитайте, восемь атрибутов, которые полностью попадают в ту же концептуальную модель, что и обычный HTML, и которые, расширяя HTML как гипермедиа, открывают внутри него совершенно новый мир возможностей взаимодействия с пользователем.
Вот таблица, в которой обобщаются эти возможности и атрибуты htmx, которые их реализуют:
Возможности улучшения HTMLЛюбой элемент должен иметь возможность отправлять HTTP-запросы.
hx-get
, hx-post
, hx-put
, hx-patch
,hx-delete
Любое событие должно иметь возможность инициировать HTTP-запрос.
hx-trigger
Любое действие HTTP должно быть доступно.
hx-put
, hx-patch
,hx-delete
Любое место на странице должно быть заменяемым (трансклюзией)
hx-target
,hx-swap
До сих пор мы рассматривали ситуацию, когда кнопка отправляет простой GET
запрос. Концептуально это очень близко к тому, что может делать тег привязки. Но в приложениях на основе HTML есть еще один встроенный элемент управления гипермедиа: формы. Формы используются для передачи дополнительной информации, помимо URL-адреса, на сервер в запросе.
Эта информация фиксируется посредством ввода и элементов, подобных вводу, внутри формы с помощью различных типов тегов ввода, доступных в HTML.
Htmx позволяет включать эту дополнительную информацию таким образом, чтобы она отражала сам HTML.
Самый простой способ передать входные значения с помощью запроса в htmx — заключить элемент, отправляющий запрос, в тег формы.
Давайте возьмем нашу оригинальную кнопку для поиска контактов и перепрофилируем ее для поиска контактов:
С закрывающим тегом формы будут отправлены все входные значения.
Новый ввод для ввода текста поиска пользователя.
Наша кнопка была преобразована в файл hx-post
.
Здесь мы добавили тег формы вокруг кнопки, а также поле поиска, которое можно использовать для ввода термина для поиска контактов.
Теперь, когда пользователь нажимает на кнопку, значение ввода с идентификатором search
будет включено в запрос. Это связано с тем, что существует тег формы, охватывающий как кнопку, так и ввод: когда запускается запрос, управляемый htmx, htmx будет искать в иерархии DOM включающую форму, и, если она найдена, она будет включать все значения из этой формы. (Иногда это называют «сериализацией» формы.)
Вы могли заметить, что кнопка переключилась с GET
запроса на POST
запрос. Это связано с тем, что по умолчанию htmx не включает ближайшую закрывающую форму для GET
запросов, но включает форму для всех других типов запросов.
Это может показаться немного странным, но это позволяет избежать мусора URL-адресов, которые используются в формах при работе с записями истории, о чем мы поговорим чуть позже. И вы всегда можете включить значения включающей формы в элемент, который использует атрибут GET
, hx-include
описанный ниже.
Хотя включение всех входных данных, которые вы хотите включить в запрос, является наиболее распространенным подходом к входным данным в htmx-запросах, это не всегда возможно или желательно: теги формы могут иметь последствия для макета и просто не могут быть размещены в некоторых местах HTML-документов. Хорошим примером последней ситуации являются tr
элементы строки таблицы ( ): form
тег не является допустимым дочерним или родительским элементом строк таблицы, поэтому вы не можете разместить форму внутри или вокруг строки данных в таблице.
Чтобы решить эту проблему, htmx предоставляет механизм включения входных значений в запросы: hx-include
атрибут. Атрибут hx-include
позволяет вам выбирать входные значения, которые вы хотите включить в запрос, с помощью селекторов CSS.
Вот приведенный выше пример, переработанный для включения ввода, с удалением формы:
hx-include
может использоваться для включения значений непосредственно в запрос.
Атрибут hx-include
принимает значение селектора CSS и позволяет вам точно указать, какие значения отправлять вместе с запросом. Это может быть полезно, если сложно совместить элемент, выдающий запрос, со всеми желаемыми входными данными.
Это также полезно, когда вы действительно хотите отправить значения с помощью запроса GET
и преодолеть поведение htmx по умолчанию.
Относительные селекторы CSS
Атрибут hx-include
и, по сути, большинство атрибутов, которые принимают селектор CSS, также поддерживают относительные селекторы CSS. Они позволяют вам указать селектор CSS относительно элемента, для которого он объявлен. Вот некоторые примеры:
closest
:: Найдите ближайший родительский элемент, соответствующий данному селектору, например, closest form
.
next
:: Найдите следующий элемент (сканируя вперед), соответствующий данному селектору, например, next input
.
previous
:: Найдите предыдущий элемент (сканируя назад), соответствующий данному селектору, например, previous input
.
find
:: Найдите следующий элемент внутри этого элемента, соответствующий данному селектору, например, find input
.
this
:: Текущий элемент.
Использование относительных селекторов CSS часто позволяет избежать генерации идентификаторов элементов, поскольку вместо этого вы можете воспользоваться их локальной структурной компоновкой.
Последний способ включения значений в запросы, управляемые htmx, — это использование атрибута hx-vals
, который позволяет включать в запрос «статические» значения. Это может быть полезно, если у вас есть дополнительная информация, которую вы хотите включить в запросы, но вы не хотите, чтобы эта информация была встроена, например, в скрытые входные данные (что было бы стандартным механизмом включения дополнительной скрытой информации в HTML). .)
Вот пример hx-vals
:
hx-vals
, значение JSON, которое нужно включить в запрос.
Параметр state
со значением MT
будет включен в GET
запрос, в результате чего путь и параметры будут выглядеть следующим образом: /contacts?state=MT
. Обратите внимание, что мы изменили hx-vals
атрибут, чтобы использовать его значение в одинарных кавычках. Это связано с тем, что JSON строго требует двойных кавычек и, следовательно, чтобы избежать экранирования, нам нужно было использовать форму одинарных кавычек для значения атрибута.
Вы также можете hx-vals
использовать префикс js:
и передавать значения, оцененные во время запроса, что может быть полезно для включения таких вещей, как динамически поддерживаемая переменная или значение из сторонней библиотеки JavaScript.
Например, если state
переменная поддерживалась динамически, с помощью некоторого JavaScript, и существовала функция JavaScript, getCurrentState()
которая возвращала текущее выбранное состояние, ее можно было бы динамически включать в htmx-запросы следующим образом:
С js:
префиксом это выражение будет оцениваться во время отправки.
Эти три механизма: использование form
тегов, использование hx-include
атрибута и использование hx-vals
атрибута позволяют вам включать значения в ваши запросы гипермедиа с помощью htmx таким образом, который должен быть очень знакомым и соответствовать духу HTML, а также дают вам гибкость добиться того, чего хочешь.
У нас есть последняя часть функциональности, чтобы завершить наш обзор htmx: поддержка истории браузера. Когда вы используете обычные HTML-ссылки и формы, ваш браузер будет отслеживать все страницы, которые вы посетили. Затем вы можете использовать кнопку «Назад», чтобы вернуться на предыдущую страницу, и как только вы это сделаете, вы можете использовать кнопку «Вперед», чтобы перейти к исходной странице, на которой вы были.
Это понятие истории было одной из убийственных особенностей раннего Интернета. К сожалению, оказывается, что история становится сложнее, когда вы переходите к парадигме одностраничного приложения. Запрос AJAX сам по себе не регистрирует веб-страницу в истории вашего браузера, и это хорошо: запрос AJAX может не иметь ничего общего с состоянием веб-страницы (возможно, он просто записывает некоторую активность в браузере). ), поэтому было бы нецелесообразно создавать новую запись истории для взаимодействия.
Однако в одностраничном приложении, вероятно, будет много взаимодействий, управляемых AJAX, где уместно создать запись истории. Существует API JavaScript для работы с историей браузера, но этот API глубоко раздражает и с ним сложно работать, поэтому разработчики JavaScript его часто игнорируют.
Если вы когда-либо использовали одностраничное приложение и случайно нажали кнопку «Назад» только для того, чтобы потерять все состояние приложения и вам пришлось начинать заново, вы видели эту проблему в действии.
В htmx, как и в средах одностраничных приложений, вам часто придется явно работать с API истории. К счастью, поскольку htmx очень близок к собственной модели Интернета и является декларативным, правильную историю веб-поиска обычно гораздо проще сделать в приложении на основе htmx.
Рассмотрим кнопку, которую мы рассматривали для загрузки контактов:
В нынешнем виде, если вы нажмете эту кнопку, он извлечет содержимое /contacts
и загрузит его в элемент с идентификатором main
, но не создаст новую запись в истории.
Если бы мы хотели, чтобы при возникновении этого запроса создавалась запись в истории, мы бы добавили к кнопке новый атрибут: атрибут hx-push-url
:
hx-push-url
создаст запись в истории при нажатии кнопки.
Теперь при нажатии кнопки путь /contacts
будет помещен в панель навигации браузера, и для него будет создана запись в истории. Более того, если пользователь нажмет кнопку «Назад», исходное содержимое страницы будет восстановлено вместе с исходным URL-адресом.
Имя hx-push-url
этого атрибута может показаться немного непонятным, но оно основано на API JavaScript, history.pushState()
. Это понятие «проталкивания» происходит от того факта, что записи истории моделируются как стек, и поэтому вы «проталкиваете» новые записи на вершину стека записей истории.
Благодаря этому относительно простому декларативному механизму htmx позволяет интегрироваться с кнопкой «Назад» таким образом, чтобы имитировать «нормальное» поведение HTML.
Теперь есть еще одна вещь, которую нам нужно обработать, чтобы получить историю «правильно»: мы /contacts
успешно «вставили» путь в строку адреса браузера, и кнопка «Назад» работает. Но что, если кто-то обновит свой браузер, находясь на /contacts
странице?
В этом случае вам нужно будет обрабатывать «частичный» ответ на основе htmx, а также «полностраничный» ответ, отличный от HTML. Вы можете сделать это с помощью HTTP-заголовков — эту тему мы подробно рассмотрим далее в книге.
Вот и закончилось наше стремительное знакомство с htmx. Мы видели только около десяти атрибутов из библиотеки, но вы можете увидеть намек на то, насколько мощными могут быть эти атрибуты. Htmx позволяет создавать гораздо более сложные веб-приложения, чем это возможно в обычном HTML, с минимальной дополнительной концептуальной нагрузкой по сравнению с большинством подходов на основе JavaScript.
Htmx стремится постепенно улучшать HTML как гипермедиа таким образом, чтобы он был концептуально согласован с базовым языком разметки. Как и любой технический выбор, здесь есть компромиссы: оставаясь настолько близким к HTML, htmx не дает разработчикам большого количества инфраструктуры, которая, по мнению многих, должна быть там «по умолчанию».
Оставаясь ближе к собственной модели Интернета, htmx стремится найти баланс между простотой и функциональностью, отдавая предпочтение другим библиотекам для более сложных расширений интерфейса поверх существующей веб-платформы. Хорошей новостью является то, что htmx хорошо взаимодействует с другими, поэтому, когда возникают такие потребности, зачастую достаточно легко подключить другую библиотеку для их решения.
Тесная связь между контентом и разметкой означает, что хороший HTML — трудоемкий процесс. На большинстве сайтов есть разделение между авторами, которые редко знакомы с HTML, и разработчиками, которым необходимо разработать общую систему, способную обрабатывать любой поступающий на него контент — это разделение обычно принимает форму CMS. В результате создание разметки, адаптированной к содержимому, что часто необходимо для расширенного HTML, редко осуществимо.
Более того, для интернационализированных сайтов содержание на разных языках, вставленное в одни и те же элементы, может ухудшить качество разметки, поскольку стилистические соглашения различаются в зависимости от языка. Это расходы, которые немногие организации могут себе позволить.
Таким образом, мы не ожидаем, что каждый сайт будет содержать полностью совместимый HTML. Самое главное — избегать неправильного HTML — возможно, лучше вернуться к более общему элементу, чем быть совершенно неверным.
Однако если у вас есть ресурсы, если вы будете уделять больше внимания HTML-коду, сайт станет более совершенным.
Оказывается, мы можем повысить интерактивность нашего приложения и решить обе эти проблемы, не прибегая к подходу SPA. Мы можем сделать это с помощью библиотеки JavaScript, ориентированной на гипермедиа , . Авторы этой книги создали HTMLX специально для расширения HTML как гипермедиа и решения проблем устаревших HTML-приложений, о которых мы упоминали выше (а также некоторых других).
Браузер отправит HTTP- GET
запрос на …
Htmx — это библиотека JavaScript, которая расширяет HTML именно таким образом, и ей будут посвящены следующие несколько глав этой книги. Опять же, htmx — не единственная библиотека JavaScript, использующая этот подход, ориентированный на гипермедиа (другими прекрасными примерами являются и ), но htmx является самой чистой в своем стремлении расширить HTML как гипермедиа.
Ниже приведен пример использования популярной сети доставки контента (CDN) для установки версии 1.9.2
библиотеки. Мы используем хеш целостности, чтобы гарантировать, что доставленный контент JavaScript соответствует тому, что мы ожидаем. Этот SHA можно найти на веб-сайте htmx.