Master-Detail Pages

SkillDev tools

Lets your agent build master-detail screens with a selectable list beside a detail form in MDL.

Available today. Use it from your connected AI after setup.

Add ahel to your AI once: Claude, ChatGPT, Cursor, Claude Code or Codex. Then ask it to use this.

Then ask your AI: use the Master-Detail Pages skill

About this skill

The master-detail page pattern in MDL: a selectable list beside a detail form driven by SELECTION. Use when building a selection-based screen rather than a plain list or form.

What this skill tells your AI

The instructions your AI receives, as published by mendixlabs/mxcli in .claude/skills/mendix/master-detail-pages/SKILL.md and read by ahel’s review.

Overview

Master-Detail is a common UI pattern showing:

  • Master list (left): Selectable list of items (Gallery widget)
  • Detail form (right): Form showing selected item details (DataView with SELECTION source)

MDL Syntax

Basic Structure

create page Module.Entity_MasterDetail
(
  title: 'Entity Master-Detail',
  layout: Atlas_Core.Atlas_Default
)
{
  layoutgrid mainGrid {
    row row1 {
      -- Master list (4 columns)
      column colMaster (desktopwidth: 4) {
        gallery entityList (datasource: database Module.Entity, selection: single) {
          template template1 {
            dynamictext name (content: '{1}', contentparams: [{1} = Name], rendermode: H4)
          }
        }
      }

      -- Detail form (8 columns)
      column colDetail (desktopwidth: 8) {
        dataview entityDetail (datasource: selection entityList) {
          textbox txtName (label: 'Name', attribute: Name)

          footer footer1 {
            actionbutton btnSave (caption: 'Save', action: save_changes, buttonstyle: success)
          }
        }
      }
    }
  }
}

Key Components

1. GALLERY Widget (Master List)
gallery widgetName (
  datasource: database from Module.Entity sort by Name asc,
  selection: single|multiple|none
) {
  template template1 {
    -- Widgets for each item
    dynamictext name (content: '{1}', contentparams: [{1} = AttrName], rendermode: H4)
  }
}

Properties:

  • datasource: database from entity sort by attr asc|desc - Entity data source with optional sorting; sort by Assoc/Attr asc sorts over an association
  • selection: single - Selection mode (Single for master-detail)
  • Template content inside TEMPLATE widget (requires name)
2. DataView with SELECTION Source
dataview widgetName (datasource: selection sourceWidgetName) {
  -- Form widgets
}

The selection source creates a binding to another widget's selection. When the user selects an item in the Gallery, the DataView displays that item.

3. LISTVIEW Widget (Nested Data)
listview widgetName (datasource: database Module.Entity, PageSize: 10) {
  template template1 {
    -- Widgets for each associated item
  }
}

Used inside the detail form to show related/associated data.

Nested list by association (to-many): Use datasource: $currentObject/Module.Assoc (or the explicit datasource: association path form) on a list widget inside a parent DATAVIEW. Example: datagrid lines (datasource: $currentObject/Order_OrderLine) inside a dataview dv (datasource: database Order).

Referenced object by association (to-one) — "data from context": To show a single referenced object's attributes, use the same datasource: $currentObject/Module.Assoc on a nested DATAVIEW. Its children bind to the referenced (destination) entity. Example: an inner dataview dvEmployee (datasource: $currentObject/Expense_Employee) { textbox (attribute: Name) } inside a dataview dvExpense (datasource: $Expense) shows the Expense's Employee. (A DataView association source is stored as a Forms$DataViewSource with an association-navigating EntityRef — do not confuse it with the list-widget Forms$AssociationSource; that one is invalid on a DataView.)

Complete Example

create page CRM.Customer_MasterDetail
(
  title: 'Customer Management',
  layout: Atlas_Core.Atlas_Default
)
{
  layoutgrid mainGrid {
    row row1 {
      column colMaster (desktopwidth: 4) {
        dynamictext heading (content: 'Customers', rendermode: H3)
        gallery customerList (datasource: database from CRM.Customer sort by Name asc, selection: single) {
          template template1 {
            dynamictext name (content: '{1}', contentparams: [{1} = Name], rendermode: H4)
            dynamictext email (content: '{1}', contentparams: [{1} = Email])
          }
        }
      }

      column colDetail (desktopwidth: 8) {
        dataview customerDetail (datasource: selection customerList) {
          dynamictext detailHeading (content: 'Customer Details', rendermode: H3)
          textbox txtName (label: 'Name', attribute: Name)
          textbox txtEmail (label: 'Email', attribute: Email)
          textbox txtPhone (label: 'Phone', attribute: Phone)

          footer footer1 {
            actionbutton btnSave (caption: 'Save', action: save_changes, buttonstyle: success)
            actionbutton btnCancel (caption: 'Cancel', action: cancel_changes)
          }
        }
      }
    }
  }
}

Key Patterns

Selection Binding

The core of master-detail is the selection binding:

  1. Gallery has selection: single - enables single item selection
  2. DataView uses datasource: selection galleryName - listens to Gallery selection
  3. When user clicks an item in Gallery, DataView automatically updates

Widget Names

The selection binding uses widget names to connect:

  • Gallery widget name: customerList
  • DataView references: datasource: selection customerList

Template Content with ContentParams

Inside Gallery templates, use contentparams to reference current item attributes:

template template1 {
  dynamictext name (content: '{1}', contentparams: [{1} = Name], rendermode: H4)
  dynamictext email (content: '{1}', contentparams: [{1} = Email])
}

Syntax Summary

ElementSyntax
Page properties(title: 'title', layout: Module.Layout)
Widget nameRequired after type: gallery myGallery (...)
Database sourcedatasource: database from Module.Entity
Selection bindingdatasource: selection widgetName
Sort bydatasource: database from entity sort by Name asc
Sort over an associationdatasource: database from entity sort by Order_BillTo/City asc — one / per hop, last segment is the attribute. Name the hop when two associations reach the same entity; inference cannot tell them apart (mendixlabs/mxcli#1152)
Where filterdatasource: database from entity where [IsActive = true]
Selection modeselection: single
Attribute bindingattribute: attributename
Action bindingaction: save_changes
Button stylebuttonstyle: success
Text contentcontent: 'text' with contentparams: [{1} = attr]
Render moderendermode: H4
Template contenttemplate template1 { ... }

Related Skills

Implementation Notes

  • Gallery is a pluggable widget (similar to DataGrid2)
  • Selection binding uses ListenTargetSource in the Model SDK
  • ListView is a built-in Mendix widget
  • All widget properties use explicit (key: value) syntax

Signals

GitHub stars
122
Forks
49
Last commit
Sep 2026
Advanced
Catalog kind
skill
Key
master-detail-pages
Source
github.com/mendixlabs/mxcli