Rules: custom views
- Правила: выбор пользовательского представления
- Правила: путь данных
- Правила: проекция и редактирование
- Правила: размер контейнера
- Правила: живые данные
- Правила: вызовы сервера
- Правила: клиентский код
Правила: выбор пользовательского представления
-
Для отображения данных ассистент ОБЯЗАН сначала рассматривать стандартные виды представления группы объектов: таблицу, сводную таблицу с её диаграммами (
PIVOT), календарь (CALENDAR), карту (MAP). Пользовательское представление применяется, когда требуется что-то сверх простой таблицы, простого календаря, простой диаграммы или сводной таблицы: канбан-доска, расписание, лента карточек, схема рассадки, перетаскивание, которого нет в стандартных видах, нестандартная раскладка или интерактивность. -
Пользовательское представление — одно из трёх, и ассистент ОБЯЗАН выбирать по тому, что оно рисует:
- опция
CUSTOMсвойства формы — функция отрисовки ОДНОГО значения ОДНОГО объекта (свой редактор, виджет для поля); CUSTOM-представление группы объектов — пара JS-функцийrender/updateнад строками ОДНОЙ группы, сdiff, фильтрами представления и размером страницы у контроллера;- контейнер в
DESIGNс атрибутомcustom— React-компонент (имя вида[A-Z][A-Za-z0-9_$]*), рисующий всё поддерево контейнера из проекцииprops.data, либо HTML-шаблон, который только размещает потомков, нарисованных платформой.
Все три — только веб-клиент: десктоп-клиент отрисовывает стандартные виды и обычное поддерево контейнера. Перед созданием React-представления ассистент ОБЯЗАН получить документацию
How-to_Custom_React_views; перед классическим —How-to_Custom_components_objectsлибоHow-to_Custom_components_properties. Эти правила не описывают API компонента и НЕ ДОЛЖНЫ использоваться вместо тех статей. - опция
Правила: путь данных
-
Пользовательское представление свойства — опция
CUSTOMсвойства формы или.value, которое компонент читает изprops.data, — рисует ОДНО значение ОДНОГО объекта. Набор объектов ОБЯЗАН приходить на клиент группой объектов — таблицей (GRID),CUSTOM-представлением группы объектов или боксом группы, вложенным в React-контейнер (props.data.<g>.list), — и НЕ ДОЛЖЕН упаковываться в одно свойствоJSON/JSONTEXT, построенноеJSON FROMпо классу. Такое свойство — одно значение: оно перечитывается и пересылается целиком при любом изменении данных, из которых построено, его размер ничем не ограничен, и платформа не может ни листать его (PAGESIZE,useSeekOnScroll), ни фильтровать на сервере (FILTERSиFILTERGROUPформы, фильтрыsetBooleanViewFilter/setDateIntervalViewFilterCUSTOM-представления), ни применить к его полям политику безопасности свойств — так что каждая колонка каждой строки попадает к каждому пользователю, которому доступно это одно свойство, какой бы ни была политика на свойствах, из которых оно построено.Экран из нескольких независимых списков делится на несколько групп объектов, а там, где стандартные виды не подходят, — на несколько контейнеров
custom, каждый над своей группой и со своим компонентом, а не один компонент над одной выгрузкой всей базы. Группа, все строки которой доска или календарь показывают разом, получаетPAGESIZE 0; список с неограниченным числом строк сохраняет страницу и следует за прокруткой черезuseSeekOnScroll.JSON FROMпо объектам МОЖЕТ передаваться представлению только небольшой ограниченной выгрузкой — ряд диаграммы, сводка, последние N записей, ось доски — и тогда ОБЯЗАН быть ограничен явно: явноеWHERE,ORDER ..., <объект>сTOP n, только те поля, которые представление рисует, длинный текст приведён кSTRING[n], никакой почты, телефонов, свободного текста и других персональных данных, которых представление не показывает. Он НЕ ДОЛЖЕН быть основным путём данных списка, который пользователь просматривает. -
Значения ОБЯЗАНЫ попадать в представление отдельными свойствами формы, а изменения — возвращаться через контроллер свойство за свойством: строка группы несёт каждое свойство под его интеграционным именем, представление свойства работает со своим единственным значением (
update(..., value)/controller.change(value)), а действие, выведенное на форму, запускается черезcontroller.changeProperty('<группа>.<действие>', row)(React-компонент) либоcontroller.form.changeProperty('<действие>')(представление свойства). Ассистент НЕ ДОЛЖЕН использовать JSON как транспорт ни в одну сторону — ниJSON FROMдля сбора нескольких значений в одну колонку, ни JSON, собранный в компоненте и разбираемый на сервере черезINPUT f = JSON DO IMPORT JSON FROM fдля диспетчеризации событий. Когда представлению свойства нужно показать несколько значений, выбран не тот вид: ассистент переходит наCUSTOM-представление группы объектов или React-контейнер.
Правила: проекция и редактирование
-
В контейнере с атрибутом
customзначения свойств платформа не показывает, а передаёт компоненту в проекцииprops.data, где значение-объект — числовой идентификатор. Объектные связи (assignedTo(s),customer(o)) там нужны именно в таком виде: по идентификатору представление раскладывает строки по ячейкам, сопоставляет строку со строкой другой группы — у группы с одним объектом пользовательского классаrow.keyстроки численно равен идентификатору этого объекта — и записывает связь обратно черезchangeProperty. Поэтому в таком контейнере ассистент МОЖЕТ добавлять на форму объектное свойство как есть — для логики компонента, а не для показа. Если связь показывается пользователю, её заголовок ДОЛЖЕН добавляться отдельной записью — композицией заголовка из правилview(captionStatus 'Статус' = caption(status(p))). Свойство с пометкойLSFрисует платформа, и запрет показывать идентификаторы действует для него как обычно. -
В контейнере с атрибутом
customзначения рисует компонент, и он же решает, что редактируется: правка идёт через контроллер (changeProperty). Статическая пометкаREADONLYв проекциюprops.dataне попадает — туда приходит только зависящий от данныхreadOnlyизREADONLYIF, — но изменение помеченного свойства сервер отклоняет, и правка через контроллер молча не выполняется. Поэтому тамREADONLYпомечаются свойства, которые представление не меняет, а свойство, которое оно меняет через контроллер,READONLYнести НЕ ДОЛЖНО. Свойство с пометкойLSFрисует платформа своим редактором, и правило оREADONLYиз правилviewдействует для него как обычно. -
Изменение через контроллер делается в сессии изменений формы, как правка в стандартной таблице: представление видит новое значение в следующей проекции сразу, а в базу оно попадает при применении формы. Результат, который пользователь должен видеть и дальше, ОБЯЗАН быть положен в свойство формы, а не храниться только в узлах DOM или в состоянии компонента: классический
update, который перестраивает элементы по новомуlist, а не применяет изменение пошагово черезcontroller.diff, теряет его при следующем изменении списка.
Правила: размер контейнера
-
Форме с контейнером
custom, открываемой окном (FLOAT; уDIALOG— расположение по умолчанию), ассистент ОБЯЗАН задать контейнеру базовый размер —size = (w, h)либо отдельные атрибутыwidthиheight: размер окна вычисляется по содержимому в момент открытия, когда компонент ещё ничего не нарисовал, поэтому без него окно с одним таким контейнером схлопывается до заголовка и системных кнопок, а нарисованное позже содержимое вытесняет кнопки OK / Закрыть за край окна. Для формы без таблиц и с умеренной высотой содержимого ассистент МОЖЕТ вместо этого не задавать базовый размер контейнеру, а задатьsize = (-1, -1)главному контейнеру самой формы: окно тогда не фиксируется и следует за содержимым (подробности вForm_design). -
Закладке (
WINDOW) размер задаёт окно форм, но высоту самого контейнера базовый размер ограничивает и там: контейнеру, чей компонент рисует больше, чем помещается на форме, — ленте карточек, представлению сuseSeekOnScroll, — ассистенту СЛЕДУЕТ задать базовую высоту (height), помещающуюся на форме: без неё контейнер растягивает форму, с ней расширяется в свободное место по коэффициенту расширения (fill) и прокручивает содержимое внутри себя.
Правила: живые данные
- Данные, которые форма должна поддерживать актуальными, —
котировки, очередь, монитор — обновляет событие
SCHEDULEформы (EVENTS ON SCHEDULE PERIOD n formRefresh(), правилаview), и платформа доставляет изменившееся черезprops.data, как любое другое изменение. Ассистент НЕ ДОЛЖЕН опрашивать сервер из пользовательского компонента React собственным таймером черезcontroller.changeProperty('<действие>'): действие, выведенное безNOWAIT, идёт синхронным запросом, блокирующим ввод во всём веб-клиенте на каждом тике, а таймер компонента продолжает опрос на скрытой форме — в фоновой закладке или на форме, которую компонент окна форм никуда не поместил. Событие по расписанию идёт асинхронным запросом и выполняется, только пока форма показана на экране.formRefresh[]перечитывает всю форму на каждом запуске, поэтому такую форму держат небольшой: обновления одной группы объектов нет —forceUpdate[STRING]лишь применяет накопленное обновление группы в режиме ручного обновления.
Правила: вызовы сервера
-
Действие, нужное представлению, СЛЕДУЕТ выводить на форму и запускать по каналу редактирования формы —
changePropertyс целевой строкой, — который не ограничен шлюзом и сверх строки передаёт только значение, которое запрашивает действие с запросом значения (changeProperty(action, row, value)). Как только нужно больше — несколько значений, массив, — вызов делается черезexec(...)/change(...)контроллера формы (props.controllerв React-компоненте,controller.formв классическом представлении), и цель ОБЯЗАНА быть перечислена в блокеCUSTOMSформы. -
Ассистент ОБЯЗАН предпочитать
CUSTOMS, а не@@api, для вызова, нужного пользовательскому представлению:CUSTOMSограничивает доступ этой формой («пользователь может открыть форму» плюс явное перечисление), тогда как@@apiзаодно открывает действие или свойство во внешний HTTP API.@@apiставится только тогда, когда это действительно часть того API.CUSTOMSне ограничивает значения аргументов, которые передаёт компонент, поэтому каждая перечисленная запись ОБЯЗАНА оставаться безопасной при произвольном идентификаторе и произвольных значениях;eval/evalActionвыполняют произвольный скрипт и остаются под шлюзом.
Правила: клиентский код
-
В проекте со сборкой браузерный код кладётся в
src/main/webмодуля логики (.js,.jsx,.ts,.tsx), каждый файл вне подпапкиlibсобирается в свой автоматически загружаемый бандл —libдержит общие вспомогательные модули, доступные только через импорт, — а компонент экспортируется именованным экспортом — тем именем, на которое ссылаютсяcustom = '...'вDESIGN/CUSTOM '...'. Имя наwindow— лишь запасной вариант для существующих скриптов. Ассистент НЕ ДОЛЖЕН собирать свою копию React или ReactDOM: их предоставляет платформа, и импортыreact/react-domразрешаются в её сборку. -
Без сборки файл кладётся в
src/main/resources/web: вweb/initдля автозагрузки — тогда он ОБЯЗАН быть независим от порядка загрузки, регистрироваться при загрузке и обращаться к другим библиотекам только при отрисовке или по событию, — либо вне его, с перечислением вonWebClientInitс целочисленным порядком, когда порядок важен (библиотека, которая должна загрузиться раньше использующего её компонента) или загрузка условна. Такой файл не можетimport-ировать локальные модули..jsxпреобразуется при выдаче, а файл.jsпишетReact.createElementчерезwindow.React. -
Стили: CSS-модули (
Component.module.css) для собственных стилей компонента, встроенныйstyle={{ ... }}для значений, вычисляемых из данных, и обычная глобальная таблица стилей — с префиксом пространства имён у классов — только для сторонних или намеренно глобальных стилей. Скомпилированный.cssбандла загружается автоматически и НЕ ДОЛЖЕН регистрироваться вonWebClientInitвторой раз.