diff --git a/.claude/skills/fix-issue/findings/mdl-executor.jsonl b/.claude/skills/fix-issue/findings/mdl-executor.jsonl index 713002bfb4..d05e03d1d6 100644 --- a/.claude/skills/fix-issue/findings/mdl-executor.jsonl +++ b/.claude/skills/fix-issue/findings/mdl-executor.jsonl @@ -735,3 +735,4 @@ {"area": "mdl/executor", "date": "2026-09-27", "symptom": "describe of a published REST service with no resources printed `create or modify published rest service M.S ( \u2026 )` followed by a bare `;`, which does not parse (`mismatched input ';' expecting '{'`); found while making describe end every statement with `;` (ako/mxcli#744).", "cause": "The resource block `{ \u2026 }` is mandatory in the grammar (publishedRestService: \u2026 RPAREN LBRACE publishedRestResource* RBRACE), but the describer only printed it when the service had resources and otherwise closed the statement with `;` alone.", "file": "mdl/executor/cmd_published_rest.go", "fix": "Always print the block: `{\\n};` when there are no resources.", "insight": "A describe test that asserts substrings never parses the output; feeding every Describe*_Mock output through the parser (assertTerminated, #744) found this at once. The empty-collection branch of a describer is the one no fixture exercises.", "test": "TestDescribePublishedRestService_Mock (assertTerminated)"} {"area": "mdl/executor", "date": "2026-09-27", "symptom": "Running an external entity's describe output (create or modify external entity, either form) turns each attribute's Rest$ODataMappedValue into a DomainModels$StoredValue (RemoteName/RemoteType/Filterable... gone), re-mints attribute $IDs on the `from odata client` form, and rewrites String(36) as unlimited", "cause": "Both rewrite paths rebuilt attributes from the AST, which has no spelling for the OData mapping; the `from odata client` path minted fresh IDs; describe external entity printed GetTypeName() (`String`) instead of formatAttributeType", "file": "mdl/executor/cmd_entities.go (carryStoredAttributeState), cmd_odata.go", "insight": "Carry per attribute by name what the statement cannot spell, in the shared merge, and enumerate every path that rebuilds attributes: there were two statements for one document kind", "refs": ["ako/mxcli#743"]} {"area": "mdl/executor", "date": "2026-09-27", "symptom": "create or modify odata service over a Studio Pro service: EntitySet PageSize 10000 -> 0, entity sets reordered, member CanBeEmpty recomputed, ExportLevel deleted, AuthenticationTypes marker 1 -> 3", "cause": "The modify path rebuilt entity types/sets from the publish block (no spelling for PageSize without paging, set order, CanBeEmpty); the model had no ExportLevel; the writer used a marker-3 list copied from the legacy writer, not Studio Pro", "file": "mdl/executor/cmd_odata.go (carryPublishedEntityState), mdl/backend/modelsdk/odata_write.go", "insight": "Parity with the legacy writer is not evidence of what Studio Pro writes; measure markers on a Studio Pro fixture. A probe that runs describe output with the verb rewritten to create or modify (mdl/roundtrip TestTestAppCreateOrModifyProbe) shows what a verb switch would lose before switching it", "refs": ["ako/mxcli#743"]} +{"area": "mdl/executor", "date": "2026-09-27", "symptom": "describe microflow printed `validation feedback $O/A message '{1} \u2026';` with no template arguments; describe -> exec stored the feedback with none", "cause": "formatAction's ValidationFeedbackAction case never read TemplateParameters, although the reader (textTemplateFromGen) and the writer both carried them", "file": "mdl/executor/cmd_microflows_format_action.go (templateArgsClause)", "insight": "A text template has two halves, the text and its parameters; check every describe case that prints one prints both. Found while switching the template form to `with ({1} = \u2026)` (ako/mxcli#751)."} diff --git a/.claude/skills/fix-issue/findings/mdl-visitor.jsonl b/.claude/skills/fix-issue/findings/mdl-visitor.jsonl index 78b509ca39..81eedde092 100644 --- a/.claude/skills/fix-issue/findings/mdl-visitor.jsonl +++ b/.claude/skills/fix-issue/findings/mdl-visitor.jsonl @@ -36,3 +36,4 @@ {"area":"mdl-visitor","date":"2026-09-24","refs":["#653"],"symptom":"describe entity on a view entity, exec'd back, was never idempotent: \"Each cycle reports `Modified view entity` and stores the query with every line after the first indented two spaces further.\" Re-exec of the SAME described file reported Unchanged, so it looked stable until you described again","cause":"The two directions did not mirror: describe (cmd_entities_describe.go, and cmd_diff_mdl.go) prefixes two spaces to every stored OQL line; the visitor stored extractOriginalText(oqlCtx), which starts at the query's first token, so line 1 lost its indentation and lines 2…n kept all of it — +2 per cycle","file":"`mdl/visitor/visitor_entity.go` (dedentOQL, leadingLineWhitespace); tests `mdl/visitor/visitor_view_entity_oql_indent_test.go`, `mdl/executor/view_entity_oql_roundtrip_test.go`; bug-test `mdl-examples/bug-tests/653-view-entity-oql-indent-drift.mdl`","insight":"**Any verbatim-source capture that starts at the first token is asymmetric**: line 1 is dedented for free, the continuation lines are not. Fix it on the way IN (exec), not by making describe emit less: strip the common leading-whitespace prefix of the non-blank lines, counting line 1 at its column when only whitespace precedes it (read it from the input stream, start.GetStart()-GetColumn()). Compare prefixes byte-wise, not by width, so a Studio Pro query indented with tabs comes back byte-identical under describe's two spaces. A round-trip test must run describe → exec at least twice AND start from stored text mxcli did not write (flat, tabs, blank lines, comments): one pass from a script is exactly how this went unnoticed. Control: stubbing dedentOQL to return raw fails every round-trip case with lines 2…n two spaces deeper; a real 11.12.1 project with the old binary printed Modified ×3 with growing indent, the fixed one Unchanged ×3. Separate, not fixed here: a comment AFTER the query's last token is outside the captured span and is dropped on exec"} {"area": "mdl/visitor", "date": "2026-09-25", "symptom": "A page action's microflow argument `Flag: true and false` was stored as the expression \"trueandfalse\", `Mode: if true then 'a' else 'b'` as \"iftruethen'a'else'b'\", and a REST call parameter `$OrderId = if $x then $id else 'none'` as \"if$xthen$idelse'none'\" (measured by decoding the units on a copy of ako/TestApp). `check -p --references` passed; describe printed the fused text back", "cause": "Four sites stored an expression as text via ANTLR's ctx.GetText(), which concatenates tokens without the hidden-channel whitespace: microflowArgV3 values (page/nanoflow call arguments), contentparams values, send-rest-request WITH parameters, and a dynamic `execute database query`. Literals, `+` and a lone $currentObject are single tokens or need no spacing, so every common case looked correct", "file": "`mdl/visitor/visitor_helpers.go` (`expressionSourceText`), `mdl/visitor/visitor_page_v3.go` (`buildMicroflowArgV3`, `buildParamAssignmentV3`), `mdl/visitor/visitor_microflow_actions.go` (dynamic query, send rest params)", "insight": "**GetText() on an expression context is always a bug** — grep `Expression().*GetText()` / `expr.GetText()` in mdl/visitor; each hit either builds the AST or must use expressionSourceText (whitespace kept, MDL comments stripped). The earlier comment-leak fix moved the six microflow sites to extractExpressionText and missed these four because they lived in page/REST code, not microflow statements: fix a text-extraction defect by searching for the call, not the feature. The tell that hid it: tests used `$currentObject` and `'a' + 'b'`, both immune; a probe with `and` / `if…then` exposed all four at once. Found while writing PROPOSAL_first_class_expressions.md §5.1. Tests `visitor_expression_source_text_test.go`", "refs": ["mendixlabs/mxcli#750"]} {"area": "mdl/visitor", "date": "2026-09-26", "refs": ["ako/mxcli#706"], "rules": ["MDL-IDX01", "MDL-ENUMDOC01"], "symptom": "Five forms parsed and `check` printed \"Syntax OK\", then the model lost or changed them: `throw 'x';` vanished from the microflow; `Amount: float` / `currency` became String(unlimited) (Void as a microflow type) and `date` became DateTime; `create association X (from A to B, type: referenceset, storage: table)` was stored as a Reference with column storage; a `/** … */` on an enumeration value and an index name (`index Idx on (…)`, `create index Idx on E (…)`) were read and written nowhere", "cause": "Three different gaps behind one symptom: throwStatement had a grammar rule and no listener (a whole statement); buildDataType/buildMicroflowDataType end in a default return (String / Void) that the FLOAT_TYPE and CURRENCY_TYPE tokens fell into, and DATE_TYPE mapped to an ast.TypeDate the writers alias to DateTime; ExitCreateAssociationStatement read only ctx.AssociationOptions(), while the parenthesised alternative puts its options directly under the statement as `(COMMA associationOption)*`. Enumeration-value docs and index names have no home in the metamodel (EnumerationValue and Index have no such property)", "file": "`mdl/visitor/visitor_silent_drops.go` (ExitThrowStatement, EnterDataType, EnterNonListDataType, rejectParenthesisedAssociation), `mdl/executor/validate_unstored_text.go` (MDL-IDX01, MDL-ENUMDOC01); tests `mdl/visitor/silent_drops_706_test.go`, `mdl/executor/validate_unstored_text_test.go`", "insight": "**Split by what is lost: refuse when the MODEL would be wrong, warn when only WORDS are lost.** throw/types/association write a different model, so they are visitor errors with the working spelling in the message; doc comments on values and index names leave a correct model, and were used in 56 places in our own examples, so they are check warnings. **Refuse from the parse tree, not by deleting the grammar alternative**: `float`, `currency`, `date` and `throw` are also keywords-as-identifiers, so removing `| FLOAT_TYPE` from dataType would let `Amount: float` re-parse as an enumeration reference named float — the same silence again. A listener on EnterDataType covers every place a type is written (attribute, parameter, return, declare, constant, service rules) in one method; checking only the direct child token keeps `M.Currency` and `enumeration(M.Float)` legal (the control). To find these: any `if … ; return ast.DataType{Kind: …}` fall-through default in a builder is a silent-substitution site, and a grammar alternative whose sub-rules differ from its sibling's (`associationOption` vs `associationOptions`) needs its own read. Sweep: run old vs new `mxcli check` over `git ls-files '*.mdl'` and diff exit codes — it found every example that relied on the dropped forms (5 files)"} +{"date": "2026-09-27", "area": "mdl/visitor", "symptom": "A member path after `*`, `div`, `mod` (`$a/X * $b/Y`) is stored as division: `describe` shows `$a/X * $b / Y`, and inside a list `filter` `$b / $currentObject/Y`. `mxcli check`, `mx check` and the build are all green; Mendix evaluates a different expression.", "cause": "`/` shares a precedence level with `*`, `div`, `mod` in `multiplicativeExpression` (MDLSettings.g4), so the left-associative chain parses `$a/X * $b/Y` as `(($a/X) * $b) / Y`. `buildMultiplicativeExpression` only turned `/ Member` into an AttributePathExpr when the WHOLE left side was a variable or path; here `$b` was already the right operand of the product, so the `/` stayed a BinaryExpr. The serializer then printed a spaced `/` and the filter's `qualifyIteratorAttributes` read the stranded `Y` as a bare attribute.", "file": "mdl/visitor/visitor_microflow_expression.go (attachPathToLastOperand)", "fix": "When a `/ Member` step cannot attach to the whole left side, attach it to the last operand of the preceding multiplicative BinaryExpr (`*`, `div`, `mod`, `%`, `:`): `(L op $b) / Y` becomes `L op ($b/Y)`. Mendix has no `/` division, so `/` followed by a member name is always navigation. Division by a parenthesised expression is left alone (control test).", "insight": "Fixing it where the AST is BUILT fixes every consumer at once (serializer, iterator qualification, MDL045's slash-division check), where the earlier #52 fix only taught MDL045 to tolerate the mis-nested tree. Test: `TestMemberPathAfterMultiplicativeOperatorIsStoredAsWritten` (flow builder, stored strings for sum, filter, declare, div chain) plus a control `TestSlashAfterNonPathIsNotMadeAMemberPath`. Revert check: stubbing the fix brings back all four wrong strings. A unary-minus branch was dropped because removing it failed nothing (`2 * -$b/Y` was already stored correctly).", "refs": ["#768", "#52"], "rules": []} diff --git a/.claude/skills/mendix/alter-page/SKILL.md b/.claude/skills/mendix/alter-page/SKILL.md index 751c31a3c0..861a4fa7a7 100644 --- a/.claude/skills/mendix/alter-page/SKILL.md +++ b/.claude/skills/mendix/alter-page/SKILL.md @@ -146,14 +146,14 @@ set (Documentation: 'What this page is for.') -- Retarget a button's on-click action. Any form `create page` accepts works -- here, including the combined ones. -set (Action: microflow Module.ACT_Other) on btnSave -set (Action: SAVE_CHANGES CLOSE_PAGE) on btnSave -set (Action: SHOW_PAGE Module.DetailPage) on btnEdit +set (Action: call microflow Module.ACT_Other) on btnSave +set (Action: SAVE CHANGES CLOSE PAGE) on btnSave +set (Action: SHOW PAGE Module.DetailPage) on btnEdit -- Retarget ONE named action slot of a pluggable widget, by the widget's own -- property key (the same key `create page` takes: `createFileAction: …`). -set ('createFileAction': microflow Module.ACT_CreateFile) on fileUploader1 -set ('onSelectionChange': show_page Module.Detail) on dgOrders +set ('createFileAction': call microflow Module.ACT_CreateFile) on fileUploader1 +set ('onSelectionChange': show page Module.Detail) on dgOrders -- Rebind a data-bound widget set (DataSource: $OrderParam) on dvOrder @@ -174,8 +174,8 @@ so a silent write would build cleanly and then fail to open. | Property | Widget Types | Value Type | Example | |----------|-------------|------------|---------| -| `Action` | Widgets with an on-click action (ACTIONBUTTON, LINKBUTTON, clickable containers) | Any `create page` action expression | `set (Action: microflow M.ACT_Go) on btnSave` | -| `''` | Pluggable widgets — any **action-typed** property (File Uploader `createFileAction`, DataGrid 2 `onSelectionChange`, …) | Any `create page` action expression | `set ('createFileAction': microflow M.ACT_Create) on fileUploader1` — refused, naming the widget's action slots, if the key is not action-typed | +| `Action` | Widgets with an on-click action (ACTIONBUTTON, LINKBUTTON, clickable containers) | Any `create page` action expression | `set (Action: call microflow M.ACT_Go) on btnSave` | +| `''` | Pluggable widgets — any **action-typed** property (File Uploader `createFileAction`, DataGrid 2 `onSelectionChange`, …) | Any `create page` action expression | `set ('createFileAction': call microflow M.ACT_Create) on fileUploader1` — refused, naming the widget's action slots, if the key is not action-typed | | `caption` | ACTIONBUTTON, LINKBUTTON | String | `set (caption: 'Submit') on btnSave` | | `content` | DYNAMICTEXT | String | `set (content: 'New Heading') on txtTitle` | | `label` | TEXTBOX, TEXTAREA, DATEPICKER, COMBOBOX, CHECKBOX, RADIOBUTTONS | String | `set (label: 'full Name') on txtName` | @@ -267,7 +267,7 @@ insert after txtName { -- Insert before a widget insert before btnSave { - actionbutton btnPreview (caption: 'Preview', action: microflow Module.ACT_Preview) + actionbutton btnPreview (caption: 'Preview', action: call microflow Module.ACT_Preview) } -- Insert INTO a container — append as its last child (works on an EMPTY container) @@ -313,8 +313,8 @@ Removes widgets and their entire subtree from the page. -- Replace a single widget with new content replace footer1 with { footer newFooter { - actionbutton btnSave (caption: 'Save', action: save_changes, buttonstyle: primary) - actionbutton btnCancel (caption: 'Cancel', action: cancel_changes) + actionbutton btnSave (caption: 'Save', action: save changes, buttonstyle: primary) + actionbutton btnCancel (caption: 'Cancel', action: cancel changes) } } ``` @@ -422,9 +422,9 @@ alter page MyModule.Customer_Edit { alter page MyModule.Customer_Edit { replace footer1 with { footer newFooter { - actionbutton btnSave (caption: 'Save', action: save_changes, buttonstyle: success) + actionbutton btnSave (caption: 'Save', action: save changes, buttonstyle: success) actionbutton btnDelete (caption: 'Delete', action: delete, buttonstyle: danger) - actionbutton btnCancel (caption: 'Cancel', action: cancel_changes) + actionbutton btnCancel (caption: 'Cancel', action: cancel changes) } } }; @@ -436,7 +436,7 @@ alter page MyModule.Customer_Edit { alter snippet MyModule.NavigationMenu { set (caption: 'Dashboard') on btnHome; insert after btnHome { - actionbutton btnReports (caption: 'Reports', action: show_page MyModule.Reports_Overview) + actionbutton btnReports (caption: 'Reports', action: show page MyModule.Reports_Overview) } }; ``` @@ -555,7 +555,7 @@ page and bind the buttons at creation time instead of rewiring afterwards: 1. **`SET` cannot rewire a button's action.** `set` accepts a fixed property list (`caption`, `class`, `visible`, …) — `action` is not on it, so - `set Action = microflow … on btnSave` is a parse error. Set the button's action + `set Action = call microflow … on btnSave` is a parse error. Set the button's action when the button is created (or `REPLACE` the button subtree). 2. **`REPLACE` cannot reuse a widget name that lives inside the subtree being diff --git a/.claude/skills/mendix/atlas-design/reference/building-blocks.md b/.claude/skills/mendix/atlas-design/reference/building-blocks.md index f903115571..ef4f318f1b 100644 --- a/.claude/skills/mendix/atlas-design/reference/building-blocks.md +++ b/.claude/skills/mendix/atlas-design/reference/building-blocks.md @@ -137,7 +137,7 @@ rebind overrides that rewrite the block's outermost datasource / first button: ```mdl use building block Atlas_Web_Content.List_Cards - (datasource: database Sales.Order, action: microflow Sales.Open) as orders_; + (datasource: database Sales.Order, action: call microflow Sales.Open) as orders_; ``` For a binding the override rule can't reach, copy the block in (`as prefix_`) and diff --git a/.claude/skills/mendix/cheatsheet-errors/SKILL.md b/.claude/skills/mendix/cheatsheet-errors/SKILL.md index 8565ba1801..03ef288d06 100644 --- a/.claude/skills/mendix/cheatsheet-errors/SKILL.md +++ b/.claude/skills/mendix/cheatsheet-errors/SKILL.md @@ -265,7 +265,7 @@ Run with `-p` for the fullest coverage. | CE0104 | Action activity is unreachable | Code after RETURN | | CE0105 | Must end with end event | Missing RETURN | | CE0117 | Error in expression | Unqualified association path | -| CE1571 | No argument selected for parameter | A microflow/nanoflow call with a parameter nothing fills — as a `datasource:` **or** an `action:`. Give it an argument (`action: nanoflow M.NF($P = $value)`), or nest the widget in a data container of the parameter's type. `check -p` reports both | +| CE1571 | No argument selected for parameter | A microflow/nanoflow call with a parameter nothing fills — as a `datasource:` **or** an `action:`. Give it an argument (`action: call nanoflow M.NF(P = $value)`), or nest the widget in a data container of the parameter's type. `check -p` reports both | | CE1571 | …in a control bar | A control bar is **not** row-scoped, so the grid's row does not fill it: pass the grid's selection (`$dgOrders`, with `Selection:` set) or move the widget into a column. `$currentObject` there is MDL-BUTTON01 | | CE1834 | The 'Page' property is required | Workflow user task without a `page` — `check` flags MDL-WF01 | | CE1876 | Single outcome must not contain activities | Single-outcome user task with a nested activity flow — `check` flags MDL-WF02 | diff --git a/.claude/skills/mendix/check-syntax/SKILL.md b/.claude/skills/mendix/check-syntax/SKILL.md index dbed9603e8..3777a7c839 100644 --- a/.claude/skills/mendix/check-syntax/SKILL.md +++ b/.claude/skills/mendix/check-syntax/SKILL.md @@ -249,14 +249,14 @@ Before writing any MDL, verify these requirements: - `retrieve $Var from Module.Entity [where condition];` - `$Result = call microflow Module.Name (Param = $value);` (NOT `set $Result = ...`) - `$Result = call nanoflow Module.Name (Param = $value);` -- `show page Module.PageName ($Param = $value);` +- `show page Module.PageName (Param = $value);` - `close page;` - `validation feedback $entity/attribute message 'message';` - `log info|warning|error [node 'name'] 'message';` - `if condition then ... [else ...] end if;` - `loop $item in $list begin ... end loop;` - `return $value;` -- `on error continue|rollback|{ handler };` +- `on error continue|rollback|[without rollback] begin handler end error;` **Now Supported (previously not):** - `rollback $entity [refresh];` - Reverts uncommitted changes diff --git a/.claude/skills/mendix/connect-rapidminer-graph/SKILL.md b/.claude/skills/mendix/connect-rapidminer-graph/SKILL.md index 8f947bc75d..adff1ad41b 100644 --- a/.claude/skills/mendix/connect-rapidminer-graph/SKILL.md +++ b/.claude/skills/mendix/connect-rapidminer-graph/SKILL.md @@ -213,7 +213,7 @@ create page MyModule.Customer_Overview ( layout: Atlas_Core.Atlas_Default ) { dynamictext heading (content: 'Customers', rendermode: H2) - actionbutton btnRefresh (caption: 'Refresh', action: microflow MyModule.ACT_RefreshCustomers, buttonstyle: primary) + actionbutton btnRefresh (caption: 'Refresh', action: call microflow MyModule.ACT_RefreshCustomers, buttonstyle: primary) datagrid gridCustomers (datasource: database MyModule.Customer sort by CustomerId asc) { column (attribute: CustomerId, caption: 'ID') column (attribute: CustomerName, caption: 'Name') diff --git a/.claude/skills/mendix/create-page/SKILL.md b/.claude/skills/mendix/create-page/SKILL.md index fb81af9fab..3f944f0807 100644 --- a/.claude/skills/mendix/create-page/SKILL.md +++ b/.claude/skills/mendix/create-page/SKILL.md @@ -70,7 +70,7 @@ Both are optional and can be changed later with `alter page … { set Class = ' | Password field | `Password: true` | `textbox tbPw (attribute: Secret, Password: true)` | | Widget validation | `Validation: ''` + `ValidationMessage: ''` | `Validation: 'length(toString($value)) > 0'` — quoted, not `[bracketed]` | | Variable binding | `datasource: $Var` | `dataview dv (datasource: $Product) { ... }` | -| Action binding | `action: type` | `actionbutton btn (caption: 'Save', action: save_changes)` | +| Action binding | `action: type` | `actionbutton btn (caption: 'Save', action: save changes)` | | Database source | `datasource: database entity` | `datagrid dg (datasource: database Module.Entity)` | | Selection binding | `datasource: selection widget` | `dataview dv (datasource: selection galleryList)` | | CSS class | `class: 'classes'` | `container c (class: 'card mx-spacing-top-large')` | @@ -200,10 +200,10 @@ create page MyModule.CustomerPage } row { column (desktopwidth: 6) { - actionbutton btnSave (caption: 'Save', action: save_changes, buttonstyle: primary) + actionbutton btnSave (caption: 'Save', action: save changes, buttonstyle: primary) } column (desktopwidth: 6) { - actionbutton btnCancel (caption: 'Cancel', action: cancel_changes) + actionbutton btnCancel (caption: 'Cancel', action: cancel changes) } } } diff --git a/.claude/skills/mendix/create-page/reference/examples.md b/.claude/skills/mendix/create-page/reference/examples.md index d700533d5a..65c1b431c3 100644 --- a/.claude/skills/mendix/create-page/reference/examples.md +++ b/.claude/skills/mendix/create-page/reference/examples.md @@ -28,8 +28,8 @@ create or replace page CRM.CustomerEdit checkbox cbActive (label: 'Active', attribute: IsActive) footer footer1 { - actionbutton btnSave (caption: 'Save', action: save_changes, buttonstyle: primary) - actionbutton btnCancel (caption: 'Cancel', action: cancel_changes) + actionbutton btnSave (caption: 'Save', action: save changes, buttonstyle: primary) + actionbutton btnCancel (caption: 'Cancel', action: cancel changes) } } } @@ -97,8 +97,8 @@ create page CRM.Customer_MasterDetail textbox txtPhone (label: 'Phone', attribute: Phone) footer footer1 { - actionbutton btnSave (caption: 'Save', action: save_changes, buttonstyle: primary) - actionbutton btnCancel (caption: 'Cancel', action: cancel_changes) + actionbutton btnSave (caption: 'Save', action: save changes, buttonstyle: primary) + actionbutton btnCancel (caption: 'Cancel', action: cancel changes) } } } diff --git a/.claude/skills/mendix/create-page/reference/widgets.md b/.claude/skills/mendix/create-page/reference/widgets.md index a2a3059599..8cfd2ee1b4 100644 --- a/.claude/skills/mendix/create-page/reference/widgets.md +++ b/.claude/skills/mendix/create-page/reference/widgets.md @@ -117,30 +117,28 @@ describe icon collection Atlas_Core.Atlas_Filled -- every icon + its reference **Action Bindings:** - `action: nothing` - Deliberately no action (a decorative button, a card that is not clickable) -- `action: save_changes` - Save changes to object -- `action: save_changes close_page` - Save and close page -- `action: cancel_changes` - Cancel changes -- `action: close_page` - Close the page +- `action: save changes` - Save changes to object +- `action: save changes close page` - Save and close page +- `action: cancel changes` - Cancel changes +- `action: close page` - Close the page - `action: delete` - Delete object -- `action: microflow Module.MicroflowName` - Call microflow -- `action: microflow Module.MicroflowName(Param: $value)` - Call microflow with parameters -- `action: nanoflow Module.NanoflowName` - Call nanoflow (client-side) -- `action: nanoflow Module.NanoflowName(Param: $value)` - Call nanoflow with parameters -- `action: nanoflow Module.NanoflowName($Param = $value)` - Also accepted (microflow-style) -- `action: open_link 'https://example.com'` - Open a fixed web address -- `action: open_link $currentObject/URL` - Open the address held in an attribute of the enclosing data container's object (inside a data container only; not over an association) +- `action: call microflow Module.MicroflowName` - Call microflow +- `action: call microflow Module.MicroflowName(Param = $value)` - Call microflow with parameters +- `action: call nanoflow Module.NanoflowName` - Call nanoflow (client-side) +- `action: call nanoflow Module.NanoflowName(Param = $value)` - Call nanoflow with parameters +- `action: open link 'https://example.com'` - Open a fixed web address +- `action: open link $currentObject/URL` - Open the address held in an attribute of the enclosing data container's object (inside a data container only; not over an association) - **Every parameter needs an argument, or an enclosing data container of its type.** A flow called with a parameter nothing fills is **CE1571**; `mxcli check -p` reports it. This is the same on every widget that takes an action, a clickable `container` included. -- `action: show_page Module.PageName` - Navigate to page -- `action: show_page Module.PageName(Param: $value)` - Navigate with parameters -- `action: show_page Module.PageName($Param = $value)` - Also accepted (microflow-style) -- `action: create_object Module.Entity then show_page Module.PageName` - Create and navigate -- **A `show_page` argument must be the context object, and there has to BE one.** +- `action: show page Module.PageName` - Navigate to page +- `action: show page Module.PageName(Param = $value)` - Navigate with parameters +- `action: create object Module.Entity then show page Module.PageName` - Create and navigate +- **A `show page` argument must be the context object, and there has to BE one.** Mendix takes the page argument from the enclosing data widget, so the only spellings that mean anything are `$currentObject` or the name of the variable - that widget is bound to (`datasource: $Customer` → `(Customer: $Customer)` is + that widget is bound to (`datasource: $Customer` → `(Customer = $Customer)` is fine). Naming any other variable is refused as **MDL-PAGEARG01** — it used to be accepted and silently opened the page with the context object anyway. - **Outside a data widget the same rule leaves nothing at all**, so a button sitting @@ -150,11 +148,11 @@ describe icon collection Atlas_Core.Atlas_Filled -- every icon + its reference mxbuild reports **CE1571** per parameter of the target page, and a page whose parameters are optional would simply show the wrong data. MDL-PAGEARG01 refuses that too (mendixlabs/mxcli#1029). To open a parameterised page from such a - button, call a microflow that does `show page Module.Page(Param: $value)` — + button, call a microflow that does `show page Module.Page(Param = $value)` — that path wires the arguments properly. - **The list above is the whole vocabulary, and a keyword without its argument is - not in it.** `action: open_link` with no URL, `action: show_page` with no page, - `action: microflow` with no name — each is **MDL-WIDGET28**. Until + not in it.** `action: open link` with no URL, `action: show page` with no page, + `action: call microflow` with no name — each is **MDL-WIDGET28**. Until mendixlabs/mxcli#1062 these were written as a widget with *no action at all*: it rendered, carried its caption, and did nothing, while `mxcli check`, `exec` and mxbuild all reported success, because a no-action widget is legal Mendix. @@ -170,22 +168,22 @@ describe icon collection Atlas_Core.Atlas_Filled -- every icon + its reference **Examples:** ```sql -- Save with style -actionbutton btnSave (caption: 'Save', action: save_changes, buttonstyle: primary) +actionbutton btnSave (caption: 'Save', action: save changes, buttonstyle: primary) -- Navigate with parameter (inside DATAVIEW) -actionbutton btnEdit (caption: 'Edit', action: show_page Module.EditPage(Product: $Product)) +actionbutton btnEdit (caption: 'Edit', action: show page Module.EditPage(Product = $Product)) -- Navigate with $currentObject (inside DATAGRID column) -actionbutton btnEdit (caption: 'Edit', action: show_page Module.EditPage(Product: $currentObject)) +actionbutton btnEdit (caption: 'Edit', action: show page Module.EditPage(Product = $currentObject)) -- Call microflow with page/dataview parameter -actionbutton btnProcess (caption: 'Process', action: microflow Module.ACT_Process(Order: $Order), buttonstyle: success) +actionbutton btnProcess (caption: 'Process', action: call microflow Module.ACT_Process(Order = $Order), buttonstyle: success) -- Call microflow with $currentObject (inside DATAGRID/LISTVIEW column) -actionbutton btnDelete (caption: 'Delete', action: microflow Module.ACT_Delete(Target: $currentObject), buttonstyle: danger) +actionbutton btnDelete (caption: 'Delete', action: call microflow Module.ACT_Delete(Target = $currentObject), buttonstyle: danger) -- Create object and show page -actionbutton btnNew (caption: 'New', action: create_object Module.Product then show_page Module.Product_Edit, buttonstyle: primary) +actionbutton btnNew (caption: 'New', action: create object Module.Product then show page Module.Product_Edit, buttonstyle: primary) ``` **Using `$currentObject`:** @@ -368,7 +366,7 @@ correctly on the default engine (mxbuild-verified, 0 errors) — an earlier CE04 ```sql column (caption: 'Actions') { - actionbutton btnView (caption: 'View', action: close_page) + actionbutton btnView (caption: 'View', action: close page) } ``` @@ -448,8 +446,8 @@ dataview dvName (datasource: $VariableName) { textarea txtDescription (label: 'Description', attribute: description) footer footer1 { - actionbutton btnSave (caption: 'Save', action: save_changes, buttonstyle: primary) - actionbutton btnCancel (caption: 'Cancel', action: cancel_changes) + actionbutton btnSave (caption: 'Save', action: save changes, buttonstyle: primary) + actionbutton btnCancel (caption: 'Cancel', action: cancel changes) } } ``` @@ -508,8 +506,8 @@ dataview dataView1 (datasource: $Customer) { datepicker dpCreated (label: 'Created', attribute: CreateDate) footer footer1 { - actionbutton btnSave (caption: 'Save', action: save_changes, buttonstyle: primary) - actionbutton btnCancel (caption: 'Cancel', action: cancel_changes) + actionbutton btnSave (caption: 'Save', action: save changes, buttonstyle: primary) + actionbutton btnCancel (caption: 'Cancel', action: cancel changes) } } ``` @@ -645,9 +643,9 @@ Create a menu with action items: ```sql navigationlist navName { - item itemEdit (caption: 'Edit', action: show_page Module.EditPage(entity: $EntityParameter)) + item itemEdit (caption: 'Edit', action: show page Module.EditPage(entity = $EntityParameter)) item itemDelete (caption: 'Delete', action: delete) - item itemBack (caption: 'Back', action: close_page) + item itemBack (caption: 'Back', action: close page) } ``` @@ -843,7 +841,7 @@ conditional visibility — or put the condition in the microflow it calls: ```sql actionbutton btnSubmit ( - caption: 'Submit', action: microflow Mod.ACT_Submit, + caption: 'Submit', action: call microflow Mod.ACT_Submit, visible: [$currentObject/Status = Mod.Status.Draft] ) ``` @@ -885,13 +883,13 @@ Two shapes, two remedies: ```sql -- WRONG: silently does nothing -dataview dvOrder (datasource: microflow Mod.DS_Order, onclick: show_page Mod.Detail) { +dataview dvOrder (datasource: microflow Mod.DS_Order, onclick: show page Mod.Detail) { dynamictext t (content: 'Open') } -- RIGHT: the container carries the click dataview dvOrder (datasource: microflow Mod.DS_Order) { - container clickable (onclick: show_page Mod.Detail) { + container clickable (onclick: show page Mod.Detail) { dynamictext t (content: 'Open') } } @@ -927,10 +925,10 @@ customcontainer outer1 (class: 'section') { **Clickable container (On click action).** A container can trigger an action when clicked — use `OnClick:` (or the equivalent `Action:` keyword) with any client -action (`microflow`, `nanoflow`, `show_page`, `save_changes`, …): +action (`microflow`, `nanoflow`, `show page`, `save changes`, …): ```sql -container card1 (OnClick: microflow MyModule.ACT_OpenDetails, class: 'clickable-card') { +container card1 (OnClick: call microflow MyModule.ACT_OpenDetails, class: 'clickable-card') { dynamictext title (content: 'Open details') } ``` @@ -947,7 +945,7 @@ as an `actionbutton`'s `action:`: ```sql -- Rich, parameterised trigger: a card that opens the object it represents -container tileCard (OnClick: microflow MyModule.ACT_Open(Item: $currentObject), class: 'tile') { +container tileCard (OnClick: call microflow MyModule.ACT_Open(Item = $currentObject), class: 'tile') { dynamictext tileValue (content: '4') dynamictext tileLabel (content: '4 LEFT', class: 'tile-label') } @@ -964,8 +962,8 @@ Container for form action buttons: ```sql footer footerName { - actionbutton btnSave (caption: 'Save', action: save_changes, buttonstyle: primary) - actionbutton btnCancel (caption: 'Cancel', action: cancel_changes) + actionbutton btnSave (caption: 'Save', action: save changes, buttonstyle: primary) + actionbutton btnCancel (caption: 'Cancel', action: cancel changes) } ``` @@ -985,7 +983,7 @@ Control bar for data widgets: ```sql controlbar { - actionbutton btnNew (caption: 'New', action: create_object Module.Entity then show_page Module.EditPage, buttonstyle: primary) + actionbutton btnNew (caption: 'New', action: create object Module.Entity then show page Module.EditPage, buttonstyle: primary) } ``` @@ -1004,7 +1002,7 @@ datagrid dgMaterials ( -- Row-scoped: the grid's row supplies the parameter, no argument needed. column (caption: 'Row') { - container cRowUnlink (action: nanoflow Module.ACT_UnLink, class: 'command') { + container cRowUnlink (action: call nanoflow Module.ACT_UnLink, class: 'command') { actionbutton btnRowUnlink (caption: 'Unlink') } } @@ -1013,7 +1011,7 @@ datagrid dgMaterials ( -- Not row-scoped: pass the selection explicitly. container cUnlink ( class: 'command', - action: nanoflow Module.ACT_UnLink ($Material = $dgMaterials) + action: call nanoflow Module.ACT_UnLink (Material = $dgMaterials) ) { actionbutton btnUnlink (caption: 'Unlink') } diff --git a/.claude/skills/mendix/custom-widgets/SKILL.md b/.claude/skills/mendix/custom-widgets/SKILL.md index 192b7c0465..bf0bd33717 100644 --- a/.claude/skills/mendix/custom-widgets/SKILL.md +++ b/.claude/skills/mendix/custom-widgets/SKILL.md @@ -368,8 +368,8 @@ object-list item mappings use: overlap with `dataSourceExprV3` and the datasource alternative has to win, or a chart series' `staticDataSource: microflow M.X` would become an action. The executor converts them, because the widget definition is the only layer that -knows the slot is action-typed. Every other action form (`show_page`, -`save_changes`, …) reaches the AST as an action directly. +knows the slot is action-typed. Every other action form (`show page`, +`save changes`, …) reaches the AST as an action directly. **A slot may be conditional, and writing into a pruned one is CE0463.** DataGrid 2's `onSelectionChange` is *hidden when `itemSelection` = None*, so it needs @@ -477,7 +477,7 @@ widgets take a different, simpler path than the MPR writer: selection, datasource, widgets (child slots), object lists, expression, texttemplate (including `{AttrName}` placeholders and `Params` / `contentparams` bindings -> template parameters), - and action (`microflow Module.Flow`, `show_page Module.Page`, or none). + and action (`microflow Module.Flow`, `show page Module.Page`, or none). - **Rejected loudly** (widget refused, nothing sent): actions *with argument mappings*, other action kinds (save/cancel/close/delete/create/open-link/ nanoflow), and any operation the MCP builder does not translate. The error diff --git a/.claude/skills/mendix/fragments/SKILL.md b/.claude/skills/mendix/fragments/SKILL.md index 2846dff2e4..5e592cf509 100644 --- a/.claude/skills/mendix/fragments/SKILL.md +++ b/.claude/skills/mendix/fragments/SKILL.md @@ -29,8 +29,8 @@ Fragments are **script-scoped, transient** widget groups: ```mdl define fragment SaveCancelFooter as { footer footer1 { - actionbutton btnSave (caption: 'Save', action: save_changes, buttonstyle: primary) - actionbutton btnCancel (caption: 'Cancel', action: cancel_changes) + actionbutton btnSave (caption: 'Save', action: save changes, buttonstyle: primary) + actionbutton btnCancel (caption: 'Cancel', action: cancel changes) } }; ``` @@ -135,7 +135,7 @@ create page Module.Orders (title: 'Orders', layout: Atlas_Core.Atlas_Default) { ``` Rules: -- Param kinds are `datasource` (`$var` / `database E` / `$currentObject/Assoc` / `microflow M`) and `action` (a microflow / nanoflow / `save_changes` / `show_page` / …). +- Param kinds are `datasource` (`$var` / `database E` / `$currentObject/Assoc` / `microflow M`) and `action` (a microflow / nanoflow / `save changes` / `show page` / …). - Every declared parameter must be supplied; unknown args and type mismatches are errors. - Values substitute at expansion — `describe page` shows the concrete datasource/action, no `$param`. @@ -145,7 +145,7 @@ outermost datasource and/or its first button after the copy: ```mdl use building block Atlas_Web_Content.List_Cards - (datasource: database Module.Order, action: microflow Module.OpenOrder) as orders_; + (datasource: database Module.Order, action: call microflow Module.OpenOrder) as orders_; ``` Binding-point rule (prototype): datasource → the first widget carrying a @@ -173,8 +173,8 @@ describe fragment SaveCancelFooter; ```mdl define fragment CrudFooter as { footer footer1 { - actionbutton btnSave (caption: 'Save', action: save_changes, buttonstyle: primary) - actionbutton btnCancel (caption: 'Cancel', action: cancel_changes) + actionbutton btnSave (caption: 'Save', action: save changes, buttonstyle: primary) + actionbutton btnCancel (caption: 'Cancel', action: cancel changes) } }; @@ -218,8 +218,8 @@ create page Module.Customer_Edit (...) { ```mdl define fragment ActionButtons as { - actionbutton btnApprove (caption: 'Approve', action: save_changes, buttonstyle: success) - actionbutton btnReject (caption: 'Reject', action: cancel_changes, buttonstyle: danger) + actionbutton btnApprove (caption: 'Approve', action: save changes, buttonstyle: success) + actionbutton btnReject (caption: 'Reject', action: cancel changes, buttonstyle: danger) }; create page Module.DualPanel (...) { diff --git a/.claude/skills/mendix/generate-domain-model/reference/patterns.md b/.claude/skills/mendix/generate-domain-model/reference/patterns.md index fc2f87f6aa..209e471e3c 100644 --- a/.claude/skills/mendix/generate-domain-model/reference/patterns.md +++ b/.claude/skills/mendix/generate-domain-model/reference/patterns.md @@ -45,13 +45,13 @@ create enumeration ECommerce.OrderStatus ( @position(50, 50) create persistent entity ECommerce.Customer ( /** Unique customer identifier */ - CustomerId: long not null error 'Customer ID is required' unique error 'Customer ID must be unique', + CustomerId: long not null error message 'Customer ID is required' unique error message 'Customer ID must be unique', /** Customer full name */ - FullName: string(200) not null error 'Full name is required', + FullName: string(200) not null error message 'Full name is required', /** Email address */ - Email: string(200) not null error 'Email is required' unique error 'Email must be unique', + Email: string(200) not null error message 'Email is required' unique error message 'Email must be unique', /** Registration date */ - RegistrationDate: datetime not null error 'Registration date is required' + RegistrationDate: datetime not null error message 'Registration date is required' ); /** @@ -64,15 +64,15 @@ create persistent entity ECommerce.Customer ( @position(50, 250) create persistent entity ECommerce.Product ( /** Unique product identifier */ - ProductId: long not null error 'Product ID is required' unique error 'Product ID must be unique', + ProductId: long not null error message 'Product ID is required' unique error message 'Product ID must be unique', /** Product name */ - ProductName: string(200) not null error 'Product name is required', + ProductName: string(200) not null error message 'Product name is required', /** Product SKU */ - SKU: string(50) not null error 'SKU is required' unique error 'SKU must be unique', + SKU: string(50) not null error message 'SKU is required' unique error message 'SKU must be unique', /** Unit price */ - Price: decimal not null error 'Price is required', + Price: decimal not null error message 'Price is required', /** Stock quantity */ - StockQuantity: integer not null error 'Stock quantity is required' + StockQuantity: integer not null error message 'Stock quantity is required' ); /** @@ -85,15 +85,15 @@ create persistent entity ECommerce.Product ( @position(300, 150) create persistent entity ECommerce.SalesOrder ( /** Unique order identifier */ - OrderId: long not null error 'Order ID is required' unique error 'Order ID must be unique', + OrderId: long not null error message 'Order ID is required' unique error message 'Order ID must be unique', /** Order number */ - OrderNumber: string(50) not null error 'Order number is required' unique error 'Order number must be unique', + OrderNumber: string(50) not null error message 'Order number is required' unique error message 'Order number must be unique', /** Order date */ - OrderDate: datetime not null error 'Order date is required', + OrderDate: datetime not null error message 'Order date is required', /** Total amount */ - TotalAmount: decimal not null error 'Total amount is required', + TotalAmount: decimal not null error message 'Total amount is required', /** Order status */ - status: enumeration(ECommerce.OrderStatus) not null error 'Status is required' + status: enumeration(ECommerce.OrderStatus) not null error message 'Status is required' ); -- Associations diff --git a/.claude/skills/mendix/generate-domain-model/reference/syntax.md b/.claude/skills/mendix/generate-domain-model/reference/syntax.md index e3a4445521..8c3247d7b4 100644 --- a/.claude/skills/mendix/generate-domain-model/reference/syntax.md +++ b/.claude/skills/mendix/generate-domain-model/reference/syntax.md @@ -180,17 +180,17 @@ Cross-module associations have no anchors at all — Mendix stores none, and @position(100, 100) create persistent entity Module.EntityName ( /** Unique identifier */ - Id: long not null error 'ID is required' unique error 'ID must be unique', + Id: long not null error message 'ID is required' unique error message 'ID must be unique', /** Attribute description */ - attributename: string(200) not null error 'Attribute name is required', + attributename: string(200) not null error message 'Attribute name is required', /** Numeric value */ Amount: decimal, /** Date field (there is no date-only type) */ CreationDate: datetime, /** Boolean flag */ - IsActive: boolean not null error 'IsActive flag is required' default true, + IsActive: boolean not null error message 'IsActive flag is required' default true, /** Enumeration field */ - status: enumeration(Module.StatusEnum) not null error 'Status is required' + status: enumeration(Module.StatusEnum) not null error message 'Status is required' ); ``` @@ -474,7 +474,7 @@ create association Module.EntityWithFK_ReferencedEntity from Module.EntityWithFK to Module.ReferencedEntity type reference owner default -delete_behavior DELETE_BUT_KEEP_REFERENCES +on delete set null comment 'Additional documentation'; ``` @@ -520,12 +520,10 @@ both is refused as **MDL067**. > to-many collection and a list widget works. See master-detail-pages for the > widget patterns. -**Delete Behaviors**: -- `DELETE_AND_REFERENCES` - Delete object and all referencing objects -- `DELETE_BUT_KEEP_REFERENCES` - Delete object, keep references (nullify) -- `DELETE_IF_NO_REFERENCES` - Only delete if no objects reference it -- `cascade` - Cascade delete to associated objects -- `prevent` - Prevent deletion if references exist +**Delete Behaviors** (`on delete …`; the old `delete_behavior …` clause is deprecated, MDL-DEPR022): +- `on delete cascade` - Delete object and all referencing objects +- `on delete set null` - Delete object, keep references (nullify) — the default +- `on delete restrict [error message '…']` - Only delete if no objects reference it **Naming Convention**: `{FromEntity}_{ToEntity}` (e.g., `Order_Customer`, `Transaction_Account`) @@ -603,13 +601,13 @@ Each constraint can have a custom error message using `error 'message'` syntax: ```sql create persistent entity Module.Customer ( /** Customer name - required with custom error */ - Name: string(200) not null error 'Name is required', + Name: string(200) not null error message 'Name is required', /** Email - required and unique with separate error messages */ - Email: string(200) not null error 'Email is required' unique error 'Email must be unique', + Email: string(200) not null error message 'Email is required' unique error message 'Email must be unique', /** Age with default value */ Age: integer default 0, /** Active status flag */ - IsActive: boolean not null error 'IsActive flag is required' default true + IsActive: boolean not null error message 'IsActive flag is required' default true ); ``` @@ -617,23 +615,23 @@ create persistent entity Module.Customer ( - Place `error 'message'` immediately after the constraint - Multiple constraints can each have their own error message - Keep messages clear and user-friendly -- Follow the pattern: `not null error 'X is required'` for required fields -- For UNIQUE: `unique error 'X must be unique'` +- Follow the pattern: `not null error message 'X is required'` for required fields +- For UNIQUE: `unique error message 'X must be unique'` - Error messages are shown to end users during validation **Common patterns:** ```sql -- Required field -Name: string(200) not null error 'Name is required', +Name: string(200) not null error message 'Name is required', -- Required and unique -Email: string(200) not null error 'Email is required' unique error 'Email must be unique', +Email: string(200) not null error message 'Email is required' unique error message 'Email must be unique', -- Required with default -IsActive: boolean not null error 'IsActive flag is required' default true, +IsActive: boolean not null error message 'IsActive flag is required' default true, -- Enum with required error -status: enumeration(Module.Status) not null error 'Status is required', +status: enumeration(Module.Status) not null error message 'Status is required', -- Enum with default value (use fully qualified Module.Enum.Value) Priority: enumeration(Module.Priority) default Module.Priority.Normal diff --git a/.claude/skills/mendix/master-detail-pages/SKILL.md b/.claude/skills/mendix/master-detail-pages/SKILL.md index 330a55280e..f60378fefc 100644 --- a/.claude/skills/mendix/master-detail-pages/SKILL.md +++ b/.claude/skills/mendix/master-detail-pages/SKILL.md @@ -39,7 +39,7 @@ create page Module.Entity_MasterDetail textbox txtName (label: 'Name', attribute: Name) footer footer1 { - actionbutton btnSave (caption: 'Save', action: save_changes, buttonstyle: success) + actionbutton btnSave (caption: 'Save', action: save changes, buttonstyle: success) } } } @@ -124,8 +124,8 @@ create page CRM.Customer_MasterDetail textbox txtPhone (label: 'Phone', attribute: Phone) footer footer1 { - actionbutton btnSave (caption: 'Save', action: save_changes, buttonstyle: success) - actionbutton btnCancel (caption: 'Cancel', action: cancel_changes) + actionbutton btnSave (caption: 'Save', action: save changes, buttonstyle: success) + actionbutton btnCancel (caption: 'Cancel', action: cancel changes) } } } @@ -172,7 +172,7 @@ template template1 { | Where filter | `datasource: database from entity where [IsActive = true]` | | Selection mode | `selection: single` | | Attribute binding | `attribute: attributename` | -| Action binding | `action: save_changes` | +| Action binding | `action: save changes` | | Button style | `buttonstyle: success` | | Text content | `content: 'text'` with `contentparams: [{1} = attr]` | | Render mode | `rendermode: H4` | diff --git a/.claude/skills/mendix/mdl-entities/SKILL.md b/.claude/skills/mendix/mdl-entities/SKILL.md index b8a8912abe..8491cb05d8 100644 --- a/.claude/skills/mendix/mdl-entities/SKILL.md +++ b/.claude/skills/mendix/mdl-entities/SKILL.md @@ -164,7 +164,7 @@ the entity being referenced (the "one" / parent side). Name convention is `Child */ create association Module.Product_Category from Module.Product to Module.Category -type reference_set +type ReferenceSet owner both; / ``` @@ -178,14 +178,14 @@ owner both; create association Module.Order_Customer from Module.Order to Module.Customer type reference -delete_behavior DELETE_AND_REFERENCES; +on delete cascade; / ``` Delete behaviors (applied to the referenced `to` entity): -- `delete_behavior DELETE_AND_REFERENCES` - delete the referencing objects too (cascade) -- `delete_behavior DELETE_BUT_KEEP_REFERENCES` - delete, nullify the reference (default) -- `delete_behavior DELETE_IF_NO_REFERENCES` - only delete when nothing references it +- `on delete cascade` - delete the referencing objects too (cascade) +- `on delete set null` - delete, nullify the reference (default) +- `on delete restrict` - only delete when nothing references it ## Enumerations @@ -330,7 +330,7 @@ type reference; create association Shop.OrderLine_Order from Shop.OrderLine to Shop.Order type reference -delete_behavior DELETE_AND_REFERENCES; +on delete cascade; / create association Shop.OrderLine_Product @@ -347,7 +347,7 @@ not by re-running `describe` output: ```mdl alter entity Shop.Order add attribute Note: string(200); -alter association Shop.Order_Customer set delete_behavior DELETE_BUT_KEEP_REFERENCES; +alter association Shop.Order_Customer set on delete set null; alter enumeration Shop.OrderStatus add value Cancelled caption 'Cancelled'; ``` @@ -374,10 +374,10 @@ attributename: type [(length)] [not null] [unique] [default value] ```mdl create association Module.Child_Parent from Module.ChildEntity to Module.ParentEntity -[type reference | reference_set] +[type reference | ReferenceSet] [owner default | both] [storage column | table] -[delete_behavior DELETE_AND_REFERENCES | DELETE_BUT_KEEP_REFERENCES | DELETE_IF_NO_REFERENCES]; +[on delete cascade | restrict | set null [error message '...']]; ``` ### Enumeration Syntax diff --git a/.claude/skills/mendix/migrate-k2-nintex/SKILL.md b/.claude/skills/mendix/migrate-k2-nintex/SKILL.md index 502a4d9039..0e09625c0e 100644 --- a/.claude/skills/mendix/migrate-k2-nintex/SKILL.md +++ b/.claude/skills/mendix/migrate-k2-nintex/SKILL.md @@ -197,8 +197,8 @@ create page CRM.Customer_Edit -- Button bar (SmartForms action buttons) footer footer1 { - actionbutton btnSave (caption: 'Save', action: save_changes, buttonstyle: primary) - actionbutton btnCancel (caption: 'Cancel', action: cancel_changes) + actionbutton btnSave (caption: 'Save', action: save changes, buttonstyle: primary) + actionbutton btnCancel (caption: 'Cancel', action: cancel changes) } } } @@ -241,7 +241,7 @@ begin commit $Order; -- Show page for review (like K2 "Task" with form) - show page CRM.Order_Review ($Order = $Order); + show page CRM.Order_Review (Order = $Order); end; -- K2 Decision "Order > $5000?" → Mendix microflow with decision @@ -252,7 +252,7 @@ begin if $Order/TotalAmount > 5000 then -- Route to manager (K2 Destination Rule equivalent) - call microflow CRM.ACT_Order_SubmitForManagerReview ($Order = $Order); + call microflow CRM.ACT_Order_SubmitForManagerReview (Order = $Order); else -- Auto-approve (K2 "Go To" equivalent) change $Order (status = CRM.OrderStatus.Approved); diff --git a/.claude/skills/mendix/migrate-oracle-forms/SKILL.md b/.claude/skills/mendix/migrate-oracle-forms/SKILL.md index 41ea558e97..b2a6a6fba5 100644 --- a/.claude/skills/mendix/migrate-oracle-forms/SKILL.md +++ b/.claude/skills/mendix/migrate-oracle-forms/SKILL.md @@ -225,7 +225,7 @@ end; | `for ... loop ... end loop` | `loop $item in $list begin ... end loop` | | `while ... loop ... end loop` | Not directly supported; use recursive microflow | | `CURSOR` | `retrieve $list from ...` then `loop` | -| `EXCEPTION when ... then` | `on error { ... }` | +| `EXCEPTION when ... then` | `on error begin ... end error` | ### Example: PL/SQL to MDL @@ -272,9 +272,9 @@ begin set $Total = $Total + $Order/Amount; change $Order (status = 'PROCESSED'); - commit $Order on error { + commit $Order on error begin log error 'Failed to process order: ' + $Order/OrderNumber; - }; + end error; end loop; log info 'Processed ' + toString($count) + ' orders, total: ' + toString($Total); @@ -358,7 +358,7 @@ widgets ( | `PRE-insert` / `PRE-update` | Before commit event handler | | `post-insert` / `post-update` | After commit event handler | | `key-commit` | Save button action microflow | -| `on-error` | `on error { ... }` blocks | +| `on-error` | `on error begin ... end error` blocks | ## Migration Checklist diff --git a/.claude/skills/mendix/odata-data-sharing/reference/walkthroughs.md b/.claude/skills/mendix/odata-data-sharing/reference/walkthroughs.md index 4245647149..fa17d592aa 100644 --- a/.claude/skills/mendix/odata-data-sharing/reference/walkthroughs.md +++ b/.claude/skills/mendix/odata-data-sharing/reference/walkthroughs.md @@ -406,7 +406,7 @@ an invoice number, an ISIN, an employee number. Mendix requires a key to be unique, required and stable (the last is the point here), and the unique validation rule it makes you add is checking exactly that. -That is also why the key needs `unique error '…'` on the attribute — see the +That is also why the key needs `unique error message '…'` on the attribute — see the CE6624 note below. Both halves of the same idea: the value identifies one row, and keeps identifying it. @@ -526,7 +526,7 @@ Measured on Mendix 11.13, each row a separate build: | shape | result | |---|---| | single key attribute, persistable, no `unique` rule | **CE6624** — add one | -| single key attribute, persistable, `unique error '…'` | 0 errors | +| single key attribute, persistable, `unique error message '…'` | 0 errors | | **single key attribute, VIEW entity, no `unique` rule** | **0 errors** | | **composite key, `OData3`** | **CE7238** "You can only have more than one key attribute when the OData version is 4" | | composite key, `OData4`, persistable, no `unique` rules | 0 errors | diff --git a/.claude/skills/mendix/overview-pages/SKILL.md b/.claude/skills/mendix/overview-pages/SKILL.md index 2fdc531dde..3faef3acb5 100644 --- a/.claude/skills/mendix/overview-pages/SKILL.md +++ b/.claude/skills/mendix/overview-pages/SKILL.md @@ -29,9 +29,9 @@ Create a reusable navigation snippet using NAVIGATIONLIST for vertical sidebar m create snippet Module.Entity_Menu { navigationlist navMenu { - item itemCustomers (caption: 'Customers', action: show_page Module.Customer_Overview) - item itemOrders (caption: 'Orders', action: show_page Module.Order_Overview) - item itemProducts (caption: 'Products', action: show_page Module.Product_Overview) + item itemCustomers (caption: 'Customers', action: show page Module.Customer_Overview) + item itemOrders (caption: 'Orders', action: show page Module.Order_Overview) + item itemProducts (caption: 'Products', action: show page Module.Product_Overview) } } ``` @@ -55,9 +55,9 @@ The NAVIGATIONLIST widget creates a vertical menu with navigation items: ```sql navigationlist widgetName { - item itemName (caption: 'Caption', action: show_page Module.PageName) - item itemName (caption: 'Caption', action: microflow Module.MicroflowName) - item itemName (caption: 'Caption', action: close_page) + item itemName (caption: 'Caption', action: show page Module.PageName) + item itemName (caption: 'Caption', action: call microflow Module.MicroflowName) + item itemName (caption: 'Caption', action: close page) } ``` @@ -105,7 +105,7 @@ create page Module.Entity_Overview textfilter textFilter2 } column (caption: 'Actions') { - actionbutton btnEdit (caption: 'Edit', action: show_page Module.Entity_NewEdit("entity": $currentObject)) + actionbutton btnEdit (caption: 'Edit', action: show page Module.Entity_NewEdit("entity" = $currentObject)) actionbutton btnDelete (caption: 'Delete', action: delete, buttonstyle: danger) } } @@ -147,7 +147,7 @@ datagrid GridName ( textfilter textFilter1 } column (caption: 'Actions') { - actionbutton btnEdit (caption: 'Edit', action: show_page Module.Entity_NewEdit("entity": $currentObject)) + actionbutton btnEdit (caption: 'Edit', action: show page Module.Entity_NewEdit("entity" = $currentObject)) } } ``` @@ -253,8 +253,8 @@ create page Module.Entity_NewEdit combobox cbStatus (label: 'Status', attribute: status) footer footer1 { - actionbutton btnSave (caption: 'Save', action: save_changes, buttonstyle: success) - actionbutton btnCancel (caption: 'Cancel', action: cancel_changes) + actionbutton btnSave (caption: 'Save', action: save changes, buttonstyle: success) + actionbutton btnCancel (caption: 'Cancel', action: cancel changes) } } } @@ -276,8 +276,8 @@ create page Module.PageName - Parameter name conventionally matches the entity name (e.g., `$store`, `$Customer`) - The DataView's binding references this parameter (`datasource: $ParamName`) -- When calling the page via SHOW_PAGE, pass the object by parameter name: `show_page Module.PageName(ParamName: $value)` -- **Reserved-word parameter names must be quoted in the SHOW_PAGE args.** The generic examples above name the parameter `entity`, which is a reserved keyword, so the call quotes it: `show_page Module.Entity_NewEdit("entity": $currentObject)`. A non-reserved name (`$store`, `$Customer`) needs no quotes: `show_page Module.Store_NewEdit(store: $currentObject)`. +- When calling the page via show page, pass the object by parameter name: `show page Module.PageName(ParamName = $value)` +- **Reserved-word parameter names must be quoted in the show page args.** The generic examples above name the parameter `entity`, which is a reserved keyword, so the call quotes it: `show page Module.Entity_NewEdit("entity" = $currentObject)`. A non-reserved name (`$store`, `$Customer`) needs no quotes: `show page Module.Store_NewEdit(store = $currentObject)`. ### NewEdit Page Components @@ -299,8 +299,8 @@ create snippet MdlTemplates.NavigationMenu layoutgrid navGrid { row { column (desktopwidth: 12) { - actionbutton btnStores (caption: 'Stores', action: show_page MdlTemplates.Store_Overview) - actionbutton btnCars (caption: 'Cars', action: show_page MdlTemplates.Car_Overview) + actionbutton btnStores (caption: 'Stores', action: show page MdlTemplates.Store_Overview) + actionbutton btnCars (caption: 'Cars', action: show page MdlTemplates.Car_Overview) } } } @@ -368,8 +368,8 @@ create page MdlTemplates.Store_NewEdit textbox txtLocation (label: 'Location', attribute: Location) footer footer1 { - actionbutton btnSave (caption: 'Save', action: save_changes, buttonstyle: success) - actionbutton btnCancel (caption: 'Cancel', action: cancel_changes) + actionbutton btnSave (caption: 'Save', action: save changes, buttonstyle: success) + actionbutton btnCancel (caption: 'Cancel', action: cancel changes) } } } @@ -425,8 +425,8 @@ create page MdlTemplates.Car_NewEdit radiobuttons rbType (label: 'Car type', attribute: CarType) footer footer1 { - actionbutton btnSave (caption: 'Save', action: save_changes, buttonstyle: success) - actionbutton btnCancel (caption: 'Cancel', action: cancel_changes) + actionbutton btnSave (caption: 'Save', action: save changes, buttonstyle: success) + actionbutton btnCancel (caption: 'Cancel', action: cancel changes) } } } @@ -535,9 +535,9 @@ create snippet Module.Entity_Menu ) { navigationlist EntityMenuNav { - item itemEdit (caption: 'Edit', action: show_page Module.Entity_NewEdit("entity": $EntityParameter)) + item itemEdit (caption: 'Edit', action: show page Module.Entity_NewEdit("entity" = $EntityParameter)) item itemDelete (caption: 'Delete', action: delete) - item itemBack (caption: 'Back', action: close_page) + item itemBack (caption: 'Back', action: close page) } } ``` @@ -551,18 +551,18 @@ navigationlist widgetName { ``` **Supported Actions:** -- `action: save_changes` - Save changes -- `action: cancel_changes` - Cancel changes -- `action: close_page` - Close current page +- `action: save changes` - Save changes +- `action: cancel changes` - Cancel changes +- `action: close page` - Close current page - `action: delete` - Delete object -- `action: microflow Module.MicroflowName` - Call microflow -- `action: microflow Module.MicroflowName(Param: $value)` - Call microflow with parameters -- `action: show_page Module.PageName` - Navigate to page -- `action: show_page Module.PageName(Param: $value)` - Navigate with parameters -- **A `show_page` argument must be the context object, and there has to BE one.** +- `action: call microflow Module.MicroflowName` - Call microflow +- `action: call microflow Module.MicroflowName(Param = $value)` - Call microflow with parameters +- `action: show page Module.PageName` - Navigate to page +- `action: show page Module.PageName(Param = $value)` - Navigate with parameters +- **A `show page` argument must be the context object, and there has to BE one.** Mendix takes the page argument from the enclosing data widget, so the only spellings that mean anything are `$currentObject` or the name of the variable - that widget is bound to (`datasource: $Customer` → `(Customer: $Customer)` is + that widget is bound to (`datasource: $Customer` → `(Customer = $Customer)` is fine). Naming any other variable is refused as **MDL-PAGEARG01** — it used to be accepted and silently opened the page with the context object anyway. - **Outside a data widget the same rule leaves nothing at all**, so a button sitting @@ -572,12 +572,12 @@ navigationlist widgetName { mxbuild reports **CE1571** per parameter of the target page, and a page whose parameters are optional would simply show the wrong data. MDL-PAGEARG01 refuses that too (mendixlabs/mxcli#1029). To open a parameterised page from such a - button, call a microflow that does `show page Module.Page(Param: $value)` — + button, call a microflow that does `show page Module.Page(Param = $value)` — that path wires the arguments properly. ## Handling Circular Dependencies -When a navigation snippet references pages (via `show_page`) and those pages reference the snippet (via `snippetcall`), you have a circular dependency. Use the **placeholder pattern**: +When a navigation snippet references pages (via `show page`) and those pages reference the snippet (via `snippetcall`), you have a circular dependency. Use the **placeholder pattern**: ### Creation Order @@ -631,7 +631,7 @@ create or modify snippet Module.NavigationMenu layoutgrid navGrid { row { column (desktopwidth: 12) { - actionbutton btnCustomers (caption: 'Customers', action: show_page Module.Customer_Overview) + actionbutton btnCustomers (caption: 'Customers', action: show page Module.Customer_Overview) } } } diff --git a/.claude/skills/mendix/patterns-crud/SKILL.md b/.claude/skills/mendix/patterns-crud/SKILL.md index 40e5429d9f..b20fbd8aae 100644 --- a/.claude/skills/mendix/patterns-crud/SKILL.md +++ b/.claude/skills/mendix/patterns-crud/SKILL.md @@ -35,7 +35,7 @@ returns boolean begin -- Validate first declare $IsValid boolean = true; - $IsValid = call microflow Module.VAL_Customer_Save($Customer = $Customer); + $IsValid = call microflow Module.VAL_Customer_Save(Customer = $Customer); if $IsValid then commit $Customer; @@ -153,7 +153,7 @@ begin CreatedDate = [%CurrentDateTime%] ); - show page Module.Customer_NewEdit ($Customer = $NewCustomer); + show page Module.Customer_NewEdit (Customer = $NewCustomer); return true; end; / @@ -198,7 +198,7 @@ create microflow Module.ACT_Customer_Edit ( ) returns boolean begin - show page Module.Customer_NewEdit ($Customer = $Customer); + show page Module.Customer_NewEdit (Customer = $Customer); return true; end; / diff --git a/.claude/skills/mendix/regular-expressions/SKILL.md b/.claude/skills/mendix/regular-expressions/SKILL.md index c115e28f4d..0f70dd8847 100644 --- a/.claude/skills/mendix/regular-expressions/SKILL.md +++ b/.claude/skills/mendix/regular-expressions/SKILL.md @@ -73,7 +73,7 @@ create regular expression Val.EmailAddress ( create validation rule for Val.Person.Email regex Val.EmailAddress - feedback 'Enter a valid email address'; + error message 'Enter a valid email address'; ``` Create the pattern first. The rule stores a **reference by qualified name**, so @@ -84,9 +84,9 @@ Ranges use the same statement. Bounds are inclusive and either may be omitted; Mendix has no strict `<` or `>`, so there is no exclusive form: ```sql -create validation rule for Val.Booking.Guests range from 1 to 100 feedback '…'; -create validation rule for Val.Product.Price range from 0 feedback '…'; -create validation rule for Val.Order.Discount range to 100 feedback '…'; +create validation rule for Val.Booking.Guests range from 1 to 100 error message '…'; +create validation rule for Val.Product.Price range from 0 error message '…'; +create validation rule for Val.Order.Discount range to 100 error message '…'; ``` Re-running a rule replaces the one of the **same type** on that attribute and @@ -98,12 +98,12 @@ constraints: ```sql create entity Val.Person ( - Email: String(200) not null error 'Email is required', - Code: String(20) unique error 'Code must be unique' + Email: String(200) not null error message 'Email is required', + Code: String(20) unique error message 'Code must be unique' ); alter entity Val.Person modify attribute Email String(200) - not null error 'Email is required'; + not null error message 'Email is required'; ``` ### What still cannot be authored diff --git a/.claude/skills/mendix/resolve-forward-references/SKILL.md b/.claude/skills/mendix/resolve-forward-references/SKILL.md index e72f1f00e4..a57a1111ec 100644 --- a/.claude/skills/mendix/resolve-forward-references/SKILL.md +++ b/.claude/skills/mendix/resolve-forward-references/SKILL.md @@ -22,7 +22,7 @@ This applies to the following reference types: | Reference | In | Fails when | |---|---|---| | `snippetcall` | page / snippet | snippet created after the page | -| `show_page` in action | page / snippet | page created after the page that references it | +| `show page` in action | page / snippet | page created after the page that references it | | `SHOW PAGE` | microflow | page created after the microflow | | `call microflow` | microflow | callee microflow created after the caller (in the same script, `exec` resolves the call against the project/backend, not later same-script definitions — so order the callee first) | @@ -49,7 +49,7 @@ The standard workaround is a three-step sequence: ## Pattern 1 — Shared Navigation Snippet (most common) -A navigation snippet contains `show_page` buttons (references pages) and pages include +A navigation snippet contains `show page` buttons (references pages) and pages include the snippet via `snippetcall` (references the snippet). Both sides reference each other. ```sql @@ -100,7 +100,7 @@ create page MyModule.Order_Overview } / --- Step 3: fill in the snippet with real content (pages now exist → show_page resolves OK) +-- Step 3: fill in the snippet with real content (pages now exist → show page resolves OK) -- Use CREATE OR MODIFY (preserves ID) or ALTER SNIPPET (in-place) create or modify snippet MyModule.NavMenu ( @@ -110,11 +110,11 @@ create or modify snippet MyModule.NavMenu layoutgrid g { row r { column c (desktopwidth: 12) { actionbutton btnCustomers ( caption: 'Customers', - action: show_page MyModule.Customer_Overview + action: show page MyModule.Customer_Overview ) actionbutton btnOrders ( caption: 'Orders', - action: show_page MyModule.Order_Overview + action: show page MyModule.Order_Overview ) }}} } @@ -125,7 +125,7 @@ create or modify snippet MyModule.NavMenu ## Pattern 2 — Page References Another Page (new/edit from overview) -An overview page has a New button that opens a NewEdit page via `show_page`. The NewEdit +An overview page has a New button that opens a NewEdit page via `show page`. The NewEdit page must exist before the overview can reference it. ```sql @@ -142,8 +142,8 @@ create page MyModule.Customer_NewEdit dataview dv (datasource: $Customer) { textbox txtName (label: 'Name', attribute: Name) } - actionbutton btnSave (caption: 'Save', action: save_changes) - actionbutton btnCancel (caption: 'Cancel', action: cancel_changes) + actionbutton btnSave (caption: 'Save', action: save changes) + actionbutton btnCancel (caption: 'Cancel', action: cancel changes) }}} } / @@ -158,7 +158,7 @@ create page MyModule.Customer_Overview layoutgrid g { row { column (desktopwidth: 12) { actionbutton btnNew ( caption: 'New', - action: microflow MyModule.ACT_Customer_New + action: call microflow MyModule.ACT_Customer_New ) datagrid dg (datasource: database MyModule.Customer) { column (caption: 'Name', attribute: Name) @@ -198,7 +198,7 @@ create page MyModule.Order_Detail create microflow MyModule.ACT_OpenOrder ($Order: MyModule.Order) begin @position(200,200) - show page MyModule.Order_Detail ($Order = $Order); + show page MyModule.Order_Detail (Order = $Order); @position(400,200) return; end; / @@ -240,7 +240,7 @@ alter snippet MyModule.NavMenu replace activity loading with actionbutton btnCustomers ( caption: 'Customers', - action: show_page MyModule.Customer_Overview + action: show page MyModule.Customer_Overview ); ``` @@ -301,7 +301,7 @@ create or modify snippet MyModule.AppNav layoutgrid g { row r { column c (desktopwidth: 12) { actionbutton btnCustomers ( caption: 'Customers', - action: show_page MyModule.Customer_Overview + action: show page MyModule.Customer_Overview ) }}} } @@ -313,7 +313,7 @@ begin @position(200,200) $c = create MyModule.Customer; @position(400,200) - show page MyModule.Customer_NewEdit ($Customer = $c); + show page MyModule.Customer_NewEdit (Customer = $c); @position(600,200) return; end; / @@ -321,7 +321,7 @@ end; -- 6. Navigation (pages already exist) alter navigation Responsive insert menu item 'Customers' - action show_page MyModule.Customer_Overview; + action show page MyModule.Customer_Overview; ``` --- diff --git a/.claude/skills/mendix/system-module/SKILL.md b/.claude/skills/mendix/system-module/SKILL.md index eee046dae9..206e524eeb 100644 --- a/.claude/skills/mendix/system-module/SKILL.md +++ b/.claude/skills/mendix/system-module/SKILL.md @@ -220,7 +220,7 @@ create persistent entity MyModule.Attachment extends System.FileDocument ( create association MyModule.Order_Attachments from MyModule.Order to MyModule.Attachment -type reference_set; +type ReferenceSet; ``` ### System.Image @@ -475,7 +475,7 @@ create persistent entity MyModule.Attachment extends System.FileDocument ( create association MyModule.Order_Attachments from MyModule.Order to MyModule.Attachment -type reference_set; +type ReferenceSet; ``` ### Workflow Context Object diff --git a/.claude/skills/mendix/test-app/SKILL.md b/.claude/skills/mendix/test-app/SKILL.md index 149c3662a5..6bc3a7791c 100644 --- a/.claude/skills/mendix/test-app/SKILL.md +++ b/.claude/skills/mendix/test-app/SKILL.md @@ -105,7 +105,7 @@ Mendix renders each widget's `name` property as a CSS class on the DOM element: This maps directly to MDL widget names. When you generate a widget in MDL: ```sql -actionbutton submitButton (caption: 'Submit', action: save_changes) +actionbutton submitButton (caption: 'Submit', action: save changes) ``` The stable CSS selector is `.mx-name-submitButton`. Use this with `eval` for reliable assertions: @@ -379,7 +379,7 @@ playwright-cli show ```sql -- MDL: names you define become test hooks -actionbutton btnDrivers (caption: 'Drivers', action: show_page Module.Drivers_Overview) +actionbutton btnDrivers (caption: 'Drivers', action: show page Module.Drivers_Overview) datagrid dgOrders (datasource: database Module.Order) { ... } ``` diff --git a/.claude/skills/mendix/validation-microflows/SKILL.md b/.claude/skills/mendix/validation-microflows/SKILL.md index 1f7e0181e7..acb6e1401a 100644 --- a/.claude/skills/mendix/validation-microflows/SKILL.md +++ b/.claude/skills/mendix/validation-microflows/SKILL.md @@ -42,7 +42,7 @@ validation feedback $VariableName/attributename message 'Error message'; With template arguments (for dynamic messages): ```mdl -validation feedback $VariableName/attributename message '{1}' objects [$MessageVariable]; +validation feedback $VariableName/attributename message '{1}' with ({1} = $MessageVariable); ``` ### CLOSE PAGE Statement @@ -131,7 +131,7 @@ returns boolean as $IsValid folder 'OverviewPages' begin -- Call validation microflow - $IsValid = call microflow MdlTemplates.VAL_Car_NewEdit($param = $Car); + $IsValid = call microflow MdlTemplates.VAL_Car_NewEdit(param = $Car); -- Only save if validation passed if $IsValid then @@ -240,7 +240,7 @@ end if; if trim($ValidationMessage) != '' then set $IsValid = false; - validation feedback $entity/value message '{1}' objects [$ValidationMessage]; + validation feedback $entity/value message '{1}' with ({1} = $ValidationMessage); end if; ``` @@ -292,4 +292,4 @@ This feature is implemented in: ## Validation rules (CREATE VALIDATION RULE) -binds a **regex** or a **range** to one attribute — `create validation rule for Mod.Entity.Attr regex Mod.Pattern feedback '…'`. The rule is anonymous and entity-scoped, so the statement names the attribute; re-running it replaces the rule of the same type and leaves the attribute's others alone. Unlocked by a `STORAGE-NAME OVERRIDE` in `modelsdk/gen`: it bound `RegularExpression` where Studio Pro stores `RegExIdentifier`, and the control (same script, key reverted) fails **CE0135 "No regular expression specified"** while the fixed one is 0 errors on mxbuild 11.13 with `RegExIdentifier` on disk. Range bounds are inclusive and map to Mendix's only three kinds (`from X to Y`/`from X`/`to Y` → Between/GreaterThanOrEqualTo/SmallerThanOrEqualTo); there is no strict `<`/`>`, and the old grammar's forms for it — plus an EXPRESSION rule type Mendix does not have and an inline regex literal — were removed, having never had a visitor or handler. Required/Unique stay attribute constraints (`not null error '…'` / `unique error '…'`), not a second spelling here. Rewriting an entity carrying **MaxLength or EqualsTo** is **refused** on both engines rather than silently downgraded to Required; that round trip was lossy and `mx check` stayed green, because a Required rule is valid. Both engines carry each rule's payload on READ (`ruleInfoFromGen` / `parseValidationRuleInfo`), which is what makes the refusal narrow instead of covering all of RegEx and Range — and what lets a **range bounded by another attribute** survive a rewrite even though MDL cannot author one (`describe entity` marks it with a comment rather than rendering it wrong). A rule whose payload did not survive the read is refused as firmly as an unknown type: a bare RuleInfo of the right `$Type` constrains nothing, which is the same silent downgrade wearing the right name +binds a **regex** or a **range** to one attribute — `create validation rule for Mod.Entity.Attr regex Mod.Pattern error message '…'`. The rule is anonymous and entity-scoped, so the statement names the attribute; re-running it replaces the rule of the same type and leaves the attribute's others alone. Unlocked by a `STORAGE-NAME OVERRIDE` in `modelsdk/gen`: it bound `RegularExpression` where Studio Pro stores `RegExIdentifier`, and the control (same script, key reverted) fails **CE0135 "No regular expression specified"** while the fixed one is 0 errors on mxbuild 11.13 with `RegExIdentifier` on disk. Range bounds are inclusive and map to Mendix's only three kinds (`from X to Y`/`from X`/`to Y` → Between/GreaterThanOrEqualTo/SmallerThanOrEqualTo); there is no strict `<`/`>`, and the old grammar's forms for it — plus an EXPRESSION rule type Mendix does not have and an inline regex literal — were removed, having never had a visitor or handler. Required/Unique stay attribute constraints (`not null error message '…'` / `unique error message '…'`), not a second spelling here. Rewriting an entity carrying **MaxLength or EqualsTo** is **refused** on both engines rather than silently downgraded to Required; that round trip was lossy and `mx check` stayed green, because a Required rule is valid. Both engines carry each rule's payload on READ (`ruleInfoFromGen` / `parseValidationRuleInfo`), which is what makes the refusal narrow instead of covering all of RegEx and Range — and what lets a **range bounded by another attribute** survive a rewrite even though MDL cannot author one (`describe entity` marks it with a comment rather than rendering it wrong). A rule whose payload did not survive the read is refused as firmly as an unknown type: a bare RuleInfo of the right `$Type` constrains nothing, which is the same silent downgrade wearing the right name diff --git a/.claude/skills/mendix/write-microflows/SKILL.md b/.claude/skills/mendix/write-microflows/SKILL.md index 2b31169d57..17e49f7c3b 100644 --- a/.claude/skills/mendix/write-microflows/SKILL.md +++ b/.claude/skills/mendix/write-microflows/SKILL.md @@ -524,14 +524,15 @@ When calling microflows, always check the target's parameter list. Use `describe ### SHOW PAGE ```mdl --- Open page with parameter (canonical syntax) -show page Module.EditPage($Product = $Product); - --- Widget-style syntax also accepted in microflows -show page Module.EditPage(Product: $Product); +-- Open page with parameter +show page Module.EditPage(Product = $Product); ``` -Both `($Param = $value)` and `(Param: $value)` syntaxes are accepted in microflow SHOW PAGE statements. Similarly, widget Action: properties accept both `show_page Module.Page(Param: $value)` and `show_page Module.Page($Param = $value)`. +Every call site binds an argument as `Param = expression`, with no `$` on the +parameter name: `call microflow`, `show page`, and widget actions +(`action: show page Module.Page(Param = $value)`) alike. `$Param = $value` and +`Param: $value` still parse but are deprecated (MDL-DEPR006/007); `mxcli fmt +--upgrade` rewrites them. ### CLOSE PAGE @@ -646,8 +647,7 @@ the short `@annotation 'text'` form. ### Page Navigation Pattern ```mdl -show page Module.Page($Param = $value); -- Canonical -show page Module.Page(Param: $value); -- Widget-style (also valid) +show page Module.Page(Param = $value); close page; show home page; ``` @@ -656,24 +656,24 @@ show home page; ```mdl call microflow ... on error continue; -- Ignore error call microflow ... on error rollback; -- Rollback on error -call microflow ... on error { log ...; return ...; }; -- Custom handler -call microflow ... on error without rollback { ... }; -- No rollback +call microflow ... on error begin log ...; return ...; end error; -- Custom handler +call microflow ... on error without rollback begin ... end error; -- No rollback ``` The clause goes on whichever activity may fail, not only on calls: ```mdl -declare $Name String = 'default' on error { return 'could not initialise'; }; -$Name = $Other/Name on error { return 'lookup failed'; }; -change $Order (Status = Shipped) on error { log error 'could not ship'; return; }; -log info node 'App' 'starting' on error { return; }; -show message 'saved' on error { return; }; +declare $Name String = 'default' on error begin return 'could not initialise'; end error; +$Name = $Other/Name on error begin return 'lookup failed'; end error; +change $Order (Status = Shipped) on error begin log error 'could not ship'; return; end error; +log info node 'App' 'starting' on error begin return; end error; +show message 'saved' on error begin return; end error; -- BLOCKING halts the client until dismissed; after `objects`, before `on error`. -show message 'Hello {1}' type Warning objects [$Name] blocking; -validation feedback $Order/Total message 'must be positive' on error { return; }; -show page Module.Page on error { return; }; -close page on error { return; }; +show message 'Hello {1}' type Warning with ({1} = $Name) blocking; +validation feedback $Order/Total message 'must be positive' on error begin return; end error; +show page Module.Page on error begin return; end error; +close page on error begin return; end error; ``` **Two limits, both reported rather than silently ignored:** diff --git a/.claude/skills/mendix/write-microflows/reference/control-flow.md b/.claude/skills/mendix/write-microflows/reference/control-flow.md index 3ebf37138a..563de70482 100644 --- a/.claude/skills/mendix/write-microflows/reference/control-flow.md +++ b/.claude/skills/mendix/write-microflows/reference/control-flow.md @@ -333,17 +333,17 @@ call microflow Module.RiskyOperation() on error continue; -- ON ERROR ROLLBACK: Rollback transaction and propagate error commit $Order on error rollback; --- ON ERROR { ... }: Custom error handler with rollback -$Result = call microflow Module.ExternalService(data = $data) on error { +-- ON ERROR BEGIN ... END ERROR: Custom error handler with rollback +$Result = call microflow Module.ExternalService(data = $data) on error begin log error node 'ServiceError' 'External service failed'; return $DefaultResult; -}; +end error; --- ON ERROR WITHOUT ROLLBACK { ... }: Custom handler, keep changes -commit $Order on error without rollback { +-- ON ERROR WITHOUT ROLLBACK BEGIN ... END ERROR: Custom handler, keep changes +commit $Order on error without rollback begin log warning node 'CommitError' 'Commit failed, using fallback'; change $Order (status = 'PENDING'); -}; +end error; ``` ### Error Handling Semantics @@ -352,23 +352,23 @@ commit $Order on error without rollback { |--------|----------| | `on error continue` | Catch error silently, continue normal flow | | `on error rollback` | Rollback database changes, propagate error | -| `on error { ... }` | Execute handler block, then continue (with rollback) | -| `on error without rollback { ... }` | Execute handler block, keep database changes | +| `on error begin ... end error` | Execute handler block, then continue (with rollback) | +| `on error without rollback begin ... end error` | Execute handler block, keep database changes | ### RAISE ERROR is handler-only `raise error;` builds Mendix's **error event**, which *re-raises the error currently being handled*. Mendix therefore allows one only where an error is in -scope — that is, inside an `on error { ... }` block. Studio Pro will not even +scope — that is, inside an `on error begin ... end error` block. Studio Pro will not even let you draw the connection from the normal flow to an error event. ```mdl -- ✅ inside a handler: an error IS in scope call microflow Module.RiskyOperation() -on error { +on error begin log error node 'Module' 'failed, re-raising'; raise error; -}; +end error; -- ❌ on the main flow: MDL084, and mxbuild rejects it with -- CE0710 "The main flow cannot join an error flow or end in an error event." @@ -409,13 +409,13 @@ returns Module.Response as $response begin -- The call output establishes $response — objects are never declared $response = call microflow Module.CallExternalAPI(data = $RequestData) - on error without rollback { + on error without rollback begin log error node 'ExternalAPI' 'API call failed for: ' + $RequestData; -- Create error response $response = create Module.Response ( success = false, message = 'External service unavailable'); - }; + end error; return $response; end; @@ -431,9 +431,9 @@ knowing which one you are writing. | Form | Error path | |------|-----------| | `on error continue` | No error path at all | -| `on error [without rollback] { … return/throw }` | Its own path, its own terminator | -| `on error [without rollback] { }` | **Not a no-op** — falls through to whatever the *enclosing branch* does next | -| `on error [without rollback] { … join L; }` | Rejoins the normal path at the merge labelled `L` | +| `on error [without rollback] begin … return/throw end error` | Its own path, its own terminator | +| `on error [without rollback] begin end error` | **Not a no-op** — falls through to whatever the *enclosing branch* does next | +| `on error [without rollback] begin … join L; end error` | Rejoins the normal path at the merge labelled `L` | The empty form is the one that surprises people. It means "on error, do whatever the enclosing branch's continuation does" — which in a branch that returns @@ -443,11 +443,11 @@ something else is a value nowhere in the text. Prefer `join` when you mean it. create microflow Module.Post (Payload: String) returns String begin declare $Status String = 'sent'; - $r = call microflow Module.Send(Payload = $Payload) on error without rollback { + $r = call microflow Module.Send(Payload = $Payload) on error without rollback begin log warning node 'Module' 'send failed, degrading'; set $Status = 'degraded'; join recovered; - }; + end error; join recovered; merge recovered; @@ -465,10 +465,10 @@ activity: ```mdl merge attempt; -$r = call microflow Module.Send(Payload = $Payload) on error without rollback { +$r = call microflow Module.Send(Payload = $Payload) on error without rollback begin log warning node 'Module' 'retrying'; join attempt; -}; +end error; return $r; ``` diff --git a/.claude/skills/mendix/write-microflows/reference/integration.md b/.claude/skills/mendix/write-microflows/reference/integration.md index b342c85232..c6295475e5 100644 --- a/.claude/skills/mendix/write-microflows/reference/integration.md +++ b/.claude/skills/mendix/write-microflows/reference/integration.md @@ -95,6 +95,10 @@ send rest request Module.ServiceName.OperationName; -- With output variable (RESPONSE JSON operation — maps to entity) $Result = send rest request Module.ServiceName.OperationName; +-- With path/query parameters, bound as at every call site: `Param = expression` +$Result = send rest request Module.ServiceName.GetItem + with (id = $ItemId, lang = 'en'); + -- With request body (POST/PUT operations) $Result = send rest request Module.ServiceName.CreateItem body $NewItem; @@ -181,7 +185,7 @@ rest call delete 'https://api.example.com/items/{1}' with ( **REST CALL response types:** - `returns string` — response body as string variable -- `returns nothing` / `returns none` — ignore response +- `returns nothing` — ignore response (`returns none` is the deprecated second spelling, MDL-DEPR024) - `returns response` — returns `System.HttpResponse` object - `returns mapping Module.ImportMapping as Module.Entity` — single object result - `returns mapping Module.ImportMapping as list of Module.Entity` — list result diff --git a/.claude/skills/mendix/write-microflows/reference/pitfalls.md b/.claude/skills/mendix/write-microflows/reference/pitfalls.md index 01e0dd4abb..615dfe0a09 100644 --- a/.claude/skills/mendix/write-microflows/reference/pitfalls.md +++ b/.claude/skills/mendix/write-microflows/reference/pitfalls.md @@ -443,9 +443,9 @@ CATCH end TRY; -- CORRECT: Use ON ERROR on specific activities -commit $Order on error { +commit $Order on error begin log error 'Commit failed'; -}; +end error; ``` ### BREAK/CONTINUE in Loops diff --git a/.claude/skills/mendix/write-nanoflows/SKILL.md b/.claude/skills/mendix/write-nanoflows/SKILL.md index afcae59da4..7c1e73f173 100644 --- a/.claude/skills/mendix/write-nanoflows/SKILL.md +++ b/.claude/skills/mendix/write-nanoflows/SKILL.md @@ -121,7 +121,7 @@ $JsResult = CALL JAVASCRIPT ACTION NanoflowCommons.SignIn (userName = $Name, pas ### UI Activities ```mdl -SHOW PAGE Sales.CartDetail ($Cart = $Cart); +SHOW PAGE Sales.CartDetail (Cart = $Cart); CLOSE PAGE; SHOW MESSAGE WARNING 'Connection unavailable. Working offline.'; VALIDATION FEEDBACK $Item/Quantity MESSAGE 'Quantity must be at least 1'; @@ -156,7 +156,7 @@ IF $Cart/ItemCount = 0 THEN VALIDATION FEEDBACK $Cart/ItemCount MESSAGE 'Cart is empty'; RETURN false; ELSE - SHOW PAGE Sales.Checkout ($Cart = $Cart); + SHOW PAGE Sales.Checkout (Cart = $Cart); RETURN true; END IF; ``` @@ -217,11 +217,11 @@ CREATE OR MODIFY NANOFLOW Inventory.NAV_OpenProductDetail ( ) FOLDER 'Navigation' BEGIN - $IsValid = CALL NANOFLOW Inventory.NAV_ValidateProduct ($Product = $Product); + $IsValid = CALL NANOFLOW Inventory.NAV_ValidateProduct (Product = $Product); IF NOT ($IsValid) THEN RETURN; END IF; - SHOW PAGE Inventory.ProductDetail ($Product = $Product); + SHOW PAGE Inventory.ProductDetail (Product = $Product); END; ``` @@ -528,9 +528,9 @@ synchronize unsynchronized; -- only objects with uncommitted offline change synchronize $Order, $Lines; -- named objects/lists ("Specific" mode) synchronize all on error continue; -synchronize all on error without rollback { +synchronize all on error without rollback begin log error 'sync failed'; -}; +end error; ``` The mode is always written out, including `all` — the statement says what it does diff --git a/.claude/skills/mendix/write-workflows/SKILL.md b/.claude/skills/mendix/write-workflows/SKILL.md index 57972e907c..a610601698 100644 --- a/.claude/skills/mendix/write-workflows/SKILL.md +++ b/.claude/skills/mendix/write-workflows/SKILL.md @@ -109,8 +109,7 @@ begin outcomes 'Done' { }; -- Call a microflow (server logic); optional name, parameter mapping + outcomes - call microflow Module.ACT_Validate as callMicroflow1 - with (Module.ACT_Validate.Item = '$WorkflowContext'); + call microflow Module.ACT_Validate(Item = $WorkflowContext) as callMicroflow1; -- Decision: a boolean or enum exclusive split. The name is optional; give one -- when a `jump to` targets it. @@ -466,11 +465,11 @@ values. The full list and the System **entities** are in `system-module`. users` is refused — each omitted clause would reset it. - **An AI agent task is `call agent microflow`** (Mendix 11.9+) — the call microflow statement stored as `Workflows$AIAgentTaskActivity`, with the same - `as`, `comment`, `with (…)`, `outcomes` and boundary events. The microflow is + argument list, `as`, `comment`, `outcomes` and boundary events. The microflow is where the agent is invoked. Measured on mxbuild 11.13 against the identical call microflow, one rule differs: **its microflow must take a parameter** - (`CE1590 "Missing parameter"`), usually the context object mapped with - `with (Param = '$WorkflowContext')`. Return Boolean or an enumeration to + (`CE1590 "Missing parameter"`), usually the context object passed as + `(Param = $WorkflowContext)`. Return Boolean or an enumeration to branch on the answer. - **Handler microflows have fixed signatures** (measured, mxbuild 11.13): - `on created microflow` takes exactly `System.WorkflowUserTask` and the context @@ -525,9 +524,11 @@ values. The full list and the System **entities** are in `system-module`. `REPLACE ACTIVITY`. A **required (`not null`) attribute does not exempt it** — measured, the empty outcome is still required. Boolean (`true`/`false`) decisions do not take one. -- **A `with (...)` parameter value is a quoted string**, not a bare variable: - `with (Request = '$WorkflowContext')`. The unquoted spelling used elsewhere in - MDL is a syntax error here (it used to crash the binary — ako/mxcli#1023). +- **Arguments go right after the callee, as bare expressions**, like every other + call: `call microflow HR.Escalate(Request = $WorkflowContext) as callMicroflow1`. + The older `with (Request = '$WorkflowContext')`, the expression inside a + string, still parses with the same meaning but is deprecated (MDL-DEPR008); + `mxcli fmt --upgrade` rewrites it. - The context **Parameter entity must be persistent**. - Write the context variable as **`$WorkflowContext`**, matching the parameter name exactly. Mendix expressions are case-sensitive on 11.9+, so a lowercase diff --git a/.claude/skills/packs/mendix-vega-charts/references/properties.md b/.claude/skills/packs/mendix-vega-charts/references/properties.md index 36d3e2bb8b..0b5a9753d7 100644 --- a/.claude/skills/packs/mendix-vega-charts/references/properties.md +++ b/.claude/skills/packs/mendix-vega-charts/references/properties.md @@ -61,7 +61,7 @@ To let a click select something: ``` selection: SelectedPoint, -onClick: microflow MyModule.ACT_SelectPoint(Context: $currentObject) +onClick: microflow MyModule.ACT_SelectPoint(Context = $currentObject) ``` `selection` receives the clicked datum as JSON, reduced to its own scalar fields — Vega's diff --git a/CHANGELOG.md b/CHANGELOG.md index 40236a6316..702afeaf7e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). ### Changed +- **A microflow error handler is `on error [without rollback] begin … end error`; `while` takes `begin … end while` like `loop`** (ako/mxcli#754, R2) — braces hold declarative children and imperative flow is `begin … end `, and the error handler was the only brace block inside a microflow. `describe microflow` / `describe nanoflow` now print `commit $O on error begin … end error;` (an empty handler `… on error without rollback begin end error;`), and so do the generated test flows of `mxcli test`. Under the `mdl 1;` preview header a `while` without `begin`, or ending in a bare `end`, is an error; without the header it keeps parsing and warns **MDL-V1-WHILE**, and `mxcli fmt --upgrade --header` inserts the missing words. Measured on the Studio Pro-authored PedApp and TestApp fixtures: the round-trip harness stays green. **Migrating a script:** nothing breaks — `on error { … }` still parses, builds the identical handler, and `check` / `exec` warn **MDL-DEPR540**; `mxcli fmt --upgrade` rewrites it. +- **One argument form at every call site: `Param = expression`; one text-template form: `with ({1} = …)`** (ako/mxcli#751, R3/R4) — `call microflow`/`nanoflow`/`java action`/…, `execute database query`, `send rest request`, `show page`, and page/button actions and microflow/nanoflow data sources all bind an argument as `Order = $Order`, with no `$` on the parameter name and `=` rather than `:`. A workflow's `call microflow` / `call workflow` takes its arguments the same way, right after the callee, as bare expressions: `call microflow Shop.ACT_Process(Order = $WorkflowContext) as act1 comment 'Process'`. `show message` and `validation feedback` take `with ({1} = $a, {2} = $b)`, like `log` and the REST templates. `describe` writes these forms; it also writes a validation feedback's template arguments, which it used to drop, so describe → exec lost them. Measured on a copy of the Studio Pro-authored TestApp (Mendix 11.14.0): a script in the new forms and the same script in the old forms describe identically, and `mx check` reports 0 errors. **Migrating a script:** nothing breaks — `$Param = e` (**MDL-DEPR006**), `Param: e` (**MDL-DEPR007**), a workflow's `with (Param = '')` (**MDL-DEPR008**, same meaning: the string's content is the expression) and `objects [..]` / `parameters [..]` (**MDL-DEPR009**) still parse and build the same model, `check` / `exec` warn, and `mxcli fmt --upgrade` rewrites them. A workflow string whose content does not read back as the same bare expression is left in place and reported, and `describe` keeps the string form for such a stored expression. - **`describe page` no longer invents names Mendix does not store; `alter page` addresses a DataGrid 2 column by what it shows** (ako/mxcli#749, R12) — a layout grid's rows and columns, a data grid's columns and control bar, a gallery's template and filter, a chart's series and other object-list items have no Name in the model, and describe used to print one it made up (`row row1`, `column col3`, `controlbar controlBar1`, and a data-grid column named after its attribute, so two columns over `FullName` were both `column FullName`). They now print without one — `row { column (DesktopWidth: 6) { … } }`, `column (Attribute: FullName, Caption: 'Full name')` — and the widget name is optional in the grammar for exactly those elements. A column is addressed as `grid column(Attr)`, written as describe writes the attribute (`Owner/Name` over an association), or `grid column('Caption')`; an address two columns share is refused, listing the matches, unless `@n` picks one (`drop dg column(FullName)@2`). A widget Mendix does store a name for still needs one: `check` reports a missing one as **MDL-WIDGET35** and the page builder refuses it. Measured on the Studio Pro-authored PedApp fixture: the round-trip harness stays green, and `mx check` 11.13 shows no new error after `alter page … column(…)` edits and a create with nameless rows, grid columns and gallery slots. **Migrating a script:** nothing breaks — a name written on one of those elements still parses, is ignored as before, and `check` / `exec` warn **MDL-DEPR005**; `mxcli fmt --upgrade` removes it. The older `grid.Column` address keeps working. The info-level MDL-WIDGET16 note about dropped column names is gone, replaced by that warning. - **`describe microflow` / `describe nanoflow` leave out layout the engine derives** (ako/mxcli#748, R12) — `@position`, `@merge`, `@anchor` and `@curve` now follow the rule `@start` has followed since #951: an annotation is printed only when re-executing the description without it would put the node, or route the flow, somewhere else. That is decided by building the flow again from the description exactly as `create or modify` would (nothing is written) and comparing it node by node with the stored flow, pinning the first misplaced node of each run and repeating until nothing moves — so a statement placed after a hand-placed one needs no annotation of its own. A flow authored with no annotations describes with none; a Studio Pro-drawn flow keeps its layout. Measured on the Studio Pro-authored PedApp fixture: re-executing the new description lands every node, anchor and curve where the old one did (all 16 microflows, and all 224 microflows of a second Studio Pro project that re-execute at all); there the output barely shrinks (433 layout lines to 430), as it should, while after `mxcli layout flows` the 24 flows it could lay out go from 163 layout lines to 3. **Migrating a script:** nothing to do — every annotation still parses and means the same, and committed describe output re-executes as before; only a fresh `describe` is shorter. diff --git a/cmd/mxcli/cmd_fmt.go b/cmd/mxcli/cmd_fmt.go index 1bf949c83e..cb11464594 100644 --- a/cmd/mxcli/cmd_fmt.go +++ b/cmd/mxcli/cmd_fmt.go @@ -21,7 +21,12 @@ var fmtCmd = &cobra.Command{ Use: "fmt [file.mdl | -]", Short: "Format an MDL file", Long: `Format an MDL script file with consistent styling: - - Uppercase MDL keywords + - Lowercase MDL keywords, the canonical case. Only words the parse tree shows + are keywords change: a name spelled like a keyword (Issue64.User, an + attribute Title), a property key (Folder:) and a CamelCase value + (ButtonStyle: Success) keep their case, and so do expressions, XPath, OQL + and SQL, which are stored as written. Formatting never changes what a + script builds. - Normalize indentation (2-space units) - Remove trailing whitespace - Normalize blank lines diff --git a/cmd/mxcli/lsp_completions_gen.go b/cmd/mxcli/lsp_completions_gen.go index 19568e7d73..0b72282c2e 100644 --- a/cmd/mxcli/lsp_completions_gen.go +++ b/cmd/mxcli/lsp_completions_gen.go @@ -15,7 +15,7 @@ var mdlGeneratedKeywords = []protocol.CompletionItem{ {Label: "SORT BY", Kind: protocol.CompletionItemKindKeyword, Detail: "Multi-word keyword"}, {Label: "SEARCH BY", Kind: protocol.CompletionItemKindKeyword, Detail: "Multi-word keyword"}, {Label: "NON-PERSISTENT", Kind: protocol.CompletionItemKindKeyword, Detail: "Multi-word keyword"}, - {Label: "REFERENCE_SET", Kind: protocol.CompletionItemKindKeyword, Detail: "Multi-word keyword"}, + {Label: "REFERENCESET", Kind: protocol.CompletionItemKindKeyword, Detail: "Multi-word keyword"}, {Label: "LIST OF", Kind: protocol.CompletionItemKindKeyword, Detail: "Multi-word keyword"}, {Label: "DELETE AND REFERENCES", Kind: protocol.CompletionItemKindKeyword, Detail: "Multi-word keyword"}, {Label: "DELETE BUT KEEP REFERENCES", Kind: protocol.CompletionItemKindKeyword, Detail: "Multi-word keyword"}, @@ -314,8 +314,8 @@ var mdlGeneratedKeywords = []protocol.CompletionItem{ {Label: "GROUPBOX", Kind: protocol.CompletionItemKindKeyword, Detail: "Widget keyword"}, {Label: "VISIBLE", Kind: protocol.CompletionItemKindKeyword, Detail: "Widget keyword"}, {Label: "SAVECHANGES", Kind: protocol.CompletionItemKindKeyword, Detail: "Widget keyword"}, - {Label: "SAVE_CHANGES", Kind: protocol.CompletionItemKindKeyword, Detail: "Widget keyword"}, - {Label: "CANCEL_CHANGES", Kind: protocol.CompletionItemKindKeyword, Detail: "Widget keyword"}, + {Label: "SAVE CHANGES", Kind: protocol.CompletionItemKindKeyword, Detail: "Widget keyword"}, + {Label: "CANCEL CHANGES", Kind: protocol.CompletionItemKindKeyword, Detail: "Widget keyword"}, {Label: "CLOSE_PAGE", Kind: protocol.CompletionItemKindKeyword, Detail: "Widget keyword"}, {Label: "SHOW_PAGE", Kind: protocol.CompletionItemKindKeyword, Detail: "Widget keyword"}, {Label: "DELETE_ACTION", Kind: protocol.CompletionItemKindKeyword, Detail: "Widget keyword"}, @@ -324,7 +324,7 @@ var mdlGeneratedKeywords = []protocol.CompletionItem{ {Label: "CALL_MICROFLOW", Kind: protocol.CompletionItemKindKeyword, Detail: "Widget keyword"}, {Label: "CALL_NANOFLOW", Kind: protocol.CompletionItemKindKeyword, Detail: "Widget keyword"}, {Label: "OPEN_LINK", Kind: protocol.CompletionItemKindKeyword, Detail: "Widget keyword"}, - {Label: "SIGN_OUT", Kind: protocol.CompletionItemKindKeyword, Detail: "Widget keyword"}, + {Label: "SIGN OUT", Kind: protocol.CompletionItemKindKeyword, Detail: "Widget keyword"}, {Label: "CANCEL", Kind: protocol.CompletionItemKindKeyword, Detail: "Widget keyword"}, {Label: "PRIMARY", Kind: protocol.CompletionItemKindKeyword, Detail: "Widget keyword"}, {Label: "SUCCESS", Kind: protocol.CompletionItemKindKeyword, Detail: "Widget keyword"}, @@ -663,7 +663,7 @@ var mdlGeneratedKeywords = []protocol.CompletionItem{ {Label: "UNLOCK", Kind: protocol.CompletionItemKindKeyword, Detail: "Keyword"}, {Label: "REASON", Kind: protocol.CompletionItemKindKeyword, Detail: "Keyword"}, {Label: "OPEN", Kind: protocol.CompletionItemKindKeyword, Detail: "Keyword"}, - {Label: "COMPLETE_TASK", Kind: protocol.CompletionItemKindKeyword, Detail: "Keyword"}, + {Label: "COMPLETE TASK", Kind: protocol.CompletionItemKindKeyword, Detail: "Keyword"}, // Operator {Label: "MOD", Kind: protocol.CompletionItemKindKeyword, Detail: "Operator"}, diff --git a/cmd/mxcli/syntax/features_domain_model.go b/cmd/mxcli/syntax/features_domain_model.go index c16b2a2edd..8f094e8420 100644 --- a/cmd/mxcli/syntax/features_domain_model.go +++ b/cmd/mxcli/syntax/features_domain_model.go @@ -69,7 +69,7 @@ func init() { "generalization", "extends", "event handler", "attribute", }, Syntax: "CREATE PERSISTENT ENTITY Module.Name (\n Attr: Type [constraints],\n ...\n) [INDEX (attr1)];\n\n-- Documentation is the /** … */ doc comment before the statement.\n-- (A `COMMENT 'text'` option existed, set nothing, and has been removed.)\n\nCREATE NON-PERSISTENT ENTITY Module.Name (...);\n\nCREATE PERSISTENT ENTITY Module.Name EXTENDS Module.Parent (...);", - Example: "/** Stores customer information. */\nCREATE PERSISTENT ENTITY MyModule.Customer (\n Name: String(100) NOT NULL ERROR 'Name is required',\n Email: String(200) UNIQUE,\n Balance: Decimal DEFAULT 0,\n IsActive: Boolean DEFAULT true,\n Status: Enumeration(MyModule.CustomerType)\n)\nINDEX (Email);", + Example: "/** Stores customer information. */\nCREATE PERSISTENT ENTITY MyModule.Customer (\n Name: String(100) NOT NULL ERROR MESSAGE 'Name is required',\n Email: String(200) UNIQUE,\n Balance: Decimal DEFAULT 0,\n IsActive: Boolean DEFAULT true,\n Status: Enumeration(MyModule.CustomerType)\n)\nINDEX (Email);", SeeAlso: []string{"domain-model.entity.create", "domain-model.entity.alter", "domain-model.entity.attributes"}, }) @@ -287,8 +287,8 @@ func init() { "many-to-one", "many-to-many", "foreign key", "owner", "delete behavior", }, - Syntax: "[@anchor(from: (x, y), to: (x, y))]\nCREATE [OR MODIFY] ASSOCIATION Module.Name\n FROM Module.FromEntity TO Module.ToEntity\n TYPE Reference|ReferenceSet\n [OWNER Default|Both]\n [DELETE_BEHAVIOR behavior]\n [COMMENT 'text'];\nALTER ASSOCIATION Module.Name SET ANCHOR FROM (x, y) TO (x, y);\nDROP ASSOCIATION Module.Name;\n\nOR MODIFY: updates type/owner/delete behavior in-place, preserves UUID.\n\n@anchor sets the LINE ANCHORS — where the connector attaches to each entity box\nin the domain model editor — as a PERCENTAGE of the box (0..100, whole numbers).\n`from` is the FROM entity's box, `to` the TO entity's: (0, 50) is the middle of\nthe left edge, (100, 50) the right, (50, 100) the bottom centre. Omitting an end\nPRESERVES what is stored, so a CREATE OR MODIFY about something else never\nflattens a hand-tuned line. Cross-module associations have no anchors.\n\nDROP reconciles the entity access rules that named the association, and is\nREFUSED while a message definition still exposes it (that one cannot be\nreconciled -- removing the member would change a published contract). The\nrefusal prints the `alter message definition ... drop member` statement for\neach definition, ready to run.", - Example: "-- Many-to-one\nCREATE ASSOCIATION Shop.Order_Customer\n FROM Shop.Order TO Shop.Customer\n TYPE Reference\n OWNER Default\n DELETE_BEHAVIOR DELETE_BUT_KEEP_REFERENCES;\n\n-- Many-to-many\nCREATE ASSOCIATION Shop.Product_Tag\n FROM Shop.Product TO Shop.Tag\n TYPE ReferenceSet\n OWNER Both;\n\n-- Line leaving the bottom of Order and entering the top of Customer\n@anchor(from: (50, 100), to: (50, 0))\nCREATE ASSOCIATION Shop.Order_Customer\n FROM Shop.Order TO Shop.Customer;\n\n-- Retune the line without restating the association\nALTER ASSOCIATION Shop.Order_Customer SET ANCHOR FROM (0, 54) TO (100, 54);", + Syntax: "[@anchor(from: (x, y), to: (x, y))]\nCREATE [OR MODIFY] ASSOCIATION Module.Name\n FROM Module.FromEntity TO Module.ToEntity\n TYPE Reference|ReferenceSet\n [OWNER Default|Both]\n [ON DELETE CASCADE|RESTRICT|SET NULL [ERROR MESSAGE 'text']]\n [COMMENT 'text'];\nALTER ASSOCIATION Module.Name SET ANCHOR FROM (x, y) TO (x, y);\nDROP ASSOCIATION Module.Name;\n\nOR MODIFY: updates type/owner/delete behavior in-place, preserves UUID.\n\n@anchor sets the LINE ANCHORS — where the connector attaches to each entity box\nin the domain model editor — as a PERCENTAGE of the box (0..100, whole numbers).\n`from` is the FROM entity's box, `to` the TO entity's: (0, 50) is the middle of\nthe left edge, (100, 50) the right, (50, 100) the bottom centre. Omitting an end\nPRESERVES what is stored, so a CREATE OR MODIFY about something else never\nflattens a hand-tuned line. Cross-module associations have no anchors.\n\nDROP reconciles the entity access rules that named the association, and is\nREFUSED while a message definition still exposes it (that one cannot be\nreconciled -- removing the member would change a published contract). The\nrefusal prints the `alter message definition ... drop member` statement for\neach definition, ready to run.", + Example: "-- Many-to-one\nCREATE ASSOCIATION Shop.Order_Customer\n FROM Shop.Order TO Shop.Customer\n TYPE Reference\n OWNER Default\n ON DELETE SET NULL;\n\n-- Many-to-many\nCREATE ASSOCIATION Shop.Product_Tag\n FROM Shop.Product TO Shop.Tag\n TYPE ReferenceSet\n OWNER Both;\n\n-- Line leaving the bottom of Order and entering the top of Customer\n@anchor(from: (50, 100), to: (50, 0))\nCREATE ASSOCIATION Shop.Order_Customer\n FROM Shop.Order TO Shop.Customer;\n\n-- Retune the line without restating the association\nALTER ASSOCIATION Shop.Order_Customer SET ANCHOR FROM (0, 54) TO (100, 54);", SeeAlso: []string{"domain-model.association.create", "domain-model.association.anchor", "domain-model.association.delete-behavior"}, }) @@ -300,8 +300,8 @@ func init() { "reference set", "junction table", "foreign key", "owner default", "owner both", "storage column", "storage table", }, - Syntax: "CREATE [OR MODIFY] ASSOCIATION Module.AssociationName\n FROM Module.FromEntity TO Module.ToEntity\n TYPE Reference|ReferenceSet\n [OWNER Default|Both]\n [STORAGE COLUMN|TABLE]\n [DELETE_BEHAVIOR behavior]\n [COMMENT 'text'];\n\nDirection:\n FROM = entity holding the FK (the \"many\" side)\n TO = entity being referenced (the \"one\" side)\n\nTypes:\n Reference = Many-to-one (FK column on FROM table)\n ReferenceSet = Many-to-many (junction table)\n\nOR MODIFY: updates in-place, preserves UUID. Safe to re-run.", - Example: "-- Many-to-one with delete behavior\nCREATE ASSOCIATION Shop.Order_Customer\n FROM Shop.Order TO Shop.Customer\n TYPE Reference\n OWNER Default\n DELETE_BEHAVIOR PREVENT;\n\n-- Many-to-many\nCREATE ASSOCIATION Shop.Product_Tag\n FROM Shop.Product TO Shop.Tag\n TYPE ReferenceSet\n OWNER Both;\n\n-- Idempotent update\nCREATE OR MODIFY ASSOCIATION Shop.Order_Customer\n FROM Shop.Order TO Shop.Customer\n TYPE Reference\n OWNER Default\n DELETE_BEHAVIOR CASCADE;", + Syntax: "CREATE [OR MODIFY] ASSOCIATION Module.AssociationName\n FROM Module.FromEntity TO Module.ToEntity\n TYPE Reference|ReferenceSet\n [OWNER Default|Both]\n [STORAGE COLUMN|TABLE]\n [ON DELETE CASCADE|RESTRICT|SET NULL [ERROR MESSAGE 'text']]\n [COMMENT 'text'];\n\nDirection:\n FROM = entity holding the FK (the \"many\" side)\n TO = entity being referenced (the \"one\" side)\n\nTypes:\n Reference = Many-to-one (FK column on FROM table)\n ReferenceSet = Many-to-many (junction table)\n\nOR MODIFY: updates in-place, preserves UUID. Safe to re-run.", + Example: "-- Many-to-one with delete behavior\nCREATE ASSOCIATION Shop.Order_Customer\n FROM Shop.Order TO Shop.Customer\n TYPE Reference\n OWNER Default\n ON DELETE RESTRICT;\n\n-- Many-to-many\nCREATE ASSOCIATION Shop.Product_Tag\n FROM Shop.Product TO Shop.Tag\n TYPE ReferenceSet\n OWNER Both;\n\n-- Idempotent update\nCREATE OR MODIFY ASSOCIATION Shop.Order_Customer\n FROM Shop.Order TO Shop.Customer\n TYPE Reference\n OWNER Default\n ON DELETE CASCADE;", SeeAlso: []string{"domain-model.association.delete-behavior", "domain-model.entity.create"}, }) @@ -327,7 +327,7 @@ func init() { "A fractional coordinate is refused: Mendix stores two integers and its\n" + "loader will not OPEN a project whose anchor is fractional.\n" + "Cross-module associations have no anchors — Mendix stores none for them.", - Example: "-- Line leaving the bottom of Order, entering the top of Customer\n@anchor(from: (50, 100), to: (50, 0))\nCREATE ASSOCIATION Shop.Order_Customer\n FROM Shop.Order TO Shop.Customer\n TYPE Reference;\n\n-- Retune the line without restating the association\nALTER ASSOCIATION Shop.Order_Customer SET ANCHOR FROM (0, 54) TO (100, 54);\n\n-- Says nothing about anchors: whatever the line was dragged to survives\nCREATE OR MODIFY ASSOCIATION Shop.Order_Customer\n FROM Shop.Order TO Shop.Customer\n DELETE_BEHAVIOR CASCADE;", + Example: "-- Line leaving the bottom of Order, entering the top of Customer\n@anchor(from: (50, 100), to: (50, 0))\nCREATE ASSOCIATION Shop.Order_Customer\n FROM Shop.Order TO Shop.Customer\n TYPE Reference;\n\n-- Retune the line without restating the association\nALTER ASSOCIATION Shop.Order_Customer SET ANCHOR FROM (0, 54) TO (100, 54);\n\n-- Says nothing about anchors: whatever the line was dragged to survives\nCREATE OR MODIFY ASSOCIATION Shop.Order_Customer\n FROM Shop.Order TO Shop.Customer\n ON DELETE CASCADE;", SeeAlso: []string{"domain-model.association.create", "domain-model.entity"}, }) @@ -340,7 +340,7 @@ func init() { "delete and references", "delete but keep references", "delete if no references", "referential integrity", }, - Syntax: "ON DELETE [ERROR_MESSAGE '']\n" + + Syntax: "ON DELETE [ERROR MESSAGE '']\n" + " ON DELETE SET NULL Keep the referencing objects, clear the reference (default)\n" + " ON DELETE CASCADE Delete the referencing objects too\n" + " ON DELETE RESTRICT Refuse the delete while references exist\n\n" + @@ -349,19 +349,18 @@ func init() { "key, TO is referenced -- so `FROM Order TO Customer ON DELETE RESTRICT` means\n" + "deleting a CUSTOMER is refused while Orders reference it, the same way it would\n" + "in CREATE TABLE.\n\n" + - "ERROR_MESSAGE is what the user sees when a RESTRICT delete is refused (Studio\n" + + "ERROR MESSAGE is what the user sees when a RESTRICT delete is refused (Studio\n" + "Pro's \"Error message if 'X' object cannot be deleted\"). SQL has no equivalent;\n" + "this is a Mendix extension. Omitting it stores an empty message.\n\n" + - "The older spelling still works and means the same thing:\n" + - " DELETE_BEHAVIOR DELETE_BUT_KEEP_REFERENCES | DELETE_AND_REFERENCES\n" + - " | DELETE_IF_NO_REFERENCES | CASCADE | PREVENT\n" + - " [ERROR_MESSAGE '']\n" + - "DESCRIBE emits the ON DELETE form, because it says which side is governed.", + "The older DELETE_BEHAVIOR clause (DELETE_AND_REFERENCES | CASCADE,\n" + + "DELETE_IF_NO_REFERENCES | PREVENT, DELETE_BUT_KEEP_REFERENCES) still parses and\n" + + "means the same thing, but is deprecated (MDL-DEPR022); `mxcli fmt --upgrade`\n" + + "rewrites it. DESCRIBE emits the ON DELETE form, because it says which side is governed.", Example: "CREATE ASSOCIATION Shop.Order_Customer\n" + " FROM Shop.Order TO Shop.Customer\n" + " TYPE Reference\n" + " ON DELETE RESTRICT\n" + - " ERROR_MESSAGE 'A customer with orders cannot be deleted';\n\n" + + " ERROR MESSAGE 'A customer with orders cannot be deleted';\n\n" + "CREATE ASSOCIATION Shop.Order_Lines\n" + " FROM Shop.OrderLine TO Shop.Order\n" + " TYPE Reference\n" + diff --git a/cmd/mxcli/syntax/features_integration.go b/cmd/mxcli/syntax/features_integration.go index cb98e9b715..48e1093fbf 100644 --- a/cmd/mxcli/syntax/features_integration.go +++ b/cmd/mxcli/syntax/features_integration.go @@ -148,7 +148,7 @@ func init() { "-- Take a $Request: System.HttpRequest parameter to see the query string.\n" + "-- MDL-ODATA02 and MDL-ODATA03 flag a read microflow that takes none.", Example: "create persistent entity Shop.Customer (\n" + - " Email: string(200) unique error 'unique' required error 'required',\n" + + " Email: string(200) unique error message 'unique' required error message 'required',\n" + " Name: string(200)\n" + ");\n" + "\n" + @@ -218,7 +218,7 @@ func init() { "RETURNS Module.MyFile -- store the body in a file document\n" + "RETURNS MAPPING Module.IMM AS Module.E -- apply an import mapping (single object)\n" + "RETURNS MAPPING Module.IMM AS LIST OF Module.E\n" + - "RETURNS NONE | NOTHING -- ignore the response\n\n" + + "RETURNS NOTHING -- ignore the response\n\n" + "-- The file document form takes a SPECIALIZATION of System.FileDocument.\n" + "-- Mendix rejects the base type as a return type (CE0362), and MDL064\n" + "-- reports that before the write. There is no matching form for an\n" + diff --git a/cmd/mxcli/syntax/features_microflow.go b/cmd/mxcli/syntax/features_microflow.go index 74d1d3af0e..660db845fc 100644 --- a/cmd/mxcli/syntax/features_microflow.go +++ b/cmd/mxcli/syntax/features_microflow.go @@ -23,7 +23,7 @@ func init() { "offline first", "nanoflow", }, Syntax: "SYNCHRONIZE ALL [ON ERROR ...];\nSYNCHRONIZE UNSYNCHRONIZED [ON ERROR ...]; -- Mendix 9.4+\nSYNCHRONIZE $Var[, $Var...] [ON ERROR ...];\n\nNanoflow only: in a microflow this is MDL057 / CE0009.", - Example: "CREATE NANOFLOW MyModule.NF_Sync ($Order: MyModule.Order)\nBEGIN\n SYNCHRONIZE ALL;\n SYNCHRONIZE UNSYNCHRONIZED;\n SYNCHRONIZE $Order;\n SYNCHRONIZE ALL ON ERROR WITHOUT ROLLBACK {\n LOG ERROR 'sync failed';\n };\nEND;", + Example: "CREATE NANOFLOW MyModule.NF_Sync ($Order: MyModule.Order)\nBEGIN\n SYNCHRONIZE ALL;\n SYNCHRONIZE UNSYNCHRONIZED;\n SYNCHRONIZE $Order;\n SYNCHRONIZE ALL ON ERROR WITHOUT ROLLBACK BEGIN\n LOG ERROR 'sync failed';\n END ERROR;\nEND;", SeeAlso: []string{"microflow.nanoflow", "microflow.error-handling"}, }) @@ -117,7 +117,10 @@ func init() { "loop", "while", "break", "continue", "return", "conditional", "branch", "iterate", }, - Syntax: "IF condition THEN\n ...\nELSIF condition THEN\n ...\nELSE\n ...\nEND IF;\n\nLOOP $Item IN $List BEGIN ... END LOOP;\nWHILE condition BEGIN ... END WHILE;\nRETURN $Value;\nRETURN empty;", + Syntax: "IF condition THEN\n ...\nELSIF condition THEN\n ...\nELSE\n ...\nEND IF;\n\nLOOP $Item IN $List BEGIN ... END LOOP;\nWHILE condition BEGIN ... END WHILE;\nRETURN $Value;\nRETURN empty;\n\n" + + "-- WHILE takes BEGIN and END WHILE like LOOP. Without the `mdl 1;` header both\n" + + "-- may still be left out, with a warning (MDL-V1-WHILE); under `mdl 1;` that\n" + + "-- is an error. `mxcli fmt --upgrade --header` inserts them.", Example: "IF $Customer = empty THEN\n LOG ERROR NODE 'Svc' 'Not found';\n RETURN empty;\nELSIF $Customer/Active = false THEN\n LOG WARNING 'Inactive customer';\nELSE\n CHANGE $Customer (LastAccess = [%CurrentDateTime%]);\nEND IF;\n\nLOOP $Item IN $OrderLines BEGIN\n COMMIT $Item;\nEND LOOP;", SeeAlso: []string{"microflow.variables", "microflow.error-handling", "microflow.splits"}, }) @@ -160,7 +163,11 @@ func init() { "rollback", "throw", "exception", "try", "catch", }, Syntax: "COMMIT $Obj ON ERROR CONTINUE;\nCOMMIT $Obj ON ERROR ROLLBACK;\n" + - "COMMIT $Obj ON ERROR { };\nCOMMIT $Obj ON ERROR WITHOUT ROLLBACK { };\n\n" + + "COMMIT $Obj ON ERROR BEGIN END ERROR;\nCOMMIT $Obj ON ERROR WITHOUT ROLLBACK BEGIN END ERROR;\n\n" + + "-- The handler is flow, so it is BEGIN ... END ERROR like IF, LOOP and WHILE.\n" + + "-- The brace form `ON ERROR { ... }` still parses and warns MDL-DEPR540;\n" + + "-- `mxcli fmt --upgrade` rewrites it.\n" + + "--\n" + "-- The clause goes on the ACTIVITY that may fail. Most statements take it:\n" + "-- DECLARE, SET, CREATE, CHANGE, COMMIT, DELETE, RETRIEVE, every CALL,\n" + "-- LOG, SHOW PAGE, CLOSE PAGE, SHOW MESSAGE, VALIDATION FEEDBACK,\n" + @@ -189,7 +196,7 @@ func init() { "-- flow, so a variable created after the merge is out of scope on the error\n" + "-- path (CE0108). End the handler, or expect that.\n" + "--\n" + - "-- An EMPTY handler `{ }` is not a no-op: it means \"on error, do whatever\n" + + "-- An EMPTY handler `BEGIN END ERROR` is not a no-op: it means \"on error, do whatever\n" + "-- the enclosing branch does next\". Say where the path goes with JOIN.\n" + "--\n" + "-- RAISE ERROR re-raises the error being handled, so it belongs INSIDE an\n" + @@ -198,9 +205,9 @@ func init() { "-- the shape, and mxbuild rejects it with CE0710 \"The main flow cannot\n" + "-- join an error flow or end in an error event.\". To fail deliberately\n" + "-- from the main flow, call a Java action that throws.", - Example: "COMMIT $Order ON ERROR {\n LOG ERROR 'Failed to save order';\n RAISE ERROR;\n};\n\n" + - "COMMIT $Batch ON ERROR WITHOUT ROLLBACK {\n LOG WARNING 'Batch save failed, continuing';\n};\n\n" + - "DECLARE $Name String = 'default' ON ERROR {\n RETURN 'could not initialise';\n};", + Example: "COMMIT $Order ON ERROR BEGIN\n LOG ERROR 'Failed to save order';\n RAISE ERROR;\nEND ERROR;\n\n" + + "COMMIT $Batch ON ERROR WITHOUT ROLLBACK BEGIN\n LOG WARNING 'Batch save failed, continuing';\nEND ERROR;\n\n" + + "DECLARE $Name String = 'default' ON ERROR BEGIN\n RETURN 'could not initialise';\nEND ERROR;", SeeAlso: []string{"microflow.control-flow"}, }) @@ -295,14 +302,14 @@ func init() { "-- Forward and backward references both resolve, so declaration order is\n" + "-- free. A backward one is how a retry loop is written:\n" + "-- MERGE attempt;\n" + - "-- $r = CALL MICROFLOW M.Post() ON ERROR WITHOUT ROLLBACK { JOIN attempt; };\n" + + "-- $r = CALL MICROFLOW M.Post() ON ERROR WITHOUT ROLLBACK BEGIN JOIN attempt; END ERROR;\n" + "--\n" + "-- What this is FOR. Nested IF can only describe a graph whose branches\n" + "-- pair up. Two cases do not:\n" + "-- 1. An ERROR path that rejoins the normal one somewhere other than the\n" + "-- enclosing branch's own continuation. Without JOIN the only\n" + "-- spellings are \"terminate\" and \"fall through\", and DESCRIBE used to\n" + - "-- emit an empty `{ }` for anything else — MDL that re-executes to a\n" + + "-- emit an empty handler for anything else — MDL that re-executes to a\n" + "-- DIFFERENT graph, with no warning.\n" + "-- 2. Crossed branches: an inner split's branch landing where an outer\n" + "-- split's branch lands. No nesting of IF reproduces that.\n" + @@ -326,11 +333,11 @@ func init() { Example: "CREATE MICROFLOW M.Post (Payload: String) RETURNS String\n" + "BEGIN\n" + " DECLARE $Status String = 'sent';\n" + - " $r = CALL MICROFLOW M.Send(Payload = $Payload) ON ERROR WITHOUT ROLLBACK {\n" + + " $r = CALL MICROFLOW M.Send(Payload = $Payload) ON ERROR WITHOUT ROLLBACK BEGIN\n" + " LOG WARNING NODE 'M' 'send failed, degrading';\n" + " SET $Status = 'degraded';\n" + " JOIN recovered;\n" + - " };\n" + + " END ERROR;\n" + " JOIN recovered;\n" + " MERGE recovered;\n" + " RETURN $Status;\n" + @@ -501,8 +508,8 @@ func init() { "show page", "open page", "close page", "display page", "navigate", "page action", }, - Syntax: "SHOW PAGE Module.Page;\nSHOW PAGE Module.Page ($Param = $value);\nCLOSE PAGE;", - Example: "SHOW PAGE MyModule.OrderDetail ($Order = $NewOrder);\nCLOSE PAGE;", + Syntax: "SHOW PAGE Module.Page;\nSHOW PAGE Module.Page (Param = $value);\nCLOSE PAGE;", + Example: "SHOW PAGE MyModule.OrderDetail (Order = $NewOrder);\nCLOSE PAGE;", SeeAlso: []string{"page"}, }) @@ -592,8 +599,8 @@ func init() { "validation", "feedback", "validation feedback", "error message", "field error", "form validation", }, - Syntax: "VALIDATION FEEDBACK $Obj/Attr MESSAGE 'error text';\nVALIDATION FEEDBACK $Obj/Attr MESSAGE '{1} is invalid'\n OBJECTS [$Value];", - Example: "VALIDATION FEEDBACK $Order/Quantity MESSAGE 'Quantity must be positive';\nVALIDATION FEEDBACK $Customer/Email MESSAGE '{1} is not valid'\n OBJECTS [$Customer/Email];", + Syntax: "VALIDATION FEEDBACK $Obj/Attr MESSAGE 'error text';\nVALIDATION FEEDBACK $Obj/Attr MESSAGE '{1} is invalid'\n WITH ({1} = $Value);", + Example: "VALIDATION FEEDBACK $Order/Quantity MESSAGE 'Quantity must be positive';\nVALIDATION FEEDBACK $Customer/Email MESSAGE '{1} is not valid'\n WITH ({1} = $Customer/Email);", SeeAlso: []string{"microflow.error-handling"}, }) diff --git a/cmd/mxcli/syntax/features_misc.go b/cmd/mxcli/syntax/features_misc.go index 2334c55fa4..dcf2255adf 100644 --- a/cmd/mxcli/syntax/features_misc.go +++ b/cmd/mxcli/syntax/features_misc.go @@ -695,11 +695,11 @@ SHOW REFERENCES TO Val.EmailAddress;`, }, Syntax: `CREATE VALIDATION RULE FOR Module.Entity.Attribute REGEX Module.PatternName - FEEDBACK ''; + ERROR MESSAGE ''; CREATE VALIDATION RULE FOR Module.Entity.Attribute RANGE FROM TO - FEEDBACK ''; + ERROR MESSAGE ''; The bounds are inclusive and either may be omitted: RANGE FROM 1 TO 100 between 1 and 100 @@ -720,24 +720,24 @@ CE0135 "No regular expression specified" at build time. REQUIRED and UNIQUE rules are written as attribute constraints instead, on CREATE ENTITY or ALTER ENTITY: ALTER ENTITY Shop.Product MODIFY ATTRIBUTE Email string(200) - NOT NULL ERROR 'Email is required'; + NOT NULL ERROR MESSAGE 'Email is required'; ALTER ENTITY Shop.Product MODIFY ATTRIBUTE Code string(20) - UNIQUE ERROR 'Code must be unique';`, + UNIQUE ERROR MESSAGE 'Code must be unique';`, Example: `CREATE REGULAR EXPRESSION Shop.EmailPattern ( Expression: '^[^@\s]+@[^@\s]+\.[^@\s]+$' ); CREATE VALIDATION RULE FOR Shop.Customer.Email REGEX Shop.EmailPattern - FEEDBACK 'Enter a valid email address'; + ERROR MESSAGE 'Enter a valid email address'; CREATE VALIDATION RULE FOR Shop.Booking.Guests RANGE FROM 1 TO 100 - FEEDBACK 'Between 1 and 100 guests are allowed'; + ERROR MESSAGE 'Between 1 and 100 guests are allowed'; CREATE VALIDATION RULE FOR Shop.Product.Price RANGE FROM 0 - FEEDBACK 'Price cannot be negative';`, + ERROR MESSAGE 'Price cannot be negative';`, }) // ── Scheduled events ──────────────────────────────────────────────── diff --git a/cmd/mxcli/syntax/features_page.go b/cmd/mxcli/syntax/features_page.go index c8952bf9a9..81e6b357bd 100644 --- a/cmd/mxcli/syntax/features_page.go +++ b/cmd/mxcli/syntax/features_page.go @@ -13,7 +13,7 @@ func init() { "widget", "layout", "screen", }, Syntax: "CREATE PAGE Module.Name\n (\n Title: 'Page Title',\n Layout: Module.LayoutName\n [, Params: { $Param: Module.Entity }]\n [, Url: 'page-url']\n [, Folder: 'FolderPath']\n [, Variables: { $var: Boolean = 'true' }]\n [, PopupWidth: 800, PopupHeight: 480, PopupResizable: true]\n [, PopupCloseAction: cancelButton1]\n [, Class: 'css-class', Style: 'css: rule']\n )\n {\n -- widgets\n }", - Example: "CREATE PAGE MyModule.EditCustomer\n (\n Params: { $Customer: MyModule.Customer },\n Title: 'Edit Customer',\n Layout: Atlas_Core.PopupLayout,\n Class: 'container-fluid'\n )\n {\n DATAVIEW dvCustomer (DataSource: $Customer) {\n TEXTBOX txtName (Label: 'Name', Attribute: Name)\n FOOTER footer1 {\n ACTIONBUTTON btnSave (Caption: 'Save', Action: SAVE_CHANGES, ButtonStyle: Primary)\n ACTIONBUTTON btnCancel (Caption: 'Cancel', Action: CANCEL_CHANGES)\n }\n }\n }", + Example: "CREATE PAGE MyModule.EditCustomer\n (\n Params: { $Customer: MyModule.Customer },\n Title: 'Edit Customer',\n Layout: Atlas_Core.PopupLayout,\n Class: 'container-fluid'\n )\n {\n DATAVIEW dvCustomer (DataSource: $Customer) {\n TEXTBOX txtName (Label: 'Name', Attribute: Name)\n FOOTER footer1 {\n ACTIONBUTTON btnSave (Caption: 'Save', Action: SAVE CHANGES, ButtonStyle: Primary)\n ACTIONBUTTON btnCancel (Caption: 'Cancel', Action: CANCEL CHANGES)\n }\n }\n }", SeeAlso: []string{"page.create", "page.widgets", "page.alter", "snippet"}, }) @@ -122,7 +122,7 @@ CREATE PAGE Sales.Detail (Title: 'Detail', Layout: Atlas_Core.Atlas_Default) { "statictext", "staticimage", "dynamicimage", "dropdown", "referenceselector", "legacydatagrid", }, - Syntax: "-- Containers\nLAYOUTGRID name { ROW r { COLUMN c (DesktopWidth: 6) { ... } } }\nCONTAINER name (Class: 'cls') { ... }\nCONTAINER name (OnClick: MICROFLOW Module.MF) { ... } -- clickable container\nCUSTOMCONTAINER name (Class: 'cls') { ... }\nGROUPBOX name (Caption: 'C') { ... }\nTABCONTAINER name { TABPAGE tp (Caption: 'One') { ... } TABPAGE tp2 (Caption: 'Two') { ... } }\n\n" + + Syntax: "-- Containers\nLAYOUTGRID name { ROW r { COLUMN c (DesktopWidth: 6) { ... } } }\nCONTAINER name (Class: 'cls') { ... }\nCONTAINER name (OnClick: CALL MICROFLOW Module.MF) { ... } -- clickable container\nCUSTOMCONTAINER name (Class: 'cls') { ... }\nGROUPBOX name (Caption: 'C') { ... }\nTABCONTAINER name { TABPAGE tp (Caption: 'One') { ... } TABPAGE tp2 (Caption: 'Two') { ... } }\n\n" + "-- Data widgets\nDATAVIEW name (DataSource: $Param) { ... FOOTER f { ... } }\nDATAGRID name (DataSource: DATABASE Module.Entity) { COLUMN c (Attribute: A) }\nGALLERY name (DataSource: DATABASE Module.Entity, DesktopColumns: 3) { ... }\nLISTVIEW name (DataSource: DATABASE Module.Entity) { ... }\nLISTVIEW name (...) { ... TEMPLATE FOR Module.Specialization { ... } }\n\n" + "-- Data grid 2 column filters go INSIDE the column's own braces\nDATAGRID dg (...) { COLUMN c (Attribute: A) { TEXTFILTER tf (Attribute: A) } }\nTEXTFILTER | NUMBERFILTER | DATEFILTER | DROPDOWNFILTER | DROPDOWNSORT\n" + "-- Match the filter to the column's type, or MxBuild refuses it: String ->\n" + @@ -144,7 +144,7 @@ CREATE PAGE Sales.Detail (Title: 'Detail', Layout: Atlas_Core.Atlas_Default) { "-- container the grid declares — it used to be dropped on write with no\n" + "-- diagnostic, and is now refused (MDL-WIDGET30).\n\n" + "-- Inputs\nTEXTBOX name (Label: 'L', Attribute: Attr)\nTEXTAREA | DATEPICKER | COMBOBOX | CHECKBOX | RADIOBUTTONS\n\n" + - "-- Actions\nACTIONBUTTON name (Caption: 'C', Action: SAVE_CHANGES, ButtonStyle: Primary)\nLINKBUTTON name (Caption: 'C', Action: ...)\n\n" + + "-- Actions\nACTIONBUTTON name (Caption: 'C', Action: SAVE CHANGES, ButtonStyle: Primary)\nLINKBUTTON name (Caption: 'C', Action: ...)\n\n" + "-- Display\nDYNAMICTEXT name (Content: 'Hello, {1}!', ContentParams: [{1} = Name])\nTITLE name (Content: 'Heading')\nIMAGE name (Image: 'Module.Collection.ImageName')\nIMAGE name (ImageType: imageUrl, ImageUrl: 'https://…')\n" + "-- IMAGE needs a source. Its default, `ImageType: image`, shows an entry from an\n" + "-- image collection, named as three parts: Module.Collection.ImageName.\n" + @@ -187,7 +187,7 @@ CREATE PAGE Sales.Detail (Title: 'Detail', Layout: Atlas_Core.Atlas_Default) { "-- Use DYNAMICTEXT with a literal Content.)\n" + "-- REFERENCESELECTOR (unsupported widget type)\n" + "-- LEGACYDATAGRID (use DATAGRID for the pluggable equivalent on Mendix 11+)", - Example: "DATAVIEW dvCustomer (DataSource: $Customer) {\n TEXTBOX txtName (Label: 'Name', Attribute: Name)\n COMBOBOX cbStatus (Label: 'Status', Attribute: Status)\n FOOTER footer1 {\n ACTIONBUTTON btnSave (Caption: 'Save', Action: SAVE_CHANGES, ButtonStyle: Primary)\n ACTIONBUTTON btnCancel (Caption: 'Cancel', Action: CANCEL_CHANGES)\n }\n}", + Example: "DATAVIEW dvCustomer (DataSource: $Customer) {\n TEXTBOX txtName (Label: 'Name', Attribute: Name)\n COMBOBOX cbStatus (Label: 'Status', Attribute: Status)\n FOOTER footer1 {\n ACTIONBUTTON btnSave (Caption: 'Save', Action: SAVE CHANGES, ButtonStyle: Primary)\n ACTIONBUTTON btnCancel (Caption: 'Cancel', Action: CANCEL CHANGES)\n }\n}", SeeAlso: []string{"page.create", "page.datasource"}, }) @@ -244,7 +244,7 @@ CREATE PAGE Sales.Detail (Title: 'Detail', Layout: Atlas_Core.Atlas_Default) { "datasource", "data source", "database", "microflow", "selection", "variable", "binding", "binds", "association", "data from context", }, - Syntax: "DataSource: $Variable -- Parameter/variable binding\nDataSource: DATABASE Module.Entity -- Database query\nDataSource: DATABASE Module.Entity WHERE [Attr != ''] SORT BY Attr ASC\n -- ...optionally constrained and sorted\nDataSource: DATABASE Module.Entity SORT BY Module.Assoc/Attr ASC\n -- ...sorted over an association. Name the\n -- hop when two reach the same entity —\n -- the wrong one builds cleanly and sorts\n -- by the wrong thing.\nDataSource: DATABASE Module.Entity SEARCH BY Attr, Attr2\n -- LIST VIEW only: the attributes its\n -- search bar filters on. Mirrors SORT BY,\n -- but takes no direction.\nDataSource: MICROFLOW Module.MF -- Microflow datasource, no parameters\nDataSource: MICROFLOW Module.MF(Param: $P) -- ...one argument per PARAMETER, required,\n -- and NAMED: the positional form\n -- MF($P) is a parse error. Mendix does\n -- NOT auto-map an object in scope, not\n -- even one of the exact type, so a\n -- missing argument is CE1571\nDataSource: SELECTION widgetName -- Selection from another widget\nDataSource: $currentObject/Module.Assoc -- Over an association (\"data from context\")\n -- list widget → to-many collection\n -- nested DATAVIEW → the to-one referenced object\nAttribute: AttributeName -- Attribute binding (inputs)", + Syntax: "DataSource: $Variable -- Parameter/variable binding\nDataSource: DATABASE Module.Entity -- Database query\nDataSource: DATABASE Module.Entity WHERE [Attr != ''] SORT BY Attr ASC\n -- ...optionally constrained and sorted\nDataSource: DATABASE Module.Entity SORT BY Module.Assoc/Attr ASC\n -- ...sorted over an association. Name the\n -- hop when two reach the same entity —\n -- the wrong one builds cleanly and sorts\n -- by the wrong thing.\nDataSource: DATABASE Module.Entity SEARCH BY Attr, Attr2\n -- LIST VIEW only: the attributes its\n -- search bar filters on. Mirrors SORT BY,\n -- but takes no direction.\nDataSource: MICROFLOW Module.MF -- Microflow datasource, no parameters\nDataSource: MICROFLOW Module.MF(Param = $P) -- ...one argument per PARAMETER, required,\n -- and NAMED: the positional form\n -- MF($P) is a parse error. Mendix does\n -- NOT auto-map an object in scope, not\n -- even one of the exact type, so a\n -- missing argument is CE1571\nDataSource: SELECTION widgetName -- Selection from another widget\nDataSource: $currentObject/Module.Assoc -- Over an association (\"data from context\")\n -- list widget → to-many collection\n -- nested DATAVIEW → the to-one referenced object\nAttribute: AttributeName -- Attribute binding (inputs)", Example: "-- Database datasource with grid\nDATAGRID grid (DataSource: DATABASE Module.Customer) {\n COLUMN colName (Attribute: Name, Caption: 'Name')\n}\n\n-- Microflow datasource\nDATAVIEW dv (DataSource: MICROFLOW Module.GetData) {\n TEXTBOX txtName (Label: 'Name', Attribute: Name)\n}\n\n-- Over an association: a nested DataView shows the referenced (to-one) object\nDATAVIEW dvOrder (DataSource: $Order) {\n DATAVIEW dvCustomer (DataSource: $currentObject/Order_Customer) {\n TEXTBOX txtCustName (Label: 'Name', Attribute: Name)\n }\n}\n\n-- Over an association: a list widget shows the (to-many) collection\nLISTVIEW lvLines (DataSource: $currentObject/Order_OrderLine) {\n DYNAMICTEXT dtLine (Content: 'Line')\n}", SeeAlso: []string{"page.widgets", "page.create"}, }) @@ -259,8 +259,8 @@ CREATE PAGE Sales.Detail (Title: 'Detail', Layout: Atlas_Core.Atlas_Default) { "icon", "linkbutton", "link button", "nothing", "no action", "inert", "dead button", }, - Syntax: "Action: NOTHING -- deliberately no action (Forms$NoAction)\nAction: SAVE_CHANGES\nAction: SAVE_CHANGES CLOSE_PAGE -- save, then close the pop-up\nAction: CANCEL_CHANGES\nAction: CANCEL_CHANGES CLOSE_PAGE\nAction: CLOSE_PAGE\nAction: DELETE\nAction: DELETE CLOSE_PAGE\nAction: DELETE_OBJECT\nAction: NANOFLOW Module.NF\nAction: NANOFLOW Module.NF(Param: $val)\nAction: OPEN_LINK 'https://example.com'\nAction: OPEN_LINK $currentObject/URL -- address read from an attribute\nAction: SIGN_OUT\nAction: COMPLETE_TASK 'OutcomeName'\nAction: SHOW_PAGE Module.Page\nAction: SHOW_PAGE Module.Page(Param: $currentObject)\nAction: MICROFLOW Module.MF\nAction: MICROFLOW Module.MF(Param: $val)\nAction: CREATE_OBJECT Module.Entity THEN SHOW_PAGE Module.Page\n\nThe list above is exhaustive. Anything else in an action slot is an\nERROR (MDL-WIDGET28), including a real keyword short its argument --\n`Action: OPEN_LINK` without a URL, `Action: SHOW_PAGE` without a page.\nSuch a widget used to be written with NO action at all and rendered as a\ndead control, with check, exec and mxbuild all clean, because a\nno-action widget is legal Mendix (mendixlabs/mxcli#1062). Write NOTHING\nwhen a control really is meant to be inert.\n\nThe same forms serve `OnClick:` (an alias of `Action:`) and `OnChange:`.\n\nA microflow or nanoflow action is a CALL: it needs an argument for every\nparameter the flow declares, or Mendix rejects the page with CE1571. The\nargument list is the same on every widget that takes an action -- a\nCONTAINER (which is clickable) as much as an ACTIONBUTTON. An enclosing\ndata container of the right type supplies it without an argument; a data\ngrid's CONTROL BAR does not, because it is not row-scoped -- pass the\ngrid's selection there (`$dgOrders`).\n\nA SHOW_PAGE argument must be the enclosing widget's context object --\neither $currentObject or the name of the variable the enclosing data\nwidget is bound to. Mendix infers it from that widget, so naming any\nother variable is refused (MDL-PAGEARG01); call a microflow instead.\nOutside any data widget there is no context object to infer, so such a\nbutton takes NO argument at all -- not a page parameter, not\n$currentObject, not a literal. mxcli used to drop it in silence and\nmxbuild then reported CE1571 per parameter of the target page\n(mendixlabs/mxcli#1029). Route that navigation through a microflow.\n\nOPEN_LINK takes a web address, stored as a Forms$StaticOrDynamicString:\neither a literal, or $currentObject/Attr to read it from an attribute of\nthe enclosing data container's object at runtime (Studio Pro's \"Address:\nattribute\"). An address over an association path is not supported yet.\n\nButton styles: Default, Primary, Success, Info, Warning, Danger\n\nMendix has THREE icon elements and the keyword picks which one:\n\nIcon: 'Atlas_Core.Atlas_Filled.pencil' -- an icon collection\nIcon: image MyModule.Images.logo -- an IMAGE collection\nIcon: glyph 57377 -- a font code point\n\nThe bare form is the icon-collection icon and any collection in the\nproject works, third-party ones included. The image form points into a\ndifferent document, and is spelled the same way apart from the keyword\n-- write it without `image` and mxcli stores a custom-icon reference,\nwhich fails the build with CE1613 (mendixlabs/mxcli#1059).\n`mxcli check -p … --references` resolves each kind against its own\ncollection and names the remedy when the kind is wrong.\n\nA glyph carries a code and no name. Codes are sparse, and an undefined\none fails only at `mxbuild --target=deploy`, naming the PAGE rather\nthan the icon -- so MDL078 checks it against the font's own table.\nList them with `show glyphs`.\n\nA name may be quoted or bare; a hyphenated segment is double-quoted on\nits own: Atlas_Core.Atlas.\"align-center\".\n\nUse `linkbutton` instead of `actionbutton` for link render mode (same properties).", - Example: "ACTIONBUTTON btnSave (Caption: 'Save', Action: SAVE_CHANGES, ButtonStyle: Primary)\nACTIONBUTTON btnEdit (Caption: 'Edit',\n Action: SHOW_PAGE Module.EditPage(Item: $currentObject))\nLINKBUTTON btnDelete (Caption: 'Delete', Action: DELETE,\n Icon: 'Atlas_Core.Atlas_Filled.pencil')\n\n-- A clickable CONTAINER in a data grid's control bar, calling a nanoflow\n-- with the grid's selection as its argument.\nDATAGRID dgOrders (DataSource: DATABASE FROM Sales.Order, Selection: Single) {\n COLUMN colNr (Attribute: Number, Caption: 'Order #')\n CONTROLBAR cb {\n CONTAINER cShip (Class: 'command',\n Action: NANOFLOW Sales.ACT_Ship($Order = $dgOrders)) {\n ACTIONBUTTON btnShip (Caption: 'Ship')\n }\n }\n}", + Syntax: "Action: NOTHING -- deliberately no action (Forms$NoAction)\nAction: SAVE CHANGES\nAction: SAVE CHANGES CLOSE PAGE -- save, then close the pop-up\nAction: CANCEL CHANGES\nAction: CANCEL CHANGES CLOSE PAGE\nAction: CLOSE PAGE\nAction: DELETE\nAction: DELETE CLOSE PAGE\nAction: CALL NANOFLOW Module.NF\nAction: CALL NANOFLOW Module.NF(Param = $val)\nAction: OPEN LINK 'https://example.com'\nAction: OPEN LINK $currentObject/URL -- address read from an attribute\nAction: SIGN OUT\nAction: COMPLETE TASK 'OutcomeName'\nAction: SHOW PAGE Module.Page\nAction: SHOW PAGE Module.Page(Param = $currentObject)\nAction: CALL MICROFLOW Module.MF\nAction: CALL MICROFLOW Module.MF(Param = $val)\nAction: CREATE OBJECT Module.Entity THEN SHOW PAGE Module.Page\n\nThe list above is exhaustive. Anything else in an action slot is an\nERROR: `Action: OPEN LINK` without a URL or `Action: SHOW PAGE` without a\npage is a syntax error, and a value that is not an action at all\n(or a snake-case keyword short its argument) is MDL-WIDGET28.\nSuch a widget used to be written with NO action at all and rendered as a\ndead control, with check, exec and mxbuild all clean, because a\nno-action widget is legal Mendix (mendixlabs/mxcli#1062). Write NOTHING\nwhen a control really is meant to be inert.\n\nThe actions are the words a microflow uses (R8). The snake-case spellings\n(SAVE_CHANGES, SHOW_PAGE, CLOSE_PAGE, DELETE_OBJECT, OPEN_LINK, SIGN_OUT, ...)\nand a flow call without CALL still parse, but are deprecated (MDL-DEPR020);\n`mxcli fmt --upgrade` rewrites them.\n\nThe same forms serve `OnClick:` (an alias of `Action:`) and `OnChange:`.\n\nA microflow or nanoflow action is a CALL: it needs an argument for every\nparameter the flow declares, or Mendix rejects the page with CE1571. The\nargument list is the same on every widget that takes an action -- a\nCONTAINER (which is clickable) as much as an ACTIONBUTTON. An enclosing\ndata container of the right type supplies it without an argument; a data\ngrid's CONTROL BAR does not, because it is not row-scoped -- pass the\ngrid's selection there (`$dgOrders`).\n\nA SHOW PAGE argument must be the enclosing widget's context object --\neither $currentObject or the name of the variable the enclosing data\nwidget is bound to. Mendix infers it from that widget, so naming any\nother variable is refused (MDL-PAGEARG01); call a microflow instead.\nOutside any data widget there is no context object to infer, so such a\nbutton takes NO argument at all -- not a page parameter, not\n$currentObject, not a literal. mxcli used to drop it in silence and\nmxbuild then reported CE1571 per parameter of the target page\n(mendixlabs/mxcli#1029). Route that navigation through a microflow.\n\nOPEN LINK takes a web address, stored as a Forms$StaticOrDynamicString:\neither a literal, or $currentObject/Attr to read it from an attribute of\nthe enclosing data container's object at runtime (Studio Pro's \"Address:\nattribute\"). An address over an association path is not supported yet.\n\nButton styles: Default, Primary, Success, Info, Warning, Danger\n\nMendix has THREE icon elements and the keyword picks which one:\n\nIcon: 'Atlas_Core.Atlas_Filled.pencil' -- an icon collection\nIcon: image MyModule.Images.logo -- an IMAGE collection\nIcon: glyph 57377 -- a font code point\n\nThe bare form is the icon-collection icon and any collection in the\nproject works, third-party ones included. The image form points into a\ndifferent document, and is spelled the same way apart from the keyword\n-- write it without `image` and mxcli stores a custom-icon reference,\nwhich fails the build with CE1613 (mendixlabs/mxcli#1059).\n`mxcli check -p … --references` resolves each kind against its own\ncollection and names the remedy when the kind is wrong.\n\nA glyph carries a code and no name. Codes are sparse, and an undefined\none fails only at `mxbuild --target=deploy`, naming the PAGE rather\nthan the icon -- so MDL078 checks it against the font's own table.\nList them with `show glyphs`.\n\nA name may be quoted or bare; a hyphenated segment is double-quoted on\nits own: Atlas_Core.Atlas.\"align-center\".\n\nUse `linkbutton` instead of `actionbutton` for link render mode (same properties).", + Example: "ACTIONBUTTON btnSave (Caption: 'Save', Action: SAVE CHANGES, ButtonStyle: Primary)\nACTIONBUTTON btnEdit (Caption: 'Edit',\n Action: SHOW PAGE Module.EditPage(Item = $currentObject))\nLINKBUTTON btnDelete (Caption: 'Delete', Action: DELETE,\n Icon: 'Atlas_Core.Atlas_Filled.pencil')\n\n-- A clickable CONTAINER in a data grid's control bar, calling a nanoflow\n-- with the grid's selection as its argument.\nDATAGRID dgOrders (DataSource: DATABASE FROM Sales.Order, Selection: Single) {\n COLUMN colNr (Attribute: Number, Caption: 'Order #')\n CONTROLBAR cb {\n CONTAINER cShip (Class: 'command',\n Action: CALL NANOFLOW Sales.ACT_Ship(Order = $dgOrders)) {\n ACTIONBUTTON btnShip (Caption: 'Ship')\n }\n }\n}", SeeAlso: []string{"page.widgets"}, }) @@ -284,7 +284,7 @@ CREATE PAGE Sales.Detail (Title: 'Detail', Layout: Atlas_Core.Atlas_Default) { "popup width", "popup height", "popup resizable", "drop template", "insert template", "list view template", }, - Syntax: "ALTER PAGE Module.Name { -- the generic ALTER: set / insert / replace / drop\n SET (property: value) ON widgetName; -- widget property names: any casing\n SET ('Row size': 'Small') ON lvOrders; -- an Atlas DESIGN property of that widget's\n -- type; quoted and case-sensitive.\n -- `show design properties for ` lists\n -- them. ON/OFF for a toggle, where OFF\n -- REMOVES the entry.\n -- A multi-select ('Hide on') or compound\n -- ('Spacing') one needs the inline\n -- DesignProperties: [...] form, because a\n -- SET assignment carries one value.\n SET (Action: MICROFLOW Module.MF) ON btnSave; -- any CREATE PAGE action form\n SET ('createFileAction': MICROFLOW Module.MF) ON fileUploader1;\n -- a pluggable widget's NAMED action slot,\n -- by the widget's own key; refused on a\n -- key that is not action-typed\n SET (DataSource: $Param) ON dvOrder; -- parameter/microflow/nanoflow/selection;\n -- DATABASE and association are REPLACE-only,\n -- and a data view takes no database source\n SET (prop1: val1, prop2: val2) ON widgetName;\n SET (Title: 'New Title'); -- page-level (case-sensitive): no ON\n SET (Documentation: 'What this page is for.');\n SET (Class: 'css-class'); -- page-level CSS class / style\n SET (Style: 'css: rule');\n SET (PopupWidth: 800, PopupHeight: 480, PopupResizable: true); -- page-level pop-up\n INSERT AFTER widgetName { };\n INSERT BEFORE widgetName { };\n INSERT INTO containerName { };\n DROP name1, name2;\n DROP TEMPLATE FOR Module.Specialization IN listViewName;\n REPLACE widgetName WITH { };\n};\n-- A target is a widget name, or a DataGrid 2 column by what it shows:\n-- grid column(Attr), grid column('Caption'), @n when two match. The old spellings `SET p = v`,\n-- `SET p: v` (no parentheses) and `DROP WIDGET a` still run and warn\n-- (MDL-DEPR101..103).\n\n-- The BULK form: one design property on every widget of a TYPE.\nALTER PAGES [IN Module]\n SET 'Compact' = ON, 'Striped' = ON\n WHERE WIDGETTYPE = datagrid -- the MDL keyword, which resolves to\n -- exactly one widget id. A full id in\n -- quotes works too. NOT a name: a widget\n -- name is unique only within its page.\n [DRY RUN]; -- run this FIRST. It reports the matches\n -- against a discardable copy and writes\n -- nothing.", + Syntax: "ALTER PAGE Module.Name { -- the generic ALTER: set / insert / replace / drop\n SET (property: value) ON widgetName; -- widget property names: any casing\n SET ('Row size': 'Small') ON lvOrders; -- an Atlas DESIGN property of that widget's\n -- type; quoted and case-sensitive.\n -- `show design properties for ` lists\n -- them. ON/OFF for a toggle, where OFF\n -- REMOVES the entry.\n -- A multi-select ('Hide on') or compound\n -- ('Spacing') one needs the inline\n -- DesignProperties: [...] form, because a\n -- SET assignment carries one value.\n SET (Action: CALL MICROFLOW Module.MF) ON btnSave; -- any CREATE PAGE action form\n SET ('createFileAction': CALL MICROFLOW Module.MF) ON fileUploader1;\n -- a pluggable widget's NAMED action slot,\n -- by the widget's own key; refused on a\n -- key that is not action-typed\n SET (DataSource: $Param) ON dvOrder; -- parameter/microflow/nanoflow/selection;\n -- DATABASE and association are REPLACE-only,\n -- and a data view takes no database source\n SET (prop1: val1, prop2: val2) ON widgetName;\n SET (Title: 'New Title'); -- page-level (case-sensitive): no ON\n SET (Documentation: 'What this page is for.');\n SET (Class: 'css-class'); -- page-level CSS class / style\n SET (Style: 'css: rule');\n SET (PopupWidth: 800, PopupHeight: 480, PopupResizable: true); -- page-level pop-up\n INSERT AFTER widgetName { };\n INSERT BEFORE widgetName { };\n INSERT INTO containerName { };\n DROP name1, name2;\n DROP TEMPLATE FOR Module.Specialization IN listViewName;\n REPLACE widgetName WITH { };\n};\n-- A target is a widget name, or a DataGrid 2 column by what it shows:\n-- grid column(Attr), grid column('Caption'), @n when two match. The old spellings `SET p = v`,\n-- `SET p: v` (no parentheses) and `DROP WIDGET a` still run and warn\n-- (MDL-DEPR101..103).\n\n-- The BULK form: one design property on every widget of a TYPE.\nALTER PAGES [IN Module]\n SET 'Compact' = ON, 'Striped' = ON\n WHERE WIDGETTYPE = datagrid -- the MDL keyword, which resolves to\n -- exactly one widget id. A full id in\n -- quotes works too. NOT a name: a widget\n -- name is unique only within its page.\n [DRY RUN]; -- run this FIRST. It reports the matches\n -- against a discardable copy and writes\n -- nothing.", Example: "ALTER PAGE Module.EditPage {\n SET (Caption: 'Save & Close', ButtonStyle: Success) ON btnSave;\n INSERT AFTER txtName {\n TEXTBOX txtMiddleName (Label: 'Middle Name', Attribute: MiddleName)\n };\n DROP txtUnused;\n};", SeeAlso: []string{"page.create", "page.show", "snippet.alter"}, }) @@ -413,7 +413,7 @@ CREATE PAGE Sales.Detail (Title: 'Detail', Layout: Atlas_Core.Atlas_Default) { // the reporter of mendixlabs/mxcli#1028 reached the bug by following // this line. A primitive is now refused as MDL087. Syntax: "CREATE SNIPPET Module.Name\n [( Params: { $P: Module.Entity } )] -- entities only; a primitive is CE0046\n [( Variables: { $isEditable: Boolean = 'true' } )]\n [( Folder: 'Snippets/Common' )]\n {\n -- widgets\n }\n\n-- To parameterise a snippet on a primitive, keep the primitive on the\n-- calling PAGE and pass an object, or read the value off an entity member.", - Example: "CREATE SNIPPET MyModule.NavigationMenu\n{\n NAVIGATIONLIST navMenu {\n ITEM itemCustomers (Action: SHOW_PAGE MyModule.CustomerOverview) {\n DYNAMICTEXT txtCustomers (Content: 'Customers')\n }\n }\n}", + Example: "CREATE SNIPPET MyModule.NavigationMenu\n{\n NAVIGATIONLIST navMenu {\n ITEM itemCustomers (Action: SHOW PAGE MyModule.CustomerOverview) {\n DYNAMICTEXT txtCustomers (Content: 'Customers')\n }\n }\n}", SeeAlso: []string{"snippet", "snippet.alter", "page.widgets"}, }) @@ -424,7 +424,7 @@ CREATE PAGE Sales.Detail (Title: 'Detail', Layout: Atlas_Core.Atlas_Default) { "alter snippet", "modify snippet", "update snippet", }, Syntax: "ALTER SNIPPET Module.Name {\n SET (property: value) ON widgetName;\n INSERT AFTER widgetName { };\n INSERT BEFORE widgetName { };\n INSERT INTO containerName { };\n DROP name1, name2;\n REPLACE widgetName WITH { };\n};", - Example: "ALTER SNIPPET Module.NavSnippet {\n REPLACE navItem1 WITH {\n ACTIONBUTTON btnHome (Caption: 'Home', Action: SHOW_PAGE Module.HomePage)\n };\n DROP txtOldField;\n INSERT AFTER txtName {\n TEXTBOX txtNewField (Label: 'New Field', Attribute: NewAttr)\n };\n};", + Example: "ALTER SNIPPET Module.NavSnippet {\n REPLACE navItem1 WITH {\n ACTIONBUTTON btnHome (Caption: 'Home', Action: SHOW PAGE Module.HomePage)\n };\n DROP txtOldField;\n INSERT AFTER txtName {\n TEXTBOX txtNewField (Label: 'New Field', Attribute: NewAttr)\n };\n};", SeeAlso: []string{"snippet", "page.alter"}, }) @@ -619,7 +619,7 @@ CREATE PAGE Sales.Detail (Title: 'Detail', Layout: Atlas_Core.Atlas_Default) { "menu", "menus", "menu document", "menu item", }, Syntax: "CREATE [OR MODIFY] MENU Module.Name [FOLDER 'path'] (\n" + - " MENU ITEM '' [PAGE Module.Page | MICROFLOW Module.Flow | SIGN_OUT] [ICON Module.Collection.name];\n" + + " MENU ITEM '' [PAGE Module.Page | MICROFLOW Module.Flow | SIGN OUT] [ICON Module.Collection.name];\n" + " MENU '' [ICON Module.Collection.name] ( );\n" + ");\n" + "DESCRIBE MENU Module.Name;\n" + @@ -638,7 +638,7 @@ CREATE PAGE Sales.Detail (Title: 'Detail', Layout: Atlas_Core.Atlas_Default) { "-- SHOW NAVIGATION MENU and ALTER NAVIGATION. Both use these same items.\n" + "-- * OR MODIFY replaces the item list wholesale; an omitted item is removed.\n" + "-- The document's identity and export level are preserved.\n" + - "-- * SIGN_OUT is the log-out menu item. It needs no target and stores the\n" + + "-- * SIGN OUT is the log-out menu item. It needs no target and stores the\n" + "-- same Forms$SignOutClientAction a sign-out BUTTON carries. Works both\n" + "-- here and in a navigation profile's menu.\n" + "-- * ICON names an icon collection entry. A glyph or image icon cannot be\n" + @@ -658,7 +658,7 @@ CREATE PAGE Sales.Detail (Title: 'Detail', Layout: Atlas_Core.Atlas_Default) { "use fragment", "template", "script scope", }, Syntax: "DEFINE FRAGMENT Name AS { };\nDEFINE FRAGMENT Name AS { SLOT [name] };\nDEFINE FRAGMENT Name ($d: datasource, $a: action) AS { };\nUSE FRAGMENT Name [(args)] [AS prefix_];\nUSE FRAGMENT Name [(args)] [AS prefix_] { };\nSHOW FRAGMENTS;\nDESCRIBE FRAGMENT Name;\nDESCRIBE FRAGMENT FROM PAGE Module.Page WIDGET widgetName;", - Example: "DEFINE FRAGMENT SaveCancelFooter AS {\n FOOTER footer1 {\n ACTIONBUTTON btnSave (Caption: 'Save', Action: SAVE_CHANGES, ButtonStyle: Primary)\n ACTIONBUTTON btnCancel (Caption: 'Cancel', Action: CANCEL_CHANGES)\n }\n};\n\nCREATE PAGE Module.EditPage (Params: { $Param: Module.Customer }, Title: 'Edit', Layout: 'Atlas_Core.Atlas_Default') {\n DATAVIEW dv (DataSource: $Param) {\n TEXTBOX txtName (Label: 'Name', Attribute: Name)\n USE FRAGMENT SaveCancelFooter\n }\n};", + Example: "DEFINE FRAGMENT SaveCancelFooter AS {\n FOOTER footer1 {\n ACTIONBUTTON btnSave (Caption: 'Save', Action: SAVE CHANGES, ButtonStyle: Primary)\n ACTIONBUTTON btnCancel (Caption: 'Cancel', Action: CANCEL CHANGES)\n }\n};\n\nCREATE PAGE Module.EditPage (Params: { $Param: Module.Customer }, Title: 'Edit', Layout: 'Atlas_Core.Atlas_Default') {\n DATAVIEW dv (DataSource: $Param) {\n TEXTBOX txtName (Label: 'Name', Attribute: Name)\n USE FRAGMENT SaveCancelFooter\n }\n};", SeeAlso: []string{"fragment.define", "fragment.use", "fragment.slot", "fragment.params", "snippet"}, }) @@ -693,7 +693,7 @@ CREATE PAGE Sales.Detail (Title: 'Detail', Layout: Atlas_Core.Atlas_Default) { "binding", "rebind", "building block override", "reusable component", }, Syntax: "-- Declare typed params, reference with $name in a datasource/action slot:\nDEFINE FRAGMENT Name ($data: datasource, $onEdit: action) AS { … };\n-- Supply values at the use site:\nUSE FRAGMENT Name ($data: , $onEdit: ) [{ payload }]\n-- Building blocks: rebind the outermost datasource / first button:\nUSE BUILDING BLOCK Module.Block (datasource: , action: ) [AS prefix_]", - Example: "DEFINE FRAGMENT DataPanel ($data: datasource, $onEdit: action) AS {\n CONTAINER panel (Class: 'card') {\n LISTVIEW lv (DataSource: $data) {\n SLOT content\n ACTIONBUTTON edit (Caption: 'Edit', Action: $onEdit, ButtonStyle: Primary)\n }\n }\n};\n\nCREATE PAGE Module.Orders (Title: 'Orders', Layout: Atlas_Core.Atlas_Default) {\n USE FRAGMENT DataPanel ($data: DATABASE Sales.Order, $onEdit: MICROFLOW Sales.Edit) {\n DYNAMICTEXT heading (Content: 'Orders', RenderMode: H4)\n }\n};\n\n-- Rebind a building block's datasource and primary button:\nUSE BUILDING BLOCK Atlas_Web_Content.List_Cards (datasource: DATABASE Sales.Order, action: MICROFLOW Sales.Open) AS orders_\n\n-- Notes:\n-- * Param kinds: datasource | action. Every declared param must be supplied.\n-- * A microflow value parses as a datasource and is reinterpreted for an action param.\n-- * BB binding-point rule: datasource → first datasource widget; action → first button.", + Example: "DEFINE FRAGMENT DataPanel ($data: datasource, $onEdit: action) AS {\n CONTAINER panel (Class: 'card') {\n LISTVIEW lv (DataSource: $data) {\n SLOT content\n ACTIONBUTTON edit (Caption: 'Edit', Action: $onEdit, ButtonStyle: Primary)\n }\n }\n};\n\nCREATE PAGE Module.Orders (Title: 'Orders', Layout: Atlas_Core.Atlas_Default) {\n USE FRAGMENT DataPanel ($data: DATABASE Sales.Order, $onEdit: MICROFLOW Sales.Edit) {\n DYNAMICTEXT heading (Content: 'Orders', RenderMode: H4)\n }\n};\n\n-- Rebind a building block's datasource and primary button:\nUSE BUILDING BLOCK Atlas_Web_Content.List_Cards (datasource: DATABASE Sales.Order, action: CALL MICROFLOW Sales.Open) AS orders_\n\n-- Notes:\n-- * Param kinds: datasource | action. Every declared param must be supplied.\n-- * A microflow value parses as a datasource and is reinterpreted for an action param.\n-- * BB binding-point rule: datasource → first datasource widget; action → first button.", SeeAlso: []string{"fragment", "fragment.slot", "fragment.use"}, }) diff --git a/cmd/mxcli/syntax/features_workflow.go b/cmd/mxcli/syntax/features_workflow.go index 14223800cc..d14dbb25a0 100644 --- a/cmd/mxcli/syntax/features_workflow.go +++ b/cmd/mxcli/syntax/features_workflow.go @@ -262,11 +262,14 @@ func init() { "call microflow", "microflow task", "automated step", "system task", }, - // The WITH values are QUOTED — the grammar takes a string literal there, - // not a bare variable. Omitting the clause from this entry is how an - // author ends up writing the unquoted form (ako/mxcli#1023). - Syntax: "CALL MICROFLOW Module.MF [AS ] [COMMENT '']\n [WITH ( = '', ...)]\n [OUTCOMES '' -> { } ...];", - Example: "CALL MICROFLOW HR.SendNotification\n COMMENT 'Notify manager';\n\n-- Parameter values are quoted, and named by their BARE parameter name:\nCALL MICROFLOW HR.Escalate AS callMicroflow1\n WITH (Request = '$WorkflowContext');", + // R4 (ako/mxcli#751): arguments are bound as at every call site, right + // after the callee, the expression bare. The older `WITH (P = '')` + // after the comment is the deprecated alias MDL-DEPR008. + Syntax: "CALL MICROFLOW Module.MF[( = , ...)] [AS ] [COMMENT '']\n" + + " [OUTCOMES '' -> { } ...];\n\n" + + "-- WITH ( = ''), the expression in a string, is the\n" + + "-- deprecated spelling of the argument list (MDL-DEPR008).", + Example: "CALL MICROFLOW HR.SendNotification\n COMMENT 'Notify manager';\n\n-- Arguments are named by their BARE parameter name:\nCALL MICROFLOW HR.Escalate(Request = $WorkflowContext) AS callMicroflow1;", SeeAlso: []string{"workflow.create", "workflow.call-workflow", "workflow.ai-agent-task"}, }) @@ -280,8 +283,7 @@ func init() { // Measured on mxbuild 11.13.0 against the same activity written as a call // microflow: the only difference in what builds is CE1590 for a microflow // with no parameters. - Syntax: "CALL AGENT MICROFLOW Module.MF [AS ] [COMMENT '']\n" + - " [WITH ( = '', ...)]\n" + + Syntax: "CALL AGENT MICROFLOW Module.MF[( = , ...)] [AS ] [COMMENT '']\n" + " [OUTCOMES -> { } ...]\n" + " [BOUNDARY EVENT ...];\n\n" + "-- The same statement as CALL MICROFLOW, stored as an AI agent task. The microflow\n" + @@ -290,8 +292,8 @@ func init() { "-- branch on the agent's answer with OUTCOMES. Needs Mendix 11.9+.", Example: "CREATE MICROFLOW HR.ACT_ClassifyRequest ($Request: HR.LeaveRequest)\n" + "RETURNS Boolean AS $Urgent\nBEGIN\n -- call the agent here\n RETURN false;\nEND;\n\n" + - "CALL AGENT MICROFLOW HR.ACT_ClassifyRequest AS aiAgentTask1 COMMENT 'Classify the request'\n" + - " WITH (Request = '$WorkflowContext')\n" + + "CALL AGENT MICROFLOW HR.ACT_ClassifyRequest(Request = $WorkflowContext) AS aiAgentTask1\n" + + " COMMENT 'Classify the request'\n" + " OUTCOMES true -> { USER TASK Expedite 'Expedite' PAGE HR.TaskPage OUTCOMES 'Done' { }; }\n" + " false -> { };", SeeAlso: []string{"workflow.call-microflow", "agents"}, @@ -303,8 +305,8 @@ func init() { Keywords: []string{ "call workflow", "sub-workflow", "nested workflow", }, - Syntax: "CALL WORKFLOW Module.WF [AS ] [COMMENT '']\n [WITH ( = '', ...)];", - Example: "CALL WORKFLOW HR.SubApproval COMMENT 'Delegate to sub-process';\n\n-- Parameter values are quoted:\nCALL WORKFLOW HR.SubApproval AS callWf1\n WITH (Request = '$WorkflowContext');", + Syntax: "CALL WORKFLOW Module.WF[( = , ...)] [AS ] [COMMENT ''];", + Example: "CALL WORKFLOW HR.SubApproval COMMENT 'Delegate to sub-process';\n\nCALL WORKFLOW HR.SubApproval(Request = $WorkflowContext) AS callWf1;", SeeAlso: []string{"workflow.create", "workflow.call-microflow"}, }) diff --git a/cmd/mxcli/testrunner/generator.go b/cmd/mxcli/testrunner/generator.go index 53770c897f..7bf25c2bfc 100644 --- a/cmd/mxcli/testrunner/generator.go +++ b/cmd/mxcli/testrunner/generator.go @@ -103,13 +103,13 @@ func writeTestBlock(b *strings.Builder, tc TestCase, index int) { // the reasons the endpoint runner exists. func writeSetupBlock(b *strings.Builder, tc TestCase) { for _, flow := range tc.Setups { - fmt.Fprintf(b, " CALL MICROFLOW %s() ON ERROR {\n", flow) + fmt.Fprintf(b, " CALL MICROFLOW %s() ON ERROR BEGIN\n", flow) fmt.Fprintf(b, " LOG ERROR NODE 'MXTEST' 'MXTEST:ERROR:%s:Setup failed: %s';\n", escapeMDLString(tc.ID), escapeMDLString(flow)) b.WriteString(" SET $TestFailed = true;\n") b.WriteString(" SET $AllPassed = false;\n") b.WriteString(" RETURN $AllPassed;\n") - b.WriteString(" };\n") + b.WriteString(" END ERROR;\n") } } @@ -262,14 +262,14 @@ func rewriteWithErrorHandling(lines []string, testID string) []string { if containsCallMicroflow(trimmed) && strings.HasSuffix(trimmed, ";") { withoutSemicolon := strings.TrimSuffix(trimmed, ";") - result = append(result, withoutSemicolon+" ON ERROR {") + result = append(result, withoutSemicolon+" ON ERROR BEGIN") result = append(result, fmt.Sprintf( " LOG ERROR NODE 'MXTEST' 'MXTEST:FAIL:%s:Exception during execution';", escapeMDLString(testID))) result = append(result, " SET $TestFailed = true;") result = append(result, " SET $AllPassed = false;") result = append(result, " RETURN $AllPassed;") - result = append(result, "};") + result = append(result, "END ERROR;") } else if containsCallMicroflow(trimmed) && !strings.HasSuffix(trimmed, ";") { var callLines []string callLines = append(callLines, line) @@ -284,14 +284,14 @@ func rewriteWithErrorHandling(lines []string, testID string) []string { joined := strings.Join(callLines, "\n") joined = strings.TrimSpace(joined) joined = strings.TrimSuffix(joined, ";") - result = append(result, joined+" ON ERROR {") + result = append(result, joined+" ON ERROR BEGIN") result = append(result, fmt.Sprintf( " LOG ERROR NODE 'MXTEST' 'MXTEST:FAIL:%s:Exception during execution';", escapeMDLString(testID))) result = append(result, " SET $TestFailed = true;") result = append(result, " SET $AllPassed = false;") result = append(result, " RETURN $AllPassed;") - result = append(result, "};") + result = append(result, "END ERROR;") } else { result = append(result, line) } @@ -315,9 +315,9 @@ func rewriteForThrowsTest(lines []string, didThrowVar string, want string) []str if containsCallMicroflow(trimmed) && strings.HasSuffix(trimmed, ";") { withoutSemicolon := strings.TrimSuffix(trimmed, ";") - result = append(result, withoutSemicolon+" ON ERROR {") + result = append(result, withoutSemicolon+" ON ERROR BEGIN") result = append(result, throwsFlagHandler(didThrowVar, want)...) - result = append(result, "};") + result = append(result, "END ERROR;") } else if containsCallMicroflow(trimmed) && !strings.HasSuffix(trimmed, ";") { var callLines []string callLines = append(callLines, line) @@ -332,9 +332,9 @@ func rewriteForThrowsTest(lines []string, didThrowVar string, want string) []str joined := strings.Join(callLines, "\n") joined = strings.TrimSpace(joined) joined = strings.TrimSuffix(joined, ";") - result = append(result, joined+" ON ERROR {") + result = append(result, joined+" ON ERROR BEGIN") result = append(result, throwsFlagHandler(didThrowVar, want)...) - result = append(result, "};") + result = append(result, "END ERROR;") } else { result = append(result, line) } diff --git a/cmd/mxcli/testrunner/generator_endpoint.go b/cmd/mxcli/testrunner/generator_endpoint.go index 9f4633239c..a49357ffaa 100644 --- a/cmd/mxcli/testrunner/generator_endpoint.go +++ b/cmd/mxcli/testrunner/generator_endpoint.go @@ -82,11 +82,11 @@ func writeTestFlow(b *strings.Builder, tc TestCase) { // produces an assertion failure that says nothing about the code under test. func writeSetupCalls(b *strings.Builder, tc TestCase) { for _, flow := range tc.Setups { - fmt.Fprintf(b, " CALL MICROFLOW %s() ON ERROR {\n", flow) + fmt.Fprintf(b, " CALL MICROFLOW %s() ON ERROR BEGIN\n", flow) fmt.Fprintf(b, " SET $Verdict = '%s';\n", escapeMDLString(verdictSetupPrefix+flow)) b.WriteString(" RETURN $Verdict;\n") - b.WriteString(" };\n") + b.WriteString(" END ERROR;\n") } } @@ -228,7 +228,7 @@ func quoteForMessage(s string) string { return "'" + s + "'" } -// attachOnError appends `ON ERROR { ... }` to each CALL statement in the body, +// attachOnError appends `ON ERROR BEGIN ... END ERROR` to each CALL statement in the body, // joining a statement that spans several lines first. func attachOnError(lines, handler []string) []string { var out []string @@ -246,9 +246,9 @@ func attachOnError(lines, handler []string) []string { } stmt = strings.TrimSuffix(strings.TrimSpace(stmt), ";") - out = append(out, stmt+" ON ERROR {") + out = append(out, stmt+" ON ERROR BEGIN") out = append(out, handler...) - out = append(out, "};") + out = append(out, "END ERROR;") } return out } diff --git a/cmd/mxcli/testrunner/generator_endpoint_test.go b/cmd/mxcli/testrunner/generator_endpoint_test.go index 3bbbbbc74f..cfaa930bda 100644 --- a/cmd/mxcli/testrunner/generator_endpoint_test.go +++ b/cmd/mxcli/testrunner/generator_endpoint_test.go @@ -94,7 +94,7 @@ func TestGenerateTestFlowsWrapsCallsWithErrorHandling(t *testing.T) { }}} mdl := GenerateTestFlows(suite) - if !strings.Contains(mdl, "ON ERROR {") { + if !strings.Contains(mdl, "ON ERROR BEGIN") { t.Errorf("the CALL was not wrapped in ON ERROR:\n%s", mdl) } if !strings.Contains(mdl, verdictFailPrefix+"exception during execution") { @@ -113,7 +113,7 @@ func TestGenerateTestFlowsThrowsTestStartsFailed(t *testing.T) { mdl := GenerateTestFlows(suite) failIdx := strings.Index(mdl, verdictFailPrefix+"expected an exception") - handlerIdx := strings.Index(mdl, "ON ERROR {") + handlerIdx := strings.Index(mdl, "ON ERROR BEGIN") if failIdx < 0 { t.Fatalf("no pre-set failure verdict:\n%s", mdl) } @@ -135,10 +135,10 @@ func TestGenerateTestFlowsMultiLineCall(t *testing.T) { }}} mdl := GenerateTestFlows(suite) - if !strings.Contains(mdl, ") ON ERROR {") { + if !strings.Contains(mdl, ") ON ERROR BEGIN") { t.Errorf("a statement spanning lines was not joined before ON ERROR was attached:\n%s", mdl) } - if strings.Count(mdl, "ON ERROR {") != 1 { + if strings.Count(mdl, "ON ERROR BEGIN") != 1 { t.Errorf("expected exactly one handler for one call:\n%s", mdl) } } diff --git a/cmd/mxcli/testrunner/generator_test.go b/cmd/mxcli/testrunner/generator_test.go index 4622a06762..2b0a434ec9 100644 --- a/cmd/mxcli/testrunner/generator_test.go +++ b/cmd/mxcli/testrunner/generator_test.go @@ -101,3 +101,27 @@ func TestGenerateTestRunner_RenamesAllAssignmentsInTestBlock(t *testing.T) { } } } + +// The generated test flows are MDL mxcli executes itself, so their error +// handlers use the canonical `on error begin … end error` and record no +// deprecated spelling (MDL-DEPR540, ako/mxcli#754). +func TestGeneratedErrorHandlersAreCanonical(t *testing.T) { + suite := &TestSuite{Tests: []TestCase{ + {ID: "t1", Name: "call", MDL: "$r = CALL MICROFLOW Mod.A();", Setups: []string{"Mod.Setup"}}, + {ID: "t2", Name: "multi", MDL: "$r = CALL MICROFLOW Mod.A(\n X = 1\n);"}, + }} + for name, src := range map[string]string{ + "endpoint": GenerateTestFlows(suite), + "legacy": GenerateTestRunner(suite), + } { + prog, errs := visitor.Build(src) + if len(errs) > 0 { + t.Fatalf("%s: generated MDL does not parse: %v\n%s", name, errs[0], src) + } + for _, d := range prog.Deprecations { + if d.Code == "MDL-DEPR540" { + t.Errorf("%s: line %d uses the brace error handler:\n%s", name, d.Line, src) + } + } + } +} diff --git a/docs-site/src/appendixes/common-mistakes.md b/docs-site/src/appendixes/common-mistakes.md index 945330d480..7d70bd926b 100644 --- a/docs-site/src/appendixes/common-mistakes.md +++ b/docs-site/src/appendixes/common-mistakes.md @@ -126,7 +126,7 @@ These constructs will cause parse errors: | Unsupported | Use Instead | |-------------|-------------| | `CASE ... WHEN 'String' ... ELSE ...` | Bare enum values, one branch per value including `(empty)` — `CASE` itself IS supported for enum splits | -| `TRY ... CATCH ... END TRY` | `ON ERROR { ... }` blocks on specific activities | +| `TRY ... CATCH ... END TRY` | `ON ERROR BEGIN ... END ERROR` blocks on specific activities | ## Boolean Attributes Must Have Defaults diff --git a/docs-site/src/appendixes/data-type-mapping.md b/docs-site/src/appendixes/data-type-mapping.md index 5129b59b2c..9ccb3bacc8 100644 --- a/docs-site/src/appendixes/data-type-mapping.md +++ b/docs-site/src/appendixes/data-type-mapping.md @@ -67,9 +67,9 @@ Comprehensive mapping between MDL data types, Mendix internal types, and backend | Constraint | Syntax | Description | |------------|--------|-------------| | Not Null | `NOT NULL` | Value is required | -| Not Null with Error | `NOT NULL ERROR 'message'` | Required with custom error | +| Not Null with Error | `NOT NULL ERROR MESSAGE 'message'` | Required with custom error | | Unique | `UNIQUE` | Value must be unique | -| Unique with Error | `UNIQUE ERROR 'message'` | Unique with custom error | +| Unique with Error | `UNIQUE ERROR MESSAGE 'message'` | Unique with custom error | Constraints must appear in this order: 1. `NOT NULL [ERROR '...']` diff --git a/docs-site/src/appendixes/quick-reference.md b/docs-site/src/appendixes/quick-reference.md index 5747f21e2c..36e678720d 100644 --- a/docs-site/src/appendixes/quick-reference.md +++ b/docs-site/src/appendixes/quick-reference.md @@ -32,7 +32,7 @@ CREATE PERSISTENT ENTITY Module.Photo ( | List entities | `LIST ENTITIES [IN Module];` | List all or filter by module | | Create enumeration | `CREATE [OR MODIFY] ENUMERATION Module.Name (Value1 'Caption', ...);` | | | Drop enumeration | `DROP ENUMERATION Module.Name;` | | -| Create association | `CREATE ASSOCIATION Module.Name FROM Parent TO Child TYPE Reference\|ReferenceSet [OWNER Default\|Both] [DELETE_BEHAVIOR ...];` | | +| Create association | `CREATE ASSOCIATION Module.Name FROM Parent TO Child TYPE Reference\|ReferenceSet [OWNER Default\|Both] [ON DELETE CASCADE\|RESTRICT\|SET NULL];` | | | Drop association | `DROP ASSOCIATION Module.Name;` | | ## ALTER ENTITY @@ -156,7 +156,7 @@ AUTHENTICATION Basic, Session | Retrieve (Assoc) | `RETRIEVE $List FROM $Parent/Module.AssocName;` | Retrieve by association | | Call microflow | `$Result = CALL MICROFLOW Module.Name (Param = $value);` | | | Call nanoflow | `$Result = CALL NANOFLOW Module.Name (Param = $value);` | | -| Show page | `LIST PAGE Module.PageName ($Param = $value);` | Also accepts `(Param: $value)` | +| Show page | `SHOW PAGE Module.PageName (Param = $value);` | `($Param = …)` and `(Param: …)` are deprecated | | Close page | `CLOSE PAGE;` | | | Validation | `VALIDATION FEEDBACK $Entity/Attribute MESSAGE 'message';` | Requires attribute path + MESSAGE | | Log | `LOG INFO\|WARNING\|ERROR [NODE 'name'] 'message';` | | @@ -170,14 +170,14 @@ AUTHENTICATION Basic, Session | WHILE | `WHILE condition BEGIN ... END WHILE;` | Condition-based loop | | Return | `RETURN $value;` | Required at end of every flow path | | Execute DB query | `$Result = EXECUTE DATABASE QUERY Module.Conn.Query;` | 3-part name; supports DYNAMIC, params, CONNECTION override | -| Error handling | `... ON ERROR CONTINUE\|ROLLBACK\|{ handler };` | Not supported on EXECUTE DATABASE QUERY | +| Error handling | `... ON ERROR CONTINUE\|ROLLBACK\|[WITHOUT ROLLBACK] BEGIN handler END ERROR;` | Not supported on EXECUTE DATABASE QUERY | ## Microflows - NOT Supported (Will Cause Parse Errors) | Unsupported | Use Instead | Notes | |-------------|-------------|-------| | `CASE ... WHEN 'String' ... ELSE ...` | Bare enum values, one branch per value | `CASE` itself IS supported for **enum splits**; what fails is quoted/qualified values, an `ELSE` branch (MDL008), and an `AS` alias | -| `TRY ... CATCH ... END TRY` | `ON ERROR { ... }` blocks | Use error handlers on specific activities | +| `TRY ... CATCH ... END TRY` | `ON ERROR BEGIN ... END ERROR` blocks | Use error handlers on specific activities | **Notes:** - `RETRIEVE ... FIRST` returns a single entity; `RETRIEVE ... LIMIT n [OFFSET n]` returns a list. A bare `LIMIT 1` is a list of one under `mdl 1;`, and without the header the single entity, with warning `MDL-V1-LIMIT1`. @@ -353,8 +353,8 @@ MDL uses explicit property declarations for pages: | Widget name | Required after type | `TEXTBOX txtName (...)` | | Attribute binding | `Attribute: AttrName` | `TEXTBOX txt (Label: 'Name', Attribute: Name)` | | Variable binding | `DataSource: $Var` | `DATAVIEW dv (DataSource: $Product) { ... }` | -| Action binding | `Action: TYPE` | `ACTIONBUTTON btn (Caption: 'Save', Action: SAVE_CHANGES)` | -| Microflow action | `Action: MICROFLOW Name(Param: val)` | `Action: MICROFLOW Mod.ACT_Process(Order: $Order)` | +| Action binding | `Action: TYPE` | `ACTIONBUTTON btn (Caption: 'Save', Action: save changes)` | +| Microflow action | `Action: MICROFLOW Name(Param = val)` | `Action: MICROFLOW Mod.ACT_Process(Order = $Order)` | | Database source | `DataSource: DATABASE Entity` | `DATAGRID dg (DataSource: DATABASE Module.Entity)` | | Selection binding | `DataSource: SELECTION widget` | `DATAVIEW dv (DataSource: SELECTION galleryList)` | | CSS class | `Class: 'classes'` | `CONTAINER c (Class: 'card mx-spacing-top-large')` | @@ -401,8 +401,8 @@ CREATE PAGE MyModule.Customer_Edit COMBOBOX cbStatus (Label: 'Status', Attribute: Status) FOOTER footer1 { - ACTIONBUTTON btnSave (Caption: 'Save', Action: SAVE_CHANGES, ButtonStyle: Primary) - ACTIONBUTTON btnCancel (Caption: 'Cancel', Action: CANCEL_CHANGES) + ACTIONBUTTON btnSave (Caption: 'Save', Action: SAVE CHANGES, ButtonStyle: Primary) + ACTIONBUTTON btnCancel (Caption: 'Cancel', Action: CANCEL CHANGES) } } } diff --git a/docs-site/src/examples/alter-page.md b/docs-site/src/examples/alter-page.md index af4461984c..2afd6c931d 100644 --- a/docs-site/src/examples/alter-page.md +++ b/docs-site/src/examples/alter-page.md @@ -46,9 +46,9 @@ ALTER PAGE CRM.Customer_Edit { ALTER PAGE CRM.Customer_Edit { REPLACE footer1 WITH { FOOTER newFooter { - ACTIONBUTTON btnSave (Caption: 'Save', Action: SAVE_CHANGES, ButtonStyle: Success) + ACTIONBUTTON btnSave (Caption: 'Save', Action: SAVE CHANGES, ButtonStyle: Success) ACTIONBUTTON btnDelete (Caption: 'Delete', Action: DELETE, ButtonStyle: Danger) - ACTIONBUTTON btnCancel (Caption: 'Cancel', Action: CANCEL_CHANGES) + ACTIONBUTTON btnCancel (Caption: 'Cancel', Action: CANCEL CHANGES) } } }; @@ -127,7 +127,7 @@ ALTER SNIPPET CRM.NavigationMenu { INSERT AFTER btnHome { ACTIONBUTTON btnReports ( Caption: 'Reports', - Action: SHOW_PAGE CRM.Reports_Overview + Action: SHOW PAGE CRM.Reports_Overview ) } }; diff --git a/docs-site/src/examples/crm-module.md b/docs-site/src/examples/crm-module.md index ca5980c00a..1d9d453b42 100644 --- a/docs-site/src/examples/crm-module.md +++ b/docs-site/src/examples/crm-module.md @@ -25,9 +25,9 @@ CREATE PERSISTENT ENTITY CRM.Customer ( /** Auto-generated unique identifier */ CustomerId: AutoNumber NOT NULL UNIQUE DEFAULT 1, /** Full legal name */ - Name: String(200) NOT NULL ERROR 'Customer name is required', + Name: String(200) NOT NULL ERROR MESSAGE 'Customer name is required', /** Primary contact email */ - Email: String(200) UNIQUE ERROR 'Email already exists', + Email: String(200) UNIQUE ERROR MESSAGE 'Email already exists', /** Phone number in international format */ Phone: String(50), /** Current account balance */ @@ -51,7 +51,7 @@ CREATE PERSISTENT ENTITY CRM.ContactLog ( /** Type of interaction */ Type: Enumeration(CRM.ContactType) DEFAULT 'Email', /** Summary of what was discussed */ - Summary: String(2000) NOT NULL ERROR 'Summary is required', + Summary: String(2000) NOT NULL ERROR MESSAGE 'Summary is required', /** Follow-up needed? */ FollowUpRequired: Boolean DEFAULT FALSE ); @@ -96,7 +96,7 @@ END; CREATE MICROFLOW CRM.ACT_Customer_Save ($Customer: CRM.Customer) RETURNS Boolean AS $IsValid BEGIN - $IsValid = CALL MICROFLOW CRM.VAL_Customer($param = $Customer); + $IsValid = CALL MICROFLOW CRM.VAL_Customer(param = $Customer); IF $IsValid THEN COMMIT $Customer; @@ -123,7 +123,7 @@ CREATE PAGE CRM.Customer_Overview ( COLUMN colStatus (Attribute: Status, Caption: 'Status') COLUMN colActive (Attribute: IsActive, Caption: 'Active') CONTROLBAR cb1 { - ACTIONBUTTON btnNew (Caption: 'New', Action: SHOW_PAGE CRM.Customer_NewEdit, ButtonStyle: Primary) + ACTIONBUTTON btnNew (Caption: 'New', Action: SHOW PAGE CRM.Customer_NewEdit, ButtonStyle: Primary) } } }; @@ -146,10 +146,10 @@ CREATE PAGE CRM.Customer_NewEdit ( FOOTER footer1 { ACTIONBUTTON btnSave ( Caption: 'Save', - Action: MICROFLOW CRM.ACT_Customer_Save, + Action: CALL MICROFLOW CRM.ACT_Customer_Save, ButtonStyle: Success ) - ACTIONBUTTON btnCancel (Caption: 'Cancel', Action: CANCEL_CHANGES) + ACTIONBUTTON btnCancel (Caption: 'Cancel', Action: CANCEL CHANGES) } } } diff --git a/docs-site/src/examples/master-detail.md b/docs-site/src/examples/master-detail.md index cb52a25c76..f6e7cda13a 100644 --- a/docs-site/src/examples/master-detail.md +++ b/docs-site/src/examples/master-detail.md @@ -38,10 +38,10 @@ CREATE PAGE CRM.Customer_MasterDetail ( FOOTER footer1 { ACTIONBUTTON btnSave ( Caption: 'Save', - Action: SAVE_CHANGES, + Action: SAVE CHANGES, ButtonStyle: Success ) - ACTIONBUTTON btnCancel (Caption: 'Cancel', Action: CANCEL_CHANGES) + ACTIONBUTTON btnCancel (Caption: 'Cancel', Action: CANCEL CHANGES) } } } diff --git a/docs-site/src/examples/rest-integration.md b/docs-site/src/examples/rest-integration.md index 49afe92195..de482eac7c 100644 --- a/docs-site/src/examples/rest-integration.md +++ b/docs-site/src/examples/rest-integration.md @@ -130,10 +130,10 @@ BEGIN HEADER Accept = 'application/json' TIMEOUT 30 RETURNS String - ON ERROR WITHOUT ROLLBACK { + ON ERROR WITHOUT ROLLBACK BEGIN LOG ERROR NODE 'Integration' 'API call failed: ' + $Url; RETURN false; - }; + END ERROR; SET $Success = true; RETURN $Success; diff --git a/docs-site/src/examples/validation.md b/docs-site/src/examples/validation.md index 91f952aa0c..18af118bef 100644 --- a/docs-site/src/examples/validation.md +++ b/docs-site/src/examples/validation.md @@ -54,7 +54,7 @@ The action microflow calls validation, and only saves if it passes: CREATE MICROFLOW Sales.ACT_Order_Save ($Order: Sales.Order) RETURNS Boolean AS $IsValid BEGIN - $IsValid = CALL MICROFLOW Sales.VAL_Order($param = $Order); + $IsValid = CALL MICROFLOW Sales.VAL_Order(param = $Order); IF $IsValid THEN COMMIT $Order; @@ -87,10 +87,10 @@ CREATE PAGE Sales.Order_Edit ( FOOTER footer1 { ACTIONBUTTON btnSave ( Caption: 'Save', - Action: MICROFLOW Sales.ACT_Order_Save, + Action: CALL MICROFLOW Sales.ACT_Order_Save, ButtonStyle: Success ) - ACTIONBUTTON btnCancel (Caption: 'Cancel', Action: CANCEL_CHANGES) + ACTIONBUTTON btnCancel (Caption: 'Cancel', Action: CANCEL CHANGES) } } } diff --git a/docs-site/src/internals/reference-tracking.md b/docs-site/src/internals/reference-tracking.md index bd8a6c7064..f1088fba24 100644 --- a/docs-site/src/internals/reference-tracking.md +++ b/docs-site/src/internals/reference-tracking.md @@ -10,7 +10,7 @@ During a full catalog refresh, every document is analyzed for outgoing reference |--------------|-------------------| | **Microflows** | CALL MICROFLOW actions, RETRIEVE data sources, entity parameters, SHOW PAGE actions, association traversals | | **Nanoflows** | Same as microflows (client-side) | -| **Pages** | Data source entities, microflow data sources, SHOW_PAGE actions, association paths, snippet calls | +| **Pages** | Data source entities, microflow data sources, SHOW PAGE actions, association paths, snippet calls | | **Snippets** | Same as pages | | **Domain Models** | Generalization references, association endpoints | diff --git a/docs-site/src/language/activity-types.md b/docs-site/src/language/activity-types.md index 9ea237b1d7..73cc4695b3 100644 --- a/docs-site/src/language/activity-types.md +++ b/docs-site/src/language/activity-types.md @@ -171,23 +171,20 @@ Java action parameters follow the same `Name = value` syntax. Opens a page, passing parameters: ```sql -SHOW PAGE Sales.Order_Edit ($Order = $Order); +SHOW PAGE Sales.Order_Edit (Order = $Order); ``` -The parameter syntax uses `$PageParam = $MicroflowVar`. Multiple parameters are comma-separated: +The parameter syntax is `PageParam = $MicroflowVar`, with no `$` on the page's parameter name. Multiple parameters are comma-separated: ```sql SHOW PAGE Sales.OrderDetail ( - $Order = $Order, - $Customer = $Customer + Order = $Order, + Customer = $Customer ); ``` -An alternate syntax using colon notation is also supported: - -```sql -SHOW PAGE Sales.Order_Edit (Order: $Order); -``` +The older spellings `($Order = $Order)` and `(Order: $Order)` still parse but are +deprecated (MDL-DEPR006, MDL-DEPR007); `mxcli fmt --upgrade` rewrites them. ### CLOSE PAGE @@ -246,8 +243,7 @@ $Result = EXECUTE DATABASE QUERY MyModule.MyConn.SearchQuery DYNAMIC 'SELECT * FROM customers WHERE name LIKE ?'; -- With parameters -$Result = EXECUTE DATABASE QUERY MyModule.MyConn.GetByEmail - PARAMETERS ($EmailParam = $Email); +$Result = EXECUTE DATABASE QUERY MyModule.MyConn.GetByEmail (EmailParam = $Email); -- With runtime connection override $Result = EXECUTE DATABASE QUERY MyModule.MyConn.GetData @@ -272,7 +268,7 @@ The query name follows a three-part naming convention: `Module.ConnectionName.Qu | Call microflow | `$Var = CALL MICROFLOW Module.Name (Param = $val);` | Any type | | Call nanoflow | `$Var = CALL NANOFLOW Module.Name (Param = $val);` | Any type | | Call Java action | `$Var = CALL JAVA ACTION Module.Name (Param = val);` | Any type | -| Show page | `SHOW PAGE Module.Page ($Param = $val);` | -- | +| Show page | `SHOW PAGE Module.Page (Param = $val);` | -- | | Close page | `CLOSE PAGE;` | -- | | Validation | `VALIDATION FEEDBACK $Var/Attr MESSAGE 'msg';` | -- | | Log | `LOG INFO\|WARNING\|ERROR [NODE 'name'] 'msg';` | -- | diff --git a/docs-site/src/language/alter-page.md b/docs-site/src/language/alter-page.md index af598a9b9c..1875473064 100644 --- a/docs-site/src/language/alter-page.md +++ b/docs-site/src/language/alter-page.md @@ -113,7 +113,7 @@ ALTER PAGE Module.EditPage { -- Insert before a widget ALTER PAGE Module.EditPage { INSERT BEFORE btnSave { - ACTIONBUTTON btnPreview (Caption: 'Preview', Action: MICROFLOW Module.ACT_Preview) + ACTIONBUTTON btnPreview (Caption: 'Preview', Action: CALL MICROFLOW Module.ACT_Preview) } }; diff --git a/docs-site/src/language/associations.md b/docs-site/src/language/associations.md index a39486b724..c1f108033f 100644 --- a/docs-site/src/language/associations.md +++ b/docs-site/src/language/associations.md @@ -18,7 +18,7 @@ CREATE ASSOCIATION . TO TYPE [OWNER ] - [DELETE_BEHAVIOR ] + [ON DELETE [ERROR MESSAGE '']] ``` ### Reference (Many-to-One) @@ -32,7 +32,7 @@ CREATE ASSOCIATION Sales.Order_Customer TO Sales.Order TYPE Reference OWNER Default - DELETE_BEHAVIOR DELETE_BUT_KEEP_REFERENCES; + ON DELETE SET NULL; ``` ### ReferenceSet (Many-to-Many) @@ -65,8 +65,11 @@ Controls what happens when an associated object is deleted. | Behavior | MDL Keyword | Description | |----------|-------------|-------------| -| Keep references | `DELETE_BUT_KEEP_REFERENCES` | Delete the object, set references to null | -| Cascade | `DELETE_AND_REFERENCES` | Delete associated objects as well | +| Keep references | `ON DELETE SET NULL` | Delete the object, set references to null (the default) | +| Cascade | `ON DELETE CASCADE` | Delete associated objects as well | +| Restrict | `ON DELETE RESTRICT [ERROR MESSAGE '…']` | Refuse the delete while references exist | + +The older `DELETE_BEHAVIOR ` clause still parses with the same meaning, but is deprecated (MDL-DEPR022). ```sql /** Invoice must be deleted with Order */ @@ -74,7 +77,7 @@ CREATE ASSOCIATION Sales.Order_Invoice FROM Sales.Order TO Sales.Invoice TYPE Reference - DELETE_BEHAVIOR DELETE_AND_REFERENCES; + ON DELETE CASCADE; ``` ## Naming Convention diff --git a/docs-site/src/language/attributes.md b/docs-site/src/language/attributes.md index 398e2ae5f2..a83ebc5b52 100644 --- a/docs-site/src/language/attributes.md +++ b/docs-site/src/language/attributes.md @@ -37,9 +37,9 @@ Validation rules are expressed as attribute constraints. When validation fails, | Validation | MDL Syntax | Description | |------------|------------|-------------| | Required | `NOT NULL` | Attribute must have a value | -| Required with message | `NOT NULL ERROR 'message'` | Custom error message on empty | +| Required with message | `NOT NULL ERROR MESSAGE 'message'` | Custom error message on empty | | Unique | `UNIQUE` | Value must be unique across all objects | -| Unique with message | `UNIQUE ERROR 'message'` | Custom error message on duplicate | +| Unique with message | `UNIQUE ERROR MESSAGE 'message'` | Custom error message on duplicate | ### Example with Validation @@ -49,14 +49,14 @@ CREATE PERSISTENT ENTITY Sales.Product ( Name: String(200) NOT NULL, -- Required with custom error - SKU: String(50) NOT NULL ERROR 'SKU is required for all products', + SKU: String(50) NOT NULL ERROR MESSAGE 'SKU is required for all products', -- Unique only Barcode: String(50) UNIQUE, -- Required and unique with custom errors - ProductCode: String(20) NOT NULL ERROR 'Product code required' - UNIQUE ERROR 'Product code must be unique', + ProductCode: String(20) NOT NULL ERROR MESSAGE 'Product code required' + UNIQUE ERROR MESSAGE 'Product code must be unique', -- Optional field (no validation) Description: String(unlimited) diff --git a/docs-site/src/language/basics.md b/docs-site/src/language/basics.md index b33d43e2ea..e109b8f5db 100644 --- a/docs-site/src/language/basics.md +++ b/docs-site/src/language/basics.md @@ -36,6 +36,22 @@ Under `mdl 1;` a missing `;` is an error, and so is the Oracle SQL*Plus-style `/ A trailing comma is allowed in every bracketed list — attributes, enumeration values, parameters, property lists, `{ … }` blocks — under every language version. `()` is the only way to write an empty list: `(,)` and `(a,,)` are errors. +## Keyword Case + +Keywords are case-insensitive, and lowercase is canonical: `describe` writes them in lowercase and `mxcli fmt` normalizes them to it. Names are not keywords, even when they are spelled like one — a module member `User`, an attribute `Title` or a property key `Folder:` keeps its case, and so does a CamelCase value such as `ButtonStyle: Success` or a type name such as `String(200)`. Expressions, XPath, OQL and SQL are stored as written, so `fmt` leaves their text alone. + +## One Spelling per Keyword + +Each keyword has one spelling, and a page action uses the words a microflow uses. The older spellings still parse with the same meaning, warn with the code shown, and are rewritten by `mxcli fmt --upgrade`: + +| Canonical | Deprecated | Code | +|---|---|---| +| `show page`, `save changes`, `cancel changes`, `close page`, `create object`, `delete`, `open link`, `sign out`, `complete task`, `call microflow M.F`, `call nanoflow M.F` | `show_page`, `save_changes`, `cancel_changes`, `close_page`, `create_object`, `delete_object`, `open_link`, `sign_out`, `complete_task`, `microflow M.F`, `nanoflow M.F` | `MDL-DEPR020` | +| `not null error message '…'` (also after `unique`, `required`), validation rule `error message '…'`, `on delete restrict error message '…'` | `not null error '…'`, `feedback '…'`, `error_message '…'`, `errormessage '…'` | `MDL-DEPR021` | +| `on delete cascade` / `restrict` / `set null` | `delete_behavior cascade` / `prevent` / `delete_and_references` / `delete_if_no_references` / `delete_but_keep_references` | `MDL-DEPR022` | +| `type ReferenceSet` | `type reference_set` | `MDL-DEPR023` | +| `returns nothing` (REST call) | `returns none` | `MDL-DEPR024` | + ## Language Version Header A script may start with a header that names the MDL language version it is written in: @@ -61,10 +77,11 @@ What `mdl 1` makes strict (each is a warning without the header, with the code s | A `/` terminator line is an error. | Accepted; `MDL-V1-SLASH`. | | `''` is the only string escape; a backslash is an ordinary character, so `'C:\temp'` is that path. | `\n`, `\r`, `\t`, `\\` and `\'` are escapes; `MDL-V1-ESCAPE` for each literal whose value would change. | | In a REST client, published REST service, business event service, model, knowledge base, consumed MCP service or agent, an unknown property key is an error that names the key it most likely meant, and so is a value its key does not take (`Response: json from $X`). | The property is ignored, or read by its shape as before; `MDL-V1-PROP` / `MDL-V1-PROPVALUE`. | +| A `while` loop is `while begin … end while;`; leaving out `begin`, or the `while` after `end`, is an error. | Accepted; `MDL-V1-WHILE`. | ### Upgrading a script: `mxcli fmt --upgrade` -`mxcli fmt --upgrade` rewrites every deprecated spelling (the `MDL-DEPRnnn` warnings) to its canonical form — `create or replace` becomes `create or modify`, `show entities` becomes `list entities` — and changes nothing else: comments, layout and keyword case are kept. A deprecated use with no mechanical rewrite is reported and left in place. +`mxcli fmt --upgrade` rewrites every deprecated spelling (the `MDL-DEPRnnn` warnings) to its canonical form — `create or replace` becomes `create or modify`, `show entities` becomes `list entities`, `on error { … }` becomes `on error begin … end error` — and changes nothing else: comments, layout and keyword case are kept. A deprecated use with no mechanical rewrite is reported and left in place. ```bash mxcli fmt --upgrade script.mdl # print the upgraded script @@ -83,6 +100,7 @@ mxcli fmt --upgrade --header -w script.mdl # also add `mdl 1;` | `MDL-V1-SET` | `$x = …` becomes `set $x = …` | | `MDL-V1-LIST`, `MDL-DEPR003`, `MDL-DEPR004` | a list operation or aggregate call becomes its statement form (`$x = filter($L, …)` → `$x = filter $L where …`); `find`/`contains` on a declared String keeps the call and gains `set` | | `MDL-V1-REPLACE02` | `create or replace user role` / `demo user` becomes a plain `create` | +| `MDL-V1-WHILE` | inserts the missing `begin` after a `while` condition and `while` after its `end` | A construct with no mechanical rewrite is reported with the reason, and `fmt` refuses to add the header rather than change the script's meaning: an unknown or mis-shaped property (`MDL-V1-PROP`, `MDL-V1-PROPVALUE`), `create or replace view entity` (`MDL-V1-REPLACE01`), a nested list operation such as `count(filter(…))`, `find`/`contains` on a variable whose type the script does not state, and an escaped line break (`\n`) inside an expression. While `mdl 1` is a preview, the header is added only when asked. Running `fmt --upgrade` on its own output changes nothing. diff --git a/docs-site/src/language/constraints.md b/docs-site/src/language/constraints.md index 287fa3351f..d61e91ad29 100644 --- a/docs-site/src/language/constraints.md +++ b/docs-site/src/language/constraints.md @@ -25,7 +25,7 @@ Name: String(200) NOT NULL With a custom error message displayed to the user: ```sql -Name: String(200) NOT NULL ERROR 'Name is required' +Name: String(200) NOT NULL ERROR MESSAGE 'Name is required' ``` ## UNIQUE @@ -39,7 +39,7 @@ Email: String(200) UNIQUE With a custom error message: ```sql -Email: String(200) UNIQUE ERROR 'Email already exists' +Email: String(200) UNIQUE ERROR MESSAGE 'Email already exists' ``` ## DEFAULT @@ -76,8 +76,8 @@ OptionalField: String(200) All three constraints can be used together: ```sql -Email: String(200) NOT NULL ERROR 'Email is required' - UNIQUE ERROR 'Email already exists' +Email: String(200) NOT NULL ERROR MESSAGE 'Email is required' + UNIQUE ERROR MESSAGE 'Email already exists' DEFAULT '' ``` @@ -89,14 +89,14 @@ CREATE PERSISTENT ENTITY Sales.Product ( Name: String(200) NOT NULL, -- Required with custom error - SKU: String(50) NOT NULL ERROR 'SKU is required for all products', + SKU: String(50) NOT NULL ERROR MESSAGE 'SKU is required for all products', -- Unique only Barcode: String(50) UNIQUE, -- Required and unique with custom errors - ProductCode: String(20) NOT NULL ERROR 'Product code required' - UNIQUE ERROR 'Product code must be unique', + ProductCode: String(20) NOT NULL ERROR MESSAGE 'Product code required' + UNIQUE ERROR MESSAGE 'Product code must be unique', -- Default only Quantity: Integer DEFAULT 0, diff --git a/docs-site/src/language/control-flow.md b/docs-site/src/language/control-flow.md index 06201caa58..f87ac5f4c0 100644 --- a/docs-site/src/language/control-flow.md +++ b/docs-site/src/language/control-flow.md @@ -170,6 +170,8 @@ END WHILE; > **Caution:** Ensure the condition will eventually become false to avoid infinite loops. +`BEGIN` and `END WHILE` are required under the `mdl 1;` header, as they are for `LOOP`. A script without the header may still leave either out, and `check` warns `MDL-V1-WHILE`. + ## Error Handling ### ON ERROR Suffix @@ -197,14 +199,16 @@ DELETE $Order ON ERROR ROLLBACK; Executes a custom error handling block when the activity fails: ```sql -COMMIT $Order ON ERROR { +COMMIT $Order ON ERROR BEGIN LOG ERROR 'Failed to commit order: ' + $Order/OrderNumber; ROLLBACK $Order; -}; +END ERROR; ``` The handler block can contain any activities -- logging, rollback, showing validation messages, etc. +The handler is flow, so it is written `BEGIN … END ERROR` like every other flow block; add `WITHOUT ROLLBACK` before `BEGIN` to keep the database changes made so far. The older brace form, `ON ERROR { … }`, still parses and builds the same handler, with warning `MDL-DEPR540`; `mxcli fmt --upgrade` rewrites it. + A handler that does not end in `RETURN` or `RAISE ERROR` falls through: after it runs, the microflow continues with the statement after the activity, through a merge placed where the two paths meet. `DESCRIBE` writes the merge's position as @@ -221,10 +225,10 @@ RETRIEVE $Config FROM Admin.SystemConfig LIMIT 1 ON ERROR CONTINUE; -- Custom error handler for external call $Response = CALL MICROFLOW Integration.CallExternalAPI ( Payload = $RequestBody -) ON ERROR { +) ON ERROR BEGIN LOG ERROR NODE 'Integration' 'External API call failed'; SET $Response = empty; -}; +END ERROR; -- Rollback on commit failure COMMIT $Order ON ERROR ROLLBACK; @@ -236,13 +240,13 @@ COMMIT $Order ON ERROR ROLLBACK; `RAISE ERROR` builds Mendix's **error event**, which re-raises the error currently being handled. Mendix allows one only where an error is in scope, so it belongs -inside an `ON ERROR { ... }` block: +inside an `ON ERROR BEGIN ... END ERROR` block: ```sql -CALL MICROFLOW Integration.CallExternalAPI (Payload = $Body) ON ERROR { +CALL MICROFLOW Integration.CallExternalAPI (Payload = $Body) ON ERROR BEGIN LOG ERROR NODE 'Integration' 'API call failed, re-raising'; RAISE ERROR; -}; +END ERROR; ``` On the **main flow** it is refused as **MDL084**, at any nesting depth — inside an @@ -331,7 +335,7 @@ The following constructs are **not** supported in MDL and will cause parse error | Unsupported | Use Instead | |-------------|-------------| | `CASE ... WHEN 'String' ... ELSE ...` | Bare enum values and a branch per value — see [CASE (Enum Split)](#case-enum-split); `CASE` itself is supported | -| `TRY ... CATCH ... END TRY` | `ON ERROR { ... }` blocks on individual activities | +| `TRY ... CATCH ... END TRY` | `ON ERROR BEGIN ... END ERROR` blocks on individual activities | ## Complete Example @@ -356,11 +360,11 @@ BEGIN @caption 'Process order' CALL MICROFLOW Sales.SUB_ProcessSingleOrder ( Order = $Order - ) ON ERROR { + ) ON ERROR BEGIN LOG ERROR 'Failed to process order: ' + $Order/OrderNumber; SET $ErrorCount = $ErrorCount + 1; CONTINUE; - }; + END ERROR; SET $SuccessCount = $SuccessCount + 1; END LOOP; diff --git a/docs-site/src/language/data-binding.md b/docs-site/src/language/data-binding.md index cb24095636..2200aaa89a 100644 --- a/docs-site/src/language/data-binding.md +++ b/docs-site/src/language/data-binding.md @@ -144,7 +144,7 @@ Action buttons can pass the current data context to microflows and pages: DATAVIEW dvOrder (DataSource: $Order) { ACTIONBUTTON btnProcess ( Caption: 'Process Order', - Action: MICROFLOW Sales.ACT_ProcessOrder(Order: $Order), + Action: MICROFLOW Sales.ACT_ProcessOrder(Order = $Order), ButtonStyle: Primary ) diff --git a/docs-site/src/language/domain-model.md b/docs-site/src/language/domain-model.md index 38b70fa63d..2649b3861b 100644 --- a/docs-site/src/language/domain-model.md +++ b/docs-site/src/language/domain-model.md @@ -43,8 +43,8 @@ CREATE ENUMERATION Sales.OrderStatus ( @Position(100, 100) CREATE PERSISTENT ENTITY Sales.Customer ( CustomerId: AutoNumber NOT NULL UNIQUE DEFAULT 1, - Name: String(200) NOT NULL ERROR 'Customer name is required', - Email: String(200) UNIQUE ERROR 'Email already registered', + Name: String(200) NOT NULL ERROR MESSAGE 'Customer name is required', + Email: String(200) UNIQUE ERROR MESSAGE 'Email already registered', Phone: String(50), IsActive: Boolean DEFAULT TRUE, CreatedAt: DateTime @@ -72,7 +72,7 @@ CREATE ASSOCIATION Sales.Order_Customer TO Sales.Order TYPE Reference OWNER Default - DELETE_BEHAVIOR DELETE_BUT_KEEP_REFERENCES; + ON DELETE SET NULL; ``` ## Further Reading diff --git a/docs-site/src/language/entities.md b/docs-site/src/language/entities.md index e5886bde16..f945111ce2 100644 --- a/docs-site/src/language/entities.md +++ b/docs-site/src/language/entities.md @@ -35,9 +35,9 @@ CREATE PERSISTENT ENTITY Sales.Customer ( /** Auto-incrementing unique identifier */ CustomerId: AutoNumber NOT NULL UNIQUE DEFAULT 1, /** Full legal name of the customer */ - Name: String(200) NOT NULL ERROR 'Name is required', + Name: String(200) NOT NULL ERROR MESSAGE 'Name is required', /** Primary contact email address */ - Email: String(200) UNIQUE ERROR 'Email must be unique', + Email: String(200) UNIQUE ERROR MESSAGE 'Email must be unique', /** Current account balance */ Balance: Decimal DEFAULT 0, /** Whether the account is active */ @@ -170,10 +170,10 @@ CREATE PERSISTENT ENTITY Sales.Customer ( CustomerId: AutoNumber NOT NULL UNIQUE DEFAULT 1, /** Full legal name of the customer */ - Name: String(200) NOT NULL ERROR 'Name is required', + Name: String(200) NOT NULL ERROR MESSAGE 'Name is required', /** Primary contact email address */ - Email: String(200) UNIQUE ERROR 'Email must be unique', + Email: String(200) UNIQUE ERROR MESSAGE 'Email must be unique', /** Current account balance in the base currency */ Balance: Decimal DEFAULT 0, diff --git a/docs-site/src/language/microflow-patterns.md b/docs-site/src/language/microflow-patterns.md index 469041b489..b2daff6f7f 100644 --- a/docs-site/src/language/microflow-patterns.md +++ b/docs-site/src/language/microflow-patterns.md @@ -22,7 +22,7 @@ BEGIN ); COMMIT $Customer; - SHOW PAGE Sales.Customer_Edit ($Customer = $Customer); + SHOW PAGE Sales.Customer_Edit (Customer = $Customer); RETURN $Customer; END; ``` @@ -135,11 +135,11 @@ BEGIN BEGIN CALL MICROFLOW Sales.SUB_ProcessOrder ( Order = $Order - ) ON ERROR { + ) ON ERROR BEGIN LOG ERROR NODE 'BatchProcess' 'Failed to process order: ' + $Order/OrderNumber; SET $ErrorCount = $ErrorCount + 1; CONTINUE; - }; + END ERROR; SET $SuccessCount = $SuccessCount + 1; END LOOP; @@ -266,19 +266,19 @@ BEGIN @caption 'Call external API' $Response = CALL MICROFLOW Integration.SUB_CallExternalAPI ( Config = $Config - ) ON ERROR { + ) ON ERROR BEGIN LOG ERROR NODE 'Integration' 'External API call failed'; RETURN false; - }; + END ERROR; IF $Response != empty THEN @caption 'Process response' CALL MICROFLOW Integration.SUB_ProcessResponse ( Response = $Response - ) ON ERROR { + ) ON ERROR BEGIN LOG ERROR NODE 'Integration' 'Failed to process response'; RETURN false; - }; + END ERROR; SET $Success = true; END IF; diff --git a/docs-site/src/language/microflows.md b/docs-site/src/language/microflows.md index 8e516f9b3b..347dacb028 100644 --- a/docs-site/src/language/microflows.md +++ b/docs-site/src/language/microflows.md @@ -171,7 +171,7 @@ BEGIN Status = 'Draft' ); COMMIT $Order; - SHOW PAGE Sales.Order_Edit ($Order = $Order); + SHOW PAGE Sales.Order_Edit (Order = $Order); RETURN $Order; END; ``` diff --git a/docs-site/src/language/nanoflows.md b/docs-site/src/language/nanoflows.md index 672e88fa18..4dfa53c9ad 100644 --- a/docs-site/src/language/nanoflows.md +++ b/docs-site/src/language/nanoflows.md @@ -79,7 +79,7 @@ $HasNetwork = CALL JAVASCRIPT ACTION NanoflowCommons.HasConnectivity(); ```sql -- Show a page -SHOW PAGE Sales.CartDetail ($Cart = $Cart); +SHOW PAGE Sales.CartDetail (Cart = $Cart); -- Close the current page CLOSE PAGE; @@ -104,7 +104,7 @@ IF $Cart/ItemCount = 0 THEN VALIDATION FEEDBACK $Cart/ItemCount MESSAGE 'Cart is empty'; RETURN false; ELSE - SHOW PAGE Sales.Checkout ($Cart = $Cart); + SHOW PAGE Sales.Checkout (Cart = $Cart); RETURN true; END IF; ``` @@ -184,7 +184,7 @@ CREATE NANOFLOW Sales.NAV_GoToOrderDetail BEGIN DECLARE $Order Sales.Order; - SHOW PAGE Sales.Order_Detail ($Order = $Order); + SHOW PAGE Sales.Order_Detail (Order = $Order); END; ``` diff --git a/docs-site/src/language/page-patterns.md b/docs-site/src/language/page-patterns.md index 89be951553..92a6caaad3 100644 --- a/docs-site/src/language/page-patterns.md +++ b/docs-site/src/language/page-patterns.md @@ -22,10 +22,10 @@ CREATE PAGE MyModule.Customer_Overview CONTROLBAR bar1 { ACTIONBUTTON btnNew ( Caption: 'New Customer', - Action: MICROFLOW MyModule.ACT_Customer_New, + Action: CALL MICROFLOW MyModule.ACT_Customer_New, ButtonStyle: Primary ) - ACTIONBUTTON btnEdit (Caption: 'Edit', Action: SHOW_PAGE MyModule.Customer_Edit) + ACTIONBUTTON btnEdit (Caption: 'Edit', Action: SHOW PAGE MyModule.Customer_Edit) ACTIONBUTTON btnDelete (Caption: 'Delete', Action: DELETE, ButtonStyle: Danger) } } @@ -41,7 +41,7 @@ BEGIN $Customer = CREATE MyModule.Customer ( IsActive = true ); - SHOW PAGE MyModule.Customer_Edit ($Customer = $Customer); + SHOW PAGE MyModule.Customer_Edit (Customer = $Customer); RETURN $Customer; END; ``` @@ -66,8 +66,8 @@ CREATE PAGE MyModule.Customer_Edit COMBOBOX cbStatus (Label: 'Status', Attribute: Status) CHECKBOX cbActive (Label: 'Active', Attribute: IsActive) FOOTER footer1 { - ACTIONBUTTON btnSave (Caption: 'Save', Action: SAVE_CHANGES, ButtonStyle: Primary) - ACTIONBUTTON btnCancel (Caption: 'Cancel', Action: CANCEL_CHANGES) + ACTIONBUTTON btnSave (Caption: 'Save', Action: SAVE CHANGES, ButtonStyle: Primary) + ACTIONBUTTON btnCancel (Caption: 'Cancel', Action: CANCEL CHANGES) } } } @@ -107,10 +107,10 @@ CREATE PAGE MyModule.Customer_Detail FOOTER footer1 { ACTIONBUTTON btnEdit ( Caption: 'Edit', - Action: SHOW_PAGE MyModule.Customer_Edit, + Action: SHOW PAGE MyModule.Customer_Edit, ButtonStyle: Primary ) - ACTIONBUTTON btnBack (Caption: 'Back', Action: CLOSE_PAGE) + ACTIONBUTTON btnBack (Caption: 'Back', Action: CLOSE PAGE) } } } @@ -140,7 +140,7 @@ CREATE PAGE MyModule.Product_MasterDetail CONTROLBAR bar1 { ACTIONBUTTON btnNew ( Caption: 'New', - Action: MICROFLOW MyModule.ACT_Product_New, + Action: CALL MICROFLOW MyModule.ACT_Product_New, ButtonStyle: Primary ) } @@ -153,7 +153,7 @@ CREATE PAGE MyModule.Product_MasterDetail TEXTBOX txtPrice (Label: 'Price', Attribute: Price) COMBOBOX cbCategory (Label: 'Category', Attribute: Category) FOOTER footer1 { - ACTIONBUTTON btnSave (Caption: 'Save', Action: SAVE_CHANGES, ButtonStyle: Primary) + ACTIONBUTTON btnSave (Caption: 'Save', Action: SAVE CHANGES, ButtonStyle: Primary) } } } @@ -199,7 +199,7 @@ CREATE PAGE MyModule.Order_MasterDetail FOOTER footer1 { ACTIONBUTTON btnEdit ( Caption: 'Edit Order', - Action: SHOW_PAGE MyModule.Order_Edit, + Action: SHOW PAGE MyModule.Order_Edit, ButtonStyle: Primary ) } @@ -236,10 +236,10 @@ CREATE PAGE HR.Employee_Overview CONTROLBAR bar1 { ACTIONBUTTON btnNew ( Caption: 'New Employee', - Action: MICROFLOW HR.ACT_Employee_New, + Action: CALL MICROFLOW HR.ACT_Employee_New, ButtonStyle: Primary ) - ACTIONBUTTON btnEdit (Caption: 'Edit', Action: SHOW_PAGE HR.Employee_Edit) + ACTIONBUTTON btnEdit (Caption: 'Edit', Action: SHOW PAGE HR.Employee_Edit) ACTIONBUTTON btnDelete (Caption: 'Delete', Action: DELETE, ButtonStyle: Danger) } } @@ -260,8 +260,8 @@ CREATE PAGE HR.Employee_Edit DATEPICKER dpHireDate (Label: 'Hire Date', Attribute: HireDate) TEXTBOX txtEmail (Label: 'Email', Attribute: Email) FOOTER footer1 { - ACTIONBUTTON btnSave (Caption: 'Save', Action: SAVE_CHANGES, ButtonStyle: Primary) - ACTIONBUTTON btnCancel (Caption: 'Cancel', Action: CANCEL_CHANGES) + ACTIONBUTTON btnSave (Caption: 'Save', Action: SAVE CHANGES, ButtonStyle: Primary) + ACTIONBUTTON btnCancel (Caption: 'Cancel', Action: CANCEL CHANGES) } } } @@ -273,7 +273,7 @@ BEGIN $Employee = CREATE HR.Employee ( HireDate = [%CurrentDateTime%] ); - SHOW PAGE HR.Employee_Edit ($Employee = $Employee); + SHOW PAGE HR.Employee_Edit (Employee = $Employee); RETURN $Employee; END; ``` diff --git a/docs-site/src/language/page-structure.md b/docs-site/src/language/page-structure.md index 615dea2f42..c19c54f8ae 100644 --- a/docs-site/src/language/page-structure.md +++ b/docs-site/src/language/page-structure.md @@ -50,8 +50,8 @@ CREATE PAGE MyModule.Customer_Edit DATAVIEW dvCustomer (DataSource: $Customer) { TEXTBOX txtName (Label: 'Name', Attribute: Name) FOOTER footer1 { - ACTIONBUTTON btnSave (Caption: 'Save', Action: SAVE_CHANGES, ButtonStyle: Primary) - ACTIONBUTTON btnCancel (Caption: 'Cancel', Action: CANCEL_CHANGES) + ACTIONBUTTON btnSave (Caption: 'Save', Action: SAVE CHANGES, ButtonStyle: Primary) + ACTIONBUTTON btnCancel (Caption: 'Cancel', Action: CANCEL CHANGES) } } } diff --git a/docs-site/src/language/pages.md b/docs-site/src/language/pages.md index 83f15e8164..05b8715dd1 100644 --- a/docs-site/src/language/pages.md +++ b/docs-site/src/language/pages.md @@ -60,8 +60,8 @@ CREATE PAGE MyModule.Customer_Edit TEXTBOX txtName (Label: 'Name', Attribute: Name) TEXTBOX txtEmail (Label: 'Email', Attribute: Email) FOOTER footer1 { - ACTIONBUTTON btnSave (Caption: 'Save', Action: SAVE_CHANGES, ButtonStyle: Primary) - ACTIONBUTTON btnCancel (Caption: 'Cancel', Action: CANCEL_CHANGES) + ACTIONBUTTON btnSave (Caption: 'Save', Action: SAVE CHANGES, ButtonStyle: Primary) + ACTIONBUTTON btnCancel (Caption: 'Cancel', Action: CANCEL CHANGES) } } } diff --git a/docs-site/src/language/qualified-names.md b/docs-site/src/language/qualified-names.md index 326f2fe56c..29f633749e 100644 --- a/docs-site/src/language/qualified-names.md +++ b/docs-site/src/language/qualified-names.md @@ -83,7 +83,7 @@ CREATE ASSOCIATION Sales.Order_Customer Status: Enumeration(Sales.OrderStatus) DEFAULT 'Active' -- Microflow call -$Result = CALL MICROFLOW Sales.ACT_ProcessOrder ($Order = $Order); +$Result = CALL MICROFLOW Sales.ACT_ProcessOrder (Order = $Order); ``` ## See Also diff --git a/docs-site/src/language/snippets.md b/docs-site/src/language/snippets.md index 04fb1c5339..8469362269 100644 --- a/docs-site/src/language/snippets.md +++ b/docs-site/src/language/snippets.md @@ -47,7 +47,7 @@ CREATE SNIPPET MyModule.CustomerCard DYNAMICTEXT txtEmail (Content: '{1}', Attribute: Email) ACTIONBUTTON btnEdit ( Caption: 'Edit', - Action: SHOW_PAGE MyModule.Customer_Edit, + Action: SHOW PAGE MyModule.Customer_Edit, ButtonStyle: Primary ) } diff --git a/docs-site/src/language/widget-types.md b/docs-site/src/language/widget-types.md index 8e13a2248e..d503360e8c 100644 --- a/docs-site/src/language/widget-types.md +++ b/docs-site/src/language/widget-types.md @@ -88,8 +88,8 @@ DATAVIEW dvCustomer (DataSource: $Customer) { TEXTBOX txtEmail (Label: 'Email', Attribute: Email) COMBOBOX cbStatus (Label: 'Status', Attribute: Status) FOOTER footer1 { - ACTIONBUTTON btnSave (Caption: 'Save', Action: SAVE_CHANGES, ButtonStyle: Primary) - ACTIONBUTTON btnCancel (Caption: 'Cancel', Action: CANCEL_CHANGES) + ACTIONBUTTON btnSave (Caption: 'Save', Action: SAVE CHANGES, ButtonStyle: Primary) + ACTIONBUTTON btnCancel (Caption: 'Cancel', Action: CANCEL CHANGES) } } ``` @@ -112,7 +112,7 @@ DATAGRID dgOrders (DataSource: DATABASE Sales.Order, PageSize: 20) { COLUMN colAmount (Attribute: Amount, Caption: 'Amount', Alignment: right) COLUMN colStatus (Attribute: Status, Caption: 'Status') CONTROLBAR bar1 { - ACTIONBUTTON btnNew (Caption: 'New', Action: MICROFLOW Sales.ACT_CreateOrder, ButtonStyle: Primary) + ACTIONBUTTON btnNew (Caption: 'New', Action: CALL MICROFLOW Sales.ACT_CreateOrder, ButtonStyle: Primary) } } ``` @@ -331,8 +331,8 @@ IMAGE imgBanner (Width: 800, Height: 200) A button that triggers an action. The primary interactive element: ```sql -ACTIONBUTTON btnSave (Caption: 'Save', Action: SAVE_CHANGES, ButtonStyle: Primary) -ACTIONBUTTON btnCancel (Caption: 'Cancel', Action: CANCEL_CHANGES) +ACTIONBUTTON btnSave (Caption: 'Save', Action: SAVE CHANGES, ButtonStyle: Primary) +ACTIONBUTTON btnCancel (Caption: 'Cancel', Action: CANCEL CHANGES) ACTIONBUTTON btnDelete (Caption: 'Delete', Action: DELETE, ButtonStyle: Danger) ``` @@ -340,13 +340,19 @@ ACTIONBUTTON btnDelete (Caption: 'Delete', Action: DELETE, ButtonStyle: Danger) | Action | Description | |--------|-------------| -| `SAVE_CHANGES` | Commit and close the page | -| `CANCEL_CHANGES` | Roll back and close the page | +| `SAVE CHANGES` | Commit and close the page | +| `CANCEL CHANGES` | Roll back and close the page | | `DELETE` | Delete the current object | -| `CLOSE_PAGE` | Close the page without saving | -| `MICROFLOW Module.MF_Name` | Call a microflow | -| `NANOFLOW Module.NF_Name` | Call a nanoflow | -| `PAGE Module.PageName` | Open a page | +| `CLOSE PAGE` | Close the page without saving | +| `CALL MICROFLOW Module.MF_Name` | Call a microflow | +| `CALL NANOFLOW Module.NF_Name` | Call a nanoflow | +| `SHOW PAGE Module.PageName` | Open a page | +| `CREATE OBJECT Module.Entity THEN SHOW PAGE Module.PageName` | Create an object and open a page for it | +| `OPEN LINK 'https://…'` | Open a web address | +| `SIGN OUT` | Sign the user out | +| `COMPLETE TASK 'Outcome'` | Complete a workflow user task | + +The snake-case spellings (`SAVE_CHANGES`, `SHOW_PAGE`, …) and `MICROFLOW …` without `CALL` are deprecated aliases (MDL-DEPR020). **Button styles:** @@ -364,7 +370,7 @@ ACTIONBUTTON btnDelete (Caption: 'Delete', Action: DELETE, ButtonStyle: Danger) ```sql ACTIONBUTTON btnProcess ( Caption: 'Process', - Action: MICROFLOW Sales.ACT_ProcessOrder(Order: $Order), + Action: MICROFLOW Sales.ACT_ProcessOrder(Order = $Order), ButtonStyle: Primary ) ``` @@ -410,8 +416,8 @@ Footer section of a DataView. Typically contains save/cancel buttons: ```sql FOOTER footer1 { - ACTIONBUTTON btnSave (Caption: 'Save', Action: SAVE_CHANGES, ButtonStyle: Primary) - ACTIONBUTTON btnCancel (Caption: 'Cancel', Action: CANCEL_CHANGES) + ACTIONBUTTON btnSave (Caption: 'Save', Action: SAVE CHANGES, ButtonStyle: Primary) + ACTIONBUTTON btnCancel (Caption: 'Cancel', Action: CANCEL CHANGES) } ``` @@ -421,7 +427,7 @@ Control bar for DataGrid widgets. Contains action buttons for the grid: ```sql CONTROLBAR bar1 { - ACTIONBUTTON btnNew (Caption: 'New', Action: MICROFLOW Module.ACT_Create, ButtonStyle: Primary) + ACTIONBUTTON btnNew (Caption: 'New', Action: CALL MICROFLOW Module.ACT_Create, ButtonStyle: Primary) ACTIONBUTTON btnEdit (Caption: 'Edit', Action: PAGE Module.Entity_Edit) ACTIONBUTTON btnDelete (Caption: 'Delete', Action: DELETE, ButtonStyle: Danger) } diff --git a/docs-site/src/language/workflow-activities.md b/docs-site/src/language/workflow-activities.md index bc5b3f30f8..bc2e58e63b 100644 --- a/docs-site/src/language/workflow-activities.md +++ b/docs-site/src/language/workflow-activities.md @@ -83,7 +83,7 @@ builds and never completes. Execute a microflow as part of the workflow. Optionally specify a comment and outcomes: ```sql -CALL MICROFLOW . [COMMENT ''] +CALL MICROFLOW . [( = , ...)] [COMMENT ''] [OUTCOMES '' { } ...]; ``` @@ -91,8 +91,15 @@ Example: ```sql CALL MICROFLOW HR.ACT_SendNotification COMMENT 'Notify the applicant'; +CALL MICROFLOW HR.ACT_Escalate(Request = $WorkflowContext) COMMENT 'Escalate'; ``` +Arguments are bound like every other call in MDL: `Param = expression` right +after the callee, the expression written bare. The older spelling after the +comment, `WITH (Param = '')` with the expression inside a string, +still parses with the same meaning but is deprecated (MDL-DEPR008); +`mxcli fmt --upgrade` rewrites it. + ## AI Agent Task A step that runs an AI agent (Mendix 11.9 or later). It is written like `CALL @@ -101,16 +108,15 @@ mappings, outcomes and boundary events. The microflow is where the agent is invoked — build agents with the Studio Pro Agent Editor, or `CREATE AGENT`. ```sql -CALL AGENT MICROFLOW . [AS ] [COMMENT ''] - [WITH ( = '', ...)] +CALL AGENT MICROFLOW . [( = , ...)] [AS ] [COMMENT ''] [OUTCOMES -> { } ...]; ``` Example — branch on the agent's answer: ```sql -CALL AGENT MICROFLOW HR.ACT_ClassifyRequest AS aiAgentTask1 COMMENT 'Classify the request' - WITH (Request = '$WorkflowContext') +CALL AGENT MICROFLOW HR.ACT_ClassifyRequest(Request = $WorkflowContext) AS aiAgentTask1 + COMMENT 'Classify the request' OUTCOMES true -> { USER TASK Expedite 'Expedite the request' PAGE HR.TaskPage OUTCOMES 'Done' { }; } false -> { }; @@ -125,7 +131,7 @@ branch with `OUTCOMES`; return nothing for a single path. Start a sub-workflow: ```sql -CALL WORKFLOW . [COMMENT '']; +CALL WORKFLOW . [( = , ...)] [COMMENT '']; ``` Example: diff --git a/docs-site/src/migration/generation.md b/docs-site/src/migration/generation.md index 7d52f148fd..1cf0860300 100644 --- a/docs-site/src/migration/generation.md +++ b/docs-site/src/migration/generation.md @@ -46,8 +46,8 @@ CREATE ENUMERATION Sales.OrderStatus ( /** Customer master data */ @Position(100, 100) CREATE PERSISTENT ENTITY CRM.Customer ( - Name: String(200) NOT NULL ERROR 'Customer name is required', - Email: String(200) UNIQUE ERROR 'Email already exists', + Name: String(200) NOT NULL ERROR MESSAGE 'Customer name is required', + Email: String(200) UNIQUE ERROR MESSAGE 'Email already exists', Phone: String(50), IsActive: Boolean DEFAULT TRUE ) @@ -106,7 +106,7 @@ CREATE PAGE CRM.Customer_Overview ( COLUMN colPhone (Attribute: Phone, Caption: 'Phone') COLUMN colActive (Attribute: IsActive, Caption: 'Active') CONTROLBAR cb1 { - ACTIONBUTTON btnNew (Caption: 'New', Action: SHOW_PAGE CRM.Customer_NewEdit, ButtonStyle: Primary) + ACTIONBUTTON btnNew (Caption: 'New', Action: SHOW PAGE CRM.Customer_NewEdit, ButtonStyle: Primary) } } }; diff --git a/docs-site/src/migration/validation.md b/docs-site/src/migration/validation.md index 0c173d2ac8..efeddfae87 100644 --- a/docs-site/src/migration/validation.md +++ b/docs-site/src/migration/validation.md @@ -65,7 +65,7 @@ $Line = CREATE Sales.OrderLine (Price = 10.00, Quantity = 3); CHANGE $Line (Sales.OrderLine_Order = $Order); COMMIT $Line; -CALL MICROFLOW Sales.ACT_Order_CalculateTotal ($Order = $Order); +CALL MICROFLOW Sales.ACT_Order_CalculateTotal (Order = $Order); -- @assert: $Order/TotalAmount = 30.00 ``` diff --git a/docs-site/src/reference/domain-model/create-association.md b/docs-site/src/reference/domain-model/create-association.md index 0835677ccb..73f203f85f 100644 --- a/docs-site/src/reference/domain-model/create-association.md +++ b/docs-site/src/reference/domain-model/create-association.md @@ -7,7 +7,7 @@ TO to_module.to_entity TYPE { Reference | ReferenceSet } [ OWNER { Default | Both | Parent | Child } ] - [ DELETE_BEHAVIOR { DELETE_BUT_KEEP_REFERENCES | DELETE_AND_REFERENCES | DELETE_IF_NO_REFERENCES | CASCADE | PREVENT } ] + [ ON DELETE { SET NULL | CASCADE | RESTRICT } [ ERROR MESSAGE 'text' ] ] ## Description @@ -29,15 +29,15 @@ The `OWNER` clause controls which side of the association can modify the relatio | `Parent` | Only the FROM side can modify the association | | `Child` | Only the TO side can modify the association | -The `DELETE_BEHAVIOR` clause controls what happens when an object on the FROM side is deleted: +The `ON DELETE` clause controls what happens when an object on the FROM side is deleted: -| Behavior | Description | +| Action | Description | |----------|-------------| -| `DELETE_BUT_KEEP_REFERENCES` | Delete the object and set references to null (the default) | -| `DELETE_AND_REFERENCES` | Delete the object and all associated objects on the TO side | -| `CASCADE` | Alias for `DELETE_AND_REFERENCES` | -| `DELETE_IF_NO_REFERENCES` | Refuse the delete while anything still references the object | -| `PREVENT` | Alias for `DELETE_IF_NO_REFERENCES` | +| `SET NULL` | Delete the object and set references to null (the default) | +| `CASCADE` | Delete the object and all associated objects on the TO side | +| `RESTRICT` | Refuse the delete while anything still references the object; `ERROR MESSAGE '…'` is the text the user sees | + +The older `DELETE_BEHAVIOR` clause (`DELETE_BUT_KEEP_REFERENCES`, `DELETE_AND_REFERENCES` / `CASCADE`, `DELETE_IF_NO_REFERENCES` / `PREVENT`) still parses with the same meaning, but is deprecated (MDL-DEPR022); `mxcli fmt --upgrade` rewrites it. If `OR MODIFY` is specified, the statement is idempotent: if the association already exists, it is updated to match the new definition. @@ -63,7 +63,7 @@ A documentation comment (`/** ... */`) placed before the statement is preserved **OWNER** : Which side can modify the association. Defaults to `Default` if omitted. -**DELETE_BEHAVIOR** +**ON DELETE** : What happens to associated objects when a FROM-side object is deleted. If omitted, references are kept (the default Mendix behavior). ## Examples @@ -76,7 +76,7 @@ CREATE ASSOCIATION Sales.Order_Customer TO Sales.Order TYPE Reference OWNER Default - DELETE_BEHAVIOR DELETE_BUT_KEEP_REFERENCES; + ON DELETE SET NULL; ``` ### Many-to-many: Order has Products @@ -97,7 +97,7 @@ CREATE ASSOCIATION Sales.Order_Invoice FROM Sales.Order TO Sales.Invoice TYPE Reference - DELETE_BEHAVIOR DELETE_AND_REFERENCES; + ON DELETE CASCADE; ``` ### Idempotent with OR MODIFY diff --git a/docs-site/src/reference/domain-model/create-entity.md b/docs-site/src/reference/domain-model/create-entity.md index 0207c6d921..ca613bdd2c 100644 --- a/docs-site/src/reference/domain-model/create-entity.md +++ b/docs-site/src/reference/domain-model/create-entity.md @@ -116,9 +116,9 @@ CREATE PERSISTENT ENTITY Sales.Customer ( CustomerId: AutoNumber NOT NULL UNIQUE DEFAULT 1, /** Customer full name */ - Name: String(200) NOT NULL ERROR 'Name is required', + Name: String(200) NOT NULL ERROR MESSAGE 'Name is required', - Email: String(200) UNIQUE ERROR 'Email must be unique', + Email: String(200) UNIQUE ERROR MESSAGE 'Email must be unique', Balance: Decimal DEFAULT 0, diff --git a/docs-site/src/reference/microflow/create-microflow.md b/docs-site/src/reference/microflow/create-microflow.md index 24d4e4be43..dc59012fa2 100644 --- a/docs-site/src/reference/microflow/create-microflow.md +++ b/docs-site/src/reference/microflow/create-microflow.md @@ -86,7 +86,7 @@ Call another microflow, nanoflow, or Java action. Parameters are passed by name. **UI Actions** ```sql -SHOW PAGE Module.PageName ( $Param = $value ); +SHOW PAGE Module.PageName ( Param = $value ); CLOSE PAGE; ``` @@ -137,12 +137,12 @@ RETURN $value; -- Suffix on any activity (except EXECUTE DATABASE QUERY) activity ON ERROR CONTINUE; activity ON ERROR ROLLBACK; -activity ON ERROR { +activity ON ERROR BEGIN handler_statements -}; +END ERROR; ``` -Error handling is attached as a suffix to an individual activity. `ON ERROR CONTINUE` suppresses the error and continues. `ON ERROR ROLLBACK` rolls back the current transaction. `ON ERROR { ... }` executes custom error-handling logic. +Error handling is attached as a suffix to an individual activity. `ON ERROR CONTINUE` suppresses the error and continues. `ON ERROR ROLLBACK` rolls back the current transaction. `ON ERROR BEGIN ... END ERROR` executes custom error-handling logic. ### Annotations @@ -199,7 +199,7 @@ BEGIN Status = 'Draft' ); COMMIT $Order; - SHOW PAGE Sales.Order_Edit ($Order = $Order); + SHOW PAGE Sales.Order_Edit (Order = $Order); RETURN $Order; END; ``` @@ -257,11 +257,11 @@ BEGIN $Result = CALL MICROFLOW Integration.SUB_FetchExternalData ( SyncResult = $Result - ) ON ERROR { + ) ON ERROR BEGIN CHANGE $Result (Status = 'Failed'); COMMIT $Result; LOG ERROR NODE 'Integration' 'Sync failed'; - }; + END ERROR; CHANGE $Result ( Status = 'Completed', diff --git a/docs-site/src/reference/microflow/create-nanoflow.md b/docs-site/src/reference/microflow/create-nanoflow.md index 99d5666fc9..e92d8a0333 100644 --- a/docs-site/src/reference/microflow/create-nanoflow.md +++ b/docs-site/src/reference/microflow/create-nanoflow.md @@ -101,7 +101,7 @@ CREATE NANOFLOW Sales.NAV_SubmitOrder BEGIN CHANGE $Order (Status = 'Submitted'); $Result = CALL MICROFLOW Sales.ACT_ProcessOrder (Order = $Order); - SHOW PAGE Sales.Order_Confirmation ($Order = $Order); + SHOW PAGE Sales.Order_Confirmation (Order = $Order); END; ``` diff --git a/docs-site/src/reference/page/create-page.md b/docs-site/src/reference/page/create-page.md index 6c27500945..925df9d77c 100644 --- a/docs-site/src/reference/page/create-page.md +++ b/docs-site/src/reference/page/create-page.md @@ -119,18 +119,18 @@ values, including the argument list. | Action | Syntax | Description | |--------|--------|-------------| -| Save | `Action: SAVE_CHANGES` | Commits and closes | -| Cancel | `Action: CANCEL_CHANGES` | Rolls back and closes | -| Microflow | `Action: MICROFLOW Module.Name(Param: val)` | Calls a microflow | -| Nanoflow | `Action: NANOFLOW Module.Name(Param: val)` | Calls a nanoflow | +| Save | `Action: save changes` | Commits and closes | +| Cancel | `Action: cancel changes` | Rolls back and closes | +| Microflow | `Action: MICROFLOW Module.Name(Param = val)` | Calls a microflow | +| Nanoflow | `Action: NANOFLOW Module.Name(Param = val)` | Calls a nanoflow | | Page | `Action: PAGE Module.PageName` | Opens a page | -| Close | `Action: CLOSE_PAGE` | Closes the current page | +| Close | `Action: CLOSE PAGE` | Closes the current page | | Delete | `Action: DELETE` | Deletes the context object | | Nothing | `Action: NOTHING` | Deliberately no action — a decorative button, a card that is not clickable | The set is closed. Anything else in an action slot is an error (**MDL-WIDGET28**), and that includes a real action keyword **missing its -argument** — `Action: OPEN_LINK` with no URL, `Action: SHOW_PAGE` with no page. +argument** — `Action: OPEN LINK` with no URL, `Action: SHOW PAGE` with no page. Such a widget used to be written with no action at all: it rendered, carried its caption, and did nothing, while `mxcli check`, `exec` and mxbuild all reported success, because a no-action widget is perfectly legal Mendix. Write `NOTHING` @@ -150,7 +150,7 @@ DATAGRID dgOrders (DataSource: DATABASE FROM Sales.Order, Selection: Single) { COLUMN colNr (Attribute: Number, Caption: 'Order #') CONTROLBAR cb { CONTAINER cShip (Class: 'command', - Action: NANOFLOW Sales.ACT_Ship($Order = $dgOrders)) { + Action: NANOFLOW Sales.ACT_Ship(Order = $dgOrders)) { ACTIONBUTTON btnShip (Caption: 'Ship') } } @@ -233,8 +233,8 @@ CREATE PAGE MyModule.Customer_Edit COMBOBOX cbStatus (Label: 'Status', Attribute: Status) FOOTER footer1 { - ACTIONBUTTON btnSave (Caption: 'Save', Action: SAVE_CHANGES, ButtonStyle: Primary) - ACTIONBUTTON btnCancel (Caption: 'Cancel', Action: CANCEL_CHANGES) + ACTIONBUTTON btnSave (Caption: 'Save', Action: SAVE CHANGES, ButtonStyle: Primary) + ACTIONBUTTON btnCancel (Caption: 'Cancel', Action: CANCEL CHANGES) } } }; @@ -256,7 +256,7 @@ CREATE PAGE Sales.Order_Overview COLUMN colStatus (Attribute: Status, Caption: 'Status') COLUMN colAmount (Attribute: TotalAmount, Caption: 'Amount', Alignment: right) CONTROLBAR cb1 { - ACTIONBUTTON btnNew (Caption: 'New Order', Action: SHOW_PAGE Sales.Order_Edit, ButtonStyle: Primary) + ACTIONBUTTON btnNew (Caption: 'New Order', Action: SHOW PAGE Sales.Order_Edit, ButtonStyle: Primary) } } }; @@ -349,13 +349,13 @@ CREATE PAGE MyModule.AdvancedForm { DATAVIEW dvItem (DataSource: $Item) { TEXTBOX txtName (Label: 'Name', Attribute: Name) - ACTIONBUTTON btnToggle (Caption: 'Show Advanced', Action: NANOFLOW MyModule.NAV_Toggle) + ACTIONBUTTON btnToggle (Caption: 'Show Advanced', Action: CALL NANOFLOW MyModule.NAV_Toggle) CONTAINER cntAdvanced (Visible: '$showAdvanced') { TEXTAREA taNotes (Label: 'Notes', Attribute: Notes) } FOOTER footer1 { - ACTIONBUTTON btnSave (Caption: 'Save', Action: SAVE_CHANGES, ButtonStyle: Primary) - ACTIONBUTTON btnCancel (Caption: 'Cancel', Action: CANCEL_CHANGES) + ACTIONBUTTON btnSave (Caption: 'Save', Action: SAVE CHANGES, ButtonStyle: Primary) + ACTIONBUTTON btnCancel (Caption: 'Cancel', Action: CANCEL CHANGES) } } }; diff --git a/docs-site/src/reference/page/create-snippet.md b/docs-site/src/reference/page/create-snippet.md index 72dbe5229d..2dce807b13 100644 --- a/docs-site/src/reference/page/create-snippet.md +++ b/docs-site/src/reference/page/create-snippet.md @@ -104,7 +104,7 @@ CREATE PAGE MyModule.Customer_Edit DATAVIEW dvCustomer (DataSource: $Customer) { SNIPPETCALL snpAddress (Snippet: MyModule.AddressFields) FOOTER footer1 { - ACTIONBUTTON btnSave (Caption: 'Save', Action: SAVE_CHANGES, ButtonStyle: Primary) + ACTIONBUTTON btnSave (Caption: 'Save', Action: SAVE CHANGES, ButtonStyle: Primary) } } }; diff --git a/docs-site/src/reference/query/describe-association.md b/docs-site/src/reference/query/describe-association.md index 62a53145f1..bdaf5415c6 100644 --- a/docs-site/src/reference/query/describe-association.md +++ b/docs-site/src/reference/query/describe-association.md @@ -32,7 +32,7 @@ CREATE ASSOCIATION Sales.Order_Customer TO Sales.Order TYPE Reference OWNER Default - DELETE_BEHAVIOR DELETE_BUT_KEEP_REFERENCES; + ON DELETE SET NULL; ``` Describe a many-to-many association: diff --git a/docs-site/src/reference/query/describe-microflow.md b/docs-site/src/reference/query/describe-microflow.md index 955e7c663c..75b72d2e61 100644 --- a/docs-site/src/reference/query/describe-microflow.md +++ b/docs-site/src/reference/query/describe-microflow.md @@ -37,7 +37,7 @@ BEGIN Status = 'Draft' ); COMMIT $Order; - SHOW PAGE Sales.Order_Edit ($Order = $Order); + SHOW PAGE Sales.Order_Edit (Order = $Order); RETURN $Order; END; ``` diff --git a/docs-site/src/reference/query/describe-page.md b/docs-site/src/reference/query/describe-page.md index 8fc6657771..183914a47a 100644 --- a/docs-site/src/reference/query/describe-page.md +++ b/docs-site/src/reference/query/describe-page.md @@ -39,8 +39,8 @@ CREATE PAGE Sales.Customer_Edit TEXTBOX txtName (Label: 'Name', Attribute: Name) TEXTBOX txtEmail (Label: 'Email', Attribute: Email) FOOTER footer1 { - ACTIONBUTTON btnSave (Caption: 'Save', Action: SAVE_CHANGES, ButtonStyle: Primary) - ACTIONBUTTON btnCancel (Caption: 'Cancel', Action: CANCEL_CHANGES) + ACTIONBUTTON btnSave (Caption: 'Save', Action: SAVE CHANGES, ButtonStyle: Primary) + ACTIONBUTTON btnCancel (Caption: 'Cancel', Action: CANCEL CHANGES) } } } diff --git a/docs-site/src/tools/theme.md b/docs-site/src/tools/theme.md index 6636339a2e..eee26b5ed0 100644 --- a/docs-site/src/tools/theme.md +++ b/docs-site/src/tools/theme.md @@ -87,7 +87,7 @@ the set that is actually installed — so a cycle button can never offer a theme whose CSS is not in the page. ```sql -actionbutton btnSkin (caption: 'Theme', action: nanoflow MyFirstModule.ACT_CycleSkin) +actionbutton btnSkin (caption: 'Theme', action: call nanoflow MyFirstModule.ACT_CycleSkin) ``` The same reload caveat as the light/dark toggle applies: the choice is remembered @@ -149,7 +149,7 @@ to run script before first paint. It creates three JavaScript actions wire a button: ```sql -actionbutton btnTheme (caption: 'Theme', action: nanoflow MyFirstModule.ACT_ToggleTheme) +actionbutton btnTheme (caption: 'Theme', action: call nanoflow MyFirstModule.ACT_ToggleTheme) ``` A click flips the palette and remembers the choice in `localStorage`. The class diff --git a/docs-site/src/tutorial/create-entity.md b/docs-site/src/tutorial/create-entity.md index 65eed3e1c8..f870022491 100644 --- a/docs-site/src/tutorial/create-entity.md +++ b/docs-site/src/tutorial/create-entity.md @@ -76,7 +76,7 @@ You can specify what happens when the "to" entity is deleted: CREATE ASSOCIATION MyModule.Order_Product FROM MyModule.Order TO MyModule.Product TYPE Reference - DELETE_BEHAVIOR PREVENT; + ON DELETE RESTRICT; ``` Options: `PREVENT` (block deletion if referenced), `CASCADE` (delete the associated objects too), or leave it out for the default behavior — delete the object and null out the references. diff --git a/docs-site/src/tutorial/create-microflow.md b/docs-site/src/tutorial/create-microflow.md index 56bbec0380..7921d6d889 100644 --- a/docs-site/src/tutorial/create-microflow.md +++ b/docs-site/src/tutorial/create-microflow.md @@ -162,10 +162,10 @@ COMMIT $Product ON ERROR CONTINUE; Options are `CONTINUE` (ignore the error and proceed), `ROLLBACK` (roll back the transaction and continue), or an inline error handler block: ```sql -COMMIT $Product ON ERROR { +COMMIT $Product ON ERROR BEGIN LOG ERROR 'Failed to commit product'; RETURN false; -}; +END ERROR; ``` ## Organizing with folders diff --git a/docs-site/src/tutorial/create-page.md b/docs-site/src/tutorial/create-page.md index ced0ca6078..706f26a98f 100644 --- a/docs-site/src/tutorial/create-page.md +++ b/docs-site/src/tutorial/create-page.md @@ -85,8 +85,8 @@ CREATE PAGE MyModule.Product_Edit CHECKBOX cbActive (Label: 'Active', Attribute: IsActive) FOOTER footer1 { - ACTIONBUTTON btnSave (Caption: 'Save', Action: SAVE_CHANGES, ButtonStyle: Primary) - ACTIONBUTTON btnCancel (Caption: 'Cancel', Action: CANCEL_CHANGES) + ACTIONBUTTON btnSave (Caption: 'Save', Action: SAVE CHANGES, ButtonStyle: Primary) + ACTIONBUTTON btnCancel (Caption: 'Cancel', Action: CANCEL CHANGES) } } }; @@ -101,8 +101,8 @@ Key differences from the overview page: | `DATAVIEW dvProduct (DataSource: $Product)` | Binds to the page parameter | | `TEXTBOX`, `CHECKBOX` | Input widgets bound to entity attributes | | `FOOTER` | A section at the bottom of the DataView for action buttons | -| `Action: SAVE_CHANGES` | Built-in action that commits the object and closes the page | -| `Action: CANCEL_CHANGES` | Built-in action that rolls back changes and closes the page | +| `Action: SAVE CHANGES` | Built-in action that commits the object and closes the page | +| `Action: CANCEL CHANGES` | Built-in action that rolls back changes and closes the page | Notice the two different page syntaxes: the overview page uses the **compact syntax** (`LAYOUT` and `TITLE` as keywords before parentheses), while the edit page uses the **property syntax** (properties inside a `(Key: value)` block followed by a `{ widget tree }` block). Both are valid -- use whichever fits better. @@ -130,10 +130,10 @@ DYNAMICTEXT dynName (Attribute: Name) ### Action buttons ```sql -ACTIONBUTTON btnSave (Caption: 'Save', Action: SAVE_CHANGES, ButtonStyle: Primary) -ACTIONBUTTON btnCancel (Caption: 'Cancel', Action: CANCEL_CHANGES) +ACTIONBUTTON btnSave (Caption: 'Save', Action: SAVE CHANGES, ButtonStyle: Primary) +ACTIONBUTTON btnCancel (Caption: 'Cancel', Action: CANCEL CHANGES) ACTIONBUTTON btnDelete (Caption: 'Delete', Action: DELETE, ButtonStyle: Danger) -ACTIONBUTTON btnProcess (Caption: 'Process', Action: MICROFLOW MyModule.ACT_ProcessProduct(Product: $Product)) +ACTIONBUTTON btnProcess (Caption: 'Process', Action: MICROFLOW MyModule.ACT_ProcessProduct(Product = $Product)) ``` ### Layout widgets diff --git a/docs-site/src/tutorial/describe-search.md b/docs-site/src/tutorial/describe-search.md index 8856cdf731..76ad32622f 100644 --- a/docs-site/src/tutorial/describe-search.md +++ b/docs-site/src/tutorial/describe-search.md @@ -42,7 +42,7 @@ CREATE ASSOCIATION MyFirstModule.Order_Customer FROM MyFirstModule.Order TO MyFirstModule.Customer TYPE Reference OWNER Default - DELETE_BEHAVIOR DELETE_BUT_KEEP_REFERENCES; + ON DELETE SET NULL; ``` ## DESCRIBE MICROFLOW @@ -96,8 +96,8 @@ CREATE PAGE MyFirstModule.Customer_Edit TEXTBOX txtPhone (Label: 'Phone', Attribute: Phone) FOOTER footer1 { - ACTIONBUTTON btnSave (Caption: 'Save', Action: SAVE_CHANGES, ButtonStyle: Primary) - ACTIONBUTTON btnCancel (Caption: 'Cancel', Action: CANCEL_CHANGES) + ACTIONBUTTON btnSave (Caption: 'Save', Action: SAVE CHANGES, ButtonStyle: Primary) + ACTIONBUTTON btnCancel (Caption: 'Cancel', Action: CANCEL CHANGES) } } }; @@ -132,7 +132,7 @@ CREATE ASSOCIATION MyFirstModule.Order_Customer FROM MyFirstModule.Order TO MyFirstModule.Customer TYPE Reference OWNER Default - DELETE_BEHAVIOR DELETE_BUT_KEEP_REFERENCES; + ON DELETE SET NULL; ``` ## DESCRIBE MODULE diff --git a/docs/01-project/MDL_QUICK_REFERENCE.md b/docs/01-project/MDL_QUICK_REFERENCE.md index f9add0af03..6135dcace9 100644 --- a/docs/01-project/MDL_QUICK_REFERENCE.md +++ b/docs/01-project/MDL_QUICK_REFERENCE.md @@ -89,7 +89,7 @@ create persistent entity Module.Photo ( | Create enumeration | `create [or modify] enumeration Module.Name (Value1 'caption', ...);` | | | Alter enumeration values | `alter enumeration Module.Name add value [if not exists] X [caption '..'] \| rename value X to Y \| modify value X caption '..' \| drop value [if exists] X;` | `modify value … caption` re-captions in place (works while referenced). `if not exists` / `if exists` make the script re-runnable — the bare forms error and stop the run | | Drop enumeration | `drop enumeration [if exists] Module.Name;` | Refused for `System.*` (read-only platform module) | -| Create association | `create [or modify] association Module.Name from Parent to Child type reference\|ReferenceSet [owner default\|both] [delete_behavior ...];` | OR MODIFY updates existing association in-place. **The FROM entity must live in `Module`** — Mendix stores an association in its FROM entity's module, so a remote FROM writes a dangling pointer and the project stops OPENING (**MDL070**). The TO entity may be remote; that direction is stored BY NAME | +| Create association | `create [or modify] association Module.Name from Parent to Child type reference\|ReferenceSet [owner default\|both] [on delete cascade\|restrict\|set null [error message '...']];` | OR MODIFY updates existing association in-place. **The FROM entity must live in `Module`** — Mendix stores an association in its FROM entity's module, so a remote FROM writes a dangling pointer and the project stops OPENING (**MDL070**). The TO entity may be remote; that direction is stored BY NAME | | Drop association | `drop association [if exists] Module.Name;` | | | Association line anchors | `@anchor(from: (0, 54), to: (100, 54))` above `create association …` | Where the connector attaches to each entity box, as a **percentage** of the box (0..100, whole numbers). `from` = the FROM entity's box, `to` = the TO entity's. Omitting an end preserves what is stored, so a `create or modify` about something else never flattens a hand-tuned line. Cross-module associations have no anchors — Mendix stores none | | Retune anchors in place | `alter association Module.Name set anchor from (50, 100) to (50, 0);` | `(0, 50)` left-middle, `(100, 50)` right-middle, `(50, 100)` bottom-centre. `describe association` re-emits a non-default pair as the same `@anchor(...)`, so describe → edit → exec round-trips | @@ -236,8 +236,8 @@ the statement names the **attribute**, not the rule. | Statement | Syntax | Notes | |-----------|--------|-------| -| Create regex rule | `create validation rule for Module.Entity.Attribute regex Module.Pattern feedback '';` | Pattern must already exist | -| Create range rule | `create validation rule for Module.Entity.Attribute range from to feedback '';` | Bounds inclusive | +| Create regex rule | `create validation rule for Module.Entity.Attribute regex Module.Pattern error message '';` | Pattern must already exist | +| Create range rule | `create validation rule for Module.Entity.Attribute range from to error message '';` | Bounds inclusive | | Lower bound only | `... range from ...` | Mendix `GreaterThanOrEqualTo` | | Upper bound only | `... range to ...` | Mendix `SmallerThanOrEqualTo` | @@ -255,8 +255,8 @@ expression specified". **Required and Unique are attribute constraints, not this statement:** ```sql -create entity Shop.Product ( Email: String(200) not null error 'Required' ); -alter entity Shop.Product modify attribute Code String(20) unique error 'Unique'; +create entity Shop.Product ( Email: String(200) not null error message 'Required' ); +alter entity Shop.Product modify attribute Code String(20) unique error message 'Unique'; ``` A range bounded by another *attribute* cannot be authored in MDL, but survives a @@ -556,13 +556,13 @@ it is for pages. | REST call (mapping single) | `$Var = rest call get '' returns mapping Module.IMM as Module.Entity;` | Single object — Studio Pro emits `ForceSingleOccurrence=true` | | REST call (mapping list) | `$Var = rest call get '' returns mapping Module.IMM as list of Module.Entity;` | List result | | REST call (none) | `rest call get '' returns nothing;` | Discard response | -| Show page | `show page Module.PageName ($Param = $value);` | Also accepts `(Param: $value)` | +| Show page | `show page Module.PageName (Param = $value);` | `Param = expression`, as at every call site. `($Param = …)` and `(Param: …)` are deprecated (MDL-DEPR006/007) | | Close page | `close page;` | | | Download file | `download file $FileDocument [show in browser];` | Streams a `System.FileDocument` | -| Show message | `show message 'text' [type Information\|Warning\|Error] [objects [$a, $b]] [blocking];` | `blocking` halts the client until the user dismisses it — Studio Pro's checkbox. It goes after `objects` and before `on error`. Without it, a describe → exec round trip turned a blocking message into a non-blocking one (16 microflows measured) | +| Show message | `show message 'text' [type Information\|Warning\|Error] [with ({1} = $a, {2} = $b)] [blocking];` | `blocking` halts the client until the user dismisses it — Studio Pro's checkbox. It goes after the `with` list and before `on error`. `with ({1} = $a, {2} = $b)` is the deprecated spelling of the list (MDL-DEPR009). Without it, a describe → exec round trip turned a blocking message into a non-blocking one (16 microflows measured) | | Database connection credentials | `connection string @Mod.Const`, `username @Mod.Const`, `password @Mod.Const` | Constant **references** only. A literal writes an unopenable project — MDL058 | | Synchronize (nanoflow only) | `synchronize all;` / `synchronize unsynchronized;` / `synchronize $Obj, $List;` | Offline sync. `unsynchronized` needs Mendix 9.4+. In a microflow this is MDL057 / CE0009 | -| Validation | `validation feedback $entity/attribute message 'message';` | Requires attribute path + MESSAGE | +| Validation | `validation feedback $entity/attribute message 'message {1}' [with ({1} = $a)];` | Requires attribute path + MESSAGE | | Log | `log info\|warning\|error [node 'name'] 'message';` | | | Apply entity access | `@applyentityaccess` / `@applyentityaccess(false)` before `create microflow` or `create rule` | Runs the flow under the **current user's** entity access rules instead of with full access. A **security** setting and only ever narrowing, so an ABSENT annotation **preserves** what is stored rather than clearing it — the same rule as `@excluded`. Not available on a nanoflow: it runs in the client and Mendix stores no such property | | Position | `@position(x, y)` | Canvas position (before activity) | @@ -589,9 +589,9 @@ it is for pages. | Execute DB query | `$Result = execute database query Module.Conn.Query;` | 3-part name; supports DYNAMIC, params, CONNECTION override | | Import mapping | `[$Var =] import from mapping Module.IMM($SourceVar) [all\|first\|limit [offset ]];` | Apply import mapping to string variable. Trailing clause is Studio Pro's Range; omitted = infer from the mapping's root. `first` binds one OBJECT (`limit 1` is a one-element LIST). Mendix rejects `offset` on a non-list mapping (CE6100) | | Export mapping | `$Var = export to mapping Module.EMM($EntityVar);` | Apply export mapping to entity, returns string | -| Error handling | `... on error continue\|rollback\|{ handler }\|without rollback { handler };` | Goes on the activity that may fail — including `declare`, `set`, `change`, `log`, `show page`, `close page`, `show message` and `validation feedback`, which gained it in mendixlabs/mxcli#1078 so a Studio Pro handler survives DESCRIBE. `on error continue` is refused (MDL076) where Mendix raises CE6035: create, change, commit, log, show page, close page, show message, validation feedback — a custom `{ handler }` is accepted on all of them. The list-operation and aggregate forms of `set` have no error handling at all (MDL077). Not supported on EXECUTE DATABASE QUERY. **In a nanoflow** only `declare` and `set` take a clause at all — `change`, `log`, `show page`, `close page`, `show message` and `validation feedback` are CE6035 there in every form, and are refused. A handler that does not end in `return`/`raise error` merges back into the main flow, so a later variable is out of scope on the error path (CE0108) | -| Re-raise the error | `raise error;` | **Inside an `on error { … }` handler only.** The error event re-raises the error being handled, so Mendix needs one in scope; Studio Pro will not draw the shape and mxbuild rejects it with **CE0710** "The main flow cannot join an error flow or end in an error event". On the main flow — at any nesting depth, and in a rule too — it is **MDL084**. Mendix has no main-flow "throw": call a Java action that throws | -| Named join point | `merge