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

Rules: view logic

Правила: формы

Правила форм

  1. Чтобы разместить несколько объектов в одной таблице сразу, ассистенту СЛЕДУЕТ объединить их в одну группу объектов с помощью скобок.

  2. В секции FORM ... ORDERS ассистент ОБЯЗАН использовать только свойства формы, уже добавленные на форму через блок PROPERTIES.

    В ORDERS ассистент ОБЯЗАН указывать одно из:

    • имя свойства формы с его параметрами, если явный алиас не задан
    • явный алиас свойства формы, если такой алиас был указан

    Сырые выражения, объекты или свойства, не добавленные на форму, НЕ ДОЛЖНЫ помещаться в ORDERS.

  3. Ассистент НЕ ДОЛЖЕН использовать INPUT внутри действий, добавленных непосредственно на форму через PROPERTIES, если это действие не используется в обработчике ON CHANGE: значение встроенного класса вводится в редакторе свойства, изменение которого обрабатывается, поэтому вне обработчика изменения его негде отобразить. Исключение — значения файлов и цветов: они вводятся отдельным диалогом, и их МОЖНО запрашивать из любого действия формы.

    Чтобы запросить значения из кнопки, ассистенту СЛЕДУЕТ либо разместить вводимые свойства на самой форме (первичные свойства, в том числе локальные, в панели) и прочитать их в действии, либо открыть диалоговую форму, возвращающую введённые значения (DIALOG с объектами, помеченными INPUT).

  4. Ассистент НЕ ДОЛЖЕН выводить на форму внутренние идентификаторы объектов, в том числе через свойства, значениями которых являются объекты: значение-объект отображается его внутренним идентификатором — числом, ничего не говорящим пользователю.

    Вместо них ДОЛЖНЫ выводиться осмысленные примитивные или производные примитивные / текстовые свойства.

    Самый частый случай — связь со статическим объектом класса-перечисления (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 рисует платформа, и для него правило действует как обычно.

  5. Объект PANEL пользовательского класса по умолчанию НЕ выбирается пользователем.

    Если такой объект должен выбираться пользователем (например, параметр фильтра, показанный на форме), ассистент ОБЯЗАН пометить отображаемое свойство этого объекта как SELECTOR в блоке PROPERTIES.

    Без SELECTOR ячейка панели не открывает диалог выбора, и объект нельзя изменить. Ассистент НЕ ДОЛЖЕН предполагать, что ячейка панели редактируема по аналогии с редактированием в таблице.

    Пока у объекта PANEL пользовательского класса нет текущего значения (тип объектов по умолчанию NULL или ни одного подходящего объекта), свойства и действия, принимающие этот объект, на форме не показываются — кроме свойства с SELECTOR и свойств с SHOWIF; свойства и действия, не принимающие этот объект (например, NEW), видны как обычно. Поэтому карточку, все поля которой должны быть доступны сразу, ассистент ДОЛЖЕН открывать для уже созданного объекта (явно: NEW o = Book { SHOW book OBJECTS o = o; }, либо оператором формы NEWEDIT / NEWSESSION NEW, который создает объект и открывает его форму редактирования), а НЕ ДОЛЖЕН строить ее на объекте без значения.

  6. В блоке 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) запись остаётся использованием свойства и после алиас =, выражение там не принимается вовсе, и единственное средство — именованное свойство, добавленное по идентификатору.

  7. Обработка изменения 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)

  1. Если не задана ни одна опция, платформа выбирает режим сама: оператор работает синхронно, когда форма открывается в модальном месте, когда модальна форма, из которой его открывают, или когда текущая сессия может использоваться дальше, — и асинхронно в остальных случаях.

    Поэтому два одинаково выглядящих вызова могут вести себя по-разному. Там, где код после вызова зависит от того, что форма закрыта, или, наоборот, должен выполниться не дожидаясь, ассистент ОБЯЗАН сказать это явно — через WAIT или NOWAIT у SHOW, единственного оператора, синтаксис которого их принимает.

    У DIALOG такой опции нет: он синхронен, когда его результат используется, и отдан той же эвристике в остальных случаях. Когда диалог должен блокировать, ассистент добивается этого тем, что использует возвращаемое им значение.

  2. DOCKED в синхронном режиме — закладка, блокирующая вызвавшую форму, а из формы, показанной окном, такая закладка показывается окном. Поэтому SHOW ... DOCKED из формы, показанной окном (FLOAT), без NOWAIT открывает окно, а не закладку: такая форма модальна, и режим там по умолчанию синхронный (п. 1). Чтобы из неё открыть закладку, ассистент ОБЯЗАН указать NOWAIT.

Правила: дизайн форм

  1. Эти правила дизайна НЕ покрывают модель раскладки DESIGN — дерево контейнеров по умолчанию, модель flexbox fill / выравнивания или идиомы контейнеров. Они дают лишь мета-советы по размещению. Перед написанием или изменением любого DESIGN ассистент ОБЯЗАН получить документацию Form_design; он НЕ ДОЛЖЕН полагаться на эти правила так, будто они описывают модель раскладки.

    Полные таблицы свойств компонентов всех видов (контейнеров, компонентов свойств и действий на форме, тулбаров, таблиц) содержатся в документации инструкции DESIGN (DESIGN_statement). Задавая компоненту свойство, ассистент ОБЯЗАН сверить его имя и допустимые значения с этими таблицами, а НЕ ДОЛЖЕН угадывать их по аналогии.

  2. Ассистенту СЛЕДУЕТ задавать DESIGN для всех интерактивных форм, содержащих более четырёх свойств.

  3. Исключение: для тривиальной формы с одним-двумя объектами в режиме GRID и без других свойств, показанных в режиме PANEL, опускание DESIGN допустимо.

  4. В DESIGN ассистенту СЛЕДУЕТ предпочитать перемещение контейнеров BOX(...) для таблиц в первую очередь.

    GRID(...) СЛЕДУЕТ использовать только при крайней необходимости.

  5. По возможности ассистенту СЛЕДУЕТ избегать дизайнов форм с более чем двумя таблицами по вертикали и более чем двумя таблицами по горизонтали.

  6. Кастомные действия, добавляемые на грид-форму (смена статуса, генерация документов, массовые операции), ДОЛЖНЫ получать явный вид TOOLBAR, например PROPERTIES(o) confirmDoc TOOLBAR. Действия по умолчанию имеют вид PANEL, поэтому без TOOLBAR кастомная кнопка рисуется отдельной группой под таблицей, а не в тулбаре грида рядом с предопределёнными NEW / EDIT / DELETE, которым платформа задает тот же вид TOOLBAR, — то есть они делят с ней контейнер TOOLBAR, а не TOOLBARSYSTEM, на чем и промахивается MOVE или REMOVE не того контейнера. Виды свойств / действий: GRID, TOOLBAR, PANEL, POPUP.

  7. Свойство типа 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.

  8. Для отображения данных ассистент ДОЛЖЕН сначала рассматривать стандартные виды представления группы объектов: таблицу, сводную таблицу с её диаграммами (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) и прокручивает содержимое внутри себя.

  9. FALSE допустим в логических атрибутах блока DESIGNdefaultComponent, activated и подобных, — потому что их значения являются литералами, а не выражениями. Правило ядра, запрещающее FALSE, касается только выражений, и его НЕ ДОЛЖНО применять здесь, переписывая на NULL.

  10. В DESIGN ассистент ОБЯЗАН указывать в PROPERTY(...) имя свойства на форме по правилу форм 2: PROPERTY(number(o)) после PROPERTIES(o) number, PROPERTY(total()) после PROPERTIES() total и PROPERTY(number) — только после явного задания имени: PROPERTIES(o) number = number. Голое имя без такого задания отклоняется как property 'number' is not found, хотя свойство на форме есть.

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

Правила: навигатор

  1. Папка, потомки которой должны появляться только при её выборе, ОБЯЗАНА размещать этих потомков в другом окне, чем сама папка (как правило, WINDOW toolbar). В горизонтальном тулбаре, таком как System.root, папка, оставляющая потомков в своём окне, ничего не переключает — они показываются расплющенными рядом с ней, и выбор папки ничего не делает. Вертикальный тулбар, наоборот, отображает потомков из того же окна как вложенную группу под папкой, поэтому там отдельное окно не требуется.

Правила: отчёты

  1. Прежде чем проектировать или править jrxml-шаблоны отчётов либо рассуждать о структуре отчёта или именовании шаблонов, ассистент ОБЯЗАН получить документацию Report_design; он НЕ ДОЛЖЕН полагаться на эти правила как на справочник по формату шаблона или раскладке.

  2. Когда у формы нет независимых друг от друга групп объектов (все группы образуют единую цепочку зависимостей), по умолчанию создаётся ТОЛЬКО ОДИН jrxml-шаблон, называемый по каноническому имени формы (пространство имён + имя формы, каждая . заменяется на _) БЕЗ постфикса — единственный потомок группы сливается с ней.

    Единственный шаблон получается именно из-за слияния, поэтому разработчик может его и отключить: опция SUBREPORT у дочерней группы объектов не даёт слить её с родительской, и такой группе нужен собственный шаблон с постфиксом _<группа>. Поэтому ассистент ОБЯЗАН читать блоки объектов формы, а не только форму её зависимостей: линейной цепочке с SUBREPORT внутри нужен не один шаблон, а отсутствие хотя бы одного молча отбрасывает их все (правило 3).

  3. Ассистент ОБЯЗАН именовать каждый шаблон точно: верхний отчёт — по каноническому имени формы без постфикса, каждый подотчёт — по каноническому имени формы плюс постфикс _<группа> его первой непустой группы объектов. Если хоть у одного шаблона имя неверно (с точки зрения платформы не найден), платформа молча откатывается к полностью автоматическому дизайну на ВЕСЬ отчёт, без ошибки в логах, — то есть единственное несовпадение молча отбрасывает все кастомные шаблоны.

Правила: интернационализация

  1. Ассистент ОБЯЗАН использовать файлы *ResourceBundle.properties для локализации UI.

    Значение внутри {...} ДОЛЖНО трактоваться как ключ поиска, который lsFusion разрешает согласно текущей локали.

  2. Ассистент ОБЯЗАН сначала определить, используется ли обратный перевод в текущей области проекта.

    Если используется, ассистент ОБЯЗАН продолжать его использовать в этой области и ОБЯЗАН следовать существующей политике проекта.

    Ассистент ОБЯЗАН сохранять выбор идентификаторов согласованным с уже установленным там паттерном.

    Ассистент НЕ ДОЛЖЕН вводить новую явную политику идентификаторов, если этого не просит пользователь.

  3. Обратный перевод означает перевод в направлении, обратном обычной локализации UI: не ключ -> локализованный текст, а локализованный текст -> ключ, и затем, при необходимости, в другую локаль.

    Если идентификаторы не заданы в коде явно, этим каноническим значением является сам текст на исходном языке. Это то, что платформа ИЩЕТ, а не то, что она хранит ключом: запись остается id = исходный текст, а словарь, строящийся для поиска, — обратный, значение -> id. Ассистент, записавший бандл наоборот, получит записи, с которыми обратный перевод никогда не совпадет.

  4. Обратный перевод включается параметром запуска, задающим язык строковых литералов lsf-кода (logics.lsfStrLiteralsLanguage). Когда он активен, ЛЮБОЙ обычный литерал '...' в позиции, допускающей локализацию, — в том числе литерал-константа в любом выражении, — совпавший со значением записи ResourceBundle, при разборе кода молча заменяется на её ключ {id} и в рантайме подставляется в текущей локали: 'position' может стать 'pozycja'. Ведущие и конечные пробелы в сопоставлении не участвуют и сохраняются вокруг подстановки; литерал пустой или состоящий из одних пробелов не заменяется никогда.

    Подмена касается и литерала, с которым сравнивается значение в условии или фильтре, в том числе в скриптах, выполняемых через /eval и /exec: сравнение идёт с подставленным текстом и без какой-либо ошибки может молча захватить лишние строки или потерять нужные. Значение для сравнения ассистент ОБЯЗАН записывать сырым литералом r'...'.

    Поэтому технические литералы — ключи JSON, URL, форматы, канонические имена, внешние идентификаторы — ассистент ОБЯЗАН записывать сырыми литералами r'...', которые не участвуют ни в локализации, ни в обратном переводе. Обычные литералы '...' предназначены для текста, видимого пользователю.