Rules: view logic
Правила: формы
Правила форм
-
Чтобы разместить несколько объектов в одной таблице сразу, ассистенту СЛЕДУЕТ объединить их в одну группу объектов с помощью скобок.
-
В секции
FORM ... ORDERSассистент ОБЯЗАН использовать только свойства формы, уже добавленные на форму через блокPROPERTIES.В
ORDERSассистент ОБЯЗАН указывать одно из:- имя свойства формы с его параметрами, если явный алиас не задан
- явный алиас свойства формы, если такой алиас был указан
Сырые выражения, объекты или свойства, не добавленные на форму, НЕ ДОЛЖНЫ помещаться в
ORDERS. -
Ассистент НЕ ДОЛЖЕН использовать
INPUTвнутри действий, добавленных непосредственно на форму черезPROPERTIES, если это действие не используется в обработчикеON CHANGE: значение встроенного класса вводится в редакторе свойства, изменение которого обрабатывается, поэтому вне обработчика изменения его негде отобразить. Исключение — значения файлов и цветов: они вводятся отдельным диалогом, и их МОЖНО запрашивать из любого действия формы.Чтобы запросить значения из кнопки, ассистенту СЛЕДУЕТ либо разместить вводимые свойства на самой форме (первичные свойства, в том числе локальные, в панели) и прочитать их в действии, либо открыть диалоговую форму, возвращающую введённые значения (
DIALOGс объектами, помеченнымиINPUT). -
Ассистент НЕ ДОЛЖЕН выводить на форму внутренние идентификаторы объектов, в том числе через свойства, значениями которых являются объекты: значение-объект отображается его внутренним идентификатором — числом, ничего не говорящим пользователю.
Вместо них ДОЛЖНЫ выводиться осмысленные примитивные или производные примитивные / текстовые свойства.
Самый частый случай — связь со статическим объектом класса-перечисления (
status = DATA Status (Project)): заголовок статического объекта платформа сама НЕ подставляет. На форму выводится композиция заголовка —captionStatus 'Статус' = caption(status(p)): запись через композицию уходит в связь, а не в заголовок статического объекта (см. правило 7), и при небольшом числе вариантов, как у перечислений, веб-клиент показывает её элементом выбора (группа кнопок / список / выпадающий список — по числу вариантов и длине заголовков).Связь «многие ко многим» через логическое первичное свойство (
in = DATA BOOLEAN (Book, Tag)) СЛЕДУЕТ выводить на форму сцеплением представлений при этом условии —tags 'Теги' (Book b) = GROUP CONCAT name(Tag t) IF in(b, t), ', ' ORDER name(t), t: веб-клиент показывает такое свойство элементом выбора нескольких значений (отдельные кнопки / столбец флажков / выпадающий список / поле с подбором по вводу — по тем же порогам длины и числа вариантов, что и выше), и каждое переключение варианта записываетTRUE/NULLвin(b, t). Условие ДОЛЖНО быть одним изменяемым логическим свойством: цепочкаname(t) IF active(t) IF in(b, t), ограничениеTOPили условие без пути записи оставляют обычную нередактируемую строку. Варианты — все объекты с непустым представлением; чтобы предлагать не все объекты класса, ассистенту СЛЕДУЕТ брать представление при дополнительном условии в собственных скобках —GROUP CONCAT (name(Tag t) IF active(t)) IF in(b, t), ', ' ORDER name(t), t— а НЕ заменять встроенное редактирование собственнымON CHANGE, который отключает элемент выбора.Правило касается того, что видит пользователь. В контейнере с атрибутом
custom(пользовательское представление на компоненте React, правило 8 дизайна форм) значения свойств платформа не показывает, а передаёт компоненту в проекцииprops.data, где значение-объект — числовой идентификатор. Объектные связи (assignedTo(s),customer(o)) там нужны именно в таком виде: по идентификатору представление раскладывает строки по ячейкам, сопоставляет строку со строкой другой группы — у группы с одним объектом пользовательского классаrow.keyстроки численно равен идентификатору этого объекта — и записывает связь обратно черезchangeProperty. Поэтому в таком контейнере ассистент МОЖЕТ добавлять на форму объектное свойство как есть — для логики компонента, а не для показа; если связь показывается пользователю, её заголовок ДОЛЖЕН добавляться отдельной записью — композицией заголовка, как выше. Свойство с пометкойLSFрисует платформа, и для него правило действует как обычно. -
Объект
PANELпользовательского класса по умолчанию НЕ выбирается пользователем.Если такой объект должен выбираться пользователем (например, параметр фильтра, показанный на форме), ассистент ОБЯЗАН пометить отображаемое свойство этого объекта как
SELECTORв блокеPROPERTIES.Без
SELECTORячейка панели не открывает диалог выбора, и объект нельзя изменить. Ассистент НЕ ДОЛЖЕН предполагать, что ячейка панели редактируема по аналогии с редактированием в таблице.Пока у объекта
PANELпользовательского класса нет текущего значения (тип объектов по умолчаниюNULLили ни одного подходящего объекта), свойства и действия, принимающие этот объект, на форме не показываются — кроме свойства сSELECTORи свойств сSHOWIF; свойства и действия, не принимающие этот объект (например,NEW), видны как обычно. Поэтому карточку, все поля которой должны быть доступны сразу, ассистент ДОЛЖЕН открывать для уже созданного объекта (явно:NEW o = Book { SHOW book OBJECTS o = o; }, либо оператором формыNEWEDIT/NEWSESSION NEW, который создает объект и открывает его форму редактирования), а НЕ ДОЛЖЕН строить ее на объекте без значения. -
В блоке
PROPERTIESформы стиль параметров у свойства или действия, добавляемого на форму, ДОЛЖЕН соответствовать заголовку блока:- С заголовком с общими параметрами
PROPERTIES(p1, ..., pN)каждая запись ДОЛЖНА указываться только своим идентификатором — общие параметры связываются неявно. ЗаписьpropName(p1, ..., pN)после идентификатора — это ошибка разбора. - Без заголовка с общими параметрами (просто
PROPERTIES) каждая запись ДОЛЖНА нести явные скобки, напримерpropName(t)с параметрами, либоpropName()/actionName()для свойств и действий без параметров. Скобки ОБЯЗАТЕЛЬНЫ, даже когда параметров нет — пустые скобки всё равно ДОЛЖНЫ быть записаны. Голое имя без скобок — это ошибка разбора. - Пустые скобки сразу после ключевого слова
(
PROPERTIES()) — это заголовок с общими параметрами, а не его отсутствие: записи такого блока — голые идентификаторы (PROPERTIES() total, newCustomer), и добавить так можно только свойства и действия без параметров. Запись со скобками в нём — та же ошибка разбора, что и в любом блоке с заголовком (mismatched input '(' expecting ';'), и на причину она не указывает. Эквивалент без заголовка —PROPERTIES total(), newCustomer().
Ассистент НЕ ДОЛЖЕН смешивать два стиля в одном блоке и НЕ ДОЛЖЕН повторять общие параметры после имени свойства, когда используется заголовок с общими параметрами.
Это правило применяется только к записи, добавляемой на форму. Списки аргументов внутри опций вроде
ON CHANGE actionName(...),READONLYIF expr,BACKGROUND exprи т. д. — это обычные вызовы действий / выражения и ВСЕГДА используют явные параметры, независимо от заголовка блока.Записи, чьё выражение несёт запятую, не заключённую в собственные скобки, нужна форма, которую принимает этот блок: без заголовка общих параметров её пишут как
алиас = (выражение), потому что голое(выражение)— parse error; сPROPERTIES(o)запись остаётся использованием свойства и послеалиас =, выражение там не принимается вовсе, и единственное средство — именованное свойство, добавленное по идентификатору. - С заголовком с общими параметрами
-
Обработка изменения
CHANGEпо умолчанию у свойства, показанного на форме, выводится не из вида его выражения, а из пути записи: изменяемыми являются первичные свойства, оператор выбора и композиции изменяемых свойств — запись передаётся через композицию в нижележащее изменяемое свойство. Путь записи не виден в месте использования, поэтому ассистент НЕ ДОЛЖЕН предполагать, что вычисляемое на вид свойство нередактируемо.Внешне похожие записи при этом редактируются по-разному:
- композиция через объектную связь (
name(customer(o))) — пользователю предлагается выбрать связанный объект, и записывается связь (customer(o)); это обычный способ ввода; - атрибут самого объекта строки (
name(c)) — введённое значение записывается на месте, то есть объект переименовывается; - свойство статического объекта (
caption(st)) — запись уходит в хранимый заголовок статического объекта и живёт до очередной синхронизации базы данных с кодом (обычно при старте сервера), которая возвращает заголовок из кода.
Каждое свойство, показываемое только для чтения, ДОЛЖНО быть явно помечено
READONLY— во всех контекстах формы: не только в таблицах списочных форм, но и в панелях, диалогах, дашбордах и графиках. Когда только для чтения показывается весь блок, СЛЕДУЕТ помечать блок целиком (PROPERTIES(o) READONLY ...), а не каждую запись; пометка у отдельной записи остаётся для блоков, где часть записей предназначена для ввода. В контекстах просмотра и выбора каждая запись явно помечаетсяREADONLY, кроме записей, сознательно предназначенных для ввода. Явная пометка заодно документирует намерение для читателя кода.В контейнере с атрибутом
custom(правило 8 дизайна форм) значения рисует компонент, и он же решает, что редактируется: правка идёт через контроллер (changeProperty). Статическая пометкаREADONLYв проекциюprops.dataне попадает — туда приходит только зависящий от данныхreadOnlyизREADONLYIF, — но изменение помеченного свойства сервер отклоняет, и правка через контроллер молча не выполняется. Поэтому тамREADONLYпомечаются свойства, которые представление не меняет, а свойство, которое оно меняет через контроллер,READONLYнести НЕ ДОЛЖНО. Свойство с пометкойLSFрисует платформа своим редактором, и для него правило действует как обычно. - композиция через объектную связь (
Правила потока управления (WAIT, NOWAIT)
-
Если не задана ни одна опция, платформа выбирает режим сама: оператор работает синхронно, когда форма открывается в модальном месте, когда модальна форма, из которой его открывают, или когда текущая сессия может использоваться дальше, — и асинхронно в остальных случаях.
Поэтому два одинаково выглядящих вызова могут вести себя по-разному. Там, где код после вызова зависит от того, что форма закрыта, или, наоборот, должен выполниться не дожидаясь, ассистент ОБЯЗАН сказать это явно — через
WAITилиNOWAITуSHOW, единственного оператора, синтаксис которого их принимает.У
DIALOGтакой опции нет: он синхронен, когда его результат используется, и отдан той же эвристике в остальных случаях. Когда диалог должен блокировать, ассистент добивается этого тем, что использует возвращаемое им значение. -
DOCKEDв синхронном режиме — закладка, блокирующая вызвавшую форму, а из формы, показанной окном, такая закладка показывается окном. ПоэтомуSHOW ... DOCKEDиз формы, показанной окном (FLOAT), безNOWAITоткрывает окно, а не закладку: такая форма модальна, и режим там по умолчанию синхронный (п. 1). Чтобы из неё открыть закладку, ассистент ОБЯЗАН указатьNOWAIT.
Правила: дизайн форм
-
Эти правила дизайна НЕ покрывают модель раскладки
DESIGN— дерево контейнеров по умолчанию, модель flexboxfill/ выравнивания или идиомы контейнеров. Они дают лишь мета-советы по размещению. Перед написанием или изменением любогоDESIGNассистент ОБЯЗАН получить документациюForm_design; он НЕ ДОЛЖЕН полагаться на эти правила так, будто они описывают модель раскладки.Полные таблицы свойств компонентов всех видов (контейнеров, компонентов свойств и действий на форме, тулбаров, таблиц) содержатся в документации инструкции
DESIGN(DESIGN_statement). Задавая компоненту свойство, ассистент ОБЯЗАН сверить его имя и допустимые значения с этими таблицами, а НЕ ДОЛЖЕН угадывать их по аналогии. -
Ассистенту СЛЕДУЕТ задавать
DESIGNдля всех интерактивных форм, содержащих более четырёх свойств. -
Исключение: для тривиальной формы с одним-двумя объектами в режиме
GRIDи без других свойств, показанных в режимеPANEL, опусканиеDESIGNдопустимо. -
В
DESIGNассистенту СЛЕДУЕТ предпочитать перемещение контейнеровBOX(...)для таблиц в первую очередь.GRID(...)СЛЕДУЕТ использовать только при крайней необходимости. -
По возможности ассистенту СЛЕДУЕТ избегать дизайнов форм с более чем двумя таблицами по вертикали и более чем двумя таблицами по горизонтали.
-
Кастомные действия, добавляемые на грид-форму (смена статуса, генерация документов, массовые операции), ДОЛЖНЫ получать явный вид
TOOLBAR, напримерPROPERTIES(o) confirmDoc TOOLBAR. Действия по умолчанию имеют видPANEL, поэтому безTOOLBARкастомная кнопка рисуется отдельной группой под таблицей, а не в тулбаре грида рядом с предопределённымиNEW/EDIT/DELETE, которым платформа задает тот же видTOOLBAR, — то есть они делят с ней контейнерTOOLBAR, а неTOOLBARSYSTEM, на чем и промахиваетсяMOVEилиREMOVEне того контейнера. Виды свойств / действий:GRID,TOOLBAR,PANEL,POPUP. -
Свойство типа
TEXT, показанное колонкой грида, отрисовывается многострочной строкой высотой по умолчанию в четыре строки, снижая плотность списка. Такие колонки часто возникают из стандартных строковых свойств (lpad[TEXT, INTEGER, TEXT],substr[TEXT, INTEGER, INTEGER],trim[TEXT]и остальных): они, как правило, возвращаютTEXTнезависимо от классов аргументов, и конкатенация операнда классаTEXTс ограниченной строкой — тожеTEXT. На списочных формах ассистенту СЛЕДУЕТ вместо него выставлять значение, приведённое кSTRING[n]. Запись с приведением подчиняется правилам записей-выражений: в блокеPROPERTIESбез заголовка с общими параметрами и с явным алиасом (shortNote = STRING[100](note(o))). Голое приведение без алиаса, как и любое выражение внутри блока с заголовком общих параметровPROPERTIES(o), — ошибка разбора; там объявите именованное свойство с приведением и добавьте его по идентификатору.В панели свойству класса
TEXT,RICHTEXTилиHTMLTEXTассистент ОБЯЗАН задать размер ячейки значения и заголовок сверху (captionVertical = TRUE): по умолчанию это поле в четыре строки, не растущее с содержимым, а заголовок слева сужает поле. Редактируемому тексту — высоту по ожидаемому объёму, в строках (charHeight) или пикселях (valueHeight), с коэффициентом расширения (PROPERTY(comment(t)) { fill = 1; charHeight = 10; captionVertical = TRUE; }), HTML и тексту только для чтения —autoSize = TRUE. -
Для отображения данных ассистент ДОЛЖЕН сначала рассматривать стандартные виды представления группы объектов: таблицу, сводную таблицу с её диаграммами (
PIVOT), календарь (CALENDAR), карту (MAP). Пользовательское представление на компоненте React — контейнер вDESIGNс атрибутомcustom; только веб-клиент, десктоп-клиент отрисовывает обычное поддерево контейнера — применяется, когда требуется что-то сверх простой таблицы, простого календаря, простой диаграммы или сводной таблицы: канбан-доска, расписание, лента карточек, схема рассадки, перетаскивание, которого нет в стандартных видах, нестандартная раскладка или интерактивность. Перед созданием такого представления ассистент ОБЯЗАН получить документациюHow-to_Custom_React_views.Форме с таким контейнером, открываемой окном (
FLOAT; уDIALOG— расположение по умолчанию), ассистент ОБЯЗАН задать контейнеру базовый размер —size = (w, h)либо отдельные атрибутыwidthиheight: размер окна вычисляется по содержимому в момент открытия, когда компонент ещё ничего не нарисовал, поэтому без него окно с одним таким контейнером схлопывается до заголовка и системных кнопок, а нарисованное позже содержимое вытесняет кнопки OK / Закрыть за край окна. Для формы без таблиц и с умеренной высотой содержимого ассистент МОЖЕТ вместо этого не задавать базовый размер контейнеру, а задатьsize = (-1, -1)главному контейнеру самой формы: окно тогда не фиксируется и следует за содержимым (подробности вForm_design).Закладке (
DOCKED) размер задаёт окно форм, но высоту самого контейнера базовый размер ограничивает и там: контейнеру, чей компонент рисует больше, чем помещается на форме, — ленте карточек, представлению сuseSeekOnScroll, — ассистенту СЛЕДУЕТ задать базовую высоту (height), помещающуюся на форме: без неё контейнер растягивает форму, с ней расширяется в свободное место по коэффициенту расширения (fill) и прокручивает содержимое внутри себя. -
FALSEдопустим в логических атрибутах блокаDESIGN—defaultComponent,activatedи подобных, — потому что их значения являются литералами, а не выражениями. Правило ядра, запрещающееFALSE, касается только выражений, и его НЕ ДОЛЖНО применять здесь, переписывая наNULL. -
В
DESIGNассистент ОБЯЗАН указывать вPROPERTY(...)имя свойства на форме по правилу форм 2:PROPERTY(number(o))послеPROPERTIES(o) number,PROPERTY(total())послеPROPERTIES() totalиPROPERTY(number)— только после явного задания имени:PROPERTIES(o) number = number. Голое имя без такого задания отклоняется какproperty 'number' is not found, хотя свойство на форме есть. -
Для данных, которые форма должна поддерживать актуальными, — котировки, очередь, монитор — ассистент ОБЯЗАН использовать событие
SCHEDULEформы:EVENTS ON SCHEDULE PERIOD n formRefresh(). Он НЕ ДОЛЖЕН опрашивать сервер из пользовательского компонента React собственным таймером черезcontroller.changeProperty('<действие>'): действие, выведенное безNOWAIT, идёт синхронным запросом, блокирующим ввод во всём веб-клиенте на каждом тике, а таймер компонента продолжает опрос на скрытой форме — в фоновой закладке или на форме, которую компонент окна форм никуда не поместил. Событие по расписанию идёт асинхронным запросом и выполняется, только пока форма показана на экране.formRefresh[]перечитывает всю форму на каждом запуске, поэтому такую форму держат небольшой: обновления одной группы объектов нет —forceUpdate[STRING]лишь применяет накопленное обновление группы в режиме ручного обновления.
Правила: навигатор
- Папка, потомки которой должны появляться только при её
выборе, ОБЯЗАНА размещать этих потомков в другом окне, чем
сама папка (как правило,
WINDOW toolbar). В горизонтальном тулбаре, таком какSystem.root, папка, оставляющая потомков в своём окне, ничего не переключает — они показываются расплющенными рядом с ней, и выбор папки ничего не делает. Вертикальный тулбар, наоборот, отображает потомков из того же окна как вложенную группу под папкой, поэтому там отдельное окно не требуется.
Правила: отчёты
-
Прежде чем проектировать или править jrxml-шаблоны отчётов либо рассуждать о структуре отчёта или именовании шаблонов, ассистент ОБЯЗАН получить документацию
Report_design; он НЕ ДОЛЖЕН полагаться на эти правила как на справочник по формату шаблона или раскладке. -
Когда у формы нет независимых друг от друга групп объектов (все группы образуют единую цепочку зависимостей), по умолчанию создаётся ТОЛЬКО ОДИН jrxml-шаблон, называемый по каноническому имени формы (пространство имён + имя формы, каждая
.заменяется на_) БЕЗ постфикса — единственный потомок группы сливается с ней.Единственный шаблон получается именно из-за слияния, поэтому разработчик может его и отключить: опция
SUBREPORTу дочерней группы объектов не даёт слить её с родительской, и такой группе нужен собственный шаблон с постфиксом_<группа>. Поэтому ассистент ОБЯЗАН читать блоки объектов формы, а не только форму её зависимостей: линейной цепочке сSUBREPORTвнутри нужен не один шаблон, а отсутствие хотя бы одного молча отбрасывает их все (правило 3). -
Ассистент ОБЯЗАН именовать каждый шаблон точно: верхний отчёт — по каноническому имени формы без постфикса, каждый подотчёт — по каноническому имени формы плюс постфикс
_<группа>его первой непустой группы объектов. Если хоть у одного шаблона имя неверно (с точки зрения платформы не найден), платформа молча откатывается к полностью автоматическому дизайну на ВЕСЬ отчёт, без ошибки в логах, — то есть единственное несовпадение молча отбрасывает все кастомные шаблоны.
Правила: интернационализация
-
Ассистент ОБЯЗАН использовать файлы
*ResourceBundle.propertiesдля локализации UI.Значение внутри
{...}ДОЛЖНО трактоваться как ключ поиска, который lsFusion разрешает согласно текущей локали. -
Ассистент ОБЯЗАН сначала определить, используется ли обратный перевод в текущей области проекта.
Если используется, ассистент ОБЯЗАН продолжать его использовать в этой области и ОБЯЗАН следовать существующей политике проекта.
Ассистент ОБЯЗАН сохранять выбор идентификаторов согласованным с уже установленным там паттерном.
Ассистент НЕ ДОЛЖЕН вводить новую явную политику идентификаторов, если этого не просит пользователь.
-
Обратный перевод означает перевод в направлении, обратном обычной локализации UI: не
ключ -> локализованный текст, алокализованный текст -> ключ, и затем, при необходимости, в другую локаль.Если идентификаторы не заданы в коде явно, этим каноническим значением является сам текст на исходном языке. Это то, что платформа ИЩЕТ, а не то, что она хранит ключом: запись остается
id = исходный текст, а словарь, строящийся для поиска, — обратный,значение -> id. Ассистент, записавший бандл наоборот, получит записи, с которыми обратный перевод никогда не совпадет. -
Обратный перевод включается параметром запуска, задающим язык строковых литералов lsf-кода (
logics.lsfStrLiteralsLanguage). Когда он активен, ЛЮБОЙ обычный литерал'...'в позиции, допускающей локализацию, — в том числе литерал-константа в любом выражении, — совпавший со значением записи ResourceBundle, при разборе кода молча заменяется на её ключ{id}и в рантайме подставляется в текущей локали:'position'может стать'pozycja'. Ведущие и конечные пробелы в сопоставлении не участвуют и сохраняются вокруг подстановки; литерал пустой или состоящий из одних пробелов не заменяется никогда.Подмена касается и литерала, с которым сравнивается значение в условии или фильтре, в том числе в скриптах, выполняемых через
/evalи/exec: сравнение идёт с подставленным текстом и без какой-либо ошибки может молча захватить лишние строки или потерять нужные. Значение для сравнения ассистент ОБЯЗАН записывать сырым литераломr'...'.Поэтому технические литералы — ключи JSON, URL, форматы, канонические имена, внешние идентификаторы — ассистент ОБЯЗАН записывать сырыми литералами
r'...', которые не участвуют ни в локализации, ни в обратном переводе. Обычные литералы'...'предназначены для текста, видимого пользователю.