
Всем привет! Почти три года назад мы выложили в опенсорс DivKit — наш BDUI‑фреймворк для отрисовки интерфейса приложения из ответа сервера. С его помощью вы можете описать элементы, состояния и анимации на бэкенде. Приложение получит это JSON‑описание и отобразит его.
Мы не перестаём развивать DivKit и за последние месяцы добавили несколько важных функций. О них и хотелось бы рассказать в этой статье.
Визуальный редактор
Написание JSON с вёрсткой или его создание через DSL требует определённых базовых знаний, и обычно этим занимаются разработчики. Нам хотелось бы снизить порог входа, чтобы любой человек, даже без знания языков программирования, смог сверстать нужный ему интерфейс.
Конечно, существует плагин для Figma, который позволяет превращать макет в вёрстку. Но у такого подхода есть свои неудобства:
лейаут Figma не всегда однозначно трансформируется в лейаут DivKit (container‑based);
трансформация не может выявлять повторяющиеся элементы и выносить их в шаблоны;
экшены и идентификаторы для логирования после экспорта всё равно нужно править вручную;
работа в Figma требует подготовки.
Решение напрашивается само собой — WYSIWYG‑редактор. Он уже доступен на странице плейграунда. Вот краткий список его возможностей:
добавление, удаление и перетаскивание компонентов;
поддержка разных типов позиционирования: абсолютного (по координатам) и container‑based;
визуальное редактирование текста: можно менять стиль и добавлять картинки;
загрузка файлов;
поддержка локализации и тем;
большое количество кастомизируемых свойств объектов: от выравнивания до параметров видео;
поддержка выражений;
кастомизация поведения и внешнего вида редактора: две темы, два языка, возможность передачи своих переменных и т. д.
Редактор, как и сам DivKit Web, использует фреймворк Svelte. Это позволило создать быстрый и реактивный интерфейс, который было бы сложно получить другими путями.

Сейчас визуальный редактор активно используется для создания промоэкранов, виджетов и сторис во многих приложениях Яндекса: в мобильном Браузере, Шедевруме, Путешествиях, Яндексе с Алисой. С его помощью контент‑менеджеры запускают промоэкраны и истории без участия разработчиков.
Благодаря редактору в DivKit Web появилось полезное улучшение — API под названием setData. Он эффективно обновляет вёрстку, переиспользуя отрисованные элементы. Например, в новой вёрстке поменялся один отступ — изменится только он, и ничего не нужно будет создавать заново.
Ряд улучшений получил и код по выполнению выражений, в некоторых случаях они теперь выполняются на несколько порядков быстрее.
Здесь вы можете познакомиться с визуальным редактором. Для развёртывания редактора вам пригодится npm‑пакет. И конечно, мы принимаем фидбэк, пожелания и пул‑реквесты в нашем репозитории.
Формы
Один из популярных сценариев использования DivKit — создание и обработка форм. Мы попытались упростить процесс их вёрстки и снизить количество необходимого для этого бойлерплейта. Так выглядит простая форма:

Посмотреть вёрстку целиком можно тут. Ниже опишем нововведения подробнее.
Локальные триггеры и локальные переменные
Шаблоны уже были достаточно развитым инструментом в DivKit — они позволяют выделять и переиспользовать одинаковые блоки вёрстки и подставлять в них поля для удобной кастомизации. Но нельзя было выделить и переиспользовать повторяющуюся в шаблонах логику.
Поэтому мы поддержали локальные переменные и триггеры, которые дают возможность на уровне шаблонов описывать логику, хранить данные, относящиеся к шаблону, и использовать шаблоны как полноценные части вёрстки со своим окружением и поведением. А значит — лучше изолировать шаблоны от внешнего контекста, чтобы использовать их в основной вёрстке без лишнего дублирования.
Например, в вёрстке выше (в шаблоне) объявлена переменная, которая хранит введённый пользователем текст:
"variables": [
{
"name": "current_text",
"type": "string",
"value": ""
}
]
И триггер, который копирует изменённое значение в переменную уровня карточки. После этого оно может быть использовано в произвольных частях вёрстки или отправлено с помощью submit action (об этом ниже).
"variable_triggers": [
{
"condition": "@{text_is_valid(current_text)}",
"mode": "on_variable",
"actions": [
{
"log_id": "update",
"typed": {
"type": "set_variable",
"$variable_name": "field_name",
"value": {
"type": "string",
"value": "@{current_text}"
}
}
}
]
}
]
Пользовательские функции
При проработке наиболее частых сценариев вёрстки форм мы столкнулись с задачей валидации введённых пользователем данных.
Для примера рассмотрим шаблон текстового поля, которое проверяет длину введённого текста.
{
"type": "text_field",
"title": "First name",
"field_name": "first_name",
"is_valid": "@{len(text) > 0}"
}
Выражение is_valid
можно использовать в триггере для проверки валидности. Однако для определения невалидного состояния придётся продублировать это поле с инвертированным условием: "is_invalid": "@{len(text) <= 0}"
. Чтобы решить проблему дублирования, мы поддержали пользовательские функции, которые можно декларировать не только на уровне карточки, но и прямо в элементах. Доступ к ним осуществляется по аналогии с локальными переменными.
Функции можно задавать в поле functions
. Схема для создания новых пользовательских функций выглядит так:
{
arguments*: [
{
name*: "string",
type*: "string"
},
...
],
body*: "string",
name*: "string",
return_type*: "string"
}
Пример использования пользовательских функций для валидации текстовых полей ввода в шаблонах:
"text_field": {
"type": "container",
"functions": [
{
"name": "is_valid",
"body": "@{len(text) <= 0}",
"arguments": [
{
"type": "string",
"name": "text"
}
],
"return_type": "boolean"
}
],
"variable_triggers": [
{
"condition": "@{is_valid(field_variable)}",
"actions": [
{
"log_id": "validation_success",
"url": "div-action://set_variable?name=state_variable&value=valid_field"
}
]
},
{
"condition": "@{!is_valid(field_variable)}",
"actions": [
{
"log_id": "validation_failed",
"url": "div-action://set_variable?name=state_variable&value=invalid_field"
}
]
}
]
...
}
Типизированные экшены
Традиционно для вызова экшенов в DivKit использовались URL. Для простых случаев это позволяет получить лаконичную запись, однако в более сложных сценариях, когда требуется передача параметров, вёрстка быстро становится нечитаемой.
Пример экшена в старом формате:
{
"log_id": "validation_success",
"url": "div-action://set_variable?name=state_variable&value=valid_field"
}
И в новом:
{
"log_id": "validation_success",
"typed": {
"type": "set_variable",
"variable_name": "state_variable",
"value": {
"type": "string",
"value": "valid_field"
}
}
}
Подробнее в документации.
Экшен для отправки данных из формы по URL
Чтобы формы были полезны, они должны обрабатывать введённые данные. Для этого не хватало экшена, и мы добавили DivActionSubmit. Он собирает все переменные, объявленные в элементе с идентификатором container_id
, и обрабатывает их, используя протокол DivSubmitter.
Клиент может сам реализовать этот протокол, подставив кастомную обработку данных из вёрстки. По умолчанию используется встроенная реализация — DivNetworkSubmitter. Она отправляет данные в сеть по параметрам, указанным в поле request экшена. В этом поле можно задать ссылку, по которой будут переданы данные, заголовок и метод запроса. По умолчанию переменные будут передаваться в теле запроса в формате JSON.
:method: POST
:scheme: https
:path: /
:authority: example.org
content-type: application/x-www-form-urlencoded
header_1: value_header_1
{"last_name":"McQueen","first_name":"Steve"}
Изменения во вводе с клавиатуры
Фильтрация ввода в div‑input. Теперь можно фильтровать ввод в div‑input с помощью поля filters. Фильтры можно задавать с помощью регулярных выражений.

По ссылке можно посмотреть пример целиком.
Экстеншен для надклавиатурной панели на iOS. Теперь можно добавить надклавиатурную панель со вспомогательными кнопками, что довольно распространено в приложениях на iOS. Для этого нужно указать в вёрстке экстеншен input_accessory_view
для div-input
и предоставить реализацию панели через протокол InputAccessoryViewProvider
. Ниже пример простой реализации из демонстрационного приложения.

Поддержка разных типов регистров клавиатуры при вводе в div‑input. Поддержали поле autocapitalization
, которое позволяет сразу открывать клавиатуру в верхнем регистре либо выставить регистр так, чтобы каждое слово или предложение начиналось с заглавной буквы.
Анимации
В DivKit уже был API для описания анимаций: DivTransition. Он позволяет анимировано перейти из одного состояния в другое. Анимации переходов позволяют оживить появление или скрытие элементов на экране. Но они требуют явного описания конечного состояния, а в некоторых случаях, например для зацикленных анимаций, это искусственно и нетривиально. Также бывают сценарии, когда нужно изменить произвольное свойство элемента.
Эти проблемы помогли решить аниматоры переменных. Рассмотрим пример: объявляем шаблон спиннера с переменной, которая будет отвечать за угол поворота элемента:
"spinner": {
"type": "image",
"image_url": "https://your_spinner_image",
"transform": {
"rotation": "@{rotation_angle}"
},
"variables": [
{
"name": "rotation_angle",
"type": "number",
"value": 0.0
},
]
}
Тут же объявляем аниматор, привязанный к переменной поворота:
"spinner": {
...
"variables": [ ... ],
"animators": [
{
"type": "number_animator",
"id": "rotation_animator",
"variable_name": "rotation_angle",
"start_value": 0,
"end_value": 360,
"duration": 1000
}
]
}
Теперь остаётся воспользоваться спиннером и запустить анимацию:
{
"type": "container",
"items": [
{
"type": "spinner",
"id": "spinner_01"
},
{
"type": "button",
"actions": [
{
"log_id": "start_spinner",
"scope_id": "spinner_01",
"typed": {
"type": "animator_start",
"animator_id": "rotation_animator",
"repeat_count": {
"type": "fixed",
"value": 4
}
}
}
]
}
]
}
Вот что получилось:

И снова интерактивный пример.
С помощью аниматоров можно менять любые свойства элементов, в которых поддерживаются выражения. С одной стороны, это даёт большую гибкость. Но с другой — может ухудшить плавность отрисовки, если анимировать свойства, отвечающие за размещение элементов.
Выражения в инициализаторах переменных
Одна из проблем, с которой сталкиваются разработчики при использовании item_builder
— установка начальных значений локальных переменных.
Можно использовать триггер…
"variables": [
{
"name": "value",
"type": "string",
"value": "initial"
}
],
"variable_triggers": [
{
"condition": "@{value == 'initial'}",
"actions": [
{
"log_id": "first",
"typed": {
"type": "set_variable",
"variable_name": "value",
"value": {
"type": "string",
"value": "@{ source.getDict(index).getString('field_value') }"
}
}
}
]
}
]
…но, такая конструкция получается громоздкой, да и условие срабатывания выглядит искусственно.
В DivKit 32 мы включили поддержку выражений прямо в инициализаторах переменных. И теперь аналогичный сценарий можно записать гораздо компактнее:
"variables": [
{
"name": "value",
"type": "string",
"value": "@{ source.getDict(index).getString('field_value') }"
}
]
Команда DivKit надеется, что новые фичи помогут пользователям фреймворка и облегчат повседневные задачи. А пока мы продолжаем развитие DivKit: сейчас наше внимание направлено на feature parity между платформами и обновление документации. Как всегда, будем рады комментариям и пул‑реквестам на гитхабе.