Rules: view logic
Forms
Form rules
-
To place several objects in one table at once, the assistant SHOULD combine them into one object group using brackets.
-
In a
FORM ... ORDERSclause, the assistant MUST use only form properties that were already added to the form via aPROPERTIESblock.In
ORDERS, the assistant MUST specify either:- the form property name with its parameters, if no explicit alias was given
- the explicit form property alias, if such an alias was specified
Raw expressions, objects, or properties not added to the form MUST NOT be placed into
ORDERS. -
The assistant MUST NOT use
INPUTinside actions added directly to a form throughPROPERTIES, unless that action is used in anON CHANGEhandler: a built-in-class value is entered in the editor of the property whose change is being handled, so outside a change handler there is nowhere to render it. The exceptions are file and color values, which are entered through a separate dialog and MAY be requested from any form action.To request values from a button instead, the assistant SHOULD either place the input properties on the form itself (data properties, including local ones, in a panel) and read them in the action, or open a dialog form returning the entered values (
DIALOGwithINPUT-marked objects). -
The assistant MUST NOT display internal object identifiers on a form, including through object-valued properties: an object value is displayed as its internal identifier — a number that tells the user nothing.
Meaningful primitive or derived primitive / text properties MUST be exposed instead.
The most common case is a link to a static object of an enumeration class (
status = DATA Status (Project)): the platform does NOT substitute the static object's caption by itself. The form exposes the caption composition —captionStatus 'Status' = caption(status(p)): a write through the composition goes into the link, not into the static object's caption (see rule 7), and with a small number of options, as enumerations have, the web client shows it as a selection element (button group / list / dropdown — by the number of options and the length of the captions).A many-to-many link through a logical data property (
in = DATA BOOLEAN (Book, Tag)) SHOULD be exposed on the form as a concatenation of the views under that condition —tags 'Tags' (Book b) = GROUP CONCAT name(Tag t) IF in(b, t), ', ' ORDER name(t), t: the web client shows such a property as an element of selection of several values (separate buttons / a column of check boxes / a dropdown / a field with lookup by typed text — by the same length and option-count thresholds as above), and each toggle of an option writesTRUE/NULLintoin(b, t). The condition MUST be a single changeable logical property: a chainname(t) IF active(t) IF in(b, t), aTOPlimit or a condition without a write path leave an ordinary non-editable string. The options are all objects with a non-empty view; to offer not all objects of the class, the assistant SHOULD take the view under an additional condition in its own parentheses —GROUP CONCAT (name(Tag t) IF active(t)) IF in(b, t), ', ' ORDER name(t), t— and NOT replace the built-in editing with its ownON CHANGE, which disables the selection element.The rule concerns what the user sees. In a container with the
customattribute (a custom view on a React component, form design rule 8) the platform does not show property values but hands them to the component in theprops.dataprojection, where an object value is the numeric identifier. Object links (assignedTo(s),customer(o)) are needed there in exactly that form: by the identifier the view lays rows out into cells, matches a row with a row of another group — in a single-object group of a custom class a row'srow.keynumerically equals that object's identifier — and writes the link back throughchangeProperty. So in such a container the assistant MAY add an object-valued property to the form as is — for the component's logic, not for display; if the link is shown to the user, its caption MUST be added as a separate entry — the caption composition, as above. A property markedLSFis drawn by the platform, and the rule applies to it as usual. -
A
PANELobject of a user-defined class is NOT user-selectable by default.If such an object is meant to be chosen by the user (for example, a filter parameter shown on the form), the assistant MUST mark a displayed property of that object with
SELECTORin thePROPERTIESblock.Without
SELECTOR, the panel cell does not open a selection dialog and the object cannot be changed. The assistant MUST NOT assume a panel cell is editable by analogy with grid editing.While a
PANELobject of a user-defined class has no current value (theNULLdefault objects type or no matching object), the properties and actions that take this object are not shown on the form — except theSELECTORproperty and properties withSHOWIF; properties and actions that do not take this object (for example,NEW) stay visible as usual. So a card whose fields must all be available at once MUST be opened for an already created object (explicitly:NEW o = Book { SHOW book OBJECTS o = o; }, or by the form operatorNEWEDIT/NEWSESSION NEW, which creates the object and opens its edit form), and the assistant MUST NOT build it on an object without a value. -
In a form
PROPERTIESblock, the parameter style on the property or action being added to the form MUST match the block header:- With a common-parameter header
PROPERTIES(p1, ..., pN), each entry MUST be specified by its ID only — the common parameters are bound implicitly. WritingpropName(p1, ..., pN)after the ID is a parse error. - With no common-parameter header (just
PROPERTIES), each entry MUST carry explicit parentheses, e.g.propName(t)with parameters, orpropName()/actionName()for parameterless properties and actions. Parentheses are MANDATORY even when there are no parameters — empty parentheses MUST still be written. Writing the bare name without parentheses is a parse error. - Empty parentheses right after the keyword
(
PROPERTIES()) are a common-parameter header, not its absence: the entries of such a block are bare IDs (PROPERTIES() total, newCustomer), and only parameterless properties and actions can be added this way. An entry with parentheses in it is the same parse error as in any header block (mismatched input '(' expecting ';'), and the error does not point at the cause. The equivalent without a header isPROPERTIES total(), newCustomer().
The assistant MUST NOT mix the two styles in one block, and MUST NOT repeat the common parameters after the property name when a common-parameter header is in use.
This rule applies only to the entry being added to the form. Argument lists inside option clauses such as
ON CHANGE actionName(...),READONLYIF expr,BACKGROUND expr, etc. are regular action calls / expressions and ALWAYS use explicit parameters, regardless of the block header.An entry whose expression carries a comma not enclosed in brackets of its own needs a form this block accepts: with no common-parameter header, write it as
alias = (expr), since a bare(expr)is a parse error; withPROPERTIES(o), an entry is a property usage even afteralias =, so no expression is accepted at all and the only remedy is a named property added by its ID. - With a common-parameter header
-
The default
CHANGEhandling of a property shown on a form is derived not from what its expression looks like, but from the property's write path: changeable properties are data properties, the selection operator, and compositions of changeable properties — a write is passed through the composition into the underlying changeable property. The write path is not visible at the usage site, so the assistant MUST NOT assume that a computed-looking property is non-editable.Outwardly similar entries are edited differently:
- a composition through an object link (
name(customer(o))) — the user is offered a choice of the linked object, and the link (customer(o)) is written; this is the usual way of entering data; - an attribute of the row object itself (
name(c)) — the entered value is written in place, that is, the object is renamed; - a property of a static object (
caption(st)) — the write goes into the stored caption of the static object and lasts until the next synchronization of the database with the code (normally at server startup), which restores the caption from the code.
Every property displayed for reading only MUST be explicitly marked
READONLY— in all form contexts: not only in the grids of list forms, but also in panels, dialogs, dashboards, and charts. When the whole block is displayed for reading only, the block SHOULD be marked as a whole (PROPERTIES(o) READONLY ...) rather than every entry; marking an individual entry remains for blocks where some entries are meant for input. In browsing and selection contexts every entry carries an explicitREADONLYexcept the ones deliberately meant for input. The explicit mark also documents intent for the code reader.In a container with the
customattribute (form design rule 8) the component draws the values and decides itself what is edited: an edit goes through the controller (changeProperty). A staticREADONLYmark does not reach theprops.dataprojection — only the data-dependentreadOnlyfromREADONLYIFarrives there — but the server refuses a change to a marked property, and an edit through the controller is silently not performed. So thereREADONLYmarks the properties the view does not change, and a property the view changes through the controller MUST NOT carryREADONLY. A property markedLSFis drawn by the platform with its own editor, and the rule applies to it as usual. - a composition through an object link (
Flow rules (WAIT, NOWAIT)
-
With neither option given, the platform picks the mode itself: the operator works synchronously when the form opens in a modal location, when the form it is opened from is modal, or when the current session may still be used afterwards, and asynchronously otherwise.
Two calls that look the same can therefore behave differently. Where the code after the call depends on the form having been closed, or must run without waiting for it, the assistant MUST say so explicitly — with
WAITorNOWAITonSHOW, which is the only operator whose syntax takes them.DIALOGhas no such option: it is synchronous whenever its result is consumed, and left to the same heuristic otherwise. When a dialog must block, the assistant makes it block by using what it returns. -
In synchronous mode
DOCKEDis a tab that blocks the calling form, and from a form shown as a window such a tab is shown as a window. SoSHOW ... DOCKEDfrom a form shown as a window (FLOAT) withoutNOWAITopens a window, not a tab: such a form is modal, so the default mode there is synchronous (rule 1). To open a tab from it, the assistant MUST specifyNOWAIT.
Form design
-
These design rules do NOT cover the
DESIGNlayout model — the default container tree, the flexboxfill/ alignment model, or the container idioms. They give only placement meta-advice. Before writing or modifying anyDESIGN, the assistant MUST retrieve theForm_designdocumentation; it MUST NOT rely on these rules as if they described the layout model.The complete tables of the properties of components of every kind (containers, components of properties and actions on the form, toolbars, grids) live in the
DESIGNstatement documentation (DESIGN_statement). When setting a component property, the assistant MUST check its name and allowed values against those tables, and MUST NOT guess them by analogy. -
The assistant SHOULD specify a
DESIGNfor all interactive forms containing more than four properties. -
Exception: for a trivial form with only one or two objects in
GRIDmode and no other properties displayed inPANELmode, omittingDESIGNis acceptable. -
In
DESIGN, the assistant SHOULD prefer movingBOX(...)containers for tables first.GRID(...)SHOULD be used only when absolutely necessary. -
If possible, the assistant SHOULD avoid form designs with more than two tables stacked vertically and more than two tables placed horizontally.
-
Custom actions added to a grid form (status changes, document generation, bulk operations) MUST be given an explicit
TOOLBARview, e.g.PROPERTIES(o) confirmDoc TOOLBAR. Actions default to thePANELview, so withoutTOOLBARthe custom button is drawn as a separate group below the table instead of in the grid toolbar next to the predefinedNEW/EDIT/DELETE, to which the platform gives that sameTOOLBARview — so they share theTOOLBARcontainer with it, not theTOOLBARSYSTEMone, which is where aMOVEorREMOVEof the wrong container goes astray. The property / action views areGRID,TOOLBAR,PANEL, andPOPUP. -
A
TEXT-typed property displayed as a grid column is rendered as a multi-line row four lines tall by default, degrading list density. Such columns commonly result from the standard string properties (lpad[TEXT, INTEGER, TEXT],substr[TEXT, INTEGER, INTEGER],trim[TEXT]and the rest): they, as a rule, returnTEXTregardless of the argument classes, and concatenating aTEXToperand with a bounded string keepsTEXTas well. On list forms, the assistant SHOULD instead expose the value cast toSTRING[n]. The cast entry follows the expression-entry rules: in aPROPERTIESblock without a common-parameter header, with an explicit alias (shortNote = STRING[100](note(o))). A bare cast without an alias, like any expression inside a common-parameter header blockPROPERTIES(o), is a parse error — there, declare a named property with the cast and add it by its ID.In a panel, the assistant MUST give a
TEXT,RICHTEXTorHTMLTEXTproperty an explicit value cell size and the caption above the value (captionVertical = TRUE): by default it is a four-line box that does not grow with its content, and a caption on the left takes width away from it. Editable text gets a height suited to its expected length, in lines (charHeight) or pixels (valueHeight), with an extension coefficient (PROPERTY(comment(t)) { fill = 1; charHeight = 10; captionVertical = TRUE; }), rendered HTML and read-only text getautoSize = TRUE. -
To display data, the assistant MUST first consider the standard object group view types: the table, the pivot table with its charts (
PIVOT), the calendar (CALENDAR), the map (MAP). A custom view on a React component — aDESIGNcontainer with thecustomattribute; web client only, the desktop client renders the container's regular subtree — is used when something beyond a simple table, a simple calendar, a simple chart, or a pivot table is needed: a kanban board, a timetable, a card feed, a seating chart, drag-and-drop the standard views do not provide, a nonstandard layout or interactivity. Before creating such a view, the assistant MUST retrieve theHow-to_Custom_React_viewsdocumentation.For a form with such a container opened as a window (
FLOAT; the default location forDIALOG), the assistant MUST give the container a base size —size = (w, h)or the separatewidthandheightattributes: the window size is computed from the content at the moment of opening, when the component has not drawn anything yet, so without it a window with a single such container collapses to the caption and the system buttons, and the content drawn later pushes the OK / Close buttons past the window's edge. For a form with no tables and content of moderate height, the assistant MAY instead leave the container without a base size and setsize = (-1, -1)on the main container of the form itself: the window is then not fixed and follows the content (details inForm_design).A tab (
DOCKED) is sized by the forms window, but the base size bounds the height of the container itself there too: the assistant SHOULD give a base height (height) that fits on the form to a container whose component draws more than fits on the form — a card feed, a view withuseSeekOnScroll: without it the container stretches the form, with it the container expands into the free space by its extension coefficient (fill) and scrolls its content inside. -
FALSEis valid in the logical attributes of aDESIGNblock —defaultComponent,activatedand the like — because their values are literals, not expressions. The core rule that bansFALSEcovers expressions only, and MUST NOT be applied here by rewriting it asNULL. -
In
DESIGN, the assistant MUST use the property's name on the form inPROPERTY(...), following form rule 2:PROPERTY(number(o))afterPROPERTIES(o) number,PROPERTY(total())afterPROPERTIES() total, andPROPERTY(number)only after the name was assigned explicitly,PROPERTIES(o) number = number. A bare name without such an assignment is reported asproperty 'number' is not foundalthough the property is on the form. -
For data a form has to keep current — quotes, a queue, a monitor — the assistant MUST use the form's
SCHEDULEevent:EVENTS ON SCHEDULE PERIOD n formRefresh(). It MUST NOT poll the server from a custom React component with a timer of its own throughcontroller.changeProperty('<action>'): an action drawn withoutNOWAITgoes as a synchronous request that blocks input to the whole web client on every tick, and the component's timer keeps polling while the form is hidden — a background tab, or a form the forms-window component places nowhere. The scheduled event goes as an asynchronous request and runs only while the form is shown on screen.formRefresh[]re-reads the whole form on every run, so such a form is kept small: there is no refresh of one object group —forceUpdate[STRING]only applies the pending update of a group in manual update mode.
Navigator
- A folder whose children should appear only when the folder
is selected MUST place those children in a different window
than the folder itself (typically
WINDOW toolbar). In a horizontal toolbar such asSystem.root, a folder that keeps its children in its own window cannot switch anything — they are shown flattened next to it and selecting the folder does nothing. A vertical toolbar instead renders same-window children as a nested group under the folder, so there the separate window is not required.
Reports
-
Before designing or editing jrxml report templates, or reasoning about report structure or template naming, the assistant MUST retrieve the
Report_designdocumentation; it MUST NOT rely on these rules as a template-format or layout reference. -
When a form has no object groups independent of each other (all groups form a single dependency chain), only ONE jrxml template is created by default, named by the form's canonical name (namespace + form name, each
.replaced by_) WITHOUT a postfix — a group's only child is merged into it.The merge is what produces the single template, so it is also what the developer can switch off: the
SUBREPORToption on a child object group keeps that group out of its parent, and it then needs a template of its own with the_<group>postfix. The assistant MUST therefore read the object blocks of the form, not just its dependency shape: a linear chain with aSUBREPORTin it needs more than one template, and a missing one silently discards all of them (rule 3). -
The assistant MUST name every template exactly: the top report by the canonical form name without a postfix, and each subreport by the canonical form name plus the
_<group>postfix of its first non-empty object group. If even one template name is wrong (not found from the platform's point of view), the platform silently falls back to a fully automatic design for the WHOLE report, with no error in the logs — so a single mismatch silently discards all custom templates.
Internationalization
-
The assistant MUST use
*ResourceBundle.propertiesfiles for UI localization.The value inside
{...}MUST be treated as the lookup key that lsFusion resolves according to the current locale. -
The assistant MUST first determine whether reverse translation is used in the current project area.
If it is used, the assistant MUST continue using it in that area and MUST follow the existing project policy.
The assistant MUST keep id selection consistent with the established pattern already used there.
The assistant MUST NOT introduce a new explicit id policy unless the user requests it.
-
Reverse translation means translating in the opposite direction of normal UI localization: not
key -> localized text, butlocalized text -> key, and then, if needed, to another locale.If ids are not specified explicitly in code, this canonical value is the source-language text itself. It is what the platform LOOKS UP, not what it stores as the key: the entry stays
id = source text, and the dictionary built for the lookup is the reversed one,value -> id. An assistant writing the bundle the other way round produces entries reverse translation never matches. -
Reverse translation is turned on by the launch parameter that sets the language of lsf string literals (
logics.lsfStrLiteralsLanguage). When it is active, ANY plain'...'literal in a localizable position — including a constant literal in any expression — that matches a ResourceBundle entry value is silently replaced at code parse time with its key{id}and is substituted in the current locale at runtime:'position'can become'pozycja'. Leading and trailing spaces take no part in the match and are kept around the substitution; a literal that is empty or made of spaces alone is never replaced.The substitution also affects a literal a value is compared with in a condition or filter, including scripts run through
/evaland/exec: the comparison is made against the substituted text and, with no error at all, can silently include unintended rows or drop expected ones. The assistant MUST write a comparison value as a raw literalr'...'.Therefore the assistant MUST write technical literals — JSON keys, URLs, formats, canonical names, external identifiers — as raw literals
r'...', which take part neither in localization nor in reverse translation. Plain'...'literals are meant for user-visible text.