Глава 07. Пользовательский интерфейс динамического архива
Contact.app прошел долгий путь от традиционного веб-приложения в стиле Web 1.0: мы добавили активный поиск, массовое удаление, несколько приятных анимаций и множество других функций. Мы достигли уровня интерактивности, который, по мнению большинства веб-разработчиков, требует какой-то инфраструктуры JavaScript для одностраничных приложений, но мы сделали это, используя вместо этого гипермедиа на базе htmx.
Давайте посмотрим, как мы можем добавить в Contact.app еще одну важную функцию: загрузку архива всех контактов.
С точки зрения гипермедиа загрузка файла не совсем сложная задача: используя Content-Disposition
заголовок ответа HTTP, мы можем легко указать браузеру загрузить и сохранить файл на локальном компьютере.
Однако давайте сделаем эту проблему более интересной: добавим тот факт, что экспорт может занять некоторое время, от пяти до десяти секунд, а иногда и дольше.
Это означает, что если бы мы реализовали загрузку как «обычный» HTTP-запрос, управляемый ссылкой или кнопкой, пользователь мог бы сидеть с очень небольшой визуальной обратной связью, задаваясь вопросом, действительно ли происходит загрузка, пока завершается экспорт. Они могут даже сдаться в отчаянии и снова щелкнуть элемент управления загрузкой гипермедиа, что вызовет второй запрос на архивирование. Не хорошо.
Это классическая проблема разработки веб-приложений. Столкнувшись с потенциально длительным процессом, подобным этому, у нас в конечном итоге есть два варианта:
Когда пользователь запускает действие, заблокируйте его до его завершения, а затем ответьте результатом.
Начните действие и немедленно вернитесь, показывая какой-то пользовательский интерфейс, указывающий, что что-то происходит.
Блокировка и ожидание завершения действия, безусловно, является более простым способом справиться с ним, но это может быть неприятным для пользователя, особенно если для завершения действия требуется некоторое время. Если вы когда-нибудь нажимали на что-то в приложении в стиле Web 1.0, а затем вам приходилось сидеть там целую вечность, прежде чем что-то произойдет, вы видели практические результаты этого выбора.
Второй вариант, запускающий действие асинхронно (скажем, путем создания потока или отправки его в систему выполнения заданий), намного удобнее с точки зрения пользовательского опыта: сервер может ответить немедленно, и пользователю не нужно сидеть и задаваться вопросом. что происходит.
Но вопрос в том, что вы ответите ? Вероятно, работа еще не завершена, поэтому вы не можете предоставить ссылку на результаты.
Мы видели несколько разных «простых» подходов в этом сценарии в различных веб-приложениях:
Сообщите пользователю, что процесс начался, и что после его завершения он получит по электронной почте ссылку на результаты завершенного процесса.
Сообщите пользователю, что процесс запущен, и порекомендуйте ему вручную обновить страницу, чтобы увидеть состояние процесса.
Сообщите пользователю, что процесс начался, и автоматически обновляйте страницу каждые несколько секунд, используя JavaScript.
Все это будет работать, но ни одно из них не принесет хорошего пользовательского опыта.
Что нам действительно хотелось бы в этом сценарии, так это что-то похожее на то, что вы видите, например, когда загружаете большой файл через браузер: красивый индикатор выполнения, показывающий, на каком этапе процесса вы находитесь, и, когда процесс завершится, ссылка, по которой можно немедленно щелкнуть, чтобы просмотреть результат процесса.
Это может показаться чем-то невозможным для реализации с помощью гипермедиа, и, честно говоря, нам придется довольно сильно протолкнуть htmx, чтобы все это заработало, но когда это будет сделано, кода будет не так много, и мы сможем добиться желаемого пользовательского опыта для этой функции архивирования.
Требования к пользовательскому интерфейсу
Прежде чем мы углубимся в реализацию, давайте обсудим в общих чертах, как должен выглядеть наш новый пользовательский интерфейс: нам нужна кнопка в приложении с надписью «Загрузить архив контактов». Когда пользователь нажимает на эту кнопку, мы хотим заменить эту кнопку пользовательским интерфейсом, который показывает ход процесса архивирования, в идеале с индикатором выполнения. По мере выполнения задания архивирования мы хотим перемещать индикатор выполнения в направлении завершения. Затем, когда задание архивирования будет завершено, мы хотим показать пользователю ссылку для загрузки файла архива контактов.
Чтобы действительно выполнить архивирование, мы собираемся использовать класс Python, Archiver
который реализует все необходимые нам функции. Как и в случае с Contact
классом, мы не будем вдаваться в подробности реализации Archiver
, поскольку это выходит за рамки данной книги. На данный момент вам просто нужно знать, что он обеспечивает все поведение на стороне сервера, необходимое для запуска процесса архивирования контактов и получения результатов после завершения этого процесса.
Archiver
дает нам следующие методы для работы:
status()
– Строка, представляющая статус загрузки: либоWaiting
,Running
либоComplete
progress()
- Число от 0 до 1, указывающее, насколько продвинулось задание архивирования.run()
- Запускает новое задание архивирования (если текущий статусWaiting
)reset()
- Отменяет текущее задание архивирования, если оно имеется, и сбрасывает его в состояние «Ожидание».archive_file()
- Путь к архивному файлу, созданному на сервере, чтобы мы могли отправить его клиенту.get()
- Метод класса, который позволяет нам получить Архиватор для текущего пользователя.
Довольно несложный API.
Единственным сложным аспектом всего API является то, что этот run()
метод неблокируется . Это означает, что он не создает файл архива сразу , а запускает фоновое задание (в виде потока) для фактического архивирования. Это может сбить с толку, если вы не привыкли к многопоточности кода: вы можете ожидать, что метод run()
«блокируется», то есть фактически выполнит весь экспорт и вернется только после его завершения. Но если бы это произошло, мы не смогли бы запустить процесс архивирования и немедленно отобразить желаемый пользовательский интерфейс процесса архивирования.
Начало нашей реализации
Теперь у нас есть все необходимое, чтобы приступить к реализации нашего пользовательского интерфейса: разумное представление о том, как он будет выглядеть, и логика предметной области для его поддержки.
Итак, для начала обратите внимание, что этот пользовательский интерфейс в значительной степени автономен: мы хотим заменить кнопку индикатором выполнения загрузки, а затем индикатор выполнения ссылкой для загрузки результатов завершенного процесса архивирования.
Тот факт, что весь наш архивный пользовательский интерфейс будет находиться в определенной части пользовательского интерфейса, является убедительным намеком на то, что мы захотим создать новый шаблон для его обработки. Назовем этот шаблон archive_ui.html
.
Также обратите внимание, что в нескольких случаях нам понадобится заменить весь пользовательский интерфейс загрузки:
Когда мы начнем загрузку, мы захотим заменить кнопку индикатором выполнения.
По мере продолжения процесса архивирования нам потребуется заменить/обновить индикатор выполнения.
Когда процесс архивирования завершится, мы захотим заменить индикатор выполнения ссылкой для загрузки.
Чтобы обновить пользовательский интерфейс таким образом, нам нужно установить хорошую цель для обновлений. Итак, давайте обернем весь пользовательский интерфейс тегом div
, а затем будем использовать его div
в качестве цели для всех наших операций.
Вот начало шаблона нашего нового пользовательского интерфейса архива:
Этот div будет целью для всех элементов внутри него.
Заменяйте весь div каждый раз, используя
outerHTML
.
Далее, давайте добавим кнопку «Загрузить архив контактов», которая div
запустит процесс архивирования и последующей загрузки. Мы будем использовать POST
путь /contacts/archive
, чтобы запустить процесс архивирования:
Эта кнопка выдаст
POST
сообщение/contacts/archive
.
Наконец, давайте добавим этот новый шаблон в наш основной index.html
шаблон над таблицей контактов:
Этот шаблон теперь будет включен в основной шаблон.
После этого в нашем веб-приложении появилась кнопка, позволяющая начать загрузку. Поскольку на корпусе div
есть hx-target="this"
, кнопка унаследует эту цель и заменит это вложение div
любым HTML-кодом, возвращаемым из POST
to /contacts/archive
.
Добавление конечной точки архивирования
Наш следующий шаг — обработать то POST
, что делает наша кнопка. Мы хотим получить имя Archiver
текущего пользователя и вызвать run()
для него метод. Это запустит процесс архивирования. Затем мы отобразим новый контент, указывающий, что процесс запущен.
Для этого мы хотим повторно использовать archive_ui
шаблон для обработки пользовательского интерфейса архива для обоих состояний: когда архиватор находится в состоянии «Ожидание» и когда он «Выполняется». (С состоянием «Завершено» мы разберемся чуть позже).
Это очень распространенный шаблон: мы помещаем все различные потенциальные пользовательские интерфейсы для данного фрагмента пользовательского интерфейса в один шаблон и условно отображаем соответствующий интерфейс. Храня все в одном файле, другим разработчикам (или нам, если мы вернемся через некоторое время!) будет намного проще понять, как именно работает пользовательский интерфейс на стороне клиента.
Поскольку мы собираемся условно отображать различные пользовательские интерфейсы в зависимости от состояния архиватора, нам нужно будет передать архиватор в шаблон в качестве параметра. Итак, еще раз: нам нужно вызвать run()
архиватор в нашем контроллере, а затем передать архиватор шаблону, чтобы он мог отображать пользовательский интерфейс, соответствующий текущему состоянию процесса архивирования.
Вот как выглядит код:
Обработать .
POST
_/contacts/archive
Найдите Архиватор.
run()
Вызовите для него неблокирующий метод.Отрендерите
archive_ui.html
шаблон, передав его в архиватор.
Условный рендеринг пользовательского интерфейса прогресса
Теперь давайте обратим внимание на обновление нашего пользовательского интерфейса архивирования, настроив archive_ui.html
условное отображение различного контента в зависимости от состояния процесса архивирования.
Напомним, что у архиватора есть status()
метод. Когда мы передаем архиватор в качестве переменной в шаблон, мы можем обратиться к этому status()
методу, чтобы увидеть состояние процесса архивирования.
Если архиватор имеет статус Waiting
, мы хотим отобразить кнопку «Скачать архив контактов». Если статус равен Running
, мы хотим отобразить сообщение, указывающее, что прогресс происходит. Давайте обновим код нашего шаблона, чтобы сделать именно это:
Отрисовывайте кнопку архивирования только в том случае, если статус «Ожидание».
Отображение другого контента, когда статус «Выполняется».
На данный момент просто текст о том, что процесс запущен.
Хорошо, отлично, у нас есть некоторая условная логика в представлении шаблона и логика на стороне сервера для поддержки запуска процесса архивирования. У нас пока нет индикатора выполнения, но мы к этому доберемся! Давайте посмотрим, как это работает в нынешнем виде, и обновим главную страницу нашего приложения…
Ой!
Мы получаем сообщение об ошибке прямо из коробки. Почему? Ах, мы включили archive_ui.html
в index.html
шаблон, но теперь archive_ui.html
шаблон ожидает, что к нему будет передан архиватор, поэтому он может условно отобразить правильный пользовательский интерфейс.
Это легко исправить: нам просто нужно передать архиватор при рендеринге шаблона index.html
:
Через архиватор перейти к основному шаблону
Теперь, когда все готово, мы можем загрузить страницу. И, конечно же, мы видим кнопку «Скачать архив контактов».
Когда мы нажимаем на нее, кнопка заменяется содержимым «Выполняется…», и мы видим в нашей консоли разработки на стороне сервера, что задание действительно запускается должным образом.
Опрос
Это определенно прогресс, но у нас здесь не лучший индикатор прогресса: просто какой-то статический текст, сообщающий пользователю, что процесс запущен.
Мы хотим обновлять контент по мере продвижения процесса и, в идеале, показывать индикатор выполнения, показывающий, насколько далеко он продвинулся. Как мы можем сделать это в Htmx, используя старую добрую гипермедиа?
Метод, который мы хотим здесь использовать, называется «опрос», при котором мы выдаем запрос через определенный интервал и обновляем пользовательский интерфейс в зависимости от нового состояния сервера.
Опрос? Действительно?
Опрос имеет плохую репутацию, и это не самый популярный метод в мире: сегодня разработчики могут рассмотреть более продвинутый метод, такой как WebSockets или Server Sent Events (SSE), чтобы решить эту ситуацию.
Но, что ни говори, опрос работает , и он чертовски прост. Вам нужно быть осторожным, чтобы не перегрузить вашу систему опросными запросами, но, проявив немного осторожности, вы можете создать с его помощью надежный, пассивно обновляемый компонент в вашем пользовательском интерфейсе.
Htmx предлагает два типа опроса. Первый — это «опрос с фиксированной частотой», в котором используется специальный hx-trigger
синтаксис, указывающий, что что-то следует опрашивать через фиксированный интервал.
Вот пример:
Триггер
GET
каждые/messages
три секунды.
Это отлично работает в ситуациях, когда вы хотите проводить опрос бесконечно, например, если вы хотите постоянно опрашивать новые сообщения для отображения пользователю. Однако опрос с фиксированной частотой не идеален, если у вас есть определенный процесс, после которого вы хотите остановить опрос: он продолжает опрос навсегда, пока элемент, на котором он находится, не будет удален из DOM.
В нашем случае мы имеем определенный процесс с его окончанием. Поэтому лучше использовать второй метод опроса, известный как «опрос нагрузки». При опросе нагрузки мы пользуемся тем фактом, что htmx запускает событие load
при загрузке контента в DOM. Мы можем создать триггер для этого load
события и добавить небольшую задержку, чтобы запрос не запускался немедленно.
Благодаря этому мы можем условно рендерить hx-trigger
каждый запрос: когда процесс завершился, мы просто не включаем триггер load
, и опрос нагрузки прекращается. Это предлагает хороший и простой способ опроса до завершения определенного процесса.
Использование опроса для обновления пользовательского интерфейса архива
Давайте воспользуемся опросом нагрузки, чтобы обновлять наш пользовательский интерфейс по мере работы архиватора. Чтобы показать прогресс, давайте воспользуемся индикатором выполнения на основе CSS, воспользовавшись методом, progress()
который возвращает число от 0 до 1, указывающее, насколько близок процесс архивирования к завершению.
Вот фрагмент HTML, который мы будем использовать:
Ширина внутреннего элемента соответствует прогрессу.
Этот индикатор выполнения на основе CSS состоит из двух компонентов: внешнего div
, который обеспечивает каркас для индикатора выполнения, и внутреннего, div
который является фактическим индикатором индикатора выполнения. Мы устанавливаем ширину внутреннего индикатора выполнения в определенный процент (обратите внимание, что нам нужно умножить результат progress()
на 100, чтобы получить процент), и это сделает индикатор прогресса соответствующей ширины в родительском div.
А как насчет элемента <progress>?
Мы решили не использовать этот progress
элемент для этого примера, потому что мы хотим, чтобы наш индикатор выполнения обновлялся плавно, и для этого нам нужно будет использовать технику CSS, недоступную для этого progress
элемента. Это прискорбно, но иногда нам приходится играть с теми картами, которые нам сдали.
Давайте обновим наш индикатор выполнения, чтобы он имел правильные роли и значения ARIA:
Этот элемент будет действовать как индикатор выполнения.
Прогрессом будет процентная завершенность архиватора, где 100 означает полную завершенность.
Наконец, для полноты картины, вот CSS, который мы будем использовать для этого индикатора выполнения:
В итоге рендеринг выглядит следующим образом:
Добавление пользовательского интерфейса индикатора выполнения
Давайте добавим в наш шаблон код нашего индикатора выполнения archive_ui.html
на случай, когда архиватор запущен, и обновим копию, написав «Создание архива…»:
Наш новый блестящий индикатор выполнения
Теперь, когда мы нажимаем кнопку «Загрузить архив контактов», мы видим индикатор выполнения. Но он по-прежнему не обновляется, потому что мы еще не реализовали опрос нагрузки: он просто находится там, на нуле.
Чтобы индикатор выполнения обновлялся динамически, нам нужно реализовать опрос нагрузки с помощью hx-trigger
. Мы можем добавить это практически к любому элементу внутри условного блока, когда архиватор работает, поэтому давайте добавим его к тому, что div
окружает текст «Создание архива…» и индикатор выполнения.
Давайте проведем опрос, отправив HTTP-запрос GET
по тому же пути, что и POST
: /contacts/archive
.
Выдайте a
GET
через/contacts/archive
500 миллисекунд после загрузки контента.
Когда это GET
будет выдано /contacts/archive
, оно заменит div
идентификатор archive-ui
, а не только себя. Атрибут hx-target
с div
идентификатором archive-ui
наследуется всеми дочерними элементами внутри этого элемента , поэтому все дочерние элементы будут нацелены на самый внешний элемент в файле .divdivarchive_ui.html
Теперь нам нужно обработать GET
to /contacts/archive
на сервере. К счастью, это довольно просто: все, что нам нужно сделать, это повторно отрендерить archive_ui.html
с помощью архиватора:
дескриптор
GET
пути/contacts/archive
_просто перерисуйте
archive_ui.html
шаблон
Как и многое другое в гипермедиа, код очень читабелен и не сложен.
Теперь, когда мы нажимаем «Загрузить архив контактов», мы действительно получаем индикатор выполнения, который обновляется каждые 500 миллисекунд. В результате вызова archiver.progress()
постепенного обновления от 0 до 1 индикатор выполнения перемещается для нас по экрану. Очень круто!
Загрузка результата
Нам нужно обработать одно последнее состояние — случай, когда archiver.status()
установлено значение «Завершено», и имеется JSON-архив данных, готовый к загрузке. Когда архиватор завершен, мы можем получить локальный JSON-файл на сервере из архиватора посредством вызова archive_file()
.
Давайте добавим еще один случай в наш оператор if для обработки состояния «Завершено», и, когда задание архивирования будет завершено, давайте отобразим ссылку на новый путь, который будет /contacts/archive/file
отвечать заархивированным файлом JSON. Вот новый код:
Если статус «Завершено», отобразите ссылку для скачивания.
Ссылка выдаст
GET
файл/contacts/archive/file
.
Обратите внимание, что ссылка hx-boost
установлена на false
. Это сделано для того, чтобы ссылка не наследовала поведение повышения, присутствующее для других ссылок, и, следовательно, не была выдана через AJAX. Нам нужно такое «нормальное» поведение ссылки, поскольку запрос AJAX не может загрузить файл напрямую, тогда как простой тег привязки может.
Загрузка готового архива
Последний шаг — обработка GET
запроса к /contacts/archive/file
. Мы хотим отправить файл, созданный архиватором, клиенту. Нам повезло: у Flask есть механизм отправки файла в качестве загруженного ответа — метод send_file()
.
Как вы видите в следующем коде, мы передаем три аргумента send_file()
: путь к архивному файлу, созданному архиватором, имя файла, который мы хотим, чтобы браузер создал, и, если мы хотим, чтобы он был отправлен «в виде вложения». » Этот последний аргумент сообщает Flask установить заголовок ответа HTTP Content-Disposition
с attachment
заданным именем файла; это то, что запускает поведение браузера при загрузке файлов.
Отправьте файл клиенту с помощью
send_file()
метода Flask.
Идеальный. Теперь у нас есть очень удобный пользовательский интерфейс архива. Вы нажимаете кнопку «Загрузить архив контактов», и появляется индикатор выполнения. Когда индикатор выполнения достигнет 100%, он исчезнет и появится ссылка для скачивания архивного файла. Затем пользователь может нажать на эту ссылку и загрузить свой архив.
Мы предлагаем пользовательский интерфейс, который гораздо более удобен для пользователя, чем обычный интерфейс многих веб-сайтов, основанный на принципах «нажми и подожди».
Сглаживание: анимация в Htmx
Каким бы приятным ни был этот пользовательский интерфейс, есть один небольшой недостаток: по мере обновления индикатора выполнения он «перепрыгивает» из одной позиции в другую. Это немного похоже на полное обновление страницы в приложениях в стиле Web 1.0. Есть ли способ это исправить? (Очевидно, что есть, поэтому мы выбрали элемент, div
а не progress
элемент!)
Давайте рассмотрим причину этой визуальной проблемы и способы ее устранения. (Если вы спешите получить ответ, смело переходите к «нашему решению».)
Оказывается, существует собственная технология HTML для сглаживания изменений элемента из одного состояния в другое: API CSS Transitions, тот самый, который мы обсуждали в главе 4. Используя CSS Transitions, вы можете плавно анимировать элемент между различными состояниями. стиль с помощью transition
свойства.
Если вы снова посмотрите на наше определение класса в CSS .progress-bar
, вы увидите следующее определение перехода: transition: width .6s ease;
. Это означает, что когда ширина индикатора выполнения изменяется, скажем, с 20% на 30%, браузер будет анимироваться в течение 0,6 секунды с использованием функции «Ease» (которая имеет хороший эффект ускорения/замедления).
Так почему же этот переход не применяется в нашем текущем пользовательском интерфейсе? Причина в том, что в нашем примере htmx заменяет индикатор выполнения новым каждый раз при опросе. Он не обновляет ширину существующего элемента. К сожалению, переходы CSS применяются только тогда, когда свойства существующего элемента изменяются внутри, а не при замене элемента.
Именно по этой причине приложения, основанные на чистом HTML, могут показаться прерывистыми и неотшлифованными по сравнению с их аналогами из SPA: сложно использовать CSS-переходы без некоторого количества JavaScript.
Но есть и хорошие новости: у htmx есть способ использовать переходы CSS, даже если он заменяет контент в DOM.
Шаг «Урегулирование» в Htmx
Когда мы обсуждали модель подкачки htmx в главе 4, мы сосредоточились на классах, которые добавляет и удаляет htmx, но пропустили процесс «согласования». В htmx согласование включает в себя несколько шагов: когда htmx собирается заменить часть контента, он просматривает новый контент и находит все элементы, в которых есть значок id
. Затем он ищет в существующем контенте элементы с таким же расширением id
.
Если он есть, он выполняет следующую довольно сложную перетасовку:
Новое содержимое временно получает атрибуты старого содержимого .
Новое содержимое вставлено.
После небольшой задержки атрибуты нового контента возвращаются к своим фактическим значениям.
Итак, чего же должен добиться этот странный маленький танец?
Что ж, если элемент имеет стабильный идентификатор между заменами, теперь вы можете писать CSS-переходы между различными состояниями. Поскольку новый контент на короткое время имеет старые атрибуты, при восстановлении фактических значений сработает обычный механизм перехода CSS.
Наше решение для сглаживания
Итак, мы подошли к нашему исправлению.
Все, что нам нужно сделать, это добавить стабильный идентификатор к нашему progress-bar
элементу.
Элемент div индикатора выполнения теперь имеет стабильный идентификатор для всех запросов.
Несмотря на сложную механику, происходящую за кулисами htmx, решение так же просто, как добавление стабильного id
атрибута к элементу, который мы хотим анимировать.
Теперь вместо того, чтобы прыгать при каждом обновлении, индикатор выполнения должен плавно перемещаться по экрану по мере обновления, используя переход CSS, определенный в нашей таблице стилей. Модель подкачки htmx позволяет нам добиться этого, даже если мы заменяем контент новым HTML.
И вуаля: у нас есть красивый, плавно анимированный индикатор выполнения нашей функции архивирования контактов. Результат выглядит как решение на основе JavaScript, но мы сделали это с простотой подхода на основе HTML.
Вот это, дорогой читатель, действительно вызывает радость.
Закрытие пользовательского интерфейса загрузки
Некоторые пользователи могут передумать и решить не скачивать архив. Возможно, они никогда не увидят нашу великолепную шкалу прогресса, но это нормально. Мы собираемся дать этим пользователям кнопку, позволяющую закрыть ссылку для скачивания и вернуться к исходному состоянию пользовательского интерфейса экспорта.
Для этого мы добавим кнопку, которая выдает к DELETE
пути /contacts/archive
, указывающий, что текущий архив можно удалить или очистить.
Мы добавим его после ссылки для скачивания, вот так:
Простая кнопка, которая выдает
DELETE
сообщение/contacts/archive
.
Теперь у пользователя есть кнопка, на которую он может нажать, чтобы закрыть ссылку на скачивание архива. Но нам нужно будет подключить его на стороне сервера. Как обычно, это довольно просто: мы создаем новый обработчик для DELETE
действия HTTP, вызываем reset()
метод архиватора и повторно отображаем archive_ui.html
шаблон.
Поскольку эта кнопка подбирается такая же hx-target
и hx-swap
по конфигурации, как и все остальное, она «просто работает».
Вот серверный код:
Вызов
reset()
архиватора
Это очень похоже на другие наши обработчики, не так ли?
Конечно, да! Это идея!
Альтернативный UX: автоматическая загрузка
Хотя мы предпочитаем текущий пользовательский интерфейс для архивирования контактов, существуют и другие альтернативы. В настоящее время индикатор выполнения показывает ход процесса, и после его завершения пользователю предоставляется ссылка для фактической загрузки файла. Другой шаблон, который мы видим в Интернете, — это «автоматическая загрузка», когда файл загружается немедленно, без необходимости нажатия пользователем ссылки.
Мы можем легко добавить эту функциональность в наше приложение, написав всего лишь немного сценариев. Мы обсудим сценарии в приложении, управляемом гипермедиа, более подробно в главе 9, но если коротко: сценарии вполне приемлемы в HDA, если они не заменяют основную механику гипермедиа приложения.
Все, что нам нужно сделать для реализации функции автоматической загрузки, это следующее: когда ссылка для скачивания отображается, автоматически щелкнуть ссылку для пользователя.
Код _hyperscript читается почти так же, как и предыдущее предложение (это основная причина, по которой мы любим гиперскрипт):
Немного _hyperscript для автоматической загрузки файла.
Важно отметить, что сценарии здесь просто улучшают существующую гипермедиа, а не заменяют ее запросом, не относящимся к гипермедиа. Это сценарии, дружественные к гипермедиа, о которых мы поговорим подробнее чуть позже.
Пользовательский интерфейс динамического архива: завершено
В этой главе нам удалось создать динамический пользовательский интерфейс для нашей функции архива контактов с индикатором выполнения и автоматической загрузкой, и мы сделали почти все это — за исключением небольшого количества сценариев для автоматической загрузки. — в чистой гипермедиа. Для сборки всего этого потребовалось около 16 строк внешнего кода и 16 строк внутреннего кода.
HTML, с небольшой помощью библиотеки JavaScript, ориентированной на гипермедиа, такой как htmx, на самом деле может быть чрезвычайно мощным и выразительным.
HTML-примечания: суп Markdown
Суп с уценкой — менее известный брат <div>
супа. Это результат того, что веб-разработчики ограничивают себя набором элементов, для которых язык Markdown обеспечивает сокращение, даже если эти элементы неверны. Если серьезно, важно осознавать всю мощь наших инструментов, включая HTML. Рассмотрим следующий пример цитирования в стиле IEEE:
Номер ссылки указан в скобках.
Подчеркивание вокруг названия книги создает элемент <em>.
Здесь используется <em>, потому что это единственный элемент Markdown, который по умолчанию отображается курсивом. Это указывает на то, что название книги подчеркивается, но цель состоит в том, чтобы обозначить его как название произведения. В HTML есть <cite>
элемент, предназначенный именно для этой цели.
Более того, несмотря на то, что это нумерованный список, идеально подходящий для <ol>
элемента, который поддерживает Markdown, вместо этого для ссылочных номеров используется простой текст. Почему это могло быть? Стиль цитирования IEEE требует, чтобы эти числа были представлены в квадратных скобках. Этого можно было бы добиться с помощью <ol>
CSS, но в Markdown нет способа добавить класс к элементам, что означает, что квадратные скобки будут применяться ко всем упорядоченным спискам.
Не уклоняйтесь от использования встроенного HTML в Markdown. Для более крупных сайтов также рассмотрите возможность расширения Markdown.
Многие диалекты Markdown позволяют добавлять идентификаторы, классы и атрибуты с помощью фигурных скобок.
Теперь мы можем использовать элемент <ol> и создать скобки в CSS.
Используем
<cite>
для обозначения названия цитируемой работы (а не всей цитаты!)
Вы также можете использовать специальные процессоры для создания более подробного HTML-кода вместо того, чтобы писать его вручную:
reference_list
— это макрос, который преобразует обычный текст в подробный HTML.Процессор также может разрешать идентификаторы, поэтому нам не нужно вручную поддерживать порядок в списке литературы и синхронизировать внутритекстовые цитаты.
Last updated