Глава 13. Расширение клиента Hyperview


В предыдущей главе мы создали полнофункциональную мобильную версию нашего приложения «Контакты». Помимо настройки URL-адреса точки входа, нам не нужно было трогать какой-либо код, который работает на мобильном устройстве. Мы полностью определили пользовательский интерфейс и логику нашего мобильного приложения во внутреннем коде, используя шаблоны Flask и HXML. Это возможно, поскольку стандартный клиент Hyperview поддерживает все основные функции мобильных приложений.

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

Прежде чем углубиться, давайте кратко рассмотрим стек технологий, которые мы будем использовать. Клиент Hyperview написан на React Native, популярной кроссплатформенной платформе для создания мобильных приложений. Он использует тот же компонентный API, что и React. Это означает, что разработчики, знакомые с JavaScript и React, могут быстро освоить React Native. React Native имеет здоровую экосистему библиотек с открытым исходным кодом. Мы будем использовать эти библиотеки для создания собственных расширений для клиента Hyperview.

Добавление телефонных звонков и электронной почты

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

В Интернете вызов телефонных номеров поддерживается с помощью tel:схемы URI, а электронная почта поддерживается с помощью mailto:схемы URI:

<a href="tel:555-555-5555">Call</a> (1)
<a href="mailto:[email protected]">Email</a> (2)
  1. При нажатии предложить пользователю позвонить по указанному номеру телефона.

  2. При нажатии откроется почтовый клиент с указанным адресом, указанным в поле to:.

Клиент Hyperview не поддерживает схемы URI tel:и mailto:. Но мы можем добавить эти возможности клиенту с помощью настраиваемых поведенческих действий. Помните, что поведение — это взаимодействия, определенные в HXML. Поведения имеют триггеры («нажать», «обновить») и действия («обновить», «поделиться»). Значения «действия» не ограничиваются набором, который входит в библиотеку Hyperview. Итак, давайте определим два новых действия: «открыть телефон» и «открыть электронную почту».

<view xmlns:comms="https://hypermedia.systems/hyperview/communications"> (1)
  <text>
    <behavior action="open-phone" comms:phone-number="555-555-5555" /> (2)
    Call
  </text>
  <text>
    <behavior action="open-email" comms:email-address="[email protected]" /> (3)
    Email
  </text>
</view>
  1. Определите псевдоним для пространства имен XML, используемого нашими новыми атрибутами.

  2. При нажатии предложит пользователю позвонить по указанному номеру телефона.

  3. При нажатии откройте почтовый клиент с указанным адресом, указанным в поле to:.

Обратите внимание, что мы определили фактический номер телефона и адрес электронной почты, используя отдельные атрибуты. В HTML схема и данные запихиваются в hrefатрибут. Элементы HXML <behavior>предоставляют больше возможностей для представления данных. Мы решили использовать атрибуты, но могли бы представить номер телефона или адрес электронной почты, используя дочерние элементы. Мы также используем пространство имен, чтобы избежать потенциальных конфликтов в будущем с другими клиентскими расширениями.

Пока все хорошо, но откуда клиент Hyperview знает, как интерпретировать open-phoneи open-emailи как ссылаться на атрибуты phone-numberи email-address? Здесь нам, наконец, нужно написать немного JavaScript.

Сначала мы добавим стороннюю библиотеку ( react-native-communications) в наше демонстрационное приложение. Эта библиотека предоставляет простой API, который взаимодействует с функциями уровня ОС для звонков и электронной почты.

> cd hyperview/demo
> yarn add react-native-communications (1)
> yarn start (2)
  1. Добавить зависимость отreact-native-communications

  2. Перезапустите мобильное приложение

Далее мы создадим новый файл, phone.jsкоторый будет реализовывать код, связанный с open-phoneдействием:

import { phonecall } from 'react-native-communications'; (1)

const namespace = "https://hypermedia.systems/hyperview/communications";

export default {
  action: "open-phone", (2)
  callback: (behaviorElement) => { (3)
    const number = behaviorElement.getAttributeNS(namespace, "phone-number"); (4)
    if (number != null) {
      phonecall(number, false); (5)
    }
  },
};
  1. Импортируйте нужную нам функцию из сторонней библиотеки.

  2. Название действия.

  3. Обратный вызов, который запускается при срабатывании действия.

  4. Получите номер телефона из <behavior>элемента.

  5. Передайте номер телефона функции из сторонней библиотеки.

Пользовательские действия определяются как объект JavaScript с двумя ключами: actionи callback. Вот как клиент Hyperview связывает настраиваемое действие в HXML с нашим настраиваемым кодом. Значение обратного вызова — это функция, которая принимает один параметр behaviorElement. Этот параметр представляет собой XML-DOM-представление элемента <behavior>, вызвавшего действие. Это означает, что мы можем вызывать его методы, например getAttribute, или получать доступ к его атрибутам, например childNodes. В этом случае мы используем getAttributeNSдля чтения номер телефона из phone-numberатрибута <behavior>элемента. Если номер телефона определен в элементе, мы можем вызвать phonecall()функцию, предоставляемую библиотекой react-native-communications.

Прежде чем мы сможем использовать наше настраиваемое действие, нужно сделать еще одну вещь: зарегистрировать действие в клиенте Hyperview. Клиент Hyperview представлен как компонент React Native под названием Hyperview. Этот компонент принимает свойство с именем behaviors, которое представляет собой массив объектов настраиваемых действий, таких как наше действие «открыть телефон». Давайте передадим нашу реализацию «открытого телефона» компоненту Hyperviewнашего демонстрационного приложения.

import React, { PureComponent } from 'react';
import Hyperview from 'hyperview';
import OpenPhone from './phone'; (1)

export default class HyperviewScreen extends PureComponent {
  // ... omitted for brevity

  behaviors = [OpenPhone]; (2)

  render() {
    return (
      <Hyperview
        behaviors={this.behaviors} (3)
        entrypointUrl={this.entrypointUrl}
        // more props...
      />
    );
  }
}
  1. Импортируйте действие открытия телефона.

  2. Создайте массив настраиваемых действий.

  3. Передайте пользовательские действия компоненту Hyperviewв виде свойства с именем behaviors.

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

Передав действие «открыть телефон» в Hyperview, мы теперь можем использовать его в качестве значения атрибута actionэлементов <behavior>. Собственно, давайте сделаем это сейчас, обновив show.xmlшаблон в нашем приложении Flask:

{% block content %}
<view style="details">
  <text style="contact-name">{{ contact.first }} {{ contact.last }}</text>

  <view style="contact-section">
    <behavior (1)
      xmlns:comms="https://hypermedia.systems/hyperview/communications"
      trigger="press"
      action="open-phone" (2)
      comms:phone-number="{{contact.phone}}" (3)
    />
    <text style="contact-section-label">Phone</text>
    <text style="contact-section-info">{{contact.phone}}</text>
  </view>

  <view style="contact-section">
    <behavior (4)
      xmlns:comms="https://hypermedia.systems/hyperview/communications"
      trigger="press"
      action="open-email"
      comms:email-address="{{contact.email}}"
    />
    <text style="contact-section-label">Email</text>
    <text style="contact-section-info">{{contact.email}}</text>
  </view>
</view>
{% endblock %}
  1. Добавьте в раздел номера телефона поведение, которое срабатывает при нажатии.

  2. Запустите новое действие «открыть телефон».

  3. Установите атрибут, ожидаемый действием «открыть телефон».

  4. Та же идея, но с другим действием («открыть письмо»).

Мы пропустим реализацию второго дополнительного действия — «открыть электронное письмо». Как вы можете догадаться, это действие откроет композитор электронной почты системного уровня, позволяющий пользователю отправить электронное письмо своему контакту. Реализация «открытой электронной почты» практически идентична реализации «открытого телефона». Библиотека react-native-communicationsпредоставляет функцию с именем email(), поэтому мы просто обертываем ее и передаем ей аргументы таким же образом.

Теперь у нас есть полный пример расширения клиента с помощью настраиваемых поведенческих действий. Мы выбрали новое имя для наших действий («открыть телефон» и «открыть электронную почту») и сопоставили эти имена с функциями. Функции принимают <behavior>элементы и могут запускать любой произвольный код React Native. Мы обернули существующую стороннюю библиотеку и прочитали атрибуты, установленные для элемента, <behavior>для передачи данных в библиотеку. После перезапуска нашего демонстрационного приложения у нашего клиента появляются новые возможности, которые мы можем немедленно использовать, ссылаясь на действия из наших шаблонов HXML.

Добавление сообщений

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

Если вы помните, наше веб-приложение «Контакты» отображает сообщения об успешных действиях, таких как удаление или создание контакта. Эти сообщения генерируются в бэкэнде Flask с помощью flash()функции, вызываемой из представлений. Затем базовый layout.htmlшаблон отображает сообщения на конечной веб-странице.

{% для сообщения в get_flashed_messages() %}
  <div class="flash">{{ сообщение }}</div>
{% конец для %}

Наше приложение Flask по-прежнему включает вызовы flash(), но приложение Hyperview не получает доступа к отображаемому сообщению для отображения пользователю. Давайте добавим эту поддержку сейчас.

Мы могли бы просто показывать сообщения, используя метод, аналогичный веб-приложению: прокручивать сообщения и отображать некоторые <text>элементы в формате layout.xml. У этого подхода есть серьезный недостаток: отображаемые сообщения будут привязаны к определенному экрану. Если этот экран был скрыт действием навигации, сообщение также будет скрыто. Чего мы действительно хотим, так это чтобы наш пользовательский интерфейс сообщений отображался «над» всеми экранами в стеке навигации. Таким образом, сообщение останется видимым (исчезнет через несколько секунд), даже если стопка экранов изменится ниже. Чтобы отобразить некоторый пользовательский интерфейс за пределами <screen>элементов, нам понадобится расширить клиент Hyperview новым настраиваемым действием show-message. Это еще одна возможность использовать библиотеку с открытым исходным кодом react-native-root-toast. Давайте добавим эту библиотеку в наше демонстрационное приложение.

> cd hyperview/demo
> yarn add react-native-root-toast (1)
> yarn start (2)
  1. Добавить зависимость отreact-native-root-toast

  2. Перезапустите мобильное приложение

Теперь мы можем написать код для реализации пользовательского интерфейса сообщений в качестве настраиваемого действия.

импортировать Toast из 'react-native-root-toast'; (1)

const namespace = "https://hypermedia.systems/hyperview/message";

экспортировать по умолчанию {
  действие: «показать-сообщение», (2) 
  обратный вызов: (behaviorElement) => { (3)
    const text = BehaviorElement.getAttributeNS(пространство имен, «текст»);
    если (текст != ноль) {
      Toast.show(text, {position: Toast.positions.TOP, продолжительность: 2000}); (4)
    }
  },
};
  1. Импортируйте ToastAPI.

  2. Название действия.

  3. Обратный вызов, который запускается при срабатывании действия.

  4. Передайте сообщение в библиотеку тостов.

Этот код очень похож на реализацию open-phone. Оба обратных вызова следуют одинаковой схеме: считывают атрибуты пространства имен из <behavior>элемента и передают эти значения в стороннюю библиотеку. Для простоты мы жестко запрограммировали параметры, позволяющие отображать сообщение в верхней части экрана, исчезающее через 2 секунды. Но react-native-root-toastпредоставляет множество вариантов позиционирования, времени анимации, цветов и многого другого. Мы могли бы указать эти параметры, используя дополнительные атрибуты, behaviorElementчтобы сделать действие более настраиваемым. Для наших целей мы просто будем придерживаться простой реализации.

Теперь нам нужно зарегистрировать наше пользовательское действие в <Hyperview>компоненте, передав его в behaviorsсвойство.

import React, { PureComponent } from 'react';
import Hyperview from 'hyperview';
import OpenEmail from './email';
import OpenPhone from './phone';
import ShowMessage from './message'; (1)

export default class HyperviewScreen extends PureComponent {
  // ... omitted for brevity

  behaviors = [OpenEmail, OpenPhone, ShowMessage]; (2)

  // ... omitted for brevity
}
  1. Импортируйте show-messageдействие.

  2. Передайте действие компоненту Hyperviewв виде свойства с именем behaviors.

Все, что осталось сделать, это запустить show-messageдействие из нашего HXML. Существует три действия пользователя, которые приводят к отображению сообщения:

  1. Создание нового контакта

  2. Обновление существующего контакта

  3. Удаление контакта

Первые два действия реализованы в нашем приложении с использованием одного и того же шаблона HXML form_fields.xml: . После успешного создания или обновления контакта этот шаблон перезагрузит экран и вызовет событие, используя поведение, которое срабатывает при «загрузке». Действие удаления также использует поведение, которое срабатывает при «загрузке», определенном в deleted.xmlшаблоне. Поэтому оба form_fields.xmlварианта deleted.xmlнеобходимо изменить, чтобы они также отображали сообщения при загрузке. Поскольку фактическое поведение в обоих шаблонах будет одинаковым, давайте создадим общий шаблон для повторного использования HXML.

{% for message in get_flashed_messages() %}
  <behavior (1)
    xmlns:message="https://hypermedia.systems/hyperview/message"
    trigger="load" (2)
    action="show-message" (3)
    message:text="{{ message }}" (4)
  />
{% endfor %}
  1. Определите поведение для каждого отображаемого сообщения.

  2. Запустите это поведение, как только элемент загрузится.

  3. Запустите новое действие «показать сообщение».

  4. Действие «show-message» отобразит мигающее сообщение в пользовательском интерфейсе.

Как и в layout.htmlвеб-приложении, мы просматриваем все отображаемые сообщения и отображаем разметку для каждого сообщения. Однако в веб-приложении сообщение было непосредственно отображено на веб-странице. В приложении Hyperview каждое сообщение отображается с использованием поведения, которое запускает наш пользовательский интерфейс. Теперь нам просто нужно включить этот шаблон в form_fields.xml:

<view xmlns="https://hyperview.org/hyperview" style="edit-group">
  {% if saved %}
    {% include "hv/messages.xml" %} (1)
    <behavior trigger="load" once="true" action="dispatch-event" event-name="contact-updated" />
    <behavior trigger="load" once="true" action="reload" href="/contacts/{{contact.id}}" />
  {% endif %}
  <!-- omitted for brevity -->
</view>
  1. Показывать сообщения сразу после загрузки экрана.

И мы можем сделать то же самое в deleted.xml:

<view xmlns="https://hyperview.org/hyperview">
  {% include "hv/messages.xml" %} (1)
  <behavior trigger="load" action="dispatch-event" event-name="contact-updated" />
  <behavior trigger="load" action="back" />
</view>
  1. Показывать сообщения сразу после загрузки экрана.

В обоих случаях form_fields.xmlи deleted.xmlпри «загрузке» запускается несколько вариантов поведения. В deleted.xmlмы сразу же возвращаемся к предыдущему экрану. В режиме form_fields.xmlмы немедленно перезагружаем текущий экран, чтобы отобразить сведения о контакте. Если бы мы отображали элементы пользовательского интерфейса сообщений непосредственно на экране, пользователь едва ли бы их увидел, прежде чем экран исчезнет или перезагрузится. Используя настраиваемое действие, пользовательский интерфейс сообщений остается видимым, даже если экраны под ним меняются.

В верхней части экрана отображается маленькое серое поле: «Контакт удален!»
Рисунок 1. Сообщение, отображаемое во время обратной навигации.

Жест смахивания по контактам

Чтобы добавить возможности связи и пользовательский интерфейс сообщений, мы расширили клиент настраиваемыми действиями поведения. Но клиент Hyperview также можно расширить с помощью пользовательских компонентов пользовательского интерфейса, отображаемых на экране. Пользовательские компоненты реализуются как компоненты React Native. Это означает, что все, что возможно в React Native, можно сделать и в Hyperview! Пользовательские компоненты открывают безграничные возможности для создания многофункциональных мобильных приложений с архитектурой Hypermedia.

Чтобы проиллюстрировать эти возможности, мы расширим клиент Hyperview в нашем мобильном приложении, добавив компонент «перелистываемой строки». Как это работает? Компонент «перелистываемой строки» поддерживает жест горизонтального пролистывания. Когда пользователь проводит по этому компоненту справа налево, он перемещается, открывая ряд кнопок действий. Каждая кнопка действия сможет запускать стандартное поведение Hyperview при нажатии. Мы будем использовать этот пользовательский компонент на экране списка контактов. Каждый элемент контакта будет представлять собой «перелистываемую строку», а действия обеспечат быстрый доступ к действиям по редактированию и удалению контакта.

Когда мы прокручиваем элемент списка контактов влево, появляются кнопки «Изменить» и «Удалить».
Рис. 2. Перелистываемый контактный элемент

Проектирование компонента

Вместо того, чтобы реализовывать жест смахивания с нуля, мы снова будем использовать стороннюю библиотеку с открытым исходным кодом: react-native-swipeable.

> cd hyperview/demo
> yarn add react-native-swipeable (1)
> yarn start (2)
  1. Добавьте зависимость от react-native-swipeable.

  2. Перезапустите мобильное приложение.

Эта библиотека предоставляет компонент React Native под названием Swipeable. Он может отображать любые компоненты React Native в качестве основного содержимого (той части, которую можно перелистывать). Он также использует массив компонентов React Native в качестве реквизита для отображения в виде кнопок действий.

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

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

<swipe:row xmlns:swipe="https://hypermedia.systems/hyperview/swipeable"> (1)
  <swipe:main> (2)
    <!-- main content shown here -->
  </swipe:main>

  <swipe:button> (3)
    <!-- first button that appears when swiping -->
  </swipe:button>

  <swipe:button> (4)
    <!-- second button that appears when swiping -->
  </swipe:button>
</swipe:row>
  1. Родительский элемент, инкапсулирующий всю прокручиваемую строку, с настраиваемым пространством имен.

  2. Основное содержимое прокручиваемой строки может содержать любой HXML.

  3. Первая кнопка, которая появляется при пролистывании, может содержать любой HXML.

  4. Вторая кнопка, которая появляется при пролистывании, может содержать любой HXML.

Эта структура четко отделяет основной контент от кнопок. Он также поддерживает одну, две или более кнопок. Кнопки появляются в порядке определения, что позволяет легко менять порядок.

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

Реализация компонента

Теперь, когда мы знаем структуру HXML нашего пользовательского компонента, мы можем написать код для его реализации. Как выглядит этот код? Компоненты Hyperview написаны как компоненты React Native. Эти компоненты React Native сопоставлены с уникальным пространством имен XML и именем тега. Когда клиент Hyperview встречает это пространство имен и имя тега в HXML, он делегирует рендеринг элемента HXML соответствующему компоненту React Native. В рамках делегирования клиент Hyperview передает несколько реквизитов компоненту React Native:

  • element: элемент XML DOM, который сопоставляется с компонентом React Native.

  • stylesheets: стили, определенные в файле <screen>.

  • onUpdate: функция, вызываемая, когда компонент запускает поведение.

  • option: разные настройки, используемые клиентом Hyperview.

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

Теперь, когда мы знаем, как реализованы пользовательские компоненты Hyperview, давайте напишем код для нашей перелистываемой строки.

import React, { PureComponent } from 'react';
import Hyperview from 'hyperview';
import Swipeable from 'react-native-swipeable';

const NAMESPACE_URI = 'https://hypermedia.systems/hyperview/swipeable';

export default class SwipeableRow extends PureComponent { (1)
  static namespaceURI = NAMESPACE_URI; (2)
  static localName = "row"; (3)

  getElements = (tagName) => {
    return Array.from(this.props.element.getElementsByTagNameNS(NAMESPACE_URI, tagName));
  };

  getButtons = () => { (4)
    return this.getElements("button").map((buttonElement) => {
      return Hyperview.renderChildren(buttonElement, this.props.stylesheets, this.props.onUpdate, this.props.options); (5)
    });
  };

  render() {
    const [main] = this.getElements("main");
    if (!main) {
      return null;
    }

    return (
      <Swipeable rightButtons={this.getButtons()}> (6)
        {Hyperview.renderChildren(main, this.props.stylesheets, this.props.onUpdate, this.props.options)} (7)
      </Swipeable>
    );
  }
}
  1. Компонент React Native на основе классов.

  2. Сопоставьте этот компонент с заданным пространством имен HXML.

  3. Сопоставьте этот компонент с данным именем тега HXML.

  4. Функция, которая возвращает массив компонентов React Native для каждого <button>элемента.

  5. Делегируйте клиенту Hyperview обработку каждой кнопки.

  6. Передайте кнопки и основной контент в стороннюю библиотеку.

  7. Делегируйте клиенту Hyperview рендеринг основного контента.

Класс SwipeableRowреализует компонент React Native. В верхней части класса мы устанавливаем статическое namespaceURIсвойство и localNameсвойство. Эти свойства сопоставляют компонент React Native с уникальной парой пространства имен и имен тегов в HXML. Именно так клиент Hyperview знает, кому следует делегировать полномочия SwipeableRowпри обнаружении пользовательских элементов в HXML. Внизу класса вы увидите render()метод. render()вызывается React Native для возврата визуализированного компонента. Поскольку React Native построен по принципу композиции, render()обычно возвращает композицию других компонентов React Native. В этом случае мы возвращаем Swipeableкомпонент (предоставленныйreact-native-swipeableбиблиотека), составленная с использованием компонентов React Native для кнопок и основного контента. Компоненты React Native для кнопок и основного контента создаются с использованием аналогичного процесса:

  • Найдите конкретные дочерние элементы ( <button>или <main>).

  • Превратите эти элементы в компоненты React Native, используя Hyperview.renderChildren().

  • Установите компоненты как дочерние элементы или реквизиты Swipeable.

HyperviewClient делегирует нам рендеринг XML-элемента swipe:row.  Мы делегируем обратно рендеринг текстовых элементов.
Рисунок 3. Делегирование рендеринга между клиентом и пользовательскими компонентами

Этот код может быть трудным для понимания, если вы никогда не работали с React или React Native. Это нормально. Важный вывод: мы можем написать код для перевода произвольного HXML в компоненты React Native. Структура HXML (как атрибуты, так и элементы) может использоваться для представления нескольких аспектов пользовательского интерфейса (в данном случае — кнопок и основного контента). Наконец, код может делегировать рендеринг дочерних компонентов обратно клиенту Hyperview.

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

Последнее, что нужно сделать, это зарегистрировать этот новый компонент в клиенте Hyperview. Этот процесс аналогичен регистрации пользовательских действий. Пользовательские компоненты передаются как отдельный componentsреквизит для Hyperviewкомпонента.

import React, { PureComponent } from 'react';
import Hyperview from 'hyperview';
import OpenEmail from './email';
import OpenPhone from './phone';
import ShowMessage from './message';
import SwipeableRow from './swipeable'; (1)

export default class HyperviewScreen extends PureComponent {
  // ... omitted for brevity

  behaviors = [OpenEmail, OpenPhone, ShowMessage];
  components = [SwipeableRow]; (2)

  render() {
    return (
      <Hyperview
        behaviors={this.behaviors}
        components={this.components} (3)
        entrypointUrl={this.entrypointUrl}
        // more props...
      />
    );
  }
}
  1. Импортируйте SwipeableRowкомпонент.

  2. Создайте массив пользовательских компонентов.

  3. Передайте пользовательский компонент компоненту Hyperviewкак свойство с именем components.

Теперь мы готовы обновить наши шаблоны HXML, чтобы использовать новый компонент перелистываемых строк.

Использование компонента

В настоящее время HXML для элемента контакта в списке состоит из элемента <behavior>и <text>:

<item key="{{ contact.id }}" style="contact-item">
  <behavior trigger="press" action="push" href="/contacts/{{ contact.id }}" /> (1)
  <text style="contact-item-label">
    <!-- omitted for brevity -->
  </text>
</item>

Благодаря нашему компоненту перелистываемых строк эта разметка станет «основным» пользовательским интерфейсом. Итак, давайте начнем с добавления <row>и <main>в качестве родительских элементов.

<item key="{{ contact.id }}">
  <swipe:row xmlns:swipe="https://hypermedia.systems/hyperview/swipeable"> (1)
    <swipe:main> (2)
      <view style="contact-item"> (3)
        <behavior trigger="press" action="push" href="/contacts/{{ contact.id }}" /> (1)
        <text style="contact-item-label">
          <!-- omitted for brevity -->
        </text>
      </view>
    </swipe:main>
  </swipe:row>
</item>
  1. Добавлен <swipe:row>родительский элемент с псевдонимом пространства имен для swipe.

  2. Добавлен <swipe:main>элемент для определения основного контента.

  3. Обернул существующие элементы <behavior>и <text>в файл <view>.

Раньше contact-itemстиль был задан для <item>элемента. Это имело смысл, когда <item>элемент был контейнером для основного содержимого элемента списка. Теперь, когда основной контент является дочерним элементом <swipe:main>, нам нужно добавить новый элемент <view>, в котором мы будем применять стили.

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

<item key="{{ contact.id }}">
  <swipe:row xmlns:swipe="https://hypermedia.systems/hyperview/swipeable"> (1)
    <swipe:main>
      <!-- omitted for brevity -->
    </swipe:main>

    <swipe:button> (1)
      <view style="swipe-button">
        <text style="button-label">Edit</text>
      </view>
    </swipe:button>

    <swipe:button> (2)
      <view style="swipe-button">
        <text style="button-label-delete">Delete</text>
      </view>
    </swipe:button>
  </swipe:row>
</item>
  1. Добавлено <swipe:button>для действия редактирования.

  2. Добавлено <swipe:button>для действия удаления.

Теперь, если мы воспользуемся нашим мобильным приложением, мы сможем увидеть прокручиваемую строку в действии! Когда вы проводите по элементу контакта, появляются кнопки «Изменить» и «Удалить». Но они пока ничего не делают. Нам нужно добавить к этим кнопкам некоторые варианты поведения. Кнопка «Редактировать» проста: нажатие на нее должно открыть экран сведений о контакте в режиме редактирования.

<swipe:button>
  <view style="swipe-button">
    <behavior trigger="press" action="push" href="/contacts/{{ contact.id }}/edit" /> (1)
    <text style="button-label">Edit</text>
  </view>
</swipe:button>
  1. При нажатии открывается новый экран с пользовательским интерфейсом редактирования контакта.

Кнопка «Удалить» немного сложнее. Нет экрана, который можно было бы открыть для удаления, так что же должно произойти, когда пользователь нажмет эту кнопку? Возможно, мы используем то же взаимодействие, что и кнопка «Удалить» на экране «Редактировать контакт». В результате этого взаимодействия открывается системный диалог, в котором пользователю предлагается подтвердить удаление. Если пользователь подтвердит, клиент Hyperview отправляет запрос POSTи /contacts/<contact_id>/deleteдобавляет ответ на экран. Ответ немедленно запускает несколько действий: перезагрузка списка контактов и отображение сообщения. Это взаимодействие будет работать и для нашей кнопки действия:

<swipe:button>
  <view style="swipe-button">
    <behavior (1)
      xmlns:alert="https://hyperview.org/hyperview-alert"
      trigger="press"
      action="alert"
      alert:title="Confirm delete"
      alert:message="Are you sure you want to delete {{ contact.first }}?"
    >
      <alert:option alert:label="Confirm">
        <behavior (2)
          trigger="press"
          action="append"
          target="item-{{ contact.id }}"
          href="/contacts/{{ contact.id }}/delete"
          verb="post"
        />
      </alert:option>
      <alert:option alert:label="Cancel" />
    </behavior>
    <text style="button-label-delete">Delete</text>
  </view>
</swipe:button>
  1. При нажатии открывается системное диалоговое окно с просьбой подтвердить действие.

  2. В случае подтверждения сделайте запрос POST к конечной точке удаления и добавьте ответ к родительскому файлу <item>.

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

Проведите пальцем по экрану и нажмите «Удалить», чтобы открыть диалоговое окно подтверждения удаления.
Рисунок 4. Кнопка «Удалить с помощью смахивания»

Обратите внимание, что кнопки действий могут поддерживать любой тип поведенческого действия, от pushдо alert. Если бы мы захотели, мы могли бы сделать так, чтобы кнопки действий запускали наши специальные действия, например open-phoneи open-email. Пользовательские компоненты и действия можно свободно смешивать со стандартными компонентами и действиями, которые входят в стандартную комплектацию Hyperview. Благодаря этому расширения клиента Hyperview кажутся первоклассными функциями.

На самом деле, мы откроем вам секрет. В клиенте Hyperview стандартные компоненты и действия реализованы так же, как и пользовательские компоненты и действия! Код рендеринга не <view>отличается от кода <swipe:row>. Код поведения не alertотличается от кода open-phone. Оба они реализованы с использованием одних и тех же методов, описанных в этом разделе. Стандартные компоненты и действия — это именно те, которые необходимы всем мобильным приложениям. Но они являются лишь отправной точкой.

Большинству мобильных приложений потребуются некоторые расширения клиента Hyperview, чтобы обеспечить удобство работы с пользователем. Расширения превращают клиент из обычного «клиента Hyperview» в клиент, специально созданный для вашего приложения. И что немаловажно, эта эволюция сохраняет Hypermedia, серверную архитектуру и все ее преимущества.

Мобильные гипермедийные приложения

На этом наша сборка мобильного Contact.app завершена. Отойдите от деталей кода и рассмотрите более широкую картину:

  • Основная логика приложения находится на сервере.

  • Шаблоны, отображаемые на сервере, используются как в веб-приложениях, так и в мобильных приложениях.

  • Настройка платформы осуществляется с помощью сценариев в Интернете и настройки клиента на мобильных устройствах.

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

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

Заметки о Hypermedia: достаточно хороший UX и острова интерактивности

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

Как мы видели во второй главе, Рой Филдинг отметил этот компромисс по отношению к сетевой архитектуре Интернета RESTful, где «информация передается в стандартизированной форме, а не в форме, специфичной для нужд приложения».

Принятие немного менее эффективного и интерактивного решения для конкретного UX может существенно сэкономить вам время при создании приложений.

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


Last updated