Глава 05. Htmx-шаблоны
Last updated
Last updated
Теперь, когда мы увидели, как HTML расширяет возможности HTML как гипермедиа, пришло время применить это на практике. Поскольку мы используем htmx, мы по-прежнему будем использовать гипермедиа: мы будем отправлять HTTP-запросы и получать обратно HTML. Но благодаря дополнительным функциям, предоставляемым htmx, у нас будет более мощная гипермедиа для работы, что позволит нам создавать гораздо более сложные интерфейсы.
Это позволит нам решать проблемы взаимодействия с пользователем, такие как длительные циклы обратной связи или болезненное обновление страниц, без необходимости писать много кода JavaScript (если таковой вообще имеется) и без создания JSON API. Все будет реализовано в гипермедиа с использованием основных концепций гипермедиа раннего Интернета.
Первое, что нам нужно сделать, это установить htmx в наше веб-приложение. Мы собираемся сделать это, загрузив исходный код и сохранив его локально в нашем приложении, чтобы не зависеть от каких-либо внешних систем. Это известно как «продажа» библиотеки. Мы можем получить последнюю версию htmx, перейдя в браузере по адресу , который перенаправит нас к источнику последней версии библиотеки.
Мы можем сохранить содержимое этого URL-адреса в static/js/htmx.js
файл нашего проекта.
Конечно, вы можете использовать более сложный менеджер пакетов JavaScript, такой как Node Package Manager (NPM) или Yarn, для установки htmx. Вы делаете это, ссылаясь на имя его пакета, htmx.org
способом, подходящим для вашего инструмента. Однако htmx очень мал (около 12 КБ в сжатом и заархивированном виде) и не зависит от зависимостей, поэтому для его использования не требуется сложный механизм или инструмент сборки.
Теперь , когда htmx загружен локально в /static/js
каталог наших приложений, мы можем загрузить его в наше приложение. Мы делаем это, добавляя следующий script
тег к head
тегу в нашем layout.html
файле, который сделает htmx доступным и активным на каждой странице нашего приложения:
Напомним, что этот layout.html
файл представляет собой файл макета , включенный в большинство шаблонов, который оборачивает содержимое этих шаблонов в общий HTML, включая элемент head
, который мы используем здесь для установки htmx.
Хотите верьте, хотите нет, но это так! Этот простой тег скрипта сделает функциональность htmx доступной для всего нашего приложения.
Чтобы освоить htmx, первая функция, которой мы собираемся воспользоваться, известна как «ускорение». Это своего рода «магическая» функция, поскольку нам не нужно ничего делать, кроме добавления hx-boost
в приложение единственного атрибута .
Когда вы добавляете hx-boost
к данному элементу значение true
, он «усиливает» все элементы привязки и формы внутри этого элемента. «Boost» здесь означает, что htmx преобразует все эти привязки и формы из «обычных» элементов управления гипермедиа в элементы управления гипермедиа на базе AJAX. Вместо выдачи «обычных» HTTP-запросов, заменяющих всю страницу, ссылки и формы будут выдавать запросы AJAX. Затем Htmx заменяет внутреннее содержимое тега <body>
в ответе на эти запросы на существующий <body>
тег страниц.
Это ускоряет навигацию, поскольку браузер не будет повторно интерпретировать большинство тегов в ответе <head>
и т. д.
Давайте рассмотрим пример усиленной ссылки. Ниже приведена ссылка на гипотетическую страницу настроек веб-приложения. Поскольку он есть hx-boost="true"
, htmx остановит нормальное поведение ссылки, выдавая запрос к /settings
пути и заменяя всю страницу ответом. Вместо этого htmx отправит AJAX-запрос /settings
, возьмет результат и заменит body
элемент новым содержимым.
Атрибут hx-boost
делает эту ссылку управляемой AJAX.
Вы можете резонно спросить: в чем здесь преимущество? Выдаем AJAX-запрос и просто заменяем все тело.
Значительно ли это отличается от простого запроса ссылки?
Да, на самом деле все по-другому: с усиленной ссылкой браузер может избежать любой обработки, связанной с тегом head. Тег head часто содержит множество скриптов и ссылок на файлы CSS. В усиленном сценарии нет необходимости повторно обрабатывать эти ресурсы: сценарии и стили уже обработаны и будут продолжать применяться к новому содержимому. Часто это может быть очень простым способом ускорить работу вашего гипермедийного приложения.
Второй вопрос, который у вас может возникнуть: нужно ли форматировать ответ специально для работы с hx-boost
? В конце концов, страница настроек обычно отображает html
тег с head
тегом и так далее. Вам нужно специально обрабатывать «форсированные» запросы?
Ответ — нет: htmx достаточно умен, чтобы извлекать только содержимое тега body
для замены на новую страницу. Тег head
в основном игнорируется: будет обрабатываться только тег заголовка, если он присутствует. Это означает, что вам не нужно делать ничего особенного на стороне сервера для отображения шаблонов, которые hx-boost
можно обработать: просто верните обычный HTML-код для вашей страницы, и он должен работать нормально.
Обратите внимание, что усиленные ссылки (и формы) также будут продолжать обновлять панель навигации и историю, как и обычные ссылки, поэтому пользователи смогут использовать кнопку «Назад» браузера, смогут копировать и вставлять URL-адреса (или «глубинные ссылки»). ) и так далее. Ссылки будут вести себя почти как «обычные», только они будут быстрее.
Теги усиленной формы работают аналогично усиленным тегам привязки: усиленная форма будет использовать запрос AJAX, а не обычный запрос, отправленный браузером, и заменит все тело ответом.
Вот пример формы, которая отправляет сообщения в /messages
конечную точку с помощью HTTP- POST
запроса. Если добавить hx-boost
к этому, эти запросы будут выполняться в AJAX, а не в обычном режиме браузера.
Как и в случае со ссылкой, hx-boost
эта форма создается с помощью AJAX.
Большим преимуществом запроса на основе AJAX, который hx-boost
использует (и отсутствие головной обработки) является то, что он позволяет избежать так называемой вспышки нестилизованного контента :
Вспышка нестилизованного контента (FOUC)
Ситуация, когда браузер отображает веб-страницу до того, как для нее станет доступна вся информация о стилях. FOUC вызывает сбивающую с толку кратковременную «вспышку» нестилизованного контента, который затем подвергается рестилированию, когда вся информация о стиле становится доступной. Вы заметите это по мерцанию при перемещении по Интернету: текст, изображения и другой контент могут «перепрыгивать» на странице, когда к ней применяются стили.
Поскольку hx-boost
стили сайта уже загружаются до получения нового контента, поэтому не возникает такой вспышки нестилизованного контента. Это может сделать «ускоренное» приложение одновременно более плавным и быстрым в целом.
Давайте расширим наш предыдущий пример усиленной ссылки и добавим рядом с ним еще несколько усиленных ссылок. Мы добавим ссылки, чтобы у нас была одна на /contacts
страницу, /settings
страницу и /help
страницу. Все эти ссылки усилены и будут вести себя так, как мы описали выше.
Это кажется немного излишним, не так ли? Кажется глупым аннотировать все три ссылки атрибутом hx-boost="true"
рядом друг с другом.
Htmx предлагает функцию, помогающую уменьшить эту избыточность: наследование атрибутов. Для большинства атрибутов в htmx, если вы поместите их в родительский элемент, атрибут также будет применяться к дочерним элементам. Именно так работают каскадные таблицы стилей, и эта идея вдохновила htmx на внедрение аналогичной функции «каскадных атрибутов htmx».
Чтобы избежать избыточности в этом примере, давайте введем элемент div
, который заключает в себе все ссылки, а затем «поднимем» hx-boost
атрибут до этого родительского элемента div
. Это позволит нам удалить избыточные hx-boost
атрибуты, но гарантировать, что все ссылки по-прежнему будут усилены, наследуя эту функциональность от родительского объекта div
.
Обратите внимание, что здесь можно использовать любой допустимый HTML-элемент, мы просто используем его div
по привычке.
Был hx-boost
перемещен в родительский div.
Теперь нам не нужно ставить аннотацию hx-boost="true"
к каждой ссылке, и, по сути, мы можем добавлять больше ссылок рядом с существующими, и они тоже будут усилены, без необходимости явно аннотировать их.
Это нормально, но что, если у вас есть ссылка, которую вы не хотите повышать внутри элемента, который hx-boost="true"
ее содержит? Хорошим примером такой ситуации является ссылка на ресурс для загрузки, например PDF-файл. Загрузка файла не может быть обработана запросом AJAX, поэтому вы, вероятно, захотите, чтобы эта ссылка вела себя «нормально», выдавая полный запрос страницы для PDF-файла, который браузер затем предложит сохранить в виде файла на локальном компьютере пользователя. система.
Чтобы справиться с этой ситуацией, вы просто переопределяете родительское hx-boost
значение в hx-boost="false"
теге привязки, который вы не хотите повышать:
Все hx-boost
еще находится в родительском div.
Для этой ссылки поведение повышения переопределяется.
Здесь у нас есть новая ссылка на PDF-файл документации, который мы хотим использовать как обычную ссылку. Мы добавили hx-boost="false"
ссылку, и это объявление переопределит hx-boost="true"
родительское объявление div
, вернув его к обычному поведению ссылки и, таким образом, обеспечив желаемое поведение загрузки файла.
Приятным аспектом hx-boost
является то, что это пример прогрессивного улучшения :
Прогрессивное улучшение
Философия разработки программного обеспечения, целью которой является предоставление как можно большего количества необходимого контента и функций как можно большему количеству пользователей, одновременно обеспечивая лучший опыт для пользователей с более продвинутыми веб-браузерами.
Рассмотрим ссылки в примере выше. Что произойдет, если у кого-то не будет включен JavaScript?
Без проблем. Приложение продолжит работать, но будет выдавать обычные HTTP-запросы, а не HTTP-запросы на основе AJAX. Это означает, что ваше веб-приложение будет работать для максимального количества пользователей; Те, у кого есть современные браузеры (или пользователи, у которых не отключен JavaScript), могут воспользоваться преимуществами навигации в стиле AJAX, которую предлагает htmx, а другие по-прежнему могут нормально использовать приложение.
Сравните поведение hx-boost
атрибута htmx с одностраничным приложением, насыщенным JavaScript: такое приложение часто вообще не будет работать без включенного JavaScript. При использовании инфраструктуры SPA часто бывает очень сложно принять подход постепенного улучшения.
Это не означает, что каждая функция HTML предлагает постепенное улучшение. Конечно, можно создавать функции, которые не предлагают запасной вариант «No JS» в htmx, и фактически многие из функций, которые мы создадим позже в книге, попадут в эту категорию. Мы отметим, когда функция поддерживает прогрессивное улучшение, а когда нет.
В конечном счете, вам, разработчику, решать, стоят ли компромиссы прогрессивного улучшения (более простой UX, ограниченные улучшения по сравнению с простым HTML) преимуществами для пользователей вашего приложения.
Для приложения контактов, которое мы создаем, нам нужно такое поведение htmx «boost»… ну, везде.
Верно? Почему нет?
Как мы могли бы этого добиться?
Что ж, это просто (и довольно часто встречается в веб-приложениях на основе HTML): мы можем просто добавить hx-boost
тег body
нашего layout.html
шаблона, и все готово.
Все ссылки и формы теперь будут прокачаны!
Теперь каждая ссылка и форма в нашем приложении по умолчанию будут использовать AJAX, что сделает его более быстрым. Рассмотрим ссылку «Новый контакт», которую мы создали на главной странице:
Несмотря на то, что мы ничего не трогали в этой ссылке или в обработке целевого URL-адреса на стороне сервера, теперь она будет «просто работать» как усиленная ссылка, используя AJAX для более быстрого взаимодействия с пользователем, включая обновление истории, кнопку «Назад». поддержка и так далее. И если JavaScript не включен, он вернется к нормальному поведению ссылки.
Все это с одним атрибутом htmx.
Атрибут hx-boost
аккуратный, но отличается от других атрибутов htmx тем, что он довольно «волшебный»: внося одно небольшое изменение, вы изменяете поведение большого количества элементов на странице, превращая их в элементы на базе AJAX. Большинство других атрибутов htmx обычно относятся к более низкому уровню и требуют более явных аннотаций, чтобы точно указать, что вы хотите, чтобы htmx делал. В общем, это философия дизайна htmx: предпочитать явное неявному и очевидное «магическому».
Однако этот hx-boost
атрибут был слишком полезен, чтобы позволить догмам преобладать над практичностью, поэтому он включен в библиотеку как функция.
Для нашего следующего шага с htmx вспомните, что Contact.app имеет небольшую форму на странице редактирования контакта, которая используется для удаления контакта:
Эта форма выдала HTTP- POST
запрос, например, /contacts/42/delete
для удаления контакта с идентификатором 42.
Ранее мы упоминали, что одна из раздражающих особенностей HTML заключается в том, что вы не можете напрямую отправить HTTP- запрос DELETE
(или PUT
или PATCH
), даже несмотря на то, что все они являются частью HTTP, а HTTP, очевидно, предназначен для передачи HTML.
К счастью, теперь, благодаря htmx, у нас есть шанс исправить эту ситуацию.
«Правильным» с точки зрения RESTful, ориентированной на ресурсы, будет вместо отправки HTTP POST
для . Мы хотим удалить контакт. Контакт — это ресурс. URL-адрес этого ресурса : . Итак, идеал — это запрос на ./contacts/42/deleteDELETE/contacts/42/contacts/42DELETE/contacts/42/
Давайте обновим наше приложение, добавив hx-delete
атрибут htmx к кнопке «Удалить контакт»:
Теперь, когда пользователь нажимает эту кнопку, htmx отправляет HTTP- DELETE
запрос через AJAX на URL-адрес соответствующего контакта.
Несколько вещей, на которые стоит обратить внимание:
Нам больше не нужен form
тег для обертывания кнопки, поскольку сама кнопка несет в себе гипермедийное действие, которое она выполняет непосредственно над собой.
Нам больше не нужно использовать несколько неуклюжий "/contacts/{{ contact.id }}/delete"
маршрут, мы можем просто использовать его "/contacts/{{ contact.id }}
, поскольку мы выдаем файл DELETE
. Используя a, DELETE
мы устраняем неоднозначность между запросом, предназначенным для обновления контакта, и запросом, предназначенным для его удаления, используя собственные инструменты HTTP, доступные именно по этой причине.
Обратите внимание, что здесь мы сделали нечто волшебное: превратили эту кнопку в элемент управления гипермедиа . Больше нет необходимости размещать эту кнопку внутри более крупного form
тега для запуска HTTP-запроса: это отдельный и полнофункциональный элемент управления гипермедиа. Это лежит в основе htmx, позволяя любому элементу стать элементом управления гипермедиа и полностью участвовать в приложении, управляемом гипермедиа.
Следует также отметить, что, в отличие от hx-boost
приведенных выше примеров, это решение не будет плавно ухудшаться. Чтобы это решение корректно ухудшалось, нам нужно будет обернуть кнопку в элемент формы и POST
также обработать ее на стороне сервера.
В интересах простоты нашего приложения мы собираемся опустить это более сложное решение.
Мы обновили код на стороне клиента (если HTML можно считать кодом), и теперь он отправляет запрос DELETE
на соответствующий URL-адрес, но нам еще есть над чем поработать. Поскольку мы обновили как маршрут, так и метод HTTP, который мы используем, нам также потребуется обновить реализацию на стороне сервера для обработки этого нового HTTP-запроса.
Нам нужно внести два изменения в наш обработчик: обновить маршрут и обновить метод HTTP, который мы используем для удаления контактов.
Обновленный путь и метод для обработчика.
Довольно просто и намного чище.
Код ответа попался
К сожалению, есть проблема с нашим обновленным обработчиком: по умолчанию во Flask метод redirect()
отвечает 302 Found
кодом ответа HTTP.
Теперь мы выдаем DELETE
запрос с помощью htmx, а затем перенаправляем его на /contacts
путь с помощью flask. Согласно этой логике, это будет означать, что перенаправленный HTTP-запрос по-прежнему будет методом DELETE
. Это означает, что в нынешнем виде браузер отправит DELETE
запрос к /contacts
.
Это определенно не то, что нам нужно: мы бы хотели, чтобы HTTP-перенаправление выдавало GET
запрос, слегка изменяя поведение Post/Redirect/Get, которое мы обсуждали ранее, на «Delete/Redirect/Get».
Итак, мы хотим обновить наш код, чтобы использовать 303
код ответа в контроллере.
К счастью, это очень просто: есть второй параметр, redirect()
который принимает числовой код ответа, который вы хотите отправить.
Код ответа теперь 303.
Теперь, когда вы хотите удалить определенный контакт, вы можете просто ввести DELETE
тот же URL-адрес, который вы использовали для доступа к контакту.
Это естественный подход к удалению ресурса на основе HTTP.
Мы еще не закончили работу над обновленной кнопкой удаления. Напомним, что по умолчанию htmx «нацеливается» на элемент, который запускает запрос, и помещает HTML, возвращаемый сервером, внутрь этого элемента. Прямо сейчас кнопка «Удалить контакт» нацелена на себя.
Это означает, что, поскольку при перенаправлении на /contacts
URL-адрес будет перерисован весь список контактов, в конечном итоге мы получим этот список контактов, помещенный внутри кнопки «Удалить контакт».
Подобный неправильный таргетинг время от времени возникает, когда вы работаете с htmx, и может привести к довольно забавным ситуациям.
Исправить это легко: добавьте явную цель к кнопке и назначьте элемент body
ответом:
К кнопке добавлена явная цель.
Теперь наша кнопка ведет себя так, как ожидалось: нажатие на кнопку отправит DELETE
серверу HTTP-запрос на URL-адрес текущего контакта, удалит контакт и перенаправит обратно на страницу списка контактов с красивым флэш-сообщением.
Сейчас все работает гладко?
Ну, почти.
Если вы нажмете кнопку, вы заметите, что, несмотря на перенаправление, URL-адрес в строке адреса неверен. Это все еще указывает на /contacts/{{ contact.id }}
. Это потому, что мы не указали htmx обновить URL-адрес: он просто выдает запрос DELETE
, а затем обновляет DOM с ответом.
Как мы уже упоминали, повышение через hx-boost
естественным образом обновит для вас строку адреса, имитируя обычные привязки и формы, но в этом случае мы создаем настраиваемый элемент управления гипермедиа «Кнопка» для выдачи файла DELETE
. Нам нужно сообщить htmx, что мы хотим, чтобы URL-адрес, полученный в результате этого запроса, «поместился» в адресную строку.
Мы можем добиться этого, добавив hx-push-url
атрибут со значением true
к нашей кнопке:
Мы приказываем htmx переместить перенаправленный URL-адрес в адресную строку.
Теперь мы закончили.
У нас есть кнопка, которая сама по себе способна отправить правильно отформатированный HTTP- DELETE
запрос на правильный URL-адрес, а пользовательский интерфейс и строка адреса обновляются правильно. Это было достигнуто с помощью трех декларативных атрибутов, размещенных непосредственно на кнопке: hx-delete
, hx-target
и hx-push-url
.
Это потребовало больше работы, чем внесение hx-boost
изменений, но явный код позволяет легко увидеть, что делает кнопка, в качестве пользовательского элемента управления гипермедиа. Полученный раствор кажется чистым; он использует встроенные функции Интернета как системы гипермедиа без каких-либо хаков URL-адресов.
Есть еще одна «бонусная» функция, которую мы можем добавить к нашей кнопке «Удалить контакт»: диалоговое окно подтверждения. Удаление контакта является деструктивной операцией, и на данный момент, если пользователь случайно нажмет кнопку «Удалить контакт», приложение просто удалит этот контакт. Жаль, так грустно для пользователя.
К счастью, в htmx есть простой механизм добавления сообщения о подтверждении таких деструктивных операций: hx-confirm
атрибут. Вы можете разместить этот атрибут в элементе с сообщением в качестве его значения, и метод JavaScript confirm()
будет вызываться перед выдачей запроса, который покажет пользователю простое диалоговое окно подтверждения с просьбой подтвердить действие. Очень простой и отличный способ предотвратить несчастные случаи.
Вот как бы мы добавили подтверждение операции удаления контакта:
Это сообщение будет показано пользователю с просьбой подтвердить удаление.
Теперь, когда кто-то нажимает кнопку «Удалить контакт», ему будет предложено спросить: «Вы уверены, что хотите удалить этот контакт?» и у них будет возможность отменить подписку, если они нажали кнопку по ошибке. Очень хорошо.
Благодаря этому последнему изменению у нас теперь есть довольно надежный механизм «удаления контакта»: мы используем правильные маршруты RESTful и методы HTTP, мы подтверждаем удаление и удалили много мусора, который навязывает нам обычный HTML, все используя при этом декларативные атрибуты в нашем HTML и оставаясь в рамках обычной гипермедийной модели Интернета.
Как мы уже отмечали ранее об этом решении: оно не является прогрессивным улучшением нашего веб-приложения. Если кто-то отключил JavaScript, кнопка «Удалить контакт» больше не будет работать. Нам потребуется проделать дополнительную работу, чтобы старый механизм на основе форм работал в среде с отключенным JavaScript.
Прогрессивное улучшение может быть горячей темой в веб-разработке, вызывающей множество восторженных мнений и точек зрения. Как и почти все библиотеки JavaScript, htmx позволяет создавать приложения, которые не работают без JavaScript. Сохранение поддержки клиентов, не использующих JavaScript, требует дополнительной работы и сложности вашего приложения. Прежде чем вы начнете использовать htmx или любую другую среду JavaScript для улучшения своих веб-приложений, важно точно определить, насколько важна поддержка клиентов, не поддерживающих JavaScript.
Перейдем к еще одному усовершенствованию нашего приложения. Большая часть любого веб-приложения — это проверка данных, отправляемых на сервер: проверка правильности формата и уникальности электронных писем, допустимости числовых значений, допустимости дат и т. д.
В настоящее время наше приложение имеет небольшой объем проверки, которая выполняется полностью на стороне сервера и отображает сообщение об ошибке при обнаружении ошибки.
Мы не будем вдаваться в подробности того, как работает валидация в объектах модели, а вспомним, как выглядит код обновления контакта из главы 3:
Пытаемся сохранить контакт.
Если сохранение не удалось, мы повторно отображаем форму для отображения сообщений об ошибках.
Поэтому мы пытаемся сохранить контакт и, если метод save()
возвращает true, мы перенаправляем на страницу сведений о контакте. Если save()
метод не возвращает true, это указывает на ошибку проверки; вместо перенаправления мы повторно отображаем HTML для редактирования контакта. Это дает пользователю возможность исправить ошибки, которые отображаются рядом с входными данными.
Давайте посмотрим на HTML для ввода электронной почты:
Отображать любые ошибки, связанные с полем электронной почты.
У нас есть метка для ввода, ввод типа, text
а затем немного HTML для отображения любых сообщений об ошибках, связанных с электронным письмом. Когда шаблон отображается на сервере, если есть ошибки, связанные с электронной почтой контакта, они будут отображаться в этом диапазоне, который будет выделен красным.
Логика проверки на стороне сервера
Прямо сейчас в классе контактов есть небольшая логика, которая проверяет, есть ли какие-либо другие контакты с тем же адресом электронной почты, и если да, добавляет ошибку в модель контакта, поскольку мы не хотим иметь повторяющиеся электронные письма в базе данных. Это очень распространенный пример проверки: электронные письма обычно уникальны, и добавление двух контактов с одним и тем же адресом электронной почты почти наверняка является ошибкой пользователя.
Опять же, мы не будем вдаваться в подробности того, как работает проверка в наших моделях, но почти все серверные платформы предоставляют способы проверки данных и сбора ошибок для отображения пользователю. Такая инфраструктура очень распространена в серверных средах Web 1.0.
Когда пользователь пытается сохранить контакт с дубликатом адреса электронной почты, отображается сообщение об ошибке «Электронный адрес должен быть уникальным»:
Все это делается с использованием простого HTML и технологий Web 1.0, и работает хорошо.
Однако в нынешнем виде приложения есть две неприятности.
Во-первых, не существует проверки формата электронной почты: вы можете вводить любые символы в качестве электронного письма, и, если они уникальны, система это позволит.
Во-вторых, мы проверяем уникальность электронного письма только при отправке всех данных: если пользователь ввел дубликат электронного адреса, он не узнает об этом, пока не заполнит все поля. Это может сильно раздражать, если пользователь случайно повторно вводит контакт и ему приходится вводить всю контактную информацию, прежде чем он узнает об этом факте.
Что касается первой проблемы, у нас есть чистый HTML-механизм для улучшения нашего приложения: HTML 5 поддерживает входные данные типа email
. Все, что нам нужно сделать, это переключить ввод с type text
на type email
, и браузер обеспечит соответствие введенного значения формату электронной почты:
Изменение атрибута type
гарантирует email
, что введенные значения являются действительными адресами электронной почты.
Благодаря этому изменению, когда пользователь вводит значение, которое не является действительным адресом электронной почты, браузер будет отображать сообщение об ошибке с просьбой указать правильно сформированный адрес электронной почты в этом поле.
Таким образом, простое изменение одного атрибута, выполненное в чистом HTML, улучшает нашу проверку и решает первую отмеченную нами проблему.
Проверка на стороне сервера и на стороне клиента
Опытные веб-разработчики могут стиснуть зубы над приведенным выше кодом: эта проверка выполняется на стороне клиента . То есть мы полагаемся на то, что браузер обнаружит неверный адрес электронной почты и исправит пользователя. К сожалению, клиентская часть не заслуживает доверия: в браузере может быть ошибка, позволяющая пользователю обойти этот код проверки. Или, что еще хуже, пользователь может действовать злонамеренно и полностью выяснить механизм нашей проверки, например использовать консоль разработчика для редактирования HTML.
Это постоянная опасность в веб-разработке: всем проверкам, выполняемым на стороне клиента, нельзя доверять, и, если проверка важна, ее необходимо перевыполнять на стороне сервера. Это меньшая проблема в приложениях, управляемых гипермедиа, чем в одностраничных приложениях, поскольку HDA фокусируется на стороне сервера, но об этом стоит помнить при создании приложения.
Хотя мы немного улучшили процесс проверки, пользователю по-прежнему необходимо отправить форму, чтобы получить отзыв о повторяющихся электронных письмах. Затем мы можем использовать htmx, чтобы улучшить взаимодействие с пользователем.
Было бы лучше, если бы пользователь мог видеть повторяющуюся ошибку электронной почты сразу после ввода значения электронной почты. Оказывается, входы запускают change
событие, и фактически это change
событие является триггером по умолчанию для входов в htmx. Итак, запустив эту функцию, мы можем реализовать следующее поведение: когда пользователь вводит адрес электронной почты, немедленно отправить запрос на сервер и проверить это письмо, а также при необходимости отобразить сообщение об ошибке.
Напомним текущий HTML-код для ввода электронной почты:
Это входные данные, которые мы хотим использовать для HTTP-запроса для проверки электронной почты.
Это диапазон, в который мы хотим поместить сообщение об ошибке, если таковое имеется.
Итак, мы хотим добавить hx-get
атрибут к этому входу. Это приведет к тому, что вход отправит HTTP- GET
запрос к заданному URL-адресу для проверки электронной почты. Затем мы хотим указать диапазон ошибок, следующий за вводом, с любым сообщением об ошибке, возвращаемым с сервера.
Давайте внесем эти изменения в наш HTML:
Отправьте HTTP- GET
запрос email
конечной точке контакта.
Нацельтесь на следующий элемент с классом error
.
Обратите внимание, что в hx-target
атрибуте мы используем относительный позиционный селектор next
. Это особенность htmx и расширение обычного CSS. Htmx поддерживает префиксы, которые будут находить цели относительно текущего элемента.
Относительные позиционные выражения в Htmxnext
Просканируйте DOM вперед в поисках следующего соответствующего элемента, например:next .error
previous
Сканируйте DOM назад в поисках ближайшего предыдущего совпадающего элемента, например:previous .alert
closest
Сканируйте родителей этого элемента на предмет соответствующего элемента, например:closest table
find
Сканируйте дочерние элементы этого элемента на предмет соответствующего элемента, например:find span
this
текущий элемент является целью (по умолчанию)
Используя выражения относительного положения, мы можем избежать добавления явных идентификаторов к элементам и воспользоваться преимуществами локальной структуры HTML.
Итак, в нашем примере с добавленными атрибутами hx-get
и hx-target
, когда кто-то меняет значение ввода (помните, change
это триггер по умолчанию для ввода в htmx), GET
на данный URL-адрес будет отправлен HTTP-запрос. Если есть какие-либо ошибки, они будут загружены в диапазон ошибок.
Далее давайте посмотрим на реализацию на стороне сервера. Мы собираемся добавить еще одну конечную точку, в некотором смысле похожую на нашу конечную точку редактирования: она будет искать контакт на основе идентификатора, закодированного в URL-адресе. Однако в этом случае мы хотим только обновить адрес электронной почты контакта и, очевидно, не хотим его сохранять! Вместо этого мы вызовем validate()
для него метод.
Этот метод подтвердит уникальность электронного письма и так далее. На этом этапе мы можем вернуть любые ошибки, связанные непосредственно с электронным письмом, или пустую строку, если таковая не существует.
Найдите контакт по идентификатору.
Обновите адрес электронной почты (обратите внимание, что, поскольку это GET
, мы используем args
свойство, а не form
свойство).
Подтвердите контакт.
Возвращает строку: либо ошибки, связанные с полем электронной почты, либо, если их нет, пустую строку.
Благодаря этому небольшому фрагменту серверного кода у нас теперь есть следующий пользовательский опыт: когда пользователь вводит адрес электронной почты и переходит к следующему полю ввода, он немедленно уведомляется, если электронное письмо уже занято.
Обратите внимание, что проверка электронной почты по-прежнему выполняется, когда весь контакт отправляется на обновление, поэтому нет опасности пропустить дубликаты контактов электронной почты: мы просто предоставили пользователям возможность обнаружить эту ситуацию раньше, используя htmx.
Также стоит отметить, что эта конкретная проверка электронной почты должна выполняться на стороне сервера: вы не можете определить, что электронная почта уникальна для всех контактов, если у вас нет доступа к хранилищу данных записей. Это еще один упрощенный аспект приложений, управляемых гипермедиа: поскольку проверки выполняются на стороне сервера, у вас есть доступ ко всем данным, которые могут вам понадобиться для выполнения любого вида проверки, которую вы захотите.
Здесь мы снова хотим подчеркнуть, что это взаимодействие осуществляется полностью в рамках модели гипермедиа: мы используем декларативные атрибуты и обмениваемся гипермедиа с сервером способом, очень похожим на то, как работают ссылки или формы. Но нам удалось значительно улучшить наш пользовательский опыт.
Несмотря на то, что мы не добавили здесь много кода, у нас довольно сложный пользовательский интерфейс, по крайней мере, по сравнению с простыми приложениями на основе HTML. Однако, если вы использовали более продвинутые одностраничные приложения, вы, вероятно, видели шаблон, в котором поле электронной почты (или аналогичный ввод) проверяется при вводе .
Кажется, что такая интерактивность возможна только с помощью сложной и сложной инфраструктуры JavaScript, не так ли?
Ну нет.
Оказывается, вы можете реализовать эту функциональность в htmx, используя чистые атрибуты HTML.
Фактически, все, что нам нужно сделать, это изменить наш триггер. В настоящее время мы используем триггер по умолчанию для входных данных — событие change
. Чтобы проверить ввод данных пользователем, нам keyup
также нужно зафиксировать событие:
keyup
Вместе с . был добавлен явный триггер change
.
Благодаря этому небольшому изменению каждый раз, когда пользователь вводит символ, мы выдаем запрос и проверяем электронное письмо. Простой.
Да, просто, но, вероятно, это не то, что нам нужно: выдача нового запроса при каждом событии нажатия клавиши была бы очень расточительной и потенциально могла бы перегрузить ваш сервер. Вместо этого мы хотим выдавать запрос только в том случае, если пользователь сделал паузу на небольшой промежуток времени. Это называется «устранением дребезга» ввода, когда запросы задерживаются до тех пор, пока все не «стабилизируется».
Htmx поддерживает delay
модификатор для триггеров, который позволяет предотвратить отказ запроса, добавив задержку перед отправкой запроса. Если в течение этого интервала появится другое событие того же типа, htmx не выдаст запрос и сбросит таймер.
Оказывается, это именно то, что нам нужно для ввода электронной почты: если пользователь занят вводом электронного письма, мы не будем его прерывать, но как только он сделает паузу или покинет поле, мы отправим запрос.
Добавим к keyup
триггеру задержку в 200 миллисекунд, которой достаточно, чтобы обнаружить, что пользователь перестал печатать:
Мы устраняем keyup
событие, добавляя delay
модификатор.
Теперь мы больше не выдаем поток запросов проверки по мере ввода пользователем. Вместо этого мы ждем, пока пользователь немного приостановится, а затем выдаем запрос. Намного лучше для нашего сервера и по-прежнему отличный пользовательский опыт.
Есть еще одна проблема, которую мы должны решить с событием keyup: в его нынешнем виде мы выдадим запрос независимо от того, какие клавиши нажаты, даже если это клавиши, которые не влияют на значение ввода, например клавиши со стрелками. Было бы лучше, если бы существовал способ выдавать запрос только в том случае, если входное значение изменилось.
И оказывается, что htmx поддерживает именно этот шаблон, используя changed
модификатор событий. (Не путать с change
событием, инициируемым DOM на входных элементах.)
При добавлении changed
к нашему keyup
триггеру входные данные не будут выдавать запросы на проверку, если событие keyup фактически не обновит входные значения:
Мы избавляемся от бессмысленных запросов, выдавая их только тогда, когда входное значение действительно изменилось.
Это довольно красивый и мощный HTML, предоставляющий возможности, которые, по мнению большинства разработчиков, требуют сложного решения на стороне клиента.
Имея в общей сложности три атрибута и новую простую конечную точку на стороне сервера, мы добавили в наше веб-приложение довольно сложный пользовательский интерфейс. Более того, любые правила проверки электронной почты, которые мы добавляем на стороне сервера, будут автоматически работать с использованием этой модели: поскольку мы используем гипермедиа в качестве нашего механизма связи, нет необходимости синхронизировать модели на стороне клиента и на стороне сервера друг с другом. .
Великолепная демонстрация мощи гипермедийной архитектуры!
Давайте немного отойдём от страницы редактирования контактов и улучшим корневую страницу приложения, находящуюся по пути /contacts
и отрисовывающую index.html
шаблон.
На данный момент Contact.app не поддерживает пейджинг: если в базе данных 10 000 контактов, мы отобразим все 10 000 контактов на корневой странице. Отображение такого большого количества данных может привести к зависанию браузера (и сервера), поэтому большинство веб-приложений используют концепцию «постраничного просмотра» для работы с такими большими наборами данных, при которых отображается только одна «страница» из меньшего числа элементов. возможность навигации по страницам набора данных.
Давайте исправим наше приложение так, чтобы мы показывали одновременно только десять контактов со ссылками «Следующий» и «Предыдущий», если в базе контактов более 10 контактов.
Первое изменение, которое мы сделаем, — это добавим в наш index.html
шаблон простой виджет подкачки.
Условно включим две ссылки:
Если мы вышли за пределы «первой» страницы, мы добавим ссылку на предыдущую страницу.
Если в текущем наборе результатов десять контактов, мы добавим ссылку на следующую страницу.
Это не идеальный виджет подкачки: в идеале мы должны показывать количество страниц и предлагать возможность более конкретной навигации по страницам, и существует вероятность того, что на следующей странице может быть 0 результатов, поскольку мы не проверяем общее количество результатов учитывается, но на данный момент этого достаточно для нашего простого приложения.
Давайте посмотрим на код шаблона Jinja для этого в формате index.html
.
Добавьте новый элемент div под таблицей для хранения наших навигационных ссылок.
Если мы вышли за пределы страницы 1, добавьте тег привязки со страницей, уменьшенной на единицу.
Если на текущей странице 10 контактов, добавьте тег привязки, ссылающийся на следующую страницу, увеличив его на единицу.
Обратите внимание, что здесь мы используем специальный синтаксис фильтра jinja contacts|length
для вычисления длины списка контактов. Детали синтаксиса этого фильтра выходят за рамки этой книги, но в данном случае вы можете думать об этом как о вызове свойства contacts.length
и последующем сравнении его с 10
.
Теперь, когда у нас есть эти ссылки, давайте рассмотрим реализацию пейджинга на стороне сервера.
Мы используем page
параметр запроса для кодирования состояния подкачки пользовательского интерфейса. Итак, в нашем обработчике нам нужно найти этот page
параметр и передать его в нашу модель как целое число, чтобы модель знала, какую страницу контактов возвращать:
Разрешите параметр страницы, по умолчанию равный странице 1, если страница не передана.
Передайте страницу модели при загрузке всех контактов, чтобы она знала, какую страницу из 10 контактов вернуть.
Это довольно просто: нам просто нужно получить другой параметр, такой как q
параметр, который мы передали ранее для поиска контактов, преобразовать его в целое число, а затем передать его модели Contact
, чтобы она знала, какую страницу возвращать.
И с этим небольшим изменением мы закончили: теперь у нас есть очень простой механизм подкачки для нашего веб-приложения.
И, хотите верьте, хотите нет, он уже использует AJAX благодаря использованию hx-boost
в приложении. Легкий!
Этот механизм подкачки подходит для базового веб-приложения и широко используется в Интернете. Но у него есть некоторые недостатки: каждый раз, когда вы нажимаете кнопки «Далее» или «Предыдущий», вы получаете совершенно новую страницу контактов и теряете весь контекст, который у вас был на предыдущей странице.
Иногда более продвинутый шаблон пользовательского интерфейса подкачки может быть лучше. Возможно, вместо того, чтобы загружать новую страницу элементов и заменять текущие элементы, было бы лучше добавить следующую страницу элементов inline после текущих элементов.
Это распространенный шаблон UX «нажми, чтобы загрузить», встречающийся в более продвинутых веб-приложениях.
Здесь у вас есть кнопка, которую вы можете нажать, и она загрузит следующий набор контактов непосредственно на страницу, а не «перелистывает» на следующую страницу. Это позволяет вам визуально сохранять текущие контакты «в контексте» на странице, но при этом продвигаться по ним так же, как в обычном страничном пользовательском интерфейсе.
Давайте посмотрим, как мы можем реализовать этот шаблон UX в htmx.
На самом деле это удивительно просто: мы можем просто взять существующую ссылку «Далее» и немного изменить ее назначение, используя только несколько атрибутов htmx!
Мы хотим иметь кнопку, которая при нажатии добавляет строки со следующей страницы контактов в текущую выходную таблицу, а не перерисовывает всю таблицу. Этого можно добиться, добавив в нашу таблицу новую строку, в которой есть именно такая кнопка:
Показывать «Загрузить больше», только если на текущей странице есть 10 результатов контактов.
Выберите ближайшую закрывающую строку.
Замените всю строку ответом сервера.
Выделите строки таблицы из ответа.
Давайте подробно рассмотрим каждый атрибут здесь.
Во-первых, мы используем hx-target
для нацеливания на «ближайший» tr
элемент, то есть ближайшую родительскую строку таблицы.
Во-вторых, мы хотим заменить всю эту строку всем содержимым, возвращаемым с сервера.
В-третьих, мы хотим извлечь только tr
элементы ответа. Мы заменяем этот tr
элемент новым набором tr
элементов, в которых будет дополнительная контактная информация, а также, при необходимости, новая кнопка «Загрузить еще», указывающая на следующую следующую страницу. Для этого мы используем селектор CSS, tbody > tr
чтобы гарантировать, что мы извлекаем только строки в теле таблицы в ответе. Это позволяет избежать, например, включения строк в заголовок таблицы.
Наконец, мы выдаем HTTP-запрос GET
на URL-адрес, который будет обслуживать следующую страницу контактов, который выглядит так же, как ссылка «Далее» сверху.
Несколько удивительно, что для этой новой функциональности не требуется никаких изменений на стороне сервера. Это связано с гибкостью, которую дает htmx в отношении обработки ответов сервера.
Итак, четыре атрибута, и теперь у нас есть сложный пользовательский интерфейс «Click To Load» через htmx.
Другой распространенный шаблон работы с большими наборами объектов известен как шаблон «Бесконечная прокрутка». В этом шаблоне, когда последний элемент списка или таблицы элементов прокручивается в поле зрения, дополнительные элементы загружаются и добавляются в список или таблицу.
Теперь такое поведение имеет больше смысла в ситуациях, когда пользователь просматривает категорию или серию публикаций в социальных сетях, а не в контексте приложения для контактов. Однако для полноты картины и просто для того, чтобы показать, что можно делать с помощью htmx, мы также реализуем этот шаблон.
Оказывается, мы можем довольно легко переназначить код «Нажмите, чтобы загрузить», чтобы реализовать этот новый шаблон: если вы на мгновение задумаетесь, бесконечная прокрутка — это на самом деле просто логика «Нажмите, чтобы загрузить», а не загрузка, когда происходит событие click, мы хотим загружаться, когда элемент «появляется» на портале просмотра браузера.
По счастливой случайности, htmx предлагает синтетическое (нестандартное) событие DOM, revealed
которое можно использовать вместе с hx-trigger
атрибутом для запуска запроса, когда, ну, когда элемент обнаружен.
Итак, давайте преобразуем нашу кнопку в диапазон и воспользуемся этим событием:
Мы преобразовали наш элемент из кнопки в диапазон, так как пользователь не будет на него нажимать.
Мы запускаем запрос, когда элемент раскрывается, то есть когда он появляется на портале.
Все, что нам нужно было сделать, чтобы преобразовать «Нажмите, чтобы загрузить» в «Бесконечную прокрутку», — это обновить наш элемент, чтобы он стал интервалом, а затем добавить триггер revealed
события.
Тот факт, что переключиться на бесконечную прокрутку было так легко, показывает, насколько хорошо htmx обобщает HTML: всего несколько атрибутов позволяют нам значительно расширить возможности гипермедиа.
И, опять же, мы делаем все это, используя преимущества модели Интернета RESTful. Несмотря на все это новое поведение, мы по-прежнему обмениваемся гипермедиа с сервером без какого-либо ответа JSON API.
Как была разработана сеть.
Подумайте дважды о модальных окнах. Модальные окна сегодня стали популярными, почти стандартными во многих веб-приложениях.
К сожалению, модальные окна плохо сочетаются с большей частью инфраструктуры Интернета и вводят состояние на стороне клиента, которое может быть сложно (хотя и возможно) интегрировать с подходом на основе гипермедиа.
Модальные окна можно безопасно использовать для представлений, которые не представляют собой ресурс или не соответствуют объекту домена:
Оповещения
Диалоги подтверждения
Формы для создания/обновления сущностей
В противном случае рассмотрите возможность использования альтернатив, таких как встроенное редактирование или отдельная страница, а не модальное окно.
Используйте display: none;
с осторожностью . Проблема в том, что это не чисто косметический эффект — он также удаляет элементы из дерева доступности и фокуса клавиатуры. Иногда это делается для представления одного и того же контента в визуальных и звуковых интерфейсах. Если вы хотите скрыть элемент визуально, не скрывая его от вспомогательных технологий (например, элемент содержит информацию, передаваемую посредством стиля), вы можете использовать этот служебный класс:
vh
является сокращением от «визуально скрытого». Этот класс использует несколько методов и обходных путей, чтобы гарантировать, что ни один браузер не удалит функцию элемента.
Согласно веб-документам Mozilla Developer Network (MDN) в ответе , это означает, что метод HTTP- запроса не изменится при выдаче перенаправленного HTTP-запроса.
К счастью, есть другой код ответа, который делает то, что мы хотим: когда браузер получает 303 See Other
ответ на перенаправление, он отправляет GET
в новое местоположение.