События
Глава описывает, какие события отправляет компонент и по каким правилам. Единые правила позволяют проекту подписываться на любой компонент одинаково.
Событие сообщает, что произошло, и передаёт данные в event.detail.
Компоненту не нужно знать, какая страница его использует: проект подписывается
на событие и решает, что делать дальше. Поэтому дочерний компонент не
связывают с конкретным глобальным объектом проекта.
Три вида событий
| Вид | Имя | Пример |
|---|---|---|
| Изменение значения | change |
Выбрана дата, введён текст, переключён флажок |
| События компонента | Своё у каждого компонента | sf-popover-open, sf-page-change, sf-file-upload:select, modal:after-close, drawer:after-open, close |
| Этапы жизненного цикла | sf-<этап> |
sf-connected, sf-after-render |
У событий компонентов нет единого шаблона имени. Точное имя каждого события
записано в разделе events манифеста компонента и на его странице в
справочнике; имя не выводят по аналогии с другим компонентом.
В классе событие компонента обычно отправляется методом
emitComponentEvent('table-query-intent', detail) — базовый класс сам
добавляет префикс sf-: вызов emitComponentEvent('confirm') создаёт
событие sf-confirm. Подписка использует полное имя.
detail частых событий:
| Событие | detail |
|---|---|
change у sf-datepicker |
value (дата ГГГГ-ММ-ДД или пара [начало, конец] при range), date, isoValue, formattedValue (дата в формате format), range, format; при range ещё startValue, endValue, formattedStartValue, formattedEndValue, startIsoValue, endIsoValue |
change у sf-dropdown |
value (значение выбранного варианта), label, values (все выбранные значения), item, root, multiple |
sf-page-change у sf-pagination |
component, tagName, template, previous, current, total, pageCount, pageSize |
sf-page-size-change у sf-pagination |
component, tagName, template, previous, pageSize, current, total, pageCount |
modal:before-open, modal:after-open, modal:after-close и другие modal:* |
component и modal (сам элемент <sf-modal>), modalId, root, phase |
sf-theme-change на window |
theme, preference (см. Как выбирается тема на странице) |
Остальные — на страницах компонентов в справочнике. У sf-datepicker
прежнее имя sf-change несёт другой detail: все данные компонента, а не
состав change; подписывайтесь на change.
Пример
Счётчик хранит значение, поэтому сообщает о его изменении событием change,
как поля Framework. Класс отправляет его сам:
this.dispatchEvent(new CustomEvent('change', { bubbles: true, composed: true, detail: { value } })).
Страница подписывается через element.addEventListener('change', handler) и
показывает данные события под примером.
<project-counter count="1"></project-counter>
<p id="counter-log" role="status">Нажмите кнопку: ниже появятся данные события.</p>
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;
// The component's own words: English in `en`, Russian in `ru`.
const TEXT = {
en: { count: 'Count', add: 'Add' },
ru: { count: 'Количество', add: 'Добавить' },
};
class ProjectCounter extends SfBaseElement {
static get props() {
// A label prop defaults to empty: the English word lives in the dictionary.
return { count: { type: Number, default: 0 }, addLabel: { default: '' } };
}
templateContext() {
// Keeps component, rootClass, rootStyle and every declared prop.
return this.createPropsTemplateContext({
countText: SF.pageText(TEXT, 'count', this),
addText: this.getProp('addLabel') || SF.pageText(TEXT, 'add', this),
});
}
increment() {
const count = this.getProp('count') + 1;
this.setAttribute('count', String(count));
this.dispatchEvent(new CustomEvent('change', { bubbles: true, composed: true, detail: { value: count } }));
}
template() {
const { count, countText, addText, rootClass } = this.templateContext();
return html`<div class="flex gap-2 items-cross-center ${rootClass}">
<output aria-live="polite">${countText}: ${count}</output>
<button type="button"
class="sf-button sf-button--outline sf-button--on-surface sf-button--size-1/2"
@click=${() => this.increment()}>
<span class="sf-button-text-container">${addText}</span>
</button>
</div>`;
}
}
ProjectCounter.define('project-counter');
document.querySelector('project-counter').addEventListener('change', event => {
document.querySelector('#counter-log').textContent = JSON.stringify({ value: event.detail.value });
});
}
start().catch(error => { document.querySelector('#counter-log').textContent = error.message; });
Соглашение change
- Любой элемент формы сообщает подтверждённое изменение значения событием
changeна своём теге. - Ровно один раз на одно действие человека.
- Непрерывное действие — удержание кнопки или клавиши (автоповтор),
перетаскивание — одно действие:
changeотправляется один раз, когда оно завершилось (кнопку или клавишу отпустили). Промежуточные значения, если подписчику они нужны, сообщаются событиемinput, как у нативного<input type="range">. Егоdetailустроен так же, как уchangeтого же компонента, и содержит по меньшей мереvalue(уsf-colorоба события несут{value, name}). Для клавишиchangeуходит, когда её отпустили, — и после одиночного нажатия, и после автоповтора. - Значение, установленное из кода, — не изменение.
- Если нативный элемент внутри уже отправил
change, компонент не повторяет его — иначе подписчик получит два события. - Если компонент сам отправляет
changeсо своимdetail, событиеchangeвнутренних нативных полей он останавливает (event.stopPropagation()): иначе подписчик получит второе событие безdetail.
Для совместимости компоненты продолжают отправлять и дополнительные имена
события изменения; в манифесте они отмечены как alias_of: "change" (см. Жизненный цикл изменений).
Правила для события
- События всплывают (
bubbles,composed): родитель получает события вложенных элементов. Проверяйтеevent.target, если внутри несколько одинаковых контролов. - Не отправляйте то же событие повторно из каждого родителя — исходное и так всплывает.
- В
detail— минимальные сведения о действии. Не передавайте секреты и целый ответ сервера. Событие, отправленное черезemitComponentEvent, получает от базового класса ещёcomponent,tagNameиtemplate. Событиеchangeи другие события, которые компонент отправляет сам черезdispatchEvent, этих полей не содержат. - Копия содержимого сохраняет то, что не переносит JSON.
JSON.parse(JSON.stringify(...))молча превращаетFile,Blob,DateиMapв{}или строку. Например, намерение загрузки придёт с пустым объектом вместо файла, и проверка размера на стороне приложения пропустит всё. Копируйте простые части, а такие значения передавайте по ссылке или черезstructuredClone. - Слушатель на
documentилиwindowкомпонент хранит как функцию и снимает при отключении (см. Освобождение ресурсов). - Каждое событие объявлено в разделе
eventsманифеста; схемаdetailзаписана в его полеpayload.
Смотрите также
- Получение результата: события — подписка на события в проекте.
- Жизненный цикл — этапы жизненного цикла.
- Разделы манифеста — раздел
eventsманифеста. - Жизненный цикл изменений — совместимость имён.