|
| 1 | +--- |
| 2 | +name: mendix-custom-widgets |
| 3 | +description: Use when writing MDL for GALLERY, COMBOBOX, or third-party pluggable widgets in CREATE PAGE / ALTER PAGE statements. Covers built-in widget syntax, child slots (TEMPLATE/FILTER), and adding new custom widgets via .def.json. |
| 4 | +--- |
| 5 | + |
| 6 | +# Custom & Pluggable Widgets in MDL |
| 7 | + |
| 8 | +## Built-in Pluggable Widgets |
| 9 | + |
| 10 | +### GALLERY |
| 11 | + |
| 12 | +Card-layout list with optional template content and filters. |
| 13 | + |
| 14 | +```sql |
| 15 | +GALLERY galleryName ( |
| 16 | + DataSource: DATABASE FROM Module.Entity SORT BY Name ASC, |
| 17 | + Selection: Single | Multiple | None |
| 18 | +) { |
| 19 | + TEMPLATE template1 { |
| 20 | + DYNAMICTEXT title (Content: '{1}', ContentParams: [{1} = Name], RenderMode: H4) |
| 21 | + DYNAMICTEXT info (Content: '{1}', ContentParams: [{1} = Email]) |
| 22 | + } |
| 23 | + FILTER filter1 { |
| 24 | + TEXTFILTER searchName (Attribute: Name) |
| 25 | + NUMBERFILTER searchScore (Attribute: Score) |
| 26 | + DROPDOWNFILTER searchStatus (Attribute: Status) |
| 27 | + DATEFILTER searchDate (Attribute: CreatedAt) |
| 28 | + } |
| 29 | +} |
| 30 | +``` |
| 31 | + |
| 32 | +- `TEMPLATE` block → mapped to `content` property (child widgets rendered per row) |
| 33 | +- `FILTER` block → mapped to `filtersPlaceholder` property (shown above list) |
| 34 | +- `Selection: None` omits the selection property (default if omitted) |
| 35 | + |
| 36 | +### COMBOBOX |
| 37 | + |
| 38 | +Two modes depending on the attribute type: |
| 39 | + |
| 40 | +```sql |
| 41 | +-- Enumeration mode (Attribute is an enum) |
| 42 | +COMBOBOX cbStatus (Label: 'Status', Attribute: Status) |
| 43 | + |
| 44 | +-- Association mode (Attribute is an association) |
| 45 | +COMBOBOX cmbCustomer ( |
| 46 | + Label: 'Customer', |
| 47 | + Attribute: Order_Customer, |
| 48 | + DataSource: DATABASE Module.Customer, |
| 49 | + CaptionAttribute: Name |
| 50 | +) |
| 51 | +``` |
| 52 | + |
| 53 | +- Engine detects association mode when `DataSource` or `CaptionAttribute` is present |
| 54 | +- `CaptionAttribute` is the display attribute on the **target** entity |
| 55 | + |
| 56 | +## Adding a Third-Party Widget |
| 57 | + |
| 58 | +### Step 1 — Extract .def.json from .mpk |
| 59 | + |
| 60 | +```bash |
| 61 | +mxcli widget extract --mpk widgets/MyWidget.mpk |
| 62 | +# Output: .mxcli/widgets/mywidget.def.json |
| 63 | + |
| 64 | +# Override MDL keyword |
| 65 | +mxcli widget extract --mpk widgets/MyWidget.mpk --mdl-name MYWIDGET |
| 66 | +``` |
| 67 | + |
| 68 | +Extraction auto-infers operations from XML property types: |
| 69 | + |
| 70 | +| XML Type | Operation | MDL Source Key | |
| 71 | +|----------|-----------|----------------| |
| 72 | +| attribute | attribute | `Attribute` | |
| 73 | +| association | association | `Association` | |
| 74 | +| datasource | datasource | `DataSource` | |
| 75 | +| selection | selection | `Selection` | |
| 76 | +| widgets | widgets (child slot) | container name | |
| 77 | +| boolean/string/enumeration | primitive | hardcoded `Value` | |
| 78 | + |
| 79 | +### Step 2 — Place .def.json |
| 80 | + |
| 81 | +``` |
| 82 | +project/.mxcli/widgets/mywidget.def.json ← project scope |
| 83 | +~/.mxcli/widgets/mywidget.def.json ← global scope |
| 84 | +``` |
| 85 | + |
| 86 | +Project definitions override global ones with the same MDL name. |
| 87 | + |
| 88 | +### Step 3 — Add template JSON |
| 89 | + |
| 90 | +Copy a Studio Pro-created widget JSON to: |
| 91 | +``` |
| 92 | +project/.mxcli/widgets/mywidget.json |
| 93 | +``` |
| 94 | + |
| 95 | +Then set `"templateFile": "mywidget.json"` in the .def.json. |
| 96 | + |
| 97 | +**CRITICAL**: Template must include both `type` (PropertyTypes) and `object` (default WidgetObject). Extract from a real Studio Pro MPR — do NOT generate programmatically. Mismatched structure causes CE0463. |
| 98 | + |
| 99 | +### Step 4 — Use in MDL |
| 100 | + |
| 101 | +```sql |
| 102 | +MYWIDGET myWidget1 (DataSource: DATABASE Module.Entity, Attribute: Name) |
| 103 | +``` |
| 104 | + |
| 105 | +## .def.json Reference |
| 106 | + |
| 107 | +```json |
| 108 | +{ |
| 109 | + "widgetId": "com.vendor.widget.web.mywidget.MyWidget", |
| 110 | + "mdlName": "MYWIDGET", |
| 111 | + "templateFile": "mywidget.json", |
| 112 | + "defaultEditable": "Always", |
| 113 | + "propertyMappings": [ |
| 114 | + {"propertyKey": "datasource", "source": "DataSource", "operation": "datasource"}, |
| 115 | + {"propertyKey": "attribute", "source": "Attribute", "operation": "attribute"}, |
| 116 | + {"propertyKey": "someFlag", "value": "true", "operation": "primitive"} |
| 117 | + ], |
| 118 | + "childSlots": [ |
| 119 | + {"propertyKey": "content", "mdlContainer": "TEMPLATE", "operation": "widgets"} |
| 120 | + ], |
| 121 | + "modes": [ |
| 122 | + { |
| 123 | + "name": "association", |
| 124 | + "condition": "hasDataSource", |
| 125 | + "propertyMappings": [ |
| 126 | + {"propertyKey": "optionsSource", "value": "association", "operation": "primitive"}, |
| 127 | + {"propertyKey": "assoc", "source": "Attribute", "operation": "association"}, |
| 128 | + {"propertyKey": "assocDS", "source": "DataSource", "operation": "datasource"} |
| 129 | + ] |
| 130 | + }, |
| 131 | + { |
| 132 | + "name": "default", |
| 133 | + "propertyMappings": [ |
| 134 | + {"propertyKey": "attr", "source": "Attribute", "operation": "attribute"} |
| 135 | + ] |
| 136 | + } |
| 137 | + ] |
| 138 | +} |
| 139 | +``` |
| 140 | + |
| 141 | +**Mode conditions**: `hasDataSource` | `hasProp:PropertyKey` |
| 142 | +Modes are evaluated in order — first match wins; no condition = default fallback. |
| 143 | + |
| 144 | +## Verify & Debug |
| 145 | + |
| 146 | +```bash |
| 147 | +# List registered widgets |
| 148 | +mxcli widget list -p App.mpr |
| 149 | + |
| 150 | +# Check after creating a page |
| 151 | +mxcli check script.mdl -p App.mpr --references |
| 152 | + |
| 153 | +# Full mx check (catches CE0463) |
| 154 | +~/.mxcli/mxbuild/*/modeler/mx check App.mpr |
| 155 | + |
| 156 | +# Debug CE0463 — compare NDSL dumps |
| 157 | +mxcli bson dump -p App.mpr --type page --object "Module.PageName" --format ndsl |
| 158 | +``` |
| 159 | + |
| 160 | +## Common Mistakes |
| 161 | + |
| 162 | +| Mistake | Fix | |
| 163 | +|---------|-----| |
| 164 | +| CE0463 after page creation | Template version mismatch — extract fresh template from Studio Pro MPR | |
| 165 | +| Widget not recognized | Check `mxcli widget list`; .def.json MDL name must match grammar keyword | |
| 166 | +| TEMPLATE content missing | Widget needs `childSlots` entry with `"mdlContainer": "TEMPLATE"` | |
| 167 | +| Association COMBOBOX shows enum behavior | Add `DataSource` or `CaptionAttribute` to trigger association mode | |
| 168 | +| COMBOBOX CE1613 after page creation | Known engine bug — ComboBox BSON serialization writes wrong pointer type; track issue separately | |
| 169 | +| Custom widget not found | Place .def.json in `.mxcli/widgets/` inside the project directory | |
0 commit comments