Жизненный цикл CustomWidget
Контракт AMD-модуля
script.js обязан вызвать define() синхронно, во время своего исполнения, а фабрика должна вернуть функцию-конструктор. Конструктор вызывается через new, и у полученного экземпляра ожидается объект callbacks.
define(['jquery'], function ($) {
return function CustomWidget() {
this.callbacks = {
render: function (container) { return true },
init: function () { return true },
bind_actions: function () { return true },
initMenuPage: function (params) { return true },
settings: function ($modal) { return true },
onSave: function (settings) { return true },
destroy: function () { return true },
}
}
})Загрузчик намеренно минимален — это не полноценная реализация AMD:
- Разрешаются только имена зависимостей
'jquery'и'$', обе — в jQuery 4, который поставляет Bito. Любое другое имя разрешается вundefined— ни загрузки с CDN, ни реестра модулей. - Нет именованных модулей, нет вложенного
require(), нет кеша модулей. - Если скрипт загрузился, но
define()так и не вызвал, илиdefine()вернул не функцию, монтирование завершается карточкой ошибки. - Скрипты виджетов загружаются по одному. Единственная прослойка
defineобслуживает одну загрузку за раз, поэтому два виджета на одном экране загрузятся последовательно, а не параллельно.
Ничто из этого не является песочницей. Скрипт исполняется в origin админ-панели; см. обзор.
Колбэки
Все колбэки необязательны. Отсутствующий колбэк просто пропускается — это не ошибка.
| Колбэк | Что получает | Когда вызывается |
|---|---|---|
render | DOM-элемент, в который виджет должен отрисоваться | Первым, при монтировании |
init | — | После render |
bind_actions | — | После init |
initMenuPage | { location, item_code, subitem_code } | Только на страницах левого меню: после bind_actions, затем при каждом переключении подпункта — см. ниже |
settings | Тело модального окна настроек, обёрнутое прослойкой $ | Когда пользователь открывает настройки виджета |
onSave | Собранный объект настроек | Когда пользователь нажимает «Сохранить» в этом окне |
destroy | — | При размонтировании, всегда |
render → init → bind_actions вызываются именно в этом порядке при каждом монтировании и прерываются, если виджет по дороге отключат.
INFO
Как и в amoCRM, Bito передаёт в settings обёрнутое тело окна: $modal — это объект-коллекция, а не сырой DOM-узел, и это тот же $, в который разрешается зависимость 'jquery' вашего модуля. Поэтому $modal.find('input[name=api_token]').val() работает как написано — см. Совместимость с jQuery.
Что приходит в onSave
Bito ищет первый <form> внутри тела окна настроек и отдаёт вам его FormData в виде обычного объекта. Если в вашей разметке настроек нет <form>, вы получите {} — делайте форму настоящим <form> с именованными полями.
onSave: function (settings) {
if (!settings.api_token) {
bito.notify({ message: bito.i18n('token_required'), severity: 'error' })
return false // блокирует сохранение
}
return bito.settings.save(settings).then(function () { return true })
}initMenuPage
Вызывается только на страницах левого меню — в точке widget_page. Получает открытую страницу — тот же объект, что и bito.context.menu:
initMenuPage: function (params) {
// { location: "widget_page", item_code: "acme_crm", subitem_code: null }
// { location: "saleMain", item_code: "acme_crm", subitem_code: "order_sync" }
root.innerHTML = renderPage(params.subitem_code) // root: контейнер, полученный render()
}- Первый раз вызывается один раз, сразу после
bind_actions. - Когда пользователь открывает другой подпункт того же блока (того же
location), Bito не перемонтирует виджет: контейнер и состояние сохраняются,bito.context.menuобновляется, иinitMenuPageвызывается снова с новым объектом. Новую страницу отрисовываете вы. - Если в виджете нет
initMenuPage, Bito вместо этого перемонтирует его при каждом переключении подпункта —destroy, затемrender→init→bind_actions, — так что простой виджет, читающийbito.context.menuвrender, всё равно работает. - Переход в другой блок или уход со страниц виджета размонтирует его и вызывает
destroy. - Исключение или отклонённый промис перехватывается, логируется и засчитывается в тот же бюджет исключений (два), что и у остальных колбэков. Возвращаемое значение игнорируется, карточка ошибки для него не показывается.
Сдерживание сбоев
Каждый вызов колбэка обёрнут. Сбой вашего виджета не должен «обелять» экран ERP, поэтому:
- Колбэк, бросивший исключение, перехватывается и логируется, а вместо виджета рисуется карточка ошибки. Виджет продолжает работать.
- Второе исключение — любого колбэка, в любой момент — отключает виджет до конца этого монтирования. Дальше ничего не вызывается.
- Флаг живёт на смонтированном экземпляре. Уход со страницы и возврат монтируют виджет заново и обнуляют счётчик; он не сохраняется.
Отклонённый промис считается точно так же, как синхронное исключение: колбэки ожидаются через await.
onSave блокирует сохранение
onSave — единственный колбэк, чьё возвращаемое значение меняет поведение хоста.
Ваш onSave | Результат |
|---|---|
Вернул false | Сохранение заблокировано с понятным сообщением. В бюджет исключений это не засчитывается — возврат false это работающий документированный API, а не сбой. |
| Бросил исключение или отклонил промис | Сохранение заблокировано, и это считается исключением. |
| Выполнялся дольше 30 секунд | Сохранение заблокировано с сообщением о таймауте, и это считается исключением. |
| Вернул что-то иное или ничего | Сохранение проходит. |
Если к моменту нажатия «Сохранить» виджет уже отключён, сохранение блокируется сразу.
destroy выполняется всегда
destroy вызывается при размонтировании даже после того, как более ранний колбэк бросил исключение, и даже когда виджет уже отключён — он намеренно не зависит от флага отключения. Его собственные исключения проглатываются и логируются: уборка не должна ломаться сама о себя.
Используйте его, чтобы снять таймеры, отменить незавершённые запросы и отсоединить всё, что вы прицепили вне собственного контейнера. Больше за вами никто не убирает: ваш <link> со стилями и ваш тег <script> удаляются, но глобальные слушатели, интервалы и DOM, который вы вставили в другом месте, — ваша забота.