Skip to content

Commit 5a5e470

Browse files
committed
Merge branch 'issues'
# Conflicts: # mdl/grammar/parser/MDLParser.interp # mdl/grammar/parser/mdl_parser.go
2 parents e05b60e + 405eb0a commit 5a5e470

File tree

16 files changed

+9589
-9494
lines changed

16 files changed

+9589
-9494
lines changed

.claude/skills/mendix/alter-page.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,30 @@ REPLACE footer1 WITH {
126126

127127
Replaces the target widget with one or more new widgets. The new widgets use the same syntax as `CREATE PAGE`.
128128

129+
### DataGrid Column Operations
130+
131+
DataGrid2 columns are addressable using dotted notation: `gridName.columnName`. The column name is derived from the attribute short name or caption (same as shown by `DESCRIBE PAGE`).
132+
133+
```sql
134+
-- SET a column property
135+
SET Caption = 'Product SKU' ON dgProducts.Code
136+
137+
-- DROP a column
138+
DROP WIDGET dgProducts.OldColumn
139+
140+
-- INSERT a column after an existing one
141+
INSERT AFTER dgProducts.Price {
142+
COLUMN Margin (Attribute: Margin, Caption: 'Margin')
143+
}
144+
145+
-- REPLACE a column
146+
REPLACE dgProducts.Description WITH {
147+
COLUMN Notes (Attribute: Notes, Caption: 'Notes')
148+
}
149+
```
150+
151+
To discover column names, run `DESCRIBE PAGE Module.PageName` and look at the COLUMN names inside the DATAGRID.
152+
129153
### ADD Variables - Add a Page Variable
130154

131155
```sql

docs-site/src/examples/alter-page.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,31 @@ ALTER PAGE CRM.Customer_Edit {
8282
};
8383
```
8484

85+
## Modify DataGrid Columns
86+
87+
Target columns using dotted notation `gridName.columnName`:
88+
89+
```sql
90+
-- Add a column
91+
ALTER PAGE CRM.Customer_List {
92+
INSERT AFTER dgCustomers.Email {
93+
COLUMN Phone (Attribute: Phone, Caption: 'Phone')
94+
}
95+
};
96+
97+
-- Remove a column
98+
ALTER PAGE CRM.Customer_List {
99+
DROP WIDGET dgCustomers.OldNotes
100+
};
101+
102+
-- Rename a column header
103+
ALTER PAGE CRM.Customer_List {
104+
SET Caption = 'E-mail Address' ON dgCustomers.Email
105+
};
106+
```
107+
108+
Use `DESCRIBE PAGE CRM.Customer_List` to discover column names.
109+
85110
## Works on Snippets Too
86111

87112
```sql

docs-site/src/language/alter-page.md

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -213,14 +213,32 @@ ALTER PAGE MyModule.Order_Edit {
213213
};
214214
```
215215

216-
### Add a DataGrid Column
216+
### DataGrid Column Operations
217217

218-
Since DataGrid columns are part of the widget tree, use INSERT to add columns:
218+
DataGrid2 columns are addressable using dotted notation: `gridName.columnName`. Use `DESCRIBE PAGE` to discover column names (derived from the attribute short name or caption).
219219

220220
```sql
221+
-- Add a column after an existing one
221222
ALTER PAGE MyModule.Customer_Overview {
222-
INSERT AFTER colEmail {
223-
COLUMN colPhone (Attribute: Phone, Caption: 'Phone')
223+
INSERT AFTER dgCustomers.Email {
224+
COLUMN Phone (Attribute: Phone, Caption: 'Phone')
225+
}
226+
};
227+
228+
-- Remove a column
229+
ALTER PAGE MyModule.Customer_Overview {
230+
DROP WIDGET dgCustomers.OldColumn
231+
};
232+
233+
-- Change a column's caption
234+
ALTER PAGE MyModule.Customer_Overview {
235+
SET Caption = 'E-mail Address' ON dgCustomers.Email
236+
};
237+
238+
-- Replace a column
239+
ALTER PAGE MyModule.Customer_Overview {
240+
REPLACE dgCustomers.Notes WITH {
241+
COLUMN Description (Attribute: Description, Caption: 'Description')
224242
}
225243
};
226244
```

docs-site/src/reference/page/alter-page.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,19 @@ Removes one or more widgets by name. The widget and all its children are removed
7676

7777
Replaces a widget (and its entire subtree) with one or more new widgets.
7878

79+
### DataGrid Column Operations
80+
81+
DataGrid2 columns are addressable using dotted notation: `gridName.columnName`. The column name matches the name shown by `DESCRIBE PAGE` (derived from the attribute short name or caption).
82+
83+
All four operations (SET, INSERT, DROP, REPLACE) support dotted column references:
84+
85+
```sql
86+
SET Caption = 'Product SKU' ON dgProducts.Code
87+
DROP WIDGET dgProducts.OldColumn
88+
INSERT AFTER dgProducts.Price { COLUMN Margin (Attribute: Margin) }
89+
REPLACE dgProducts.Description WITH { COLUMN Notes (Attribute: Notes) }
90+
```
91+
7992
### SET Layout
8093

8194
Changes the page's layout without rebuilding the widget tree. Placeholder names are auto-mapped by default. If the new layout has different placeholder names, use `MAP` to specify the mapping.

docs/01-project/MDL_QUICK_REFERENCE.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -629,6 +629,9 @@ Modify an existing page or snippet's widget tree in-place without full `CREATE O
629629
| Drop widgets | `DROP WIDGET name1, name2` | Remove widgets by name |
630630
| Replace widget | `REPLACE widgetName WITH { widgets }` | Replace widget subtree |
631631
| Pluggable prop | `SET 'showLabel' = false ON cbStatus` | Quoted name for pluggable widgets |
632+
| Set column prop | `SET Caption = 'New' ON dgGrid.colName` | Dotted ref targets DataGrid column |
633+
| Drop column | `DROP WIDGET dgGrid.colName` | Remove a DataGrid column |
634+
| Insert column | `INSERT AFTER dgGrid.colName { COLUMN ... }` | Add column to DataGrid |
632635
| Add variable | `ADD Variables $name: Type = 'expr'` | Add a page variable |
633636
| Drop variable | `DROP Variables $name` | Remove a page variable |
634637
| Set layout | `SET Layout = Module.LayoutName` | Change page layout, auto-maps placeholders |

mdl-examples/doctype-tests/03-page-examples.mdl

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2259,6 +2259,20 @@ ALTER PAGE PgTest.P033b_DataGrid_ColumnProperties {
22592259
SET Layout = Atlas_Core.Atlas_TopBar;
22602260
};
22612261

2262+
-- MARK: ALTER PAGE DataGrid Column Operations
2263+
2264+
-- =============================================================================
2265+
-- ALTER PAGE - DataGrid Column Operations (dotted widget references)
2266+
-- =============================================================================
2267+
--
2268+
-- DataGrid2 columns are addressable using gridName.columnName syntax.
2269+
-- Column names are derived from the attribute short name or caption.
2270+
2271+
-- DROP a column by dotted reference
2272+
ALTER PAGE PgTest.P033b_DataGrid_ColumnProperties {
2273+
DROP WIDGET dgProducts.Description
2274+
};
2275+
22622276
-- MARK: Conditional Visibility/Editability and Responsive Widths
22632277

22642278
-- =============================================================================

mdl/ast/ast_alter_page.go

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,34 +20,55 @@ type AlterPageOperation interface {
2020
isAlterPageOperation()
2121
}
2222

23-
// SetPropertyOp represents: SET prop = value ON widgetName
24-
// or SET prop = value (page-level, WidgetName empty)
23+
// WidgetRef represents a widget reference, optionally with a sub-element path.
24+
// Plain: "btnSave" (Widget="btnSave", Column="")
25+
// Dotted: "dgProducts.Name" (Widget="dgProducts", Column="Name")
26+
type WidgetRef struct {
27+
Widget string // widget name (always set)
28+
Column string // column name within widget (empty for plain widget refs)
29+
}
30+
31+
// Name returns the full reference string for error messages.
32+
func (r WidgetRef) Name() string {
33+
if r.Column != "" {
34+
return r.Widget + "." + r.Column
35+
}
36+
return r.Widget
37+
}
38+
39+
// IsColumn returns true if this is a column reference (dotted path).
40+
func (r WidgetRef) IsColumn() bool {
41+
return r.Column != ""
42+
}
43+
44+
// SetPropertyOp represents: SET prop = value ON widgetRef
45+
// or SET prop = value (page-level, Target.Widget empty)
2546
type SetPropertyOp struct {
26-
WidgetName string // empty for page-level SET
47+
Target WidgetRef // empty Widget for page-level SET
2748
Properties map[string]interface{} // property name -> value
2849
}
2950

3051
func (s *SetPropertyOp) isAlterPageOperation() {}
3152

32-
// InsertWidgetOp represents: INSERT AFTER/BEFORE widgetName { widgets }
53+
// InsertWidgetOp represents: INSERT AFTER/BEFORE widgetRef { widgets }
3354
type InsertWidgetOp struct {
34-
Position string // "AFTER" or "BEFORE"
35-
TargetName string // widget to insert relative to
36-
Widgets []*WidgetV3
55+
Position string // "AFTER" or "BEFORE"
56+
Target WidgetRef // widget/column to insert relative to
57+
Widgets []*WidgetV3
3758
}
3859

3960
func (s *InsertWidgetOp) isAlterPageOperation() {}
4061

41-
// DropWidgetOp represents: DROP WIDGET name1, name2, ...
62+
// DropWidgetOp represents: DROP WIDGET ref1, ref2, ...
4263
type DropWidgetOp struct {
43-
WidgetNames []string
64+
Targets []WidgetRef
4465
}
4566

4667
func (s *DropWidgetOp) isAlterPageOperation() {}
4768

48-
// ReplaceWidgetOp represents: REPLACE widgetName WITH { widgets }
69+
// ReplaceWidgetOp represents: REPLACE widgetRef WITH { widgets }
4970
type ReplaceWidgetOp struct {
50-
WidgetName string
71+
Target WidgetRef
5172
NewWidgets []*WidgetV3
5273
}
5374

mdl/executor/alter_page_test.go

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ func TestApplyDropWidget_Single(t *testing.T) {
9595
w3 := makeWidget("txtPhone", "Pages$TextBox")
9696
rawData := makeRawPage(w1, w2, w3)
9797

98-
op := &ast.DropWidgetOp{WidgetNames: []string{"txtEmail"}}
98+
op := &ast.DropWidgetOp{Targets: []ast.WidgetRef{{Widget: "txtEmail"}}}
9999
if err := applyDropWidget(rawData, op); err != nil {
100100
t.Fatalf("applyDropWidget failed: %v", err)
101101
}
@@ -126,7 +126,7 @@ func TestApplyDropWidget_Multiple(t *testing.T) {
126126
w3 := makeWidget("c", "Pages$TextBox")
127127
rawData := makeRawPage(w1, w2, w3)
128128

129-
op := &ast.DropWidgetOp{WidgetNames: []string{"a", "c"}}
129+
op := &ast.DropWidgetOp{Targets: []ast.WidgetRef{{Widget: "a"}, {Widget: "c"}}}
130130
if err := applyDropWidget(rawData, op); err != nil {
131131
t.Fatalf("applyDropWidget failed: %v", err)
132132
}
@@ -150,7 +150,7 @@ func TestApplyDropWidget_NotFound(t *testing.T) {
150150
w1 := makeWidget("txtName", "Pages$TextBox")
151151
rawData := makeRawPage(w1)
152152

153-
op := &ast.DropWidgetOp{WidgetNames: []string{"nonexistent"}}
153+
op := &ast.DropWidgetOp{Targets: []ast.WidgetRef{{Widget: "nonexistent"}}}
154154
err := applyDropWidget(rawData, op)
155155
if err == nil {
156156
t.Fatal("Expected error for nonexistent widget")
@@ -163,7 +163,7 @@ func TestApplyDropWidget_Nested(t *testing.T) {
163163
container := makeContainerWidget("ctn1", inner1, inner2)
164164
rawData := makeRawPage(container)
165165

166-
op := &ast.DropWidgetOp{WidgetNames: []string{"txtInner1"}}
166+
op := &ast.DropWidgetOp{Targets: []ast.WidgetRef{{Widget: "txtInner1"}}}
167167
if err := applyDropWidget(rawData, op); err != nil {
168168
t.Fatalf("applyDropWidget failed: %v", err)
169169
}
@@ -186,7 +186,7 @@ func TestApplySetProperty_Name(t *testing.T) {
186186
rawData := makeRawPage(w1)
187187

188188
op := &ast.SetPropertyOp{
189-
WidgetName: "txtOld",
189+
Target: ast.WidgetRef{Widget: "txtOld"},
190190
Properties: map[string]interface{}{
191191
"Name": "txtNew",
192192
},
@@ -211,7 +211,7 @@ func TestApplySetProperty_ButtonStyle(t *testing.T) {
211211
rawData := makeRawPage(w1)
212212

213213
op := &ast.SetPropertyOp{
214-
WidgetName: "btnSave",
214+
Target: ast.WidgetRef{Widget: "btnSave"},
215215
Properties: map[string]interface{}{
216216
"ButtonStyle": "Success",
217217
},
@@ -234,7 +234,7 @@ func TestApplySetProperty_WidgetNotFound(t *testing.T) {
234234
rawData := makeRawPage(w1)
235235

236236
op := &ast.SetPropertyOp{
237-
WidgetName: "nonexistent",
237+
Target: ast.WidgetRef{Widget: "nonexistent"},
238238
Properties: map[string]interface{}{
239239
"Name": "new",
240240
},
@@ -279,7 +279,7 @@ func TestApplySetProperty_PluggableWidget(t *testing.T) {
279279
rawData := makeRawPage(w1)
280280

281281
op := &ast.SetPropertyOp{
282-
WidgetName: "cb1",
282+
Target: ast.WidgetRef{Widget: "cb1"},
283283
Properties: map[string]interface{}{
284284
"showLabel": false,
285285
},
@@ -448,7 +448,7 @@ func TestApplyDropWidget_Snippet(t *testing.T) {
448448
w2 := makeWidget("txtEmail", "Pages$TextBox")
449449
rawData := makeRawSnippet(w1, w2)
450450

451-
op := &ast.DropWidgetOp{WidgetNames: []string{"txtEmail"}}
451+
op := &ast.DropWidgetOp{Targets: []ast.WidgetRef{{Widget: "txtEmail"}}}
452452
if err := applyDropWidgetWith(rawData, op, findBsonWidgetInSnippet); err != nil {
453453
t.Fatalf("applyDropWidgetWith failed: %v", err)
454454
}
@@ -473,7 +473,7 @@ func TestApplySetProperty_Snippet(t *testing.T) {
473473
rawData := makeRawSnippet(w1)
474474

475475
op := &ast.SetPropertyOp{
476-
WidgetName: "btnAction",
476+
Target: ast.WidgetRef{Widget: "btnAction"},
477477
Properties: map[string]interface{}{
478478
"ButtonStyle": "Danger",
479479
},

0 commit comments

Comments
 (0)