Глава 12. Создание приложения контактов с помощью Hyperview
В предыдущих главах этой книги объяснялись преимущества создания приложений с использованием архитектуры гипермедиа. Эти преимущества были продемонстрированы при создании надежного веб-приложения «Контакты». Затем в главе 11 утверждалось, что концепции гипермедиа могут и должны применяться к платформам, отличным от Интернета. Мы представили Hyperview как пример гипермедийного формата и клиента, специально предназначенного для создания мобильных приложений. Но вам все еще может быть интересно: каково это — создать полнофункциональное, готовое к работе мобильное приложение с помощью Hyperview? Должны ли мы изучать совершенно новый язык и структуру? В этой главе мы покажем Hyperview в действии, перенеся веб-приложение «Контакты» в собственное мобильное приложение. Вы увидите, что многие методы веб-разработки (и большая часть кода) полностью идентичны при разработке с помощью Hyperview. Как это возможно?
Наше веб-приложение «Контакты» было построено по принципу HATEOAS (Гипермедиа как двигатель состояния приложения). Все функции приложения (получение, поиск, редактирование и создание контактов) реализованы в бэкэнде (классе
Contacts
Python). Наше мобильное приложение, созданное с помощью Hyperview, также использует HATEOAS и использует серверную часть для всей логики приложения. Это означает, чтоContacts
класс Python может обеспечивать работу нашего мобильного приложения так же, как и веб-приложение, без каких-либо изменений.Взаимодействие клиент-сервер в веб-приложении происходит с использованием HTTP. HTTP-сервер для нашего веб-приложения написан с использованием инфраструктуры Flask. Hyperview также использует HTTP для связи клиент-сервер. Таким образом, мы можем повторно использовать маршруты и представления Flask из веб-приложения и для мобильного приложения.
Веб-приложение использует HTML в качестве формата гипермедиа, а Hyperview использует HXML. HTML и HXML — это разные форматы, но базовый синтаксис схож (вложенные теги с атрибутами). Это означает, что мы можем использовать одну и ту же библиотеку шаблонов (Jinja) для HTML и HXML. Кроме того, многие концепции htmx встроены в HXML. Мы можем напрямую переносить функции веб-приложений, реализованные с помощью htmx (поиск, бесконечная загрузка), в HXML.
По сути, мы можем повторно использовать практически все из серверной части веб-приложения, но нам нужно будет заменить шаблоны HTML шаблонами HXML. В большинстве разделов этой главы предполагается, что приложение веб-контактов работает локально и прослушивает порт 5000. Готовы? Давайте создадим новые шаблоны HXML для пользовательского интерфейса нашего мобильного приложения.
Создание мобильного приложения
Чтобы начать работу с HXML, есть одно неприятное требование: клиент Hyperview. При разработке веб-приложений вам нужно беспокоиться только о сервере, поскольку клиент (веб-браузер) доступен повсеместно. На каждом мобильном устройстве не установлен эквивалентный клиент Hyperview. Вместо этого мы создадим собственный клиент Hyperview, настроенный только для взаимодействия с нашим сервером. Этот клиент можно упаковать в мобильное приложение для Android или iOS и распространить через соответствующие магазины приложений.
К счастью, нам не нужно начинать с нуля, чтобы реализовать клиент Hyperview. Репозиторий кода Hyperview поставляется с демонстрационным сервером и демонстрационным клиентом, созданным с использованием Expo. Мы будем использовать этот демонстрационный клиент, но в качестве отправной точки укажем его на серверную часть нашего приложения контактов.
Установите зависимости для демонстрационного приложения
Запустите сервер Expo, чтобы запустить мобильное приложение в симуляторе iOS.
После запуска yarn start
вам будет предложено открыть мобильное приложение с помощью эмулятора Android или симулятора iOS. Выберите вариант в зависимости от того, какой SDK разработчика у вас установлен. (Снимки экрана в этой главе взяты из симулятора iOS.) Если повезет, вы увидите мобильное приложение Expo, установленное в симуляторе. Мобильное приложение автоматически запустится и отобразит экран с надписью «Ошибка сетевого запроса». Это связано с тем, что по умолчанию это приложение настроено на отправку запроса на http://0.0.0.0:8085/index.xml, но наш сервер прослушивает порт 5000. Чтобы исправить это, мы можем внести простое изменение конфигурации в demo/src/constants.js
файл:
URL-адрес точки входа по умолчанию в демонстрационном приложении.
Настройка URL-адреса, указывающего на наше приложение контактов
Мы еще не в деле. Теперь, когда наш клиент Hyperview указывает на правильную конечную точку, мы видим другую ошибку — «ParseError». Это связано с тем, что серверная часть отвечает на запросы содержимым HTML, а клиент Hyperview ожидает ответа в формате XML (в частности, HXML). Итак, пришло время обратить внимание на наш сервер Flask. Мы рассмотрим представления Flask и заменим шаблоны HTML шаблонами HXML. В частности, давайте поддержим следующие функции в нашем мобильном приложении:
Список контактов с возможностью поиска
Просмотр сведений о контакте
Редактирование контакта
Удаление контакта
Добавление нового контакта
Нулевая конфигурация клиента в приложениях Hypermedia
Для многих мобильных приложений, использующих клиент Hyperview, настройка этого URL-адреса точки входа — единственный код на устройстве, который необходимо написать для доставки полнофункционального приложения. Рассматривайте URL-адрес точки входа как адрес, который вы вводите в веб-браузер, чтобы открыть веб-приложение. За исключением Hyperview, здесь нет адресной строки, а браузер жестко запрограммирован на открытие только одного URL-адреса. Этот URL-адрес будет загружать первый экран, когда пользователь запускает приложение. Любое другое действие, которое может предпринять пользователь, будет объявлено в HXML этого первого экрана. Эта минимальная конфигурация является одним из преимуществ архитектуры на основе Hypermedia.
Конечно, вам может потребоваться написать больше кода на устройстве, чтобы поддерживать больше функций в вашем мобильном приложении. Мы продемонстрируем, как это сделать, позже в этой главе, в разделе «Расширение клиента».
Список контактов с возможностью поиска
Мы начнем создавать наше приложение Hyperview с экрана точки входа и списка контактов. Для начальной версии этого экрана давайте поддержим следующие функции веб-приложения:
отображать прокручиваемый список контактов
Поле «поиск по мере ввода» над списком
«бесконечная прокрутка» для загрузки большего количества контактов по мере прокрутки пользователем
Кроме того, мы добавим в список взаимодействие «по запросу и обновлению», поскольку пользователи ожидают этого от пользовательского интерфейса списков в мобильных приложениях.
Если вы помните, все страницы веб-приложения «Контакты» имеют общий базовый шаблон layout.html
. Нам нужен аналогичный базовый шаблон для экранов мобильного приложения. Этот базовый шаблон будет содержать правила стиля нашего пользовательского интерфейса и базовую структуру, общую для всех экранов. Давайте назовем это layout.xml
.
Раздел заголовка шаблона с заголовком по умолчанию.
Раздел содержимого шаблона, предоставляемый другими шаблонами.
Мы используем теги и атрибуты HXML, описанные в предыдущей главе. Этот шаблон устанавливает базовый макет экрана с использованием тегов <doc>
, <screen>
, <body>
, <header>
и <view>
. Обратите внимание, что синтаксис HXML хорошо сочетается с библиотекой шаблонов Jinja. Здесь мы используем блоки Jinja для определения двух разделов ( header
и content
), которые будут содержать уникальное содержимое экрана. Закончив наш базовый шаблон, мы можем создать шаблон специально для экрана списка контактов.
Расширение базового шаблона макета
Переопределить
content
блок шаблона макетаСоздайте форму поиска, которая будет выдавать HTTP-запрос
GET
на/contacts
Список контактов с использованием
include
тега Jinja.
Этот шаблон расширяет базу layout.xml
и переопределяет content
блок с помощью <form>
. На первый взгляд может показаться странным, что форма охватывает как элементы, <text-field>
так и <list>
элементы. Но помните: в Hyperview данные формы включаются в любой запрос, исходящий от дочернего элемента. Вскоре мы добавим в список взаимодействия (потяните, чтобы обновить), для которых потребуются данные формы. Обратите внимание на использование include
тега Jinja для отображения HXML для строк контактов в списке ( hv/rows.xml
). Как и в шаблонах HTML, мы можем использовать, include
чтобы разбить наш HXML на более мелкие части. Это также позволяет серверу отвечать только шаблоном rows.xml
для таких взаимодействий, как поиск, бесконечная прокрутка и обновление по запросу.
Элемент HXML, группирующий набор
<item>
элементов в одном родительском элементе.Переберите контакты, которые были переданы в шаблон.
Отобразите значок
<item>
для каждого контакта, указав имя, номер телефона или адрес электронной почты.
В веб-приложении каждая строка списка отображала имя контакта, номер телефона и адрес электронной почты. Но в мобильном приложении у нас меньше недвижимости. Всю эту информацию было бы сложно уместить в одну строку. Вместо этого в строке отображаются только имя и фамилия контакта и возвращается адрес электронной почты или телефон, если имя не задано. Чтобы отобразить строку, мы снова используем синтаксис шаблона Jinja для визуализации динамического текста с данными, переданными в шаблон.
Теперь у нас есть шаблоны для базового макета, экрана контактов и строк контактов. Но нам все равно придется обновить представления Flask, чтобы использовать эти шаблоны. Давайте посмотрим на contacts()
представление в его текущей форме, написанное для веб-приложения:
Это представление поддерживает выборку набора контактов на основе двух параметров запроса q
: page
. Он также решает, следует ли отображать всю страницу ( index.html
) или только строки контактов ( rows.html
) на основе HX-Trigger
заголовка. Это представляет собой небольшую проблему. Заголовок HX-Trigger
устанавливается библиотекой htmx; в Hyperview нет эквивалентной функции. Более того, в Hyperview существует несколько сценариев, которые требуют от нас ответа только строками контактов:
идет поиск
потянуть, чтобы обновить
загрузка следующей страницы контактов
Поскольку мы не можем зависеть от заголовка типа HX-Trigger
, нам нужен другой способ определить, нужен ли клиенту полноэкранный режим или только строки в ответе. Мы можем сделать это, введя новый параметр запроса rows_only
. Если этот параметр имеет значение true
, представление ответит на запрос, отобразив rows.xml
шаблон. В противном случае он ответит шаблоном index.xml
:
Проверьте наличие нового
rows_only
параметра запроса.Отобразите соответствующий шаблон HXML на основе
rows_only
.
Нам нужно внести еще одно изменение. Flask предполагает, что большинство представлений будут отвечать HTML. Поэтому Flask по умолчанию присваивает Content-Type
заголовку ответа значение text/html
. Но клиент Hyperview ожидает получить контент HXML, указанный в Content-Type
заголовке ответа со значением application/vnd.hyperview+xml
. Клиент будет отклонять ответы с другим типом контента. Чтобы это исправить, нам нужно явно установить Content-Type
заголовок ответа в наших представлениях Flask. Мы сделаем это, представив новую вспомогательную функцию render_to_response()
:
Отображает заданный шаблон с предоставленными аргументами и аргументами ключевого слова.
Создайте явный объект ответа с помощью визуализированного шаблона.
Устанавливает заголовок ответа
Content-Type
в формате XML.
Как видите, эта вспомогательная функция работает render_template()
скрытно. render_template()
возвращает строку. Эта вспомогательная функция использует эту строку для создания явного Response
объекта. У объекта ответа есть headers
атрибут, позволяющий нам устанавливать и изменять заголовки ответа. В частности, render_to_response()
устанавливается Content-Type
значение application/vnd.hyperview+xml
, позволяющее клиенту Hyperview распознавать содержимое. Этот помощник является заменой для render_template
in наших представлений. Итак, все, что нам нужно сделать, это обновить последнюю строку функции contacts()
.
Преобразуйте шаблон HXML в ответ XML.
Благодаря этим изменениям в contacts()
представлении мы наконец сможем увидеть плоды нашего труда. После перезапуска серверной части и обновления экрана в нашем мобильном приложении мы видим экран контактов!
Поиск контактов
На данный момент у нас есть мобильное приложение, которое отображает экран со списком контактов. Но наш пользовательский интерфейс не поддерживает никаких взаимодействий. Ввод запроса в поле поиска не фильтрует список контактов. Давайте добавим поведение в поле поиска, чтобы реализовать взаимодействие поиска по мере ввода. Это требует расширения <text-field>
, чтобы добавить <behavior>
элемент.
Это поведение сработает при изменении значения текстового поля.
Когда поведение сработает, действие заменит содержимое внутри целевого элемента.
Целью действия является элемент с идентификатором
contacts-list
.Содержимое для замены будет получено по этому URL-пути.
Содержимое замены будет получено методом
GET
HTTP.
Первое, что вы заметите, это то, что мы изменили текстовое поле с использования самозакрывающегося тега ( <text-field />
) на использование открывающего и закрывающего тегов ( <text-field>…</text-field>
). Это позволяет нам добавить дочерний <behavior>
элемент для определения взаимодействия.
Атрибут trigger="change"
сообщает Hyperview, что изменение значения текстового поля вызовет действие. Каждый раз, когда пользователь редактирует содержимое текстового поля, добавляя или удаляя символы, срабатывает действие.
Остальные атрибуты элемента <behavior>
определяют действие. action="replace-inner"
означает, что действие обновит содержимое на экране, заменив содержимое HXML элемента новым содержимым. Чтобы replace-inner
выполнить свою задачу, нам нужно знать две вещи: текущий элемент на экране, на который будет нацелено действие, и контент, который будет использоваться для замены. target="contacts-list"
сообщает нам идентификатор текущего элемента. Обратите внимание, что мы устанавливаем id="contacts-list"
элемент <list>
в index.xml
. Поэтому, когда пользователь вводит поисковый запрос в текстовое поле, Hyperview заменит содержимое <list>
(группу <item>
элементов) новым содержимым (<item>
элементы, соответствующие поисковому запросу), полученные в соответствующем ответе href. Домен здесь выводится из домена, используемого для получения экрана. Обратите внимание, что это href
включает в себя наш rows_only
параметр запроса; мы хотим, чтобы ответ включал только строки, а не весь экран.
Это все, что нужно, чтобы добавить в наше мобильное приложение функцию поиска по мере ввода! Когда пользователь вводит поисковый запрос, клиент будет отправлять запросы к серверной части и заменять список результатами поиска. Вам может быть интересно, откуда серверная часть узнает, какой запрос использовать? Атрибут href
в поведении не включает параметр, q
ожидаемый нашим сервером. Но помните, что в index.xml
, мы обернули элементы <text-field>
и <list>
родительским <form>
элементом. Элемент <form>
определяет группу входных данных, которые будут сериализованы и включены в любые HTTP-запросы, инициированные его дочерними элементами. В этом случае <form>
элемент окружает поведение поиска и текстовое поле. Таким образом, ценность<text-field>
будет включен в наш HTTP-запрос для результатов поиска. Поскольку мы делаем GET
запрос, имя и значение текстового поля будут сериализованы как параметр запроса. Любые существующие параметры запроса href
будут сохранены. Это означает, что фактический HTTP-запрос к нашему серверу выглядит так GET /contacts?rows_only=true&q=Car
: Наша серверная часть уже поддерживает q
параметр для поиска, поэтому ответ будет включать строки, соответствующие строке «Автомобиль».
Бесконечная прокрутка
Если у пользователя сотни или тысячи контактов, загрузка их всех одновременно может привести к снижению производительности приложения. Вот почему большинство мобильных приложений с длинными списками реализуют взаимодействие, известное как «бесконечная прокрутка». Приложение загружает в список фиксированное количество начальных элементов, скажем, 100 элементов. Если пользователь прокручивает список до конца, он видит счетчик, указывающий, что загружается больше контента. Как только контент станет доступен, счетчик заменяется следующей страницей из 100 элементов. Эти элементы добавляются в список, а не заменяют первый набор элементов. Итак, список теперь содержит 200 пунктов. Если пользователь снова прокрутит список до конца, он увидит еще один счетчик, и приложение загрузит следующий набор контента. Бесконечная прокрутка повышает производительность приложения двумя способами:
Первоначальный запрос на 100 элементов будет обработан быстро с предсказуемой задержкой.
Последующие запросы также могут быть быстрыми и предсказуемыми.
Если пользователь не прокрутит список до конца, приложению не придется выполнять последующие запросы.
Наш бэкэнд Flask уже поддерживает нумерацию страниц на /contacts
конечной точке через page
параметр запроса. Нам просто нужно изменить наши шаблоны HXML, чтобы использовать этот параметр. Для этого давайте отредактируем rows.xml
и добавим новый <item>
ниже цикла for Jinja:
Включите в список дополнительный элемент
<item>
, чтобы отобразить счетчик.Поведение элемента срабатывает, когда он виден в области просмотра.
При срабатывании поведение заменит элемент на экране.
Заменяемым элементом является сам элемент (ID
load-more
).Замените элемент следующей страницей содержимого.
Спиннерный элемент.
Если текущий список контактов, переданный в шаблон, пуст, мы можем предположить, что больше нет контактов, которые можно было бы получить из серверной части. Поэтому мы используем условие Jinja, чтобы включать это новое только <item>
в том случае, если список контактов не пуст. Этот новый <item>
элемент получает идентификатор и поведение. Поведение определяет взаимодействие бесконечной прокрутки.
До сих пор мы видели trigger
значения change
и refresh
. Но для реализации бесконечной прокрутки нам нужен способ запускать действие, когда пользователь прокручивает список до конца. Триггер visible
можно использовать именно для этой цели. Это вызовет действие, когда элемент с таким поведением будет виден в окне просмотра устройства. В этом случае новый <item>
элемент является последним элементом в списке, поэтому действие сработает, когда пользователь прокрутит вниз достаточно далеко, чтобы элемент попал в область просмотра. Как только элемент станет видимым, действие выполнит HTTP-запрос GET и заменит <item>
элемент загрузки содержимым ответа.
Обратите внимание, что наш href должен включать rows_only=true
параметр запроса, чтобы наш ответ включал HXML только для элементов контакта, а не для всего экрана. Кроме того, мы передаем page
параметр запроса, увеличивая номер текущей страницы, чтобы гарантировать загрузку следующей страницы.
Что происходит, когда элементов имеется более одной страницы? Начальный экран будет включать первые 100 элементов, а также элемент «Загрузить еще» внизу. Когда пользователь прокручивает экран до нижней части, Hyperview запросит вторую страницу элементов ( &page=2
) и заменит элемент «Загрузить больше» новыми элементами. Но на этой второй странице элементов будет новый элемент «Загрузить больше». Поэтому, как только пользователь прокрутит все элементы на второй странице, Hyperview снова запросит дополнительные элементы ( &page=3
). И снова пункт «загрузить еще» будет заменен на новые элементы. Это будет продолжаться до тех пор, пока все элементы не будут загружены на экран. В этот момент больше не будет контактов для возврата, ответ не будет включать еще один элемент «загрузить больше», и наша нумерация страниц будет завершена.
Обновление по запросу
Обновление по запросу — обычное взаимодействие в мобильных приложениях, особенно на экранах с динамическим контентом. Это работает следующим образом: в верхней части прокручиваемого представления пользователь тянет прокручиваемый контент вниз жестом прокрутки вниз. Это покажет счетчик «под» содержимым. Если переместить контент вниз на достаточное расстояние, произойдет обновление. Пока содержимое обновляется, счетчик остается видимым на экране, указывая пользователю, что действие все еще происходит. После обновления содержимого оно возвращается в положение по умолчанию, скрывая счетчик и давая пользователю знать, что взаимодействие завершено.
Этот шаблон настолько распространен и полезен, что встроен в Hyperview через refresh
действие. Давайте добавим функцию обновления по запросу в наш список контактов, чтобы увидеть ее в действии.
Это поведение сработает, когда пользователь выполнит жест «потянуть, чтобы обновить».
Когда поведение сработает, это действие заменит содержимое внутри целевого элемента.
Целью действия является
<list>
сам элемент.Содержимое для замены будет получено по этому URL-пути.
Содержимое замены будет получено методом
GET
HTTP.
В приведенном выше фрагменте вы заметите кое-что необычное: вместо добавления элемента <behavior>
в <list>
, мы добавили атрибуты поведения непосредственно к <list>
элементу. Это сокращенное обозначение, которое иногда полезно для указания отдельного поведения элемента. Это эквивалентно добавлению <behavior>
элемента <list>
с теми же атрибутами.
Так почему же мы использовали здесь сокращенный синтаксис? Это связано с действием replace-inner
. Помните, что это действие заменяет все дочерние элементы цели новым содержимым. Это включает в себя <behavior>
и элементы! Допустим, наш <list>
файл содержал файл <behavior>
. Если бы пользователь выполнил поиск или обновил по запросу, мы бы заменили содержимое <list>
содержимым из rows.xml
. Больше <behavior>
не будет определяться в <list>
, и последующие попытки обновления не сработают. Если поведение определить как атрибуты <list>
, оно будет сохраняться даже при замене элементов в списке. Обычно мы предпочитаем использовать явные<behavior>
элементы в HXML. Это упрощает определение нескольких вариантов поведения и их перемещение во время рефакторинга. Но сокращенный синтаксис удобен для применения в подобных ситуациях.
Просмотр сведений о контакте
Теперь, когда наш экран списка контактов находится в хорошей форме, мы можем начать добавлять в наше приложение другие экраны. Следующим естественным шагом будет создание экрана подробностей, который появляется, когда пользователь касается элемента в списке контактов. Давайте обновим шаблон, отображающий <item>
элементы контакта, и добавим поведение для отображения экрана сведений.
Поведение, позволяющее помещать экран сведений о контакте в стек при нажатии.
В нашем бэкэнде Flask уже есть маршрут для предоставления контактных данных по адресу /contacts/<contact_id>
. В нашем шаблоне мы используем переменную Jinja для динамического создания URL-пути для текущего контакта в цикле for. Мы также использовали действие «push», чтобы показать детали, помещая новый экран в стек. Если вы перезагрузите приложение, вы сможете коснуться любого контакта в списке, и Hyperview откроет новый экран. Однако на новом экране появится сообщение об ошибке. Это потому, что наш сервер по-прежнему возвращает в ответ HTML, а клиент Hyperview ожидает HXML. Давайте обновим серверную часть, чтобы она отвечала HXML и соответствующими заголовками.
Создайте ответ XML из нового файла шаблона.
Как и contacts()
представление, contacts_view()
используется render_to_response()
для установки Content-Type
заголовка ответа. Мы также генерируем ответ из нового шаблона HXML, который мы можем создать сейчас:
Расширьте базовый шаблон макета.
Переопределите
header
блок шаблона макета, включив в него кнопку «Назад».Поведение для перехода к предыдущему экрану при нажатии.
Переопределите
content
блок, чтобы отобразить полную информацию о выбранном контакте.
Экран сведений о контактах расширяет базовый layout.xml
шаблон, как мы это делали в index.xml
. На этот раз мы переопределяем контент как в header
блоке, так и content
в блоке. Переопределение блока заголовка позволяет нам добавить кнопку «Назад» с поведением. При нажатии клиент Hyperview раскрутит стек навигации и вернет пользователя в список контактов.
Обратите внимание, что запуск этого поведения — не единственный способ вернуться назад. Клиент Hyperview соблюдает соглашения о навигации на разных платформах. В iOS пользователи также могут перейти к предыдущему экрану, проведя пальцем вправо от левого края устройства. На Android пользователи также могут перейти к предыдущему экрану, нажав аппаратную кнопку «Назад». Нам не нужно указывать что-либо дополнительно в HXML, чтобы получить эти взаимодействия.
С помощью всего лишь нескольких простых изменений мы перешли от одноэкранного приложения к многоэкранному. Обратите внимание: нам не нужно было ничего менять в коде мобильного приложения для поддержки нашего нового экрана. Это большое дело. При разработке традиционных мобильных приложений добавление экранов может оказаться серьезной задачей. Разработчикам необходимо создать новый экран, вставить его в соответствующее место иерархии навигации и написать код для открытия нового экрана из существующих экранов. В Hyperview мы только что добавили поведение с action="push"
.
Редактирование контакта
На данный момент наше приложение позволяет нам просматривать список контактов и просматривать сведения о конкретном контакте. Разве не было бы неплохо обновить имя, номер телефона или адрес электронной почты контакта? Давайте добавим пользовательский интерфейс для редактирования контактов в качестве нашего следующего улучшения.
Сначала нам нужно выяснить, как мы хотим отображать пользовательский интерфейс редактирования. Мы могли бы поместить в стек новый экран редактирования точно так же, как мы поместили экран с контактными данными. Но это не лучший дизайн с точки зрения пользовательского опыта. Добавление новых экранов имеет смысл при детализации данных, например при переходе от списка к одному элементу. Но редактирование — это не «детализация», а переключение режима между просмотром и редактированием. Поэтому вместо того, чтобы создавать новый экран, давайте заменим текущий экран пользовательским интерфейсом редактирования. Это означает, что нам нужно добавить кнопку и поведение, использующее это reload
действие. Эту кнопку можно добавить в заголовок экрана сведений о контакте.
Новая кнопка «Редактировать».
Поведение для перезагрузки текущего экрана с экраном редактирования при нажатии.
Мы снова используем существующий маршрут Flask ( /contacts/<contact_id>/edit
) для пользовательского интерфейса редактирования и заполняем идентификатор контакта, используя данные, переданные в шаблон Jinja. Нам также необходимо обновить contacts_edit_get()
представление, чтобы оно возвращало ответ XML на основе шаблона HXML ( hv/edit.xml
). Мы пропустим пример кода, поскольку необходимые изменения идентичны тем, которые мы применили contacts_view()
в предыдущем разделе. Вместо этого давайте сосредоточимся на шаблоне экрана редактирования.
Форма, обертывающая поля ввода и кнопки.
Контейнер с идентификатором, содержащий поля ввода.
Шаблон включает в себя визуализацию полей ввода.
Кнопка для отправки данных формы и обновления контейнера полей ввода.
Поскольку экрану редактирования необходимо отправлять данные на серверную часть, мы заключаем весь раздел содержимого в <form>
элемент. Это гарантирует, что данные полей формы будут включены в HTTP-запросы к нашему серверу. Внутри <form>
элемента наш пользовательский интерфейс разделен на две части: поля формы и кнопку «Сохранить». Фактические поля формы определяются в отдельном шаблоне ( form_fields.xml
) и добавляются на экран редактирования с помощью тега включения Jinja.
Ввод текста, содержащий текущее значение имени контакта.
Текстовый элемент, который может отображать ошибки из модели контакта.
Еще одно текстовое поле, на этот раз для фамилии контакта.
Мы опустили код номера телефона и адреса электронной почты контакта, поскольку они соответствуют тому же шаблону, что и имя и фамилия. Каждое поле контакта имеет свой собственный элемент <text-field>
, а также <text>
элемент под ним для отображения возможных ошибок. Имеет <text-field>
два важных атрибута:
name
определяет имя, которое будет использоваться при сериализации значения текстового поля в данные формы для HTTP-запросов. Мы используем те же имена, что и веб-приложение из предыдущих глав (first_name
,last_name
,phone
,email
). Таким образом, нам не нужно вносить изменения в наш сервер для анализа данных формы.value
определяет предварительно заполненные данные в текстовом поле. Поскольку мы редактируем существующий контакт, имеет смысл предварительно заполнить текстовое поле текущим именем, телефоном или электронной почтой.
Вам может быть интересно, почему мы решили определить поля формы в отдельном шаблоне ( form_fields.xml
)? Чтобы понять это решение, нам нужно сначала обсудить кнопку «Сохранить». При нажатии клиент Hyperview отправит HTTP- POST
запрос к contacts/<contact_id>/edit
, с сериализацией данных формы из <text-field>
входных данных. Ответ HXML заменит содержимое контейнера поля формы (ID form-fields
). Но каким должен быть этот ответ? Это зависит от достоверности данных формы:
Если данные недействительны (например, повторяющийся адрес электронной почты), наш пользовательский интерфейс останется в режиме редактирования и будет отображать сообщения об ошибках в недопустимых полях. Это позволяет пользователю исправить ошибки и повторить попытку сохранения.
Если данные действительны, наша серверная часть сохранит изменения, а наш пользовательский интерфейс снова переключится в режим отображения (пользовательский интерфейс контактных данных).
Поэтому нашему серверу необходимо различать допустимое и недействительное редактирование. Чтобы поддержать эти два сценария, давайте внесем некоторые изменения в существующее contacts_edit_post()
представление в приложении Flask.
Обновите объект контакта из данных формы запроса.
Попытайтесь сохранить обновления. Это возвращает
False
неверные данные.В случае успеха отобразите шаблон полей формы и передайте флаг
saved
шаблону.В случае неудачи отобразить шаблон полей формы. На объекте контакта присутствуют сообщения об ошибках.
Это представление уже содержит условную логику, основанную на успешности модели контакта save()
. Если save()
не получается, мы визуализируем form_fields.xml
шаблон. contact.errors
будет содержать сообщения об ошибках для недопустимых полей, которые будут отображаться в <text style="edit-field-error">
элементах. Если save()
получится, мы также отрендерим form_fields.xml
шаблон. Но на этот раз шаблон получит saved
флаг, указывающий на успех. Мы обновим шаблон, чтобы использовать этот флаг для реализации желаемого пользовательского интерфейса: переключение пользовательского интерфейса обратно в режим отображения.
Включайте это поведение только после успешного сохранения контакта.
Немедленно запустите поведение.
Поведение перезагрузит весь экран.
Экран будет перезагружен с экраном контактной информации.
Условие шаблона Jinja гарантирует, что наше поведение отображается только при успешном сохранении, а не при первом открытии экрана (или при отправке пользователем неверных данных). В случае успеха шаблон включает поведение, которое срабатывает немедленно благодаря trigger="load"
. Действие перезагружает текущий экран с экраном «Сведения о контакте» (из маршрута /contacts/<contact_id>
).
Результат? Когда пользователь нажимает «Сохранить», наша серверная часть сохраняет новые контактные данные, и экран переключается обратно на экран «Подробности». Поскольку приложение отправит новый HTTP-запрос для получения контактной информации, оно гарантированно покажет только что сохраненные изменения.
Почему бы не перенаправить?
Возможно, вы помните, что версия этого кода для веб-приложения вела себя немного иначе. При успешном сохранении представление вернулось redirect("/contacts/" + str(contact_id))
. Это перенаправление HTTP сообщит веб-браузеру перейти на страницу контактных данных.
Этот подход не поддерживается в Hyperview. Почему? Стек навигации веб-приложения прост: линейная последовательность страниц, активная только одна. Навигация в мобильном приложении значительно сложнее. Мобильные приложения используют вложенную иерархию стеков навигации, модальных окон и вкладок. Все экраны в этой иерархии активны и могут отображаться мгновенно в ответ на действия пользователя. Как в этом мире клиент Hyperview интерпретирует перенаправление HTTP? Должен ли он перезагрузить текущий экран, отправить новый или перейти к экрану в стеке с тем же URL-адресом?
Вместо того, чтобы делать выбор, который был бы неоптимален для многих сценариев, Hyperview использует другой подход. Перенаправления, управляемые сервером, невозможны, но серверная часть может отображать поведение навигации в HXML. Это то, что мы делаем, чтобы переключиться с пользовательского интерфейса редактирования на пользовательский интерфейс сведений в приведенном выше коде. Думайте об этом как о перенаправлениях на стороне клиента или, еще лучше, о навигации на стороне клиента.
Теперь у нас есть рабочий интерфейс редактирования в нашем приложении контактов. Пользователи могут войти в режим редактирования, нажав кнопку на экране контактной информации. В режиме редактирования они могут обновить данные контакта и сохранить их на сервере. Если серверная часть отклоняет изменения как недействительные, приложение остается в режиме редактирования и отображает ошибки проверки. Если серверная часть примет и сохранит изменения, приложение переключится обратно в режим подробностей, отображая обновленные контактные данные.
Давайте добавим еще одно улучшение в интерфейс редактирования. Было бы неплохо позволить пользователю выходить из режима редактирования без необходимости сохранять контакт. Обычно это делается с помощью действия «Отмена». Мы можем добавить это как новую кнопку под кнопкой «Сохранить».
Новая кнопка «Отмена» на экране редактирования.
При нажатии перезагрузка всего экрана.
Экран будет перезагружен с экраном контактной информации.
Это тот же метод, который мы использовали для переключения с пользовательского интерфейса редактирования на пользовательский интерфейс сведений после успешного редактирования контакта. Но нажатие «Отменить» обновит пользовательский интерфейс быстрее, чем нажатие «Сохранить». При сохранении приложение сначала сделает POST
запрос на сохранение данных, а затем GET
запросит экран с подробностями. Отмена пропускает POST
и немедленно выполняет GET
запрос.
Обновление списка контактов
На этом этапе мы можем утверждать, что полностью реализовали интерфейс редактирования. Но есть проблема. На самом деле, если бы мы остановились на этом, пользователи могли бы даже счесть приложение глючным! Почему? Это связано с синхронизацией состояния приложения на нескольких экранах. Давайте пройдемся по этой серии взаимодействий:
Запустите приложение в списке контактов.
Нажмите на контакт «Джо Блоу», чтобы загрузить его контактную информацию.
Нажмите «Изменить», чтобы переключиться в режим редактирования, и измените имя контакта на «Джозеф».
Нажмите «Сохранить», чтобы вернуться в режим просмотра. Имя контакта теперь «Джозеф Блоу».
Нажмите кнопку «Назад», чтобы вернуться в список контактов.
Вы уловили проблему? В нашем списке контактов по-прежнему отображается тот же список имен, что и при запуске приложения. Контакт, который мы только что переименовали в «Джозеф», по-прежнему отображается в списке как «Джо». Это общая проблема в гипермедийных приложениях. Клиент не имеет понятия об общих данных в разных частях пользовательского интерфейса. Обновления в одной части приложения не будут автоматически обновлять другие части приложения.
К счастью, в Hyperview есть решение этой проблемы: события. События встроены в систему поведения и обеспечивают упрощенное взаимодействие между различными частями пользовательского интерфейса.
Поведение событий
События в Hyperview намного проще, но они не требуют каких-либо сценариев и могут быть определены декларативно в HXML. Это делается через систему поведения. События требуют добавления нового атрибута поведения, типа действия и типа триггера:
event-name
: Этот атрибут<behavior>
определяет имя события, которое будет либо отправлено, либо прослушиваться.action="dispatch-event"
: при срабатывании это поведение отправляет событие с именем, определенным атрибутомevent-name
. Это событие отправляется глобально по всему приложению Hyperview.trigger="on-event"
: это поведение сработает, если другое поведение в приложении отправит событие, соответствующееevent-name
атрибуту.
Если <behavior>
элемент использует action="dispatch-event"
или trigger="on-event"
, он также должен определить event-name
. Обратите внимание, что несколько вариантов поведения могут отправлять событие с одним и тем же именем. Аналогично, одно и то же событие может активировать несколько вариантов поведения.
Давайте посмотрим на это простое поведение:
<behavior trigger="press" action="toggle" target="container" />
.
Нажатие на элемент, содержащий это поведение, переключит видимость элемента с идентификатором «контейнер». Но что, если элемент, который мы хотим переключить, находится на другом экране? Действие «переключение» и поиск идентификатора цели работают только на текущем экране, поэтому это решение не будет работать. Решение состоит в том, чтобы создать два поведения, по одному на каждом экране, взаимодействующие через события:
Экран А:
<behavior trigger="press" action="dispatch-event" event-name="button-pressed" />
Экран Б:
<behavior trigger="on-event" event-name="button-pressed" action="toggle" target="container" />
Нажатие на элемент, содержащий первое поведение (на экране A), отправит событие с именем «нажатие кнопки». Второе поведение (на экране B) сработает при событии с этим именем и переключит видимость элемента с идентификатором «контейнер».
События имеют множество применений, но наиболее распространенным из них является информирование различных экранов об изменениях состояния серверной части, которые требуют повторной загрузки пользовательского интерфейса.
Теперь мы знаем достаточно о системе событий Hyperview, чтобы устранить ошибку в нашем приложении. Когда пользователь сохраняет изменение контакта, нам нужно отправить событие с экрана «Сведения». И экран «Контакты» должен прослушивать это событие и перезагружаться, чтобы отразить изменения. Поскольку form_fields.xml
шаблон уже получает saved
флаг, когда серверная часть успешно сохраняет контакт, это хорошее место для отправки события:
Немедленно запустите поведение.
Поведение отправит событие.
Название события — «обновление контактов».
Существующее поведение для отображения пользовательского интерфейса Details.
Теперь нам просто нужен список контактов, чтобы прослушать событие contact-updated
и перезагрузиться:
Запускайте поведение при отправке события.
Запускайте поведение для отправленных событий с именем «contact-updated».
При срабатывании замените содержимое элемента
<list>
строками из серверной части.
Каждый раз, когда пользователь редактирует контакт, экран списка контактов обновляется, отражая изменения. Добавление этих двух <behavior>
элементов исправляет ошибку: на экране «Список контактов» в списке будет правильно отображаться «Джозеф Блоу». Обратите внимание: мы намеренно добавили новое поведение внутри <form>
элемента. Это гарантирует, что инициируемый запрос сохранит любой поисковый запрос.
Чтобы показать, что мы имеем в виду, давайте вернемся к набору шагов, демонстрирующих ошибочное поведение. Предположим, что прежде чем нажать «Джо Блоу», пользователь выполнил поиск контактов, введя «Джо» в поле поиска. Когда позже пользователь обновляет контакт на «Джозеф Блоу», наш шаблон отправляет событие «contact-updated», которое запускает поведение replace-inner
на экране списка контактов. Благодаря родительскому <form>
элементу поисковый запрос «Джо» будет сериализован с запросом: GET /contacts?rows_only=true&q=Joe
. Поскольку имя «Джозеф» не соответствует запросу «Джо», отредактированный нами контакт не появится в списке (пока пользователь не очистит запрос). Состояние нашего приложения остается неизменным на всей серверной части и на всех активных экранах.
События привносят в поведение определенный уровень абстракции. До сих пор мы видели, что редактирование контакта приводит к обновлению списка контактов. Но список контактов также должен обновляться после других действий, например, удаления контакта или добавления нового контакта. Пока наши ответы HXML на удаление или создание включают поведение для отправки события contact-updated
, мы получим желаемое поведение обновления на экране списка контактов.
Экрану все равно, что вызывает contact-updated
отправку события. Он просто знает, что ему нужно делать, когда это происходит.
Удаление контакта
Говоря об удалении контакта, это хорошая следующая функция, которую стоит реализовать. Мы разрешим пользователям удалять контакт из пользовательского интерфейса редактирования. Итак, давайте добавим новую кнопку в edit.xml
.
Новая кнопка «Удалить контакт» на экране редактирования.
При нажатии добавьте HXML в контейнер на экране.
HXML будет получен по запросу
POST /contacts/<contact_id>/delete
.
HXML для кнопки «Удалить» очень похож на кнопку «Сохранить», но есть несколько тонких отличий. Помните, что нажатие кнопки «Сохранить» приводит к одному из двух ожидаемых результатов: сбой и отображение ошибок проверки в форме или успех и переключение на экран контактных данных. Для поддержки первого результата (сбой и отображение ошибок проверки) поведение сохранения заменяет содержимое контейнера <view id="form-fields">
повторно обработанной версией form_fields.xml
. Поэтому использование replace-inner
действия имеет смысл.
Удаление не требует этапа проверки, поэтому ожидаемый результат только один: успешное удаление контакта. После успешного удаления контакт больше не существует. Нет смысла показывать интерфейс редактирования или контактные данные для несуществующего контакта. Вместо этого наше приложение вернется к предыдущему экрану (списку контактов). Наш ответ будет включать только действия, которые срабатывают немедленно, пользовательский интерфейс менять не нужно. Таким образом, использование append
действия сохранит текущий пользовательский интерфейс, пока Hyperview выполняет действия.
При загрузке отправьте
contact-updated
событие для обновления экрана списков контактов.Вернитесь к экрану списка контактов.
Обратите внимание: помимо поведения для перехода назад, этот шаблон также включает поведение для отправки события contact-updated
. В разделе предыдущей главы мы добавили поведение, позволяющее index.xml
обновлять список при отправке этого события. Отправляя событие после удаления, мы гарантируем, что удаленный контакт будет удален из списка.
Еще раз пропустим изменения в бэкэнде Flask. Достаточно сказать, что нам нужно будет обновить contacts_delete()
представление, чтобы оно отвечало шаблоном hv/deleted.xml
. И нам нужно обновить маршрут для поддержки POST
помимо DELETE
, так как клиент Hyperview понимает только GET
и POST
.
Теперь у нас есть полнофункциональная функция удаления! Но он не самый удобный: для окончательного удаления контакта достаточно одного случайного нажатия. Для деструктивных действий, таких как удаление контакта, всегда полезно запросить у пользователя подтверждение.
Мы можем добавить подтверждение к поведению удаления, используя alert
системное действие, описанное в предыдущей главе. Как вы помните, это alert
действие покажет системное диалоговое окно с кнопками, которые могут запускать другие действия. Все, что нам нужно сделать, это обернуть удаление <behavior>
в поведение, использующее action="alert"
.
Нажатие «Удалить» запускает действие по отображению системного диалога с заданным заголовком и сообщением.
Первая нажимаемая опция в системном диалоге.
Нажатие первой опции приведет к удалению контакта.
Вторая нажимаемая опция не имеет никакого поведения, поэтому она только закрывает диалоговое окно.
В отличие от предыдущего случая, нажатие кнопки удаления не будет иметь немедленного эффекта. Вместо этого пользователю будет представлено диалоговое окно и предложено подтвердить или отменить действие. Наше основное поведение при удалении не изменилось, мы просто связали его с другим поведением.
Добавление нового контакта
Добавление нового контакта — последняя функция, которую мы хотим поддерживать в нашем мобильном приложении. И, к счастью, это еще и самый простой. Мы можем повторно использовать концепции (и даже некоторые шаблоны) из уже реализованных функций. В частности, добавление нового контакта очень похоже на редактирование существующего контакта. Обе функции должны:
Показать форму для сбора информации о контакте.
Иметь способ сохранить введенную информацию.
Покажите ошибки проверки в форме.
Сохраните контакт, если нет ошибок проверки.
Поскольку функциональные возможности очень похожи, мы суммируем здесь изменения, не показывая код.
Обновление
index.xml
.Переопределите
header
блок, чтобы добавить новую кнопку «Добавить».Включите поведение в кнопку. При нажатии нажмите новый экран как модальное с помощью
action="new"
и запросите содержимое экрана из/contacts/new
.
Создайте шаблон
hv/new.xml
.Переопределите блок заголовка, включив в него кнопку, закрывающую модальное окно, используя
action="close"
.Включите
hv/form_fields.xml
шаблон для отображения пустых полей формы.Добавьте кнопку «Добавить контакт» под полями формы.
Включите поведение в кнопку. При нажатии сделайте
POST
запрос/contacts/new
и используйтеaction="replace-inner"
для обновления полей формы.
Обновите представление Flask.
Измените
contacts_new_get()
для использованияrender_to_response()
сhv/new.xml
шаблоном.Измените
contacts_new()
для использованияrender_to_response()
сhv/form_fields.xml
шаблоном. Пройтиsaved=True
при отрисовке шаблона после успешного сохранения нового контакта.
Повторно используя form_fields.xml
как для редактирования, так и для добавления контакта, мы можем повторно использовать некоторый код и гарантировать, что обе функции имеют согласованный пользовательский интерфейс. Кроме того, наш экран «Добавить контакт» выиграет от «сохраненной» логики, которая уже является частью form_fields.xml
. После успешного добавления нового контакта на экране будет отправлено событие contact-updated
, которое обновит список контактов и отобразит вновь добавленный контакт. Экран перезагрузится и отобразит контактную информацию.
Развертывание приложения
После завершения пользовательского интерфейса создания контактов у нас есть полностью реализованное мобильное приложение. Он поддерживает поиск по списку контактов, просмотр сведений о контакте, редактирование и удаление контакта, а также добавление нового контакта. Но до сих пор мы разрабатывали приложение с помощью симулятора на нашем настольном компьютере. Как мы можем увидеть его работающим на мобильном устройстве? И как мы можем передать его в руки наших пользователей?
Чтобы увидеть приложение, работающее на физическом устройстве, давайте воспользуемся функцией предварительного просмотра приложения на платформе Expo.
Загрузите приложение Expo Go на устройство Android или iOS.
Перезапустите приложение Flask, привязавшись к интерфейсу, доступному в вашей сети. Это может выглядеть примерно так
flask run --host 192.168.7.229
: где хост — это IP-адрес вашего компьютера в сети.Обновите код клиента Hyperview, чтобы
ENTRY_POINT_URL
(indemo/src/constants.js
) указывал на IP-адрес и порт, к которым привязан сервер Flask.После запуска
yarn start
демо-приложения Hyperview вы увидите напечатанный в консоли QR-код с инструкциями по его сканированию на Android и iOS.
После сканирования QR-кода на устройстве запустится полная версия приложения. При взаимодействии с приложением вы увидите HTTP-запросы, отправленные на сервер Flask. Вы даже можете использовать физическое устройство во время разработки. Каждый раз, когда вы вносите изменения в HXML, просто перезагрузите экран, чтобы увидеть обновления пользовательского интерфейса.
Итак, у нас есть приложение, работающее на физическом устройстве, но оно еще не готово к работе. Чтобы передать приложение в руки наших пользователей, нам нужно сделать несколько вещей:
Разверните наш бэкэнд в производстве. Нам нужно использовать веб-сервер производственного уровня, такой как Gunicorn, вместо сервера разработки Flask. И нам следует запустить наше приложение на машине, доступной через Интернет, скорее всего, с использованием облачного провайдера, такого как AWS или Heroku.
Создавайте автономные бинарные приложения. Следуя инструкциям проекта Expo, мы можем создать файл
.ipa
или.apk
для платформ iOS и Android. Не забудьте обновитьENTRY_POINT_URL
клиент Hyperview, чтобы он указывал на производственный сервер.Отправьте наши двоичные файлы в iOS App Store или Google Play Store и дождитесь одобрения приложения.
Как только приложение будет одобрено, поздравляем! Наше мобильное приложение могут скачать пользователи Android и iOS. И вот что самое приятное: поскольку наше приложение использует архитектуру гипермедиа, мы можем добавлять в него функции, просто обновляя серверную часть. Пользовательский интерфейс и взаимодействие полностью определяются с помощью HXML, созданного на основе серверных шаблонов. Хотите добавить новый раздел на экран? Просто обновите существующий шаблон HXML. Хотите добавить в приложение новый тип экрана? Создайте новый маршрут, представление и шаблон HXML. Затем добавьте к существующему экрану поведение, которое будет открывать новый экран. Чтобы донести эти изменения до своих пользователей, вам просто нужно повторно развернуть серверную часть. Наше приложение умеет интерпретировать HXML, и этого достаточно, чтобы понять, как обращаться с новыми функциями.
Один бэкэнд, несколько форматов гипермедиа
На этом этапе вы наверняка задаетесь вопросом: можно ли использовать один и тот же бэкэнд для обслуживания как веб-приложения, так и мобильного приложения? Ответ – да! Фактически, это одно из преимуществ использования архитектуры гипермедиа на нескольких платформах. Нам не нужно переносить какую-либо клиентскую логику с одной платформы на другую, нам просто нужно отвечать на запросы в соответствующем формате Hypermedia. Для этого мы будем использовать согласование контента, встроенное в HTTP.
Что такое согласование контента?
В архитектуре REST домашняя страница Google считается единым «ресурсом», представленным уникальным URL-адресом. Однако этот единственный ресурс может иметь несколько «представлений». Представления — это варианты того, как содержимое ресурса представляется клиенту. Немецкая и японская версии главной страницы Google представляют собой два представления одного и того же ресурса. Чтобы определить наилучшее представление возвращаемого ресурса, HTTP-клиенты и серверы участвуют в процессе, называемом «согласованием контента». Это работает следующим образом:
Клиенты указывают предпочтительное представление через
Accept-*
заголовки запросов.Сервер пытается максимально соответствовать предпочтительному представлению и возвращает выбранное представление, используя
Content-*
.
В примере с домашней страницей Google говорящий по-немецки использует браузер, в котором настроено предпочтение контента, локализованного на немецкий язык. Каждый HTTP-запрос, сделанный веб-браузером, будет включать заголовок Accept-Language: de-DE
. Сервер видит заголовок запроса и вернет ответ, локализованный для немецкого языка (если сможет). Ответ HTTP будет включать Content-Language: de-DE
заголовок, информирующий клиента о языке содержимого ответа.
Язык — это лишь один из факторов представления ресурсов. Что еще более важно для нас, ресурсы могут быть представлены с использованием различных типов контента, таких как HTML или HXML. Согласование контента по типу контента выполняется с использованием Accept
заголовка запроса и Content-Type
заголовка ответа. Веб-браузеры установлены text/html
в качестве предпочтительного типа контента в Accept
заголовке. Клиент Hyperview устанавливает application/vnd.hyperview+xml
предпочтительный тип контента. Это дает нашему серверу возможность различать запросы, поступающие от веб-браузера или клиента Hyperview, и предоставлять каждому соответствующий контент.
Существует два основных подхода к согласованию содержания: детальный и глобальный.
Подход 1: переключение шаблонов
Когда мы портировали приложение «Контакты» из Интернета на мобильные устройства, мы сохранили все представления Flask, но внесли некоторые незначительные изменения. В частности, мы представили новую функцию render_to_response()
и вызвали ее в операторе возврата каждого представления. Вот еще раз функция, чтобы освежить вашу память:
render_to_response()
отображает шаблон с заданным контекстом и превращает его в объект ответа Flask с соответствующим Content-Type
заголовком Hyperview. Очевидно, что реализация очень специфична для обслуживания нашего мобильного приложения Hyperview. Но мы можем изменить функцию для согласования содержимого на основе Accept
заголовка запроса:
Сигнатура функции принимает два шаблона: один для HTML и один для HXML.
Определите, хочет ли клиент HTML или HXML.
Выберите шаблон, наиболее подходящий для клиента.
Установите
Content-Type
заголовок на основе наилучшего соответствия клиенту.
Объект запроса Flask предоставляет accept_mimetypes
свойство, помогающее при согласовании содержимого. Мы передаем два наших MIME-типа контента request.accept_mimetypes.best_match()
и получаем обратно тот MIME-тип, который работает для нашего клиента. В зависимости от наиболее соответствующего типа MIME мы выбираем отображение шаблона HTML или шаблона HXML. Мы также обязательно устанавливаем Content-Type
для заголовка соответствующий тип MIME. Единственная разница в наших представлениях Flask заключается в том, что нам нужно предоставить шаблон как HTML, так и HXML:
Переключение шаблонов между шаблонами HTML и HXML в зависимости от клиента.
После обновления всех представлений Flask для поддержки обоих шаблонов наш бэкэнд будет поддерживать как веб-браузеры, так и наше мобильное приложение! Этот метод хорошо работает для приложения «Контакты», поскольку экраны мобильного приложения напрямую связаны со страницами веб-приложения. Каждое приложение имеет специальную страницу (или экран) для перечисления контактов, отображения и редактирования сведений, а также создания нового контакта. Это означало, что представления Flask можно было оставить без каких-либо серьезных изменений.
Но что, если мы захотим переосмыслить пользовательский интерфейс приложения «Контакты» для нашего мобильного приложения? Возможно, мы хотим, чтобы мобильное приложение использовало один экран со строками, которые расширялись по ходу дела для поддержки просмотра и редактирования информации? В ситуациях, когда пользовательский интерфейс на разных платформах различается, переключение шаблонов становится затруднительным или невозможным. Нам нужен другой подход, чтобы один сервер обслуживал оба формата гипермедиа.
Подход 2: Вилка перенаправления
Если вы помните, веб-приложение «Контакты» имеет index
представление, маршрутизируемое из корневого пути /
:
Перенаправить запросы с «/» на «/contacts»
Определите, хочет ли клиент HTML или HXML.
Если клиенту нужен HXML, перенаправьте его на
/mobile/contacts
.Если клиенту нужен HTML, перенаправьте его на
/web/contacts
.
Точка входа — это развилка: если клиенту нужен HTML, мы перенаправляем его по одному пути. Если клиенту нужен HXML, мы перенаправляем его по другому пути. Эти перенаправления будут обрабатываться различными представлениями Flask:
Представление mobile_contacts()
будет отображать шаблон HXML со списком контактов. Прикосновение к элементу контакта откроет запрошенный экран /mobile/contacts/1
, обрабатываемый представлением mobile_contacts_view
. После первоначального разветвления все последующие запросы от нашего мобильного приложения передаются по путям с префиксом /mobile/
и обрабатываются представлениями Flask, специфичными для мобильных устройств. Аналогичным образом, все последующие запросы из веб-приложения передаются по путям с префиксом /web/
и обрабатываются веб-представлениями Flask. (Обратите внимание, что на практике нам бы хотелось разделить веб-представление и представление для мобильных устройств на отдельные части нашей кодовой базы: web_app.py
и mobile_app.py
. Мы также можем отказаться от префикса веб-путей с префиксом /web/
, если хотим, чтобы в адресной строке браузера отображались более элегантные URL-адреса. )
Вы можете подумать, что вилка Redirect приводит к большому дублированию кода. Ведь нам нужно прописать двойное количество просмотров: один набор для веб-приложения и один набор для мобильного приложения. Это правда, поэтому Redirect Fork предпочтителен только в том случае, если двум платформам требуется несвязанный набор логики представления. Если приложения на обеих платформах одинаковы, переключение шаблонов сэкономит много времени и обеспечит единообразие приложений. Даже если нам понадобится использовать Redirect Fork, основная часть логики в наших моделях может использоваться обоими наборами представлений.
На практике вы можете начать использовать переключение шаблонов, но затем осознаете, что вам нужно реализовать форк для функций, специфичных для платформы. Фактически, мы уже делаем это в приложении «Контакты». При переносе приложения с веб-версии на мобильное устройство мы не задействовали некоторые функции, такие как функция архивирования. Пользовательский интерфейс динамического архива — это мощная функция, которая не имеет смысла на мобильном устройстве. Поскольку наши шаблоны HXML не предоставляют никаких точек входа в функцию архива, мы можем рассматривать ее как «только для Интернета» и не беспокоиться о ее поддержке в Hyperview.
Contact.app в Hyperview
В этой главе мы рассмотрели очень многое. Переведите дух и подведите итоги того, как далеко мы продвинулись: мы перенесли основные функции веб-приложения Contact.app на мобильные устройства. И мы сделали это, повторно используя большую часть нашей серверной части Flask и придерживаясь шаблонов Jinja. Мы снова увидели полезность событий для связи различных аспектов приложения.
Мы еще не закончили. В следующей главе мы реализуем пользовательское поведение и элементы пользовательского интерфейса, чтобы завершить наше мобильное приложение Contact.app.
Примечания к Hypermedia: конечные точки API
В отличие от API JSON, API гипермедиа, который вы создаете для своего приложения, управляемого гипермедиа, должен содержать конечные точки, специализированные для потребностей пользовательского интерфейса вашего конкретного приложения.
Поскольку API-интерфейсы гипермедиа не предназначены для использования клиентами общего назначения, вы можете отказаться от необходимости сохранять их обобщенными и создавать контент, специально необходимый для вашего приложения. Ваши конечные точки должны быть оптимизированы для поддержки потребностей пользовательского интерфейса и пользовательского интерфейса ваших конкретных приложений, а не для модели общего доступа к данным для вашей модели предметной области.
Связанный с этим совет: если у вас есть API на основе гипермедиа, вы можете агрессивно реорганизовать свой API способом, который категорически не рекомендуется при написании SPA или мобильных клиентов на основе JSON API. Поскольку приложения на основе гипермедиа используют гипермедиа в качестве механизма состояния приложения, вы можете и, по сути, поощряетесь изменять их форму по мере изменения разработчиков вашего приложения и вариантов использования.
Сильная сторона подхода гипермедиа заключается в том, что вы можете полностью переработать свой API, чтобы со временем адаптироваться к новым потребностям, без необходимости изменять версию API или даже документировать его. Воспользуйтесь этим!
Last updated