alter-page
Modify an existing page or snippet's widget tree in place with ALTER PAGE / ALTER SNIPPET — SET, INSERT, DROP, REPLACE and SET Layout. Use when changing a caption, style or property, adding or removin
SKILL.md.claude/skills/mendix/alter-page/SKILL.md
GitHub<!-- skill-index: generated by scripts/skill-index.py; make check-skill-index -->
<!-- /skill-index -->
Find it fast
sed -n '<from>,<to>p' SKILL.md one section rather than reading the file;
./mxcli syntax <topic> answers how do I write this for a fraction of either.
| lines | section |
|---|---|
| 28-31 | Overview |
| 32-56 | When to Use |
| 57-107 | Syntax |
| 108-473 | Operations |
| 474-552 | Examples |
| 553-643 | DataGrid 2 columns: how to address them, and what you can set |
| 644-655 | Common Mistakes |
| 656-684 | Limitations — prefer binding at page creation (ledger finding #45) |
| 685-692 | Validation Checklist |
| 693-702 | Related Commands |
| 703-707 | Related Skills |
ALTER PAGE / ALTER SNIPPET - Modify Existing Pages and Snippets
Overview
ALTER PAGE and ALTER SNIPPET modify an existing page or snippet's widget tree in-place without requiring a full create or modify. Operations work directly on the raw BSON tree, preserving widget types and properties that MDL doesn't explicitly model.
When to Use
| Scenario | Use |
|---|---|
| Change a button caption, label, or style | alter page with set |
| Add a field to an existing form | alter page with insert |
| Remove unused widgets | alter page with drop |
| Replace a footer or section | alter page with replace |
| Several related changes on the same page | alter page with multiple operations in one block |
Same property across many pages (e.g., add Class to every Container) |
update widgets — see bulk-widget-updates |
| Rebuild entire page from scratch (pages your scripts own only) | create or modify page |
| Create a new page | create page |
Rule of thumb:
alter page— targeted edits to one page. Combine multiple ops in one block when they belong together.update widgets— cross-page bulk updates withWHEREfiltering andDRY RUN.create or modify page— redefining the full page structure of a page your MDL scripts own.
Who owns the page decides (choose-edit-mode). A page
or snippet authored or edited in Studio Pro is changed with alter, however large the
change. describe → edit → create or modify is only for pages your
MDL scripts created and nobody has changed in Studio Pro since: on a Studio Pro page that
round trip has dropped translations and filled an empty English caption from another
language, and on a snippet it has dropped the snippet's type.
Syntax
alter page Module.PageName {
operation1;
operation2;
...
};
alter snippet Module.SnippetName {
operation1;
operation2;
...
};
This is the generic ALTER — the same four verbs for every document type
(alter layout too):
alter page Module.PageName {
set (Key: value, …) on <target>; -- no `on`: the page itself
insert before|after|into <target> { <widgets as in create page> }
replace <target> with { <widgets> }
drop <target>, <target>;
};
A <target> on a page is a widget name, a DataGrid 2 column grid column(Attr)
(or grid column('Caption')), a navigation list item nav item(t1) /
nav item('Text') / nav item@n, or a layout region layoutContainer.top. Properties go in parentheses with :, exactly as in
create page. The older spellings set Key = value on w, set Key: value
(no parentheses) and drop widget w still run, and warn with MDL-DEPR101,
MDL-DEPR102 and MDL-DEPR103 — write the form above.
Multiple operations can be combined in a single ALTER statement. They are applied sequentially; later operations see the page state produced by earlier ones, so you can set on a widget you just inserted.
mdl 1;
-- Rename a column, add a sibling, drop an obsolete one — all in one block.
alter page MyMod.Product_Overview {
set (caption: 'Product Name') on dgProducts column(Name);
insert after dgProducts column(Lifecycle) {
column (attribute: Sku, caption: 'SKU')
};
drop dgProducts column('Old')
};
For changes that should be applied across many pages (e.g., "add Class='card' to every Container in MyMod"), use UPDATE WIDGETS instead — see bulk-widget-updates.
Operations
List View Specialization Templates
A List View template has no name, so it cannot be reached by a widget ref like
every other target. Adding one reuses INSERT INTO with the same
template for block create page uses — a template has one spelling
everywhere. Removing one has its own form:
mdl 1;
alter page Pages.Vehicle_Overview {
insert into vehicleListView {
template for Pages.Motorcycle {
dynamictext mcLabel (content: 'Motorcycle {1}', contentparams: ({1} = Brand))
}
};
drop template for Pages.SUV in vehicleListView
};
Naming the list view in the drop is required, not optional: one page can hold
two list views with a template for the same entity.
Most template edits need none of this. The widgets inside a template are
ordinary named widgets, so set (content: '…') on busLabel and
insert after busLabel { … } already work and land in the right template. To
replace a whole template, drop it and insert the new one in the same block —
operations apply in order.
Refused, each naming the problem:
insert before/insert aftera template — templates are not siblings of the widgets in the list view's body, so onlyinsert intomakes sense.- mixing
template for …blocks with ordinary widgets in oneinsert— they go to different places (the Templates array and the default body). Use two inserts. - a template for an entity that is not the list view's entity or a specialization of it — it could never match an object the list view shows.
- a second template for an entity that already has one.
drop template foran entity with no template — the error names the ones that are there, because dropping nothing and reporting success is how a typo becomes a silent no-op.
SET - Modify Widget Properties
-- Single property
set (caption: 'New Caption') on widgetName
-- Multiple properties
set (caption: 'Save & Close', buttonstyle: success) on btnSave
-- Page-level property (no ON clause). Page-level property names are
-- case-sensitive and must match the Mendix property exactly.
set (Title: 'New Page Title')
-- Pop-up dimensions (apply when the page is opened in a pop-up)
set (PopupWidth: 800)
set (PopupHeight: 480)
set (PopupResizable: true)
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: 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': 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
set (DataSource: microflow Module.MF_Get) on dvOrder
Prefer set Action over replace when only the action changes. replace
rebuilds the widget from what the statement says, so any property you do not
restate — ButtonStyle, Class, design properties, tooltip — is dropped. set
edits the one property and leaves the rest of the widget alone.
The exception is a pluggable widget replaced by one of the same kind (a combo
box by a combo box): there replace keeps every stored property the statement
does not change — a translated placeholder, readOnlyStyle, anything MDL has no
word for — and writes only what differs from the widget as describe prints it.
So a sort can be added to a combo box's options by restating its describe
line with sort by appended.
set Action is refused on a widget that has no action (a plain container, say),
rather than writing a property the widget type does not define — Studio Pro
refuses to open a document with an unknown property while MxBuild tolerates it,
so a silent write would build cleanly and then fail to open.
Supported SET properties:
| Property | Widget Types | Value Type | Example |
|---|---|---|---|
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 |
'<slotKey>' |
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 |
RenderMode |
DYNAMICTEXT | Text, Paragraph, H1–H6 (any case; another value is refused, naming the set) | set (RenderMode: H2) on txtTitle |
label |
TEXTBOX, TEXTAREA, DATEPICKER, COMBOBOX, CHECKBOX, RADIOBUTTONS | String | set (label: 'full Name') on txtName |
buttonstyle |
ACTIONBUTTON, LINKBUTTON | Primary, Default, Success, Danger, Warning, Info | set (buttonstyle: danger) on btnDelete |
class |
Any widget | CSS class string | set (class: 'card mx-2') on container1 |
style |
Any widget (see warning below) | Inline CSS string | set (style: 'padding: 16px;') on container1 |
editable |
Input widgets | String | set (editable: 'Never') on txtReadOnly |
visible |
Any widget | String or Boolean | set (visible: false) on txtHidden |
Name |
Any widget | String | set (Name: 'newName') on oldName |
Title |
Page-level only (case-sensitive) | String | set (Title: 'Edit Customer') |
Documentation |
Page-level only (case-sensitive) | String ('' clears) |
set (Documentation: 'Coordinator triage step.') |
layout |
Page-level only | Qualified name | set layout = Atlas_Core.Atlas_Default |
PopupWidth |
Page-level only (case-sensitive) | Positive integer (pixels) | set (PopupWidth: 800) |
PopupHeight |
Page-level only (case-sensitive) | Positive integer (pixels) | set (PopupHeight: 480) |
PopupResizable |
Page-level only (case-sensitive) | Boolean | set (PopupResizable: true) |
Class |
Page-level (case-sensitive, no ON) | CSS class string | set (Class: 'container-fluid bg-light') |
Style |
Page-level (case-sensitive, no ON) | Inline CSS string | set (Style: 'min-height: 100vh') |
Visible (conditional) |
Any widget | expression | set (Visible: $currentObject/Name != '') on ctnDetails |
Editable (conditional) |
Input widgets | expression | set (Editable: $currentObject/Active) on txtName |
'quotedProp' |
Pluggable widgets | String, Boolean, Number | set ('showLabel': false) on cbStatus |
Conditional visibility/editability —
set (Visible: <expr>) on widget(andEditable) attach a per-object client expression, stored as written: name attributes as$currentObject/Name. The bracketedset (Visible: [Name != '']), which roots a bare attribute for you, still works and warns MDL-DEPR081. SettingEditableon a non-input widget is rejected. This mirrors CREATE PAGE'svisible:— see the create-page skill for enum-value rules.
Pluggable widget properties use quoted names to set values in the widget's Object.Properties[]. Boolean values are stored as "yes"/"no" in BSON.
Column property names are case-insensitive in MDL — set (caption: …) and set (Caption: …) both work. The internal BSON keys are dictated by the widget schema and stay case-sensitive on the storage side.
Warning: Style on DYNAMICTEXT — Setting
styledirectly on a DYNAMICTEXT widget crashes MxBuild with a NullReferenceException. Wrap the DYNAMICTEXT in a CONTAINER and apply styling to the container instead:-- Wrong: crashes MxBuild SET (Style: 'color: red;') ON txtHeading -- Correct: style the container REPLACE txtHeading WITH { CONTAINER ctnHeading (Style: 'color: red;') { DYNAMICTEXT txtHeading (Content: 'Heading', RenderMode: H2) } }
Changing a widget's DataSource
SET DataSource retypes a data source in place — including across shapes, e.g.
from a microflow to a page parameter:
mdl 1;
ALTER PAGE MyModule.OrderPage {
SET (DataSource: $Order) ON dvOrder; -- page/snippet parameter
SET (DataSource: microflow MyModule.MF_Get) ON dvOrder; -- microflow
SET (DataSource: nanoflow MyModule.NF_Get) ON dvOrder; -- nanoflow
SET (DataSource: selection dgOrders) ON dvDetail; -- listen to widget
};
The parameter must exist on the page (or snippet) being altered — its entity is read from the container's own parameter list, and an unknown name is refused rather than written as an unresolved reference.
association and database sources are not supported by SET. Use REPLACE
for those, which rebuilds the widget through the CREATE PAGE path and handles
every datasource type; the error message says so.
A database source has no single stored shape — the widget holding it decides
which element Mendix writes (a list view, a data grid and a pluggable widget
each store a different one), and SET writes the property directly rather than
rebuilding the widget, so it has nothing to choose from. This used to be
accepted and half-applied: the widget was left with a source that DESCRIBE read
back as absent and mxbuild rejected as CE7007, on a page exec had just
reported as altered (mendixlabs/mxcli#1032).
A data view is the one case REPLACE does not rescue: it binds to a single
object, so Mendix gives it no database form at all and the CREATE PAGE path
refuses one too. Point it at a context parameter, a microflow, a nanoflow or
selection <widget>, and use a list view or a data grid to show a query. The
refusal says which of the two situations you are in.
INSERT - Add Widgets
-- Insert after a widget
insert after txtName {
textbox txtMiddleName (label: 'Middle Name', attribute: MiddleName)
}
-- Insert before a widget
insert before btnSave {
actionbutton btnPreview (caption: 'Preview', action: call microflow Module.ACT_Preview)
}
-- Insert INTO a container — append as its last child (works on an EMPTY container)
insert into ctnToolbar {
actionbutton btnNew (caption: 'New', action: nothing, buttonstyle: primary)
}
Inserted widgets use the same syntax as create page. Multiple widgets can be inserted in a single block.
insert into <container> appends as the last child of the named container — the
only way to fill an empty container, and handy for adding to a container/dataview
without needing a sibling to anchor to. Widgets inserted into a dataview take that
dataview's entity as their context. Supported on simple containers (container,
dataview, groupbox, tab page, scroll-container region); for a layout grid, insert
relative to a widget inside the target column instead.
Adding a tab: insert into <tabcontainer> { tabpage … } appends a tab page, and
insert after|before <tabpage> { tabpage … } places it next to that sibling. A tab
page cannot go next to an ordinary widget or inside another tab page, and one insert
cannot mix tab pages and widgets. Dropping or reordering tab pages still needs
create or modify page.
Navigation list items: insert into <navigationlist> { item … } appends an item
and leaves the others untouched. An item has no name (Studio Pro stores none), so it
is addressed like a DataGrid 2 column — by what it holds or where it is:
nav item(t1) (the item holding widget t1), nav item('Home') (the item showing
that text), nav item@2 (the second). @n also picks one of several content
matches; an ambiguous address is refused and a miss lists every item's addresses.
Prefer the content forms — a position shifts when an item is inserted before it.
insert before|after <item> { item … }, replace <item> with { item … },
drop <item>, set (Action: …) on <item> and insert into <item> { widgets } all
work; widgets never go next to an item. An item takes Class, Style and
DynamicClasses like a widget: item (Action: …, Class: 'nav-home') { … }, or
set (Class: 'nav-home') on navList1 item@1.
The context comes from the nearest enclosing data source, whatever kind it is
— a database or association source, a microflow/nanoflow source (the entity is
the flow's return type), or datasource: selection <list>, which takes the
entity of the list it listens to. A bare attribute in the inserted or replaced
widget resolves against that entity, exactly as it would in create page. When
no enclosing source can be resolved, the binding is written unset rather than
guessed at — describe page then prints <unbound>, and mxbuild reports
CE0402 "No value specified.", so re-describe the page after an ALTER that
moves data-bound widgets.
DROP - Remove Widgets
-- Drop a single widget
drop txtUnused
-- Drop multiple widgets
drop txtOldField, lblOldLabel, container2
Removes widgets and their entire subtree from the page.
REPLACE - Replace Widget Subtree
mdl 1;
-- Replace a data view's footer (it has no name: address it by its data view)
alter page MyModule.Customer_Edit {
replace dvMain.footer with {
footer {
actionbutton btnSave (caption: 'Save', action: save changes, buttonstyle: primary)
actionbutton btnCancel (caption: 'Cancel', action: cancel changes)
}
}
};
Replaces the target widget with one or more new widgets. The new widgets use the same syntax as create page, and may reuse the names of the widgets the replace removes. insert into dvMain.footer { … } appends to a footer and drop dvMain.footer empties it.
DataGrid Column Operations
A DataGrid 2 column is addressed by what describe page prints in it: grid column(Attr) for its attribute:, grid column('Caption') for its caption:. See "DataGrid 2 columns: how to address them" below.
-- SET a column property
set (caption: 'Product SKU') on dgProducts column(Code)
-- DROP a column
drop dgProducts column('Old column')
-- INSERT a column after an existing one
insert after dgProducts column(Price) {
column (attribute: Margin, caption: 'Margin')
}
-- REPLACE a column
replace dgProducts column(Description) with {
column (attribute: Notes, caption: 'Notes')
}
-- Two columns over one attribute: pick one with @n
drop dgProducts column(Name)@2
The older dotted form gridName.columnName still works; it matches a name mxcli derives (the short attribute name, else the sanitized caption, else colN).
A legacy (Dojo) data grid (Forms$DataGrid, from an upgraded app) cannot be created in MDL, so describe page prints it as comments — but it lists every column, search field and control-bar button by its stored name. Those are addressed by that bare name, like any widget:
mdl 1;
alter page MyMod.Order_Overview {
drop column14, searchField3; -- one column and its search field
set (caption: 'Note') on column2
};
Only that item is removed or changed; the rest of the grid is left as stored. To restructure the grid itself, replace it with a DataGrid 2.
ADD Variables - Add a Page Variable
add variables $showStockColumn: boolean = true
Adds a new page variable (Forms$LocalVariable) to the page/snippet. DataType can be boolean, string, integer, decimal, datetime, or an entity type. Default value is a Mendix expression in single quotes.
DROP Variables - Remove a Page Variable
drop variables $showStockColumn
Removes a page variable by name.
ADD Parameters - Add a Page or Snippet Parameter
mdl 1;
alter page MyModule.Order_Edit {
add parameters $Order: MyModule.Order;
add parameters $Count: integer;
};
Adds a Forms$PageParameter (or Forms$SnippetParameter) to the stored document and touches nothing else — reach for it instead of create or replace page, which rebuilds every widget and loses whatever describe does not round-trip. Same declaration as Params: on CREATE.
- A page with a
Urlneeds a{Name}segment per parameter (CE5601). Set it in the same statement —set (Url: 'orders/{Customer}/{Order}');— or exec refuses and prints the URL to use. - A snippet parameter must be an entity (MDL087 / CE0046).
- Callers are not updated. Every page that opens this page (or places this snippet) must now pass the parameter;
mxcli docker checknames the ones that do not.
DROP Parameters - Remove a Parameter
mdl 1;
alter page MyModule.Order_Edit {
drop parameters $Order;
};
Refused while the page still uses the parameter — a data source bound to it (DataSource: $Order) or an expression naming $Order. Rebind or drop those first.
SET Layout - Change Page Layout
-- Auto-map placeholders by name (most common case)
set layout = Atlas_Core.Atlas_Default
-- Explicit mapping when placeholder names differ
set layout = Atlas_Core.Atlas_SideBar map (Main as content, Extra as Sidebar)
Changes the page's layout without rebuilding the widget tree. Only rewrites the FormCall.Form and FormCall.Arguments[].Parameter BSON fields — all widget content is preserved. Not supported for snippets.
When placeholders have the same names in both layouts (e.g., both have Main), auto-mapping works. Use map when placeholder names differ between the old and new layout.
Examples
Change button text and style
mdl 1;
alter page MyModule.Customer_Edit {
set (caption: 'Save & Close', buttonstyle: success) on btnSave
};
Add a field to a form
mdl 1;
alter page MyModule.Customer_Edit {
insert after txtEmail {
textbox txtPhone (label: 'Phone', attribute: Phone)
}
};
Add a page variable for column visibility
mdl 1;
alter page MyModule.ProductOverview {
add variables $showStockColumn: boolean = if (3 < 4) then true else false
};
Remove unused fields and update title
mdl 1;
alter page MyModule.Customer_Edit {
set (title: 'Edit Customer Details');
drop txtLegacyField, lblOldNote;
set (label: 'Email Address') on txtEmail
};
Replace a footer section
mdl 1;
alter page MyModule.Customer_Edit {
replace dvMain.footer with {
footer {
actionbutton btnSave (caption: 'Save', action: save changes, buttonstyle: success)
actionbutton btnDelete (caption: 'Delete', action: delete, buttonstyle: danger)
actionbutton btnCancel (caption: 'Cancel', action: cancel changes)
}
}
};
Modify a snippet
mdl 1;
alter snippet MyModule.NavigationMenu {
set (caption: 'Dashboard') on btnHome;
insert after btnHome {
actionbutton btnReports (caption: 'Reports', action: show page MyModule.Reports_Overview)
}
};
Set pluggable widget properties
mdl 1;
alter page MyModule.Customer_Edit {
set ('showLabel': false) on cbStatus;
set ('labelWidth': 4) on cbCategory
};
DataGrid 2 columns: how to address them, and what you can set
Mendix stores no column name. A DataGrid 2 column's schema has no name or
identifier key — the only human-facing label is its caption — so a column is
written without one, and describe page prints none:
create or modify page Mod.P (...) {
datagrid dg1 (datasource: database Mod.Item) {
column (attribute: Label, caption: 'The Label')
column (caption: 'Actions', ShowContentAs: customContent) { … }
}
};
A name written there anyway (column colLabel (…), the old describe output) is
dropped and reported as MDL-DEPR005; mxcli fmt --upgrade removes it.
ALTER PAGE addresses a column by what describe prints in it — its
attribute: value, or its caption::
mdl 1;
alter page Mod.P { set (Caption: 'Renamed') on dg1 column(Label) }; -- the column bound to Label
alter page Mod.P { drop dg1 column('Actions') }; -- the column captioned Actions
alter page Mod.P { set (Sortable: false) on dg1 column(Owner/Name) }; -- over an association, as describe writes it
Two columns over the same attribute (or with the same caption) share the
address. ALTER refuses it rather than picking one, and the error lists the
matches — add @n to choose: drop dg1 column(FullName)@2.
The older dg1.Label form still works: it addresses a column by a name mxcli
derives (the attribute's short name, else the sanitized caption, else colN by
position). Prefer column(…), which says what it matches.
Setting column properties
Property names resolve against the keys the installed widget declares, so both
the schema key and mxcli's MDL alias work (DynamicCellClass and ColumnClass
both reach columnClass). An unknown name lists what is settable on that grid.
DynamicCellClass (and a widget's DynamicClasses) take a Mendix expression,
written as-is. A quoted value is a Mendix string, so a literal CSS class is just
the quoted class name, and a computed one is the expression itself:
mdl 1;
-- a literal class: the string 'highlight'
alter page Mod.P { SET (DynamicCellClass: 'highlight') ON dg1 column(Label) };
-- a computed class
alter page Mod.P { SET (DynamicCellClass: if $currentObject/Price > 100 then 'highlight' else '') ON dg1 column(Label) };
-- WRONG: a bare name is an identifier, not a string — mxbuild reports CE0117
alter page Mod.P { SET (DynamicCellClass: highlight) ON dg1 column(Label) };
The old spelling — the expression's text in quotes, 'if … then ''a'' else '''''
— is refused under mdl 1; as MDL-WIDGET33, because there it stores that text
as a class name. A script without the header keeps its old meaning and warns
MDL-V1-QUOTEDEXPR; mxcli fmt --upgrade writes it bare. This applies equally to
create page; the two paths behave identically.
A column's pluggable Visible expression is not converted yet: there a quoted
value is still the expression's text, so a literal needs the doubled quotes.
Properties holding a structured value — attribute, filter, content,
actions — cannot be set by ALTER at all. It refuses them and points at
create or modify page, rather than writing a string where Mendix expects a
reference.
Widget property names are matched case-insensitively, pluggable ones
included, so a spelling CREATE PAGE accepts is a spelling ALTER PAGE accepts
— set (PageSize: 10) on dgProducts and set (pageSize: 10) on dgProducts are the
same statement. This is what makes DESCRIBE output re-executable: describe page
prints the capitalised PageSize:, while the widget template stores pageSize
(mendixlabs/mxcli#1069). A property the widget does not declare is still an
error — and mxcli check … --references reports it before the script runs,
so a typo no longer lands halfway through. The pre-flight resolves the name
against the stored document rather than a list, so it is right about whatever
widget package this project has installed; the error names the widget's own
property keys. ON a widget the page does not have is caught the same way.
Two things it deliberately stays quiet about, because it cannot answer them: a
page the script itself creates (nothing is stored yet — the widgets there are
checked where they are written), and a widget an INSERT in the same script
adds. Both still fail at exec if they are genuinely wrong.
Common Mistakes
| Mistake | Fix |
|---|---|
Missing on widgetName for widget SET |
Add on widgetName (only page-level properties — Title, Documentation, PopupWidth, PopupHeight, PopupResizable, Class, Style — omit ON) |
unsupported page-level property: title |
Page-level property names are case-sensitive — use Title, PopupWidth, PopupHeight, PopupResizable, Class, Style |
| Using unquoted pluggable property names | Quote pluggable props: set ('showLabel': false) on cb |
pluggable property "X" not found |
The widget does not declare it — casing is not the problem (any casing resolves). The error lists the keys it does declare; describe widget type <type> or describe page shows them in context. Run mxcli check … --references to get this before the script runs |
| Wrong widget name | Use describe page Module.Name to see widget names |
| SET on non-existent widget | Widget names are case-sensitive; check with DESCRIBE |
| Missing semicolons between operations | Each operation inside { } ends with ; |
Limitations — prefer binding at page creation (ledger finding #45)
ALTER PAGE is best for content edits (add/remove/retitle widgets). When you hit
the limit below, define the referenced microflows before the
page and bind the buttons at creation time instead of rewiring afterwards:
SETcannot rewire a button's action.setaccepts a fixed property list (caption,class,visible, …) —actionis not on it, soset Action = call microflow … on btnSaveis a parse error. Set the button's action when the button is created (orREPLACEthe button subtree).
A data view's footer has no name. Its widgets are stored in the data
view, and the footer itself is not, so a name written on it is never kept
(MDL-DEPR005) and describe prints footer { … }. Address it by its data
view: replace dvMain.footer with { footer { … } }, insert into dvMain.footer { … }, drop dvMain.footer.
A pluggable widget's slot has no name either. A Data grid 2's control bar
(controlbar { … } in describe) is a property of the grid, so address it by
the grid: insert into dataGrid1.controlbar { actionbutton … }, replace dataGrid1.controlbar with { … }, drop dataGrid1.controlbar. This is the
way back in after the bar's last button was dropped — describe then prints
no controlbar block, but the slot is still there.
Recommended pattern: put save/reset microflows in a file that runs before the page definition, and bind the popup/footer buttons to them at creation. The apparent "page needs microflow, microflow needs page" cycle usually exists only between different microflows, not within the page itself.
Validation Checklist
- Get widget names first: Run
describe page Module.PageNameto see all widget names - Check syntax:
mxcli check script.mdl - Check references:
mxcli check script.mdl -p app.mpr --references - Verify result: Run
describe page Module.PageNameafter ALTER to confirm changes - Validate project:
mxcli docker check -p app.mpr(ormxcli docker check -p app.mpr)
Related Commands
describe page Module.PageName- View current page structure (get widget names)describe snippet Module.SnippetName- View current snippet structurecreate [or replace] page- Create or fully rebuild a pagecreate [or replace] snippet- Create or fully rebuild a snippetupdate widgets set ... where ...- Bulk update widget properties across pagesdrop page Module.PageName- Delete a pagedrop snippet Module.SnippetName- Delete a snippet
Related Skills
- Create Page - Full page creation syntax
- Overview Pages - CRUD page patterns
- Master-Detail Pages - Selection binding pattern