Skip to content

Commit 8852bce

Browse files
committed
docs: add pluggable widget engine plan, skill, tests, and roundtrip notes
- Add engine design plan document - Add custom-widgets skill for Mendix projects - Add pluggable widget describe/parse tests - Add gallery user definition example - Add roundtrip issues tracking document
1 parent 41d01f0 commit 8852bce

5 files changed

Lines changed: 1064 additions & 0 deletions

File tree

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
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 |

.mxcli/widgets/gallery.def.json

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
{
2+
"widgetId": "com.mendix.widget.web.gallery.Gallery",
3+
"mdlName": "GALLERY",
4+
"templateFile": "gallery.json",
5+
"defaultEditable": "Always",
6+
"propertyMappings": [
7+
{
8+
"propertyKey": "advanced",
9+
"value": "false",
10+
"operation": "primitive"
11+
},
12+
{
13+
"propertyKey": "datasource",
14+
"source": "DataSource",
15+
"operation": "datasource"
16+
},
17+
{
18+
"propertyKey": "itemSelection",
19+
"source": "Selection",
20+
"operation": "selection"
21+
},
22+
{
23+
"propertyKey": "itemSelectionMode",
24+
"value": "clear",
25+
"operation": "primitive"
26+
},
27+
{
28+
"propertyKey": "desktopItems",
29+
"value": "1",
30+
"operation": "primitive"
31+
},
32+
{
33+
"propertyKey": "tabletItems",
34+
"value": "1",
35+
"operation": "primitive"
36+
},
37+
{
38+
"propertyKey": "phoneItems",
39+
"value": "1",
40+
"operation": "primitive"
41+
},
42+
{
43+
"propertyKey": "pageSize",
44+
"value": "20",
45+
"operation": "primitive"
46+
},
47+
{
48+
"propertyKey": "pagination",
49+
"value": "buttons",
50+
"operation": "primitive"
51+
},
52+
{
53+
"propertyKey": "pagingPosition",
54+
"value": "below",
55+
"operation": "primitive"
56+
},
57+
{
58+
"propertyKey": "showEmptyPlaceholder",
59+
"value": "none",
60+
"operation": "primitive"
61+
},
62+
{
63+
"propertyKey": "onClickTrigger",
64+
"value": "single",
65+
"operation": "primitive"
66+
}
67+
],
68+
"childSlots": [
69+
{
70+
"propertyKey": "content",
71+
"mdlContainer": "TEMPLATE",
72+
"operation": "widgets"
73+
},
74+
{
75+
"propertyKey": "emptyPlaceholder",
76+
"mdlContainer": "EMPTYPLACEHOLDER",
77+
"operation": "widgets"
78+
},
79+
{
80+
"propertyKey": "filtersPlaceholder",
81+
"mdlContainer": "FILTERSPLACEHOLDER",
82+
"operation": "widgets"
83+
}
84+
]
85+
}

0 commit comments

Comments
 (0)