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-компонент так же, и он рисует на их месте то, что этот компонент показывает из своей группы.
Все три — только веб-клиент: десктоп-клиент отрисовывает стандартные виды и обычное поддерево контейнера. Перед созданием 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, по одному на область экрана (правило 3), а не один компонент над одной выгрузкой всей базы. Группа, все строки которой доска или календарь показывают разом, получаетPAGESIZE 0; список с неограниченным числом строк сохраняет страницу и следует за прокруткой черезuseSeekOnScroll.JSON FROMпо объектам МОЖЕТ передаваться представлению только небольшой ограниченной выгрузкой — ряд диаграммы, сводка, последние N записей, ось доски — и тогда ОБЯЗАН быть ограничен явно: явноеWHERE,ORDER ..., <объект>сTOP n, только те поля, которые представление рисует, длинный текст приведён кSTRING[n], никакой почты, телефонов, свободного текста и других персональных данных, которых представление не показывает. Он НЕ ДОЛЖЕН быть основным путём данных списка, который пользователь просматривает. -
Значения ОБЯЗАНЫ попадать в представление отдельными свойствами формы, а изменения — возвращаться через контроллер свойство за свойством: строка группы несёт каждое свойство под его интеграционным именем, представление свойства работает со своим единственным значением (
update(..., value)/controller.change(value)), а действие, выведенное на форму, запускается через его член,controller.<группа>.<действие>.exec(row)(React-компонент), либо черезcontroller.form.exec('<действие>'), когда действие перечислено вCUSTOMSформы (представление свойства, у контроллера формы которого членов нет). Ассистент НЕ ДОЛЖЕН использовать JSON как транспорт ни в одну сторону — ниJSON FROMдля сбора нескольких значений в одну колонку, ни JSON, собранный в компоненте и разбираемый на сервере черезINPUT f = JSON DO IMPORT JSON FROM fдля диспетчеризации событий. Когда представлению свойства нужно показать несколько значений, выбран не тот вид: ассистент переходит наCUSTOM-представление группы объектов или React-контейнер. -
Страница делится на контейнеры
customпо областям — список, панель деталей, панель фильтров, корзина, — а раскладывают их обычные контейнерыDESIGN. Ассистент НЕ ДОЛЖЕН заворачивать форму целиком в один контейнерcustom(MOVE PANEL; MOVE OBJECTS): компонент контейнера перерисовывается при любом изменении своей проекции, а композицию React другой модуль изDESIGNрасширить не может. Зависимые друг от друга группы (категория → элемент → деталь) всё равно разделяются: фильтры нижестоящих групп и так следуют за текущим объектом вышестоящей. Один корневой компонент над всей страницей оправдан только для небольшой формы, которая меняется как единое целое, либо когда сама раскладка вычисляется из данных или её диктует библиотека интерфейса, и причина указана в коде. Контейнеру даётся только то, что он рисует, а часть группы проецируется туда, где стоит её компонент, — строки туда, где грид группы, панельное свойство туда, где само свойство: то, что другой контейнер показывает из текущей строки, добавляется панельным свойством под алиасом и переносится к нему (PROPERTIES(i) nameItem = name PANEL,MOVE PROPERTY(nameItem)— how-to,Страница из нескольких контейнеров), а свойство уровня формы, нужное двум контейнерам, добавляется дважды, под двумя алиасами, по одному в каждый. -
Отбор, порядок и итоги принадлежат форме, а не компоненту. Любое состояние интерфейса, от которого зависит, какие строки показаны, — выбранный раздел или закладка, категория, текст поиска, открытая запись, — ОБЯЗАНО принадлежать форме: текущим объектом группы (
<group>.change(row)), когда запись или категория выбирается из списка, а иначе свойством формы (обычноDATA LOCAL), изменяемым через контроллер, а строки ОБЯЗАНЫ отбиратьсяFILTERS/FILTERGROUPформы и упорядочиватьсяORDERSпо нему. Компонент НЕ ДОЛЖЕН фильтровать, искать, сортировать, считать или суммировать спроецированные строки и вычислять значение по двум строкам из них: количество или итог по строкам, прошедшим фильтр формы, — свойство (GROUP SUM 1 IF [ FILTER orders.o](Order o), how-to), заголовок — свойство, раздел строки в плоском списке — свойство строки. Только число уже прочитанных строк — этоlist.length. Раскладывать отобранные сервером строки по визуальным блокам по такому вычисленному сервером значению — отрисовка (bucketOfиз how-to), и она допустима. В состоянии React живёт только состояние представления — раскрытая карточка, открытая панель, черновик ввода.
Правила: проекция и редактирование
-
В контейнере с атрибутом
customзначения свойств платформа не показывает, а передаёт компоненту в проекцииprops.data, где значение-объект — числовой идентификатор. Объектные связи (assignedTo(s),customer(o)) там нужны именно в таком виде: по идентификатору представление раскладывает строки по ячейкам, сопоставляет строку со строкой другой группы — у группы с одним объектом пользовательского классаrow.keyстроки численно равен идентификатору этого объекта — и записывает связь обратно через член свойства (.change(id, row)). Поэтому в таком контейнере ассистент МОЖЕТ добавлять на форму объектное свойство как есть — для логики компонента, а не для показа. Если связь показывается пользователю, её заголовок ДОЛЖЕН добавляться отдельной записью — композицией заголовка из правилview(captionStatus 'Статус' = caption(status(p))). Свойство с пометкойNOCUSTOM, с функцией отображенияCUSTOMили сSELECT, заданными вFORM, рисует платформа, и запрет показывать идентификаторы действует для него как обычно. -
В контейнере с атрибутом
customзначения рисует компонент, и он же решает, что редактируется: правка идёт через контроллер (член свойства,.change(value[, row])). Статическая пометкаREADONLYв проекциюprops.dataне попадает — туда приходит только зависящий от данныхreadOnlyизREADONLYIF, — но изменение помеченного свойства сервер отклоняет, и правка через контроллер молча не выполняется. Поэтому тамREADONLYпомечаются свойства, которые представление не меняет, а свойство, которое оно меняет через контроллер,READONLYнести НЕ ДОЛЖНО. Свойство с пометкойNOCUSTOM, с функцией отображенияCUSTOMили сSELECT, заданными вFORM, рисует платформа своим редактором, и правило о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.<действие>.exec()): действие, выведенное безNOWAIT, идёт синхронным запросом, блокирующим ввод во всём веб-клиенте на каждом тике, а таймер компонента продолжает опрос на скрытой форме — в фоновой закладке или на форме, которую компонент окна форм никуда не поместил. Событие по расписанию идёт асинхронным запросом и выполняется, только пока форма показана на экране.formRefresh[]перечитывает всю форму на каждом запуске, поэтому такую форму держат небольшой: обновления одной группы объектов нет —forceUpdate[STRING]лишь применяет накопленное обновление группы в режиме ручного обновления. Форма, обновляемая так, читает только то, что показано: строки вне текущего раздела, поиска или выбора исключают фильтры формы (правило 4 пути данных), а не прячет компонент, и представлению СЛЕДУЕТ читать данные черезList, мемоизированные строки иuseData, чтобы изменившееся значение не перерисовывало все строки представления.
Правила: вызовы сервера
-
Действие, нужное представлению, СЛЕДУЕТ выводить на форму и запускать по каналу редактирования формы, который не ограничен шлюзом: в React-компоненте — через член действия,
controller.<группа>.<действие>.exec(row), который сверх строки ничего не передаёт, в классическом представлении группы объектов — черезchangePropertyс целевой строкой, который сверх неё передаёт только значение, запрашиваемое действием с запросом значения (changeProperty(action, row, value)). Как только нужно больше — значение из React-компонента, несколько значений, массив, — вызов делается через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второй раз.