К содержанию

Навигация

Составные компоненты

Составной Smart-компонент собирает несколько Smart-компонентов в один сценарий. Глава описывает, как распределяется ответственность между уровнями, какие требования предъявляет стандарт и как связать компоненты без кода проекта.

Определение

Составной Smart-компонент (Composite Smart Component) объединяет несколько Smart-компонентов в один сценарий и согласует их настройки, события и состояния. Дети тоже могут быть составными. Несколько компонентов, просто стоящих рядом, составным компонентом не являются. Подключается составной компонент так же, как любой другой.

В манифесте у него kind: "composite".

Примеры: выбор даты (sf-datepicker: поле, кнопки и календарь), представление данных (таблица с панелью инструментов и меню), административное меню, редактор страницы.

Составной компонент принадлежит своему владельцу. Framework содержит универсальные составные компоненты. Составной компонент, который нужен одному продукту, — например, представление записей с панелью инструментов, — продукт хранит в своём пакете (см. Пакеты компонентов продуктов и Пакеты компонентов) и связывает с данными приложения через публичный порт Framework (см. Порт хоста). Так устроены компоненты Larena из пакета larena (см. Подключение к странице).

Ответственность уровней

Пример: заказ → карточка товара → выбор количества.

  • выбор количества сообщает новое число;
  • карточка отвечает за товар;
  • заказ связывает карточки с данными проекта.

Нижний уровень не ищет родителя через фиксированное число parentElement: такая связь ломается при изменении разметки. Он отправляет событие, родитель слушает.

Сама вложенность не требует нового уровня API для каждого контейнера: объединяйте компоненты, когда повторяется целый сценарий с общей логикой.

Пример

Три класса — заказ, карточка товара и выбор количества — зарегистрированы отдельно. Нажмите кнопку в карточке: событие change самого глубокого элемента всплывает до заказа, а заказ показывает, от какого элемента оно пришло. Код JavaScript показывает все уровни и подписку родителя.

HTML
<project-order></project-order>
<p id="order-log" role="status">Выберите количество в карточке.</p>
JavaScript
async function start() {
  if (typeof window.SF?.loadSmartBase !== 'function') {
    await new Promise((resolve, reject) => {
      const ready = () => { clearTimeout(timeout); resolve(); };
      const timeout = setTimeout(() => { window.removeEventListener('sf-loader-init', ready); reject(new Error('Loader не запустился')); }, 15000);
      window.addEventListener('sf-loader-init', ready, { once: true });
    });
  }
  await SF.loadSmartBase();
  const { SfBaseElement, html } = SF.smart;
  class ProjectQuantity extends SfBaseElement {
    static get props() { return { count: { type: Number, default: 1 } }; }
    template() {
      return html`<button type="button" @click=${() => {
        const value = this.getProp('count') + 1;
        this.setAttribute('count', String(value));
        this.dispatchEvent(new CustomEvent('change', { bubbles: true, composed: true, detail: { value } }));
      }}>Количество: ${this.getProp('count')}</button>`;
    }
  }
  class ProjectProduct extends SfBaseElement {
    template() { return html`<article class="p-2 bg-surface-container radius-2"><h3>Кружка</h3><project-quantity></project-quantity></article>`; }
  }
  class ProjectOrder extends SfBaseElement {
    template() { return html`<section aria-label="Заказ"><h2>Мой заказ</h2><project-product></project-product></section>`; }
  }
  ProjectQuantity.define('project-quantity');
  ProjectProduct.define('project-product');
  ProjectOrder.define('project-order');
  document.querySelector('project-order').addEventListener('change', event => {
    document.querySelector('#order-log').textContent = `Заказ получил событие от ${event.target.localName}: ${event.detail.value}`;
  });
}
start().catch(error => { document.querySelector('#order-log').textContent = error.message; });

Готовность составного компонента

Регистрация родителя не гарантирует готовность детей: тег каждого библиотечного компонента регистрируется, когда пришли его скрипт и общая среда Smart-компонентов. Используйте customElements.whenDefined(tag) для класса и whenRendered() конкретного элемента. Собственное условие готовности составного сценария задавайте явно.

Требования стандарта

Составной компонент соответствует стандарту simai.composite-component. Основные требования:

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

Связь компонентов без кода проекта

Составные компоненты можно связать через общего родителя декларативно: документ композиции с типом layout.scope описывает маршруты «из выхода одного компонента во вход другого». Пример связывает выбор строк в таблице заказов со входом таблицы строк заказа:

JSON
{
  "id": "workspace",
  "type": "layout.scope",
  "props": {"routes": [
    {"id": "order-lines",
     "from": {"endpoint": "orders", "port": "selection"},
     "to": {"endpoint": "lines", "port": "context"}}
  ]}
}

Что важно заметить:

  • маршрут называет только конечные точки и опубликованные порты;
  • выбор строк в одной таблице приходит во вход context другой; та входит в загрузку и просит данные у приложения намерением;
  • применяется только самый новый ответ;
  • значение маршрута имеет тип из закрытого перечня, и типы выхода и входа совпадают (Типы значений маршрутов).

Браузерная вложенность и документ композиции

Число уровней вложенности тегов в браузере и ограничения JSON-документа композиции — разные вещи. У документа свои лимиты глубины и числа узлов, а работающий браузерный класс не означает, что для него зарегистрирован декларативный тип.

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

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

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

    Настройки

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