Smart-компонент в макете
Эта глава описывает, как Smart-компонент становится узлом макета и во что он превращается при отрисовке.
Smart-компоненты и составные компоненты в макете
Smart-компоненты попадают в макет как типы узлов через декларативный контракт. Эта глава описывает, как это устроено; поведение Smart-компонентов, их события, состояния и порты хоста описаны в части о Smart-компонентах (см. Составные компоненты и Связь с приложением: порты хоста и намерения).
Наличие компонента в справочнике само по себе не значит, что его можно вставить в слот макета: нужен опубликованный и проверенный декларативный контракт.
Декларативный контракт
Smart-компонент можно вставить в макет, только если он публикует
декларативный контракт: в его манифесте есть раздел
composition.declarative с именем типа, допустимыми настройками, слотами,
рендерером и ресурсами. Framework строит из этого раздела манифест типа
композиции (compositionTypeFromSmartManifest).
Раздел обязательно содержит standard, type, mode, profiles, props,
slots, renderer и assets; необязательно — presentation, data,
events, persistence и host_port (стык с приложением, см. Порты и наборы проверок).
Правила контракта:
- в
propsпопадают только настройки, перечисленные вcomposition.declarative.props; каждая из них объявлена в разделеinputsманифеста компонента. Любая другая настройка, даже существующая у компонента в разметке, отклоняется с кодомschema_additional_property; - значение настройки — того же типа, что у атрибута в манифесте: строковое
пишется строкой (
"size": "1"; число1отклоняется с кодомschema_enum), логическое —trueилиfalse; - данные, которые приходят от приложения (например, строки и колонки таблицы), описываются собственными контрактами данных компонента, а не атрибутами;
- события перечисляются явно и берутся из раздела
eventsманифеста.
В поставке Framework декларативный контракт публикует кнопка
(smart.buttons). Компоненты пакета продукта публикуют свои контракты в своих
манифестах: например, таблица пакета larena — тип smart.data-view с
данными columns и rows. Актуальный перечень — раздел
composition.declarative в манифестах (distr/smart/<папка>/smart.manifest.json
в поставке ui, src/smart/<папка>/smart.manifest.json в пакете).
Настройки, которые узел smart.buttons принимает в документе: template,
size, appearance, spacing, scheme, status, text, icon,
icon-position, loading, disabled, native-type, aria-label. Значения
у них те же, что у атрибутов кнопки (Кнопки). Вид кнопки задаёт
appearance; прежнего имени type в этом списке нет, и в документе макета
его не пишут.
Как Smart-узел превращается в HTML
Пример ниже показывает узел кнопки:
{
"id": "contact",
"type": "smart.buttons",
"props": {"text": "Связаться", "native-type": "button"}
}
Рендерер такого типа — пользовательский элемент (custom-element): узел
становится тегом <sf-button native-type="button" text="Связаться">, а
настройки — его атрибутами с экранированными значениями.
render() добавляет объявленный ресурс (smart/buttons) в список ресурсов и
запись в список hydration — какие узлы нужно оживить в браузере. Оживляет
их Loader, как любой Smart-компонент на странице.
Тип smart.buttons не входит во встроенные типы: его добавляют в реестр.
Реестр, в котором есть встроенные типы и тип кнопки, собирает
extendRegistry. Манифест кнопки лежит в поставке рядом с её кодом:
const buttonManifest = await fetch(window.sfPath + 'smart/buttons/smart.manifest.json')
.then((response) => response.json());
const registry = SF.Composition.extendRegistry([
SF.Composition.compositionTypeFromSmartManifest(buttonManifest),
]);
const result = await SF.Composition.render(doc, { registry });
Документ doc в этом примере — секция с заголовком и двумя колонками: в
первой абзац, во второй узел кнопки из примера выше.
{
"schema": "simai.composition.document",
"schema_version": 1,
"id": "about-page",
"profile": "ui-layout",
"locale": "ru",
"root": {"id": "page", "type": "layout.page", "slots": {"default": [
{"id": "intro", "type": "layout.section", "slots": {"default": [
{"id": "title", "type": "content.heading",
"data": {"level": 1, "content": [{"type": "text", "value": "О компании"}]}},
{"id": "grid", "type": "layout.columns", "props": {"columns": 2}, "slots": {"columns": [
{"id": "col-left", "type": "content.paragraph",
"data": {"content": [{"type": "text", "value": "Мы делаем интерфейсы."}]}},
{"id": "contact", "type": "smart.buttons",
"props": {"text": "Связаться", "native-type": "button"}}
]}}
]}}
]}}
}
Для этого документа result.html такой:
<main data-sf-composition-id="page"><section data-sf-composition-id="intro"><h1 data-sf-composition-id="title">О компании</h1><div class="sf-composition-columns" data-sf-composition-id="grid" data-composition-columns="2"><div class="sf-composition-column"><p data-sf-composition-id="col-left">Мы делаем интерфейсы.</p></div><div class="sf-composition-column"><sf-button native-type="button" text="Связаться"></sf-button></div></div></section></main>
Атрибуты Smart-узла выводятся в алфавитном порядке: свойства узла
приводятся к канонической форме с упорядоченными ключами, поэтому
native-type стоит перед text, в каком бы порядке их ни записали. Явно
заданные свойства остаются атрибутами, даже если равны значению по
умолчанию.
result.assets — ["composition/layout", "smart/buttons"],
result.hydration — [{"id": "contact", "type": "smart.buttons", "element": "sf-button"}].
После вставки result.html на страницу Loader находит <sf-button> и
загружает кнопку. Кнопка в документе композиции — всегда Smart-узел: среди
встроенных типов нет кнопки на классах, поэтому странице с таким документом
нужен Loader. Кнопку на классах (<button class="sf-button …">) выводит
только рендерер собственного типа (см. Собственный тип). Код, которому нужен готовый элемент, ждёт его через
SF.whenDefined('sf-button') или customElements.whenDefined('sf-button')
(см. События загрузчика).
Своих событий у кнопки нет: нажатие мышью и клавишами приходит обычным
click, и обработчик удобно вешать делегированием на общий родитель.
Что важно заметить: createRegistry(список) берёт список как реестр целиком.
Реестр из одного типа кнопки не знает layout.page, и документ отклоняется с
type_unknown. Чтобы добавить свой тип к встроенным, используйте
extendRegistry.
Документ не содержит обработчиков JavaScript. Компонент сообщает о действии событием, проект решает, что делать (см. Граница ответственности).
Смотрите также
- Составные компоненты и Связь с приложением: порты хоста и намерения — составные компоненты, маршруты и порты хоста.
- Встроенные типы —
layout.scopeсреди встроенных типов. - Где взять спецификацию — комплект Composition Routing.