Перейти к основному содержимому
Версия: 7.0

Rules: custom views

Правила: выбор пользовательского представления​

  1. Для отображения данных ассистент ОБЯЗАН сначала рассматривать стандартные виды представления группы объектов: таблицу, сводную таблицу с её диаграммами (PIVOT), календарь (CALENDAR), карту (MAP). Пользовательское представление применяется, когда требуется что-то сверх простой таблицы, простого календаря, простой диаграммы или сводной таблицы: канбан-доска, расписание, лента карточек, схема рассадки, перетаскивание, которого нет в стандартных видах, нестандартная раскладка или интерактивность.

  2. Пользовательское представление — одно из трёх, и ассистент ОБЯЗАН выбирать по тому, что оно рисует:

    • опция 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 компонента и НЕ ДОЛЖНЫ использоваться вместо тех статей.

Правила: путь данных​

  1. Пользовательское представление свойства — опция CUSTOM свойства формы или .value, которое компонент читает из props.data, — рисует ОДНО значение ОДНОГО объекта. Набор объектов ОБЯЗАН приходить на клиент группой объектов — таблицей (GRID), CUSTOM-представлением группы объектов или боксом группы, вложенным в React-контейнер (props.data.<g>.list), — и НЕ ДОЛЖЕН упаковываться в одно свойство JSON / JSONTEXT, построенное JSON FROM по классу. Такое свойство — одно значение: оно перечитывается и пересылается целиком при любом изменении данных, из которых построено, его размер ничем не ограничен, и платформа не может ни листать его (PAGESIZE, useSeekOnScroll), ни фильтровать на сервере (FILTERS и FILTERGROUP формы, фильтры setBooleanViewFilter / setDateIntervalViewFilter CUSTOM-представления), ни применить к его полям политику безопасности свойств — так что каждая колонка каждой строки попадает к каждому пользователю, которому доступно это одно свойство, какой бы ни была политика на свойствах, из которых оно построено.

    Экран из нескольких независимых списков делится на несколько групп объектов, а там, где стандартные виды не подходят, — на несколько контейнеров custom, каждый над своей группой и со своим компонентом, а не один компонент над одной выгрузкой всей базы. Группа, все строки которой доска или календарь показывают разом, получает PAGESIZE 0; список с неограниченным числом строк сохраняет страницу и следует за прокруткой через useSeekOnScroll.

    JSON FROM по объектам МОЖЕТ передаваться представлению только небольшой ограниченной выгрузкой — ряд диаграммы, сводка, последние N записей, ось доски — и тогда ОБЯЗАН быть ограничен явно: явное WHERE, ORDER ..., <объект> с TOP n, только те поля, которые представление рисует, длинный текст приведён к STRING[n], никакой почты, телефонов, свободного текста и других персональных данных, которых представление не показывает. Он НЕ ДОЛЖЕН быть основным путём данных списка, который пользователь просматривает.

  2. Значения ОБЯЗАНЫ попадать в представление отдельными свойствами формы, а изменения — возвращаться через контроллер свойство за свойством: строка группы несёт каждое свойство под его интеграционным именем, представление свойства работает со своим единственным значением (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-контейнер.

Правила: проекция и редактирование​

  1. В контейнере с атрибутом custom значения свойств платформа не показывает, а передаёт компоненту в проекции props.data, где значение-объект — числовой идентификатор. Объектные связи (assignedTo(s), customer(o)) там нужны именно в таком виде: по идентификатору представление раскладывает строки по ячейкам, сопоставляет строку со строкой другой группы — у группы с одним объектом пользовательского класса row.key строки численно равен идентификатору этого объекта — и записывает связь обратно через changeProperty. Поэтому в таком контейнере ассистент МОЖЕТ добавлять на форму объектное свойство как есть — для логики компонента, а не для показа. Если связь показывается пользователю, её заголовок ДОЛЖЕН добавляться отдельной записью — композицией заголовка из правил view (captionStatus 'Статус' = caption(status(p))). Свойство с пометкой LSF рисует платформа, и запрет показывать идентификаторы действует для него как обычно.

  2. В контейнере с атрибутом custom значения рисует компонент, и он же решает, что редактируется: правка идёт через контроллер (changeProperty). Статическая пометка READONLY в проекцию props.data не попадает — туда приходит только зависящий от данных readOnly из READONLYIF, — но изменение помеченного свойства сервер отклоняет, и правка через контроллер молча не выполняется. Поэтому там READONLY помечаются свойства, которые представление не меняет, а свойство, которое оно меняет через контроллер, READONLY нести НЕ ДОЛЖНО. Свойство с пометкой LSF рисует платформа своим редактором, и правило о READONLY из правил view действует для него как обычно.

  3. Изменение через контроллер делается в сессии изменений формы, как правка в стандартной таблице: представление видит новое значение в следующей проекции сразу, а в базу оно попадает при применении формы. Результат, который пользователь должен видеть и дальше, ОБЯЗАН быть положен в свойство формы, а не храниться только в узлах DOM или в состоянии компонента: классический update, который перестраивает элементы по новому list, а не применяет изменение пошагово через controller.diff, теряет его при следующем изменении списка.

Правила: размер контейнера​

  1. Форме с контейнером custom, открываемой окном (FLOAT; у DIALOG — расположение по умолчанию), ассистент ОБЯЗАН задать контейнеру базовый размер — size = (w, h) либо отдельные атрибуты width и height: размер окна вычисляется по содержимому в момент открытия, когда компонент ещё ничего не нарисовал, поэтому без него окно с одним таким контейнером схлопывается до заголовка и системных кнопок, а нарисованное позже содержимое вытесняет кнопки OK / Закрыть за край окна. Для формы без таблиц и с умеренной высотой содержимого ассистент МОЖЕТ вместо этого не задавать базовый размер контейнеру, а задать size = (-1, -1) главному контейнеру самой формы: окно тогда не фиксируется и следует за содержимым (подробности в Form_design).

  2. Закладке (WINDOW) размер задаёт окно форм, но высоту самого контейнера базовый размер ограничивает и там: контейнеру, чей компонент рисует больше, чем помещается на форме, — ленте карточек, представлению с useSeekOnScroll, — ассистенту СЛЕДУЕТ задать базовую высоту (height), помещающуюся на форме: без неё контейнер растягивает форму, с ней расширяется в свободное место по коэффициенту расширения (fill) и прокручивает содержимое внутри себя.

Правила: живые данные​

  1. Данные, которые форма должна поддерживать актуальными, — котировки, очередь, монитор — обновляет событие SCHEDULE формы (EVENTS ON SCHEDULE PERIOD n formRefresh(), правила view), и платформа доставляет изменившееся через props.data, как любое другое изменение. Ассистент НЕ ДОЛЖЕН опрашивать сервер из пользовательского компонента React собственным таймером через controller.changeProperty('<действие>'): действие, выведенное без NOWAIT, идёт синхронным запросом, блокирующим ввод во всём веб-клиенте на каждом тике, а таймер компонента продолжает опрос на скрытой форме — в фоновой закладке или на форме, которую компонент окна форм никуда не поместил. Событие по расписанию идёт асинхронным запросом и выполняется, только пока форма показана на экране. formRefresh[] перечитывает всю форму на каждом запуске, поэтому такую форму держат небольшой: обновления одной группы объектов нет — forceUpdate[STRING] лишь применяет накопленное обновление группы в режиме ручного обновления.

Правила: вызовы сервера​

  1. Действие, нужное представлению, СЛЕДУЕТ выводить на форму и запускать по каналу редактирования формы — changeProperty с целевой строкой, — который не ограничен шлюзом и сверх строки передаёт только значение, которое запрашивает действие с запросом значения (changeProperty(action, row, value)). Как только нужно больше — несколько значений, массив, — вызов делается через exec(...) / change(...) контроллера формы (props.controller в React-компоненте, controller.form в классическом представлении), и цель ОБЯЗАНА быть перечислена в блоке CUSTOMS формы.

  2. Ассистент ОБЯЗАН предпочитать CUSTOMS, а не @@api, для вызова, нужного пользовательскому представлению: CUSTOMS ограничивает доступ этой формой («пользователь может открыть форму» плюс явное перечисление), тогда как @@api заодно открывает действие или свойство во внешний HTTP API. @@api ставится только тогда, когда это действительно часть того API. CUSTOMS не ограничивает значения аргументов, которые передаёт компонент, поэтому каждая перечисленная запись ОБЯЗАНА оставаться безопасной при произвольном идентификаторе и произвольных значениях; eval / evalAction выполняют произвольный скрипт и остаются под шлюзом.

Правила: клиентский код​

  1. В проекте со сборкой браузерный код кладётся в src/main/web модуля логики (.js, .jsx, .ts, .tsx), каждый файл вне подпапки lib собирается в свой автоматически загружаемый бандл — lib держит общие вспомогательные модули, доступные только через импорт, — а компонент экспортируется именованным экспортом — тем именем, на которое ссылаются custom = '...' в DESIGN / CUSTOM '...'. Имя на window — лишь запасной вариант для существующих скриптов. Ассистент НЕ ДОЛЖЕН собирать свою копию React или ReactDOM: их предоставляет платформа, и импорты react / react-dom разрешаются в её сборку.

  2. Без сборки файл кладётся в src/main/resources/web: в web/init для автозагрузки — тогда он ОБЯЗАН быть независим от порядка загрузки, регистрироваться при загрузке и обращаться к другим библиотекам только при отрисовке или по событию, — либо вне его, с перечислением в onWebClientInit с целочисленным порядком, когда порядок важен (библиотека, которая должна загрузиться раньше использующего её компонента) или загрузка условна. Такой файл не может import-ировать локальные модули. .jsx преобразуется при выдаче, а файл .js пишет React.createElement через window.React.

  3. Стили: CSS-модули (Component.module.css) для собственных стилей компонента, встроенный style={{ ... }} для значений, вычисляемых из данных, и обычная глобальная таблица стилей — с префиксом пространства имён у классов — только для сторонних или намеренно глобальных стилей. Скомпилированный .css бандла загружается автоматически и НЕ ДОЛЖЕН регистрироваться в onWebClientInit второй раз.