К содержанию

Навигация

Фрагменты интерфейса

Страницу рабочего интерфейса удобно собирать из фрагментов: список файлов, дерево папок, счётчик уведомлений, карточка записи. Фрагмент — составной Smart-компонент, поставленный в именованную область страницы, например content. У него ровно один порт хоста, через который он говорит с приложением, и опубликованные порты композиции, через которые он говорит с соседями. Фрагмент интерфейса не страница, не шаблон и не серверная вставка со своим поведением; не путайте его с фрагментом Recipe — повторно используемой частью плана сборки (Словарь Recipe). Правила фрагмента задаёт стандарт simai.interface-fragment 1.0.2.

Как фрагменты связаны

Фрагменты не знают друг о друге:

  • фрагмент не вызывает методы другого фрагмента, не обращается к его порту хоста и не ищет его в DOM;
  • то, что фрагмент хочет сказать соседям, уходит через его опубликованный выход, а то, что он слушает, приходит на объявленный вход;
  • страница — один layout.scope: фрагменты — его конечные точки, а маршруты между их портами объявляет страница, а не фрагменты;
  • маршрут соединяет две существующие конечные точки, и типы значений на его концах совпадают (Типы значений маршрутов).

Значение между фрагментами говорит, что и где изменилось, но не несёт данные. Список файлов отправляет files-moved с идентификаторами и папками; дерево папок получает его и перечитывает нужные папки через свой порт хоста со своими правами. Последствия ответов приложения приходят к фрагментам тем же путём — через хост страницы.

JSON
{
  "id": "files-page",
  "type": "layout.scope",
  "props": {"routes": [
    {"id": "moved-to-tree",
     "from": {"endpoint": "files", "port": "moved"},
     "to": {"endpoint": "folders", "port": "moved"}}
  ]}
}

Хост страницы

Приложение в ответе порта может сообщить последствия (effects): что ещё изменилось на странице из-за выполненного намерения. Например, перемещение файлов меняет и список, и дерево папок, и счётчик. Последствия публикует элемент <sf-page-host> — конечная точка layout.scope, у которой есть только выходы, по одному на каждый тип значений страницы. Выход назван типом без версии: record-changed, record-removed, collection-changed, files-moved, counter, navigation, preference-changed. Хост сам ни одного фрагмента не вызывает: последствия доставляют маршруты, которые объявила страница.

HTML
<sf-composition-scope data-sf-routes='[{"id": "files-refresh",
    "from": {"endpoint": "host", "port": "collection-changed", "value": "collection-changed.v1"},
    "to": {"endpoint": "files", "port": "changed", "value": "collection-changed"}}]'>
  <sf-page-host data-sf-endpoint="host"></sf-page-host>
  <sf-fragment data-sf-fragment="shop.file-list">
    <shop-file-list data-sf-endpoint="files"></shop-file-list>
  </sf-fragment>
</sf-composition-scope>

Разметку области обычно выводит сервер из документа композиции; здесь она показана готовой. <sf-page-host> скрыт и входов не имеет. Каждая сторона маршрута несёт тип в той форме, в какой его объявил порт: выходы хоста объявлены прежними метками (collection-changed.v1), а список файлов — именем без версии. Это один тип, и маршрут допустим (Имя типа).

Последствия — список {type, value}, где type — имя типа без версии (record-changed); прежняя метка (record-changed.v1) читается до 2026-12-01 и уходит на тот же выход. Их публикует SF.Composition.publishEffects(effects, {origin}): функция находит хост в той области sf-composition-scope, где стоит элемент origin (обычно фрагмент, получивший ответ), а если origin не задан или стоит вне области — единственный хост страницы.

JavaScript
const answer = await host.raise('files.move', payload, {signal});
const {published, refused} = SF.Composition.publishEffects(answer.effects ?? [], {origin: fileList});

Каждое последствие проверяется по своему типу. Неверное значение или тип, которого хост не публикует, отклоняется, а остальные всё равно уходят. Ответ функции — {published, refused}: published — опубликованные типы, refused — список {type, reason}, где reason — type_not_published, value_invalid (с message) или no_page_host, если хост не найден или их несколько. Об отклонённых хост сообщает событием sf-page-host-refused с detail: {refused}. Тот же publish(effects) есть у самого элемента <sf-page-host>.

Описание фрагмента

Каждый фрагмент публикует описание — JSON с меткой "schema": "simai.interface-fragment.v1". В описании объявлено всё, чем фрагмент обменивается и что ему нужно; необъявленного нет.

JSON
{
  "schema": "simai.interface-fragment.v1",
  "id": "shop.order-list",
  "element": "shop-order-list",
  "regions": ["content"],
  "port": {"standard": "simai.composite-host-port", "version": "1.0.4"},
  "outputs": {
    "changed": {"value": "record-changed", "summary": "Заказ изменён в списке."}
  },
  "inputs": {
    "removed": {"value": "record-removed", "summary": "Убрать заказ из списка."}
  },
  "operation": "orders.read",
  "url_state": ["status", "page"],
  "budget": {"kb": 40, "first_answer_ms": 300}
}
Поле Что означает Правило
schema метка формата simai.interface-fragment.v1 или simai.interface-fragment с "schema_version": 1
id имя фрагмента не меньше двух частей через точку из строчных латинских букв, цифр и дефиса: shop.order-list
element тег элемента имя пользовательского элемента с дефисом: shop-order-list
regions области страницы, где фрагмент может стоять от одной до восьми уникальных имён: строчная буква, затем до 31 строчной буквы, цифры или дефиса
port стандарт и версия порта хоста {standard, version}, версия полная: 1.0.4
outputs выходы до 16 портов; у каждого value — тип значения из перечня, имя без версии (record-changed) или прежняя метка (record-changed.v1) до 2026-12-01, и summary — описание до 1000 символов
inputs входы то же, что outputs
operation операция доступа, без которой фрагмент не показывают слово-метка: строчная буква, затем до 127 символов из строчных букв, цифр, точки, подчёркивания, двоеточия и дефиса
url_state ключи адреса, которыми владеет фрагмент до 16 уникальных ключей: строчная буква, затем до 63 строчных букв, цифр, подчёркиваний или дефисов
budget бюджет kb — килобайты сверх общей основы, от 0 до 2048; first_answer_ms — миллисекунды до первого ответа, от 0 до 10000

Все поля обязательны. Поле, которого нет в таблице, — ошибка; у портов нет других ключей, кроме value и summary.

Проверка описания

SF.Composition.checkFragmentDescriptor(descriptor) возвращает список проблем; пустой список означает, что описание верно. Функция ничего не исправляет и не приводит. Тот же вызов есть в модуле distr/core/js/composition/index.mjs, поэтому гейт пакета, страница и тест читают одно правило. Метка формата лежит в SF.Composition.FRAGMENT_DESCRIPTOR_SCHEMA, схема 1.2.0 — в поставке ui (distr/core/contracts/interface-fragment.v1.schema.json).

JavaScript
SF.Composition.checkFragmentDescriptor(descriptor); // []

SF.Composition.checkFragmentDescriptor({
  ...descriptor,
  regions: [],
  inputs: {removed: {value: 'files.v9', summary: 'Убрать заказ.'}},
  script: 'init.js'
});
// [
//   'script: undeclared',
//   'regions: one to eight region names',
//   'inputs.removed: unknown value type files.v9'
// ]

Гейт пакета проверяет каждое описание до выпуска. Фрагмент без верного описания на страницу не ставится.

Граница фрагмента

Фрагмент на странице оборачивают в <sf-fragment> — границу, которая не даёт сбою одного фрагмента сломать остальные:

HTML
<sf-fragment data-sf-fragment="shop.order-list" load-timeout="10000">
  <shop-order-list data-sf-endpoint="orders"></shop-order-list>
</sf-fragment>

Граница заменяет фрагмент на его месте состоянием ошибки (<sf-empty-state kind="error"> с кнопкой «Повторить»), если:

  • Smart-компонент внутри неё упал при отрисовке. Компонент сообщает об этом событием sf-component-error с detail: {phase, message, element} и атрибутом data-sf-error; ошибка по-прежнему пишется в консоль;
  • элемент фрагмента — первый пользовательский элемент внутри границы — не определился за load-timeout миллисекунд (по умолчанию 10000).

Ушедший фрагмент отключается вместе со своими маршрутами и слушателями, а остальные фрагменты и их маршруты продолжают работать. Граница получает data-sf-fragment-state="error" и отправляет sf-fragment-error с detail: {fragment, phase, message}, где fragment — значение data-sf-fragment, а phase — render или load. «Повторить» возвращает исходную разметку фрагмента, он запускается заново, и приходит sf-fragment-retry с detail: {fragment}. Заголовок ошибки берётся из языка страницы.

Состояния, доступ и адрес

  • Пять состояний. Фрагмент показывает те же состояния, что любой Smart-компонент: загрузка, есть данные, пусто, нет доступа, ошибка. Загрузка — скелетон в своей области, а не пустой ответ (Загрузка данных и скелетоны).
  • Сбой не выходит за фрагмент. Фрагмент, упавший при загрузке или отрисовке, заменяется на своём месте состоянием ошибки с повтором (Граница фрагмента).
  • Доступ до размещения. Если человеку недоступна операция operation, фрагмент не ставится на страницу или ставится в состоянии «нет доступа». Элементов управления, которые хост отклонит, он не показывает.
  • Состояние в адресе. Папка, фильтр, выбранная запись — всё, чем делятся или к чему возвращаются, живёт в адресе под ключами из url_state. Два фрагмента одной страницы не владеют одним ключом.
  • Уход со страницы. Фрагмент, ушедший при замене области (Навигация по областям страницы), отключается: его маршруты, слушатели и таймеры освобождаются.

Бюджет и версии

Бюджет из описания измеряют проверки пакета; фрагмент сверх бюджета не проходит гейт. Область приносит ресурсы своих фрагментов, а ресурс, который уже есть на странице, повторно не запрашивается.

Описание следует версиям порта. Младшая версия только добавляет необязательные выходы, входы или ключи адреса. Удаление, переименование или смена типа значения — новая старшая версия.

Смотрите также

Поиск по документации

Введите минимум 2 символа

    Настройки

    Тема
    Размер шрифта
    Ширина страницы