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

GitHub
<!-- skill-index: generated by scripts/skill-index.py; make check-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
<!-- /skill-index -->

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 with WHERE filtering and DRY 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 after a template — templates are not siblings of the widgets in the list view's body, so only insert into makes sense.
  • mixing template for … blocks with ordinary widgets in one insert — 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 for an 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 (and Editable) attach a per-object client expression, stored as written: name attributes as $currentObject/Name. The bracketed set (Visible: [Name != '']), which roots a bare attribute for you, still works and warns MDL-DEPR081. Setting Editable on a non-input widget is rejected. This mirrors CREATE PAGE's visible: — 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 style directly 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 Url needs 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 check names 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:

  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 = call microflow … on btnSave is a parse error. Set the button's action when the button is created (or REPLACE the 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

  1. Get widget names first: Run describe page Module.PageName to see all widget names
  2. Check syntax: mxcli check script.mdl
  3. Check references: mxcli check script.mdl -p app.mpr --references
  4. Verify result: Run describe page Module.PageName after ALTER to confirm changes
  5. Validate project: mxcli docker check -p app.mpr (or mxcli 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 structure
  • create [or replace] page - Create or fully rebuild a page
  • create [or replace] snippet - Create or fully rebuild a snippet
  • update widgets set ... where ... - Bulk update widget properties across pages
  • drop page Module.PageName - Delete a page
  • drop snippet Module.SnippetName - Delete a snippet

Related Skills