Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"area": "mdl/executor", "date": "2026-10-13", "symptom": "No MDL form showed an uploaded image (a System.Image specialisation) on a page in a React-client app. `image i (Image: $currentObject)` failed check with MDL-WIDGET22 (\"names no image\") and MDL-WIDGET42 (\"cannot hold the expression $currentObject\"); `image i (ImageType: dynamicImage)` passed check and mx check but is not a binding (`dynamicImage` is not one of the widget's datasource values); the legacy `dynamicimage` is CE0582 in React. `describe page` of a Studio Pro page bound this way printed `image i` with no source, which re-executes into \"No image selected.\" Agents worked around it with a JavaScript action computing the file URL into a String attribute.", "cause": "The pluggable Image widget's `imageObject` is of type `image`, which Studio Pro can set to a collection image OR to the image object in context; the `image` operation only ever wrote the first (WidgetValue.Image), and `defaultImageDynamic` had no mapping at all. Measured on Studio Pro 11.12.1 (Image widget 1.5.0) by setting \"Image source\" to the data view's object and decoding the unit before and after: exactly two values change — imageObject gets EntityRef DirectEntityRef{Entity: the data view's entity} and Image '', defaultImageDynamic gets the same EntityRef; no SourceVariable. Fix: `Image: $currentObject` (already parsed, as a variable-led data source) resolves to the enclosing data container's entity and is written as that EntityRef on every image-typed mapping of the widget that names no image of its own (SetImageObject on WidgetObjectBuilder); DefaultImage maps to defaultImageDynamic as a plain collection image, without the EntityRef; describe reads the EntityRef back.", "file": "`mdl/executor/widget_image_object.go`, `mdl/executor/widget_engine.go` (image operation), `mdl/backend/widgetobj/builder.go` (setImageObjectValue), `sdk/widgets/definitions/image.def.json` + `modelsdk/…`, `mdl/executor/cmd_pages_describe_pluggable.go` (extractImageObjectBinding), `mdl/executor/validate_widget_image.go` (MDL-WIDGET22)", "insight": "**A widget property's TYPE says what it can hold; the mapping only says what mxcli wrote.** `imageObject` is `type=\"image\"`, and Mendix's image type is static-or-dynamic — the editorConfig even branches on `imageObject.type === \"static\"` to hide `defaultImageDynamic`. Reading the widget XML and editorConfig first would have shown the gap without an agent spending six minutes on three variants. **The decisive measurement was one Studio Pro edit on an mxcli-written page, diffed as decoded BSON with sorted keys and $IDs stripped**: the before/after diff was 20 lines and named both properties, including the non-obvious EntityRef on the fallback property; the same diff run between Studio Pro's unit and mxcli's was empty, which is a stronger round-trip proof than the describe text. mxbuild's own error for a non-image entity (CE7247, once per bound property) also confirmed that BOTH properties are checked against the entity. The page-parameter and association-path forms Studio Pro also offers were not measured and are refused rather than guessed. **The Studio Pro diff is not the whole contract: it covered only the unset fallback.** Writing the same EntityRef beside a chosen `DefaultImage` passed `mx check` and rendered nothing in the browser (the widget hid itself for an object with HasContents=false); without it the fallback rendered. Only a runtime check with an empty image object told the two apart. And when re-testing after the change, ALTER REPLACE kept the stale EntityRef: #1247's keep-unstated merge treats what describe cannot tell apart as unstated — test a writer change on a fresh copy, not on a page the previous build wrote.", "refs": ["mendixlabs/mxcli#1476", "mendixlabs/mxcli#1057", "mendixlabs/mxcli#1149"]}
21 changes: 16 additions & 5 deletions .claude/skills/mendix/create-page/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@ description: "CREATE PAGE syntax reference — parameters, variables, layouts, a
| 196-266 | Basic Examples |
| 267-288 | Modifying Existing Pages |
| 289-376 | Conditional Visibility and Editability |
| 377-526 | Known Limitations |
| 527-535 | Tips |
| 536-562 | Related Commands |
| 563-587 | PLUGGABLEWIDGET Escape Hatch |
| 588-591 | See Also |
| 377-537 | Known Limitations |
| 538-546 | Tips |
| 547-573 | Related Commands |
| 574-598 | PLUGGABLEWIDGET Escape Hatch |
| 599-602 | See Also |

<!-- /skill-index -->
# CREATE PAGE - MDL Syntax Guide
Expand Down Expand Up @@ -428,6 +428,17 @@ image imgRemote (ImageType: imageUrl, ImageUrl: 'https://example.com/logo.svg')
image imgIcon (ImageType: icon)
```

An image a **user uploaded** — an object of an entity that extends `System.Image` —
is the object of the enclosing data view, list view or gallery. Bind the widget to
it with `Image: $currentObject`; `DefaultImage:` is shown while the object holds no
image. This is the form for a React-client app, where `dynamicimage` is CE0582:

```sql
dataview dvPhoto (DataSource: $Photo) {
image imgPhoto (Image: $currentObject, DefaultImage: 'MyFirstModule.Images._1')
}
```

### A button's icon is one of three elements

Mendix stores **three different icon elements**, and the keyword picks which:
Expand Down
7 changes: 6 additions & 1 deletion .claude/skills/mendix/create-page/reference/widgets.md
Original file line number Diff line number Diff line change
Expand Up @@ -731,7 +731,12 @@ Display images on pages:
image imgLogo (width: 200, height: 100)
staticimage imgBanner (Image: 'MyModule.Images.banner', width: 400, height: 120)

-- Dynamic image (from entity data source, e.g. inside a DataView)
-- An uploaded image: the object of the enclosing data view, list view or
-- gallery, whose entity extends System.Image (mendixlabs/mxcli#1476)
image imgPhoto (Image: $currentObject, DefaultImage: 'MyModule.Images.placeholder')

-- Legacy dynamic image (from entity data source, e.g. inside a DataView);
-- CE0582 in a React-client app, where the `image` form above is the one to use
dynamicimage imgProduct (width: 300, height: 200)

-- Image without explicit dimensions
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).

## [Unreleased]

### Added

- **`image` shows an uploaded image: `Image: $currentObject`** (mendixlabs/mxcli#1476) — the pluggable Image widget can now be bound to the object of the enclosing data view, list view or gallery, an entity that extends `System.Image`, with an optional `DefaultImage: 'Module.Collection.Image'` shown while that object has no image. Before, `Image:` could only name an image collection entry, and the legacy `dynamicimage` is CE0582 in a React-client app, so MDL had no way to show an uploaded photo on a Mendix 11 page. The stored shape is Studio Pro 11.12.1's, measured by setting "Image source" to a data view's object and decoding the unit before and after: a `DirectEntityRef` to the entity on both `imageObject` and `defaultImageDynamic`, and an empty collection image. A `DefaultImage` is written as a plain collection image on `defaultImageDynamic`: with the `DirectEntityRef` beside it the running widget showed nothing for an object without contents, and without it the widget shows the fallback. A page mxcli writes this way is identical to Studio Pro's apart from `$ID`s, passes `mx check` with 0 errors, re-executes from `describe page` as `Unchanged page`, and renders the uploaded file in the browser (`/file?guid=…`, natural width 40 for a 40-pixel PNG), or the `DefaultImage` when the object has no contents. `describe page` prints the binding back, which it dropped before. An entity that is not an image is refused before the write (mxbuild: CE7247 "Widget expects entity of type System.Image."), as is a page parameter or an association path, whose stored shapes have not been measured

## [0.26.0] - 2026-10-11

Headline: **Documents mxcli writes now survive Studio Pro's merge and diff: every element carries exactly the property names Studio Pro writes for the project's Mendix version.** Before, a key mxcli left out, or wrote for a newer version, made a document unmergeable the first time Studio Pro saved it, although `mx check`, the build and the runtime stayed green. `mxcli fix property-sets` repairs projects an older mxcli wrote. Alongside it: a Claude Code companion mod with a Project pane, `describe` → `exec` round trips that keep loop back-edges, shared error handlers and page design properties, `check -p` resolving script sets and security references the way `exec` does, `mxcli run --local` as a background service, and a run of Windows fixes for the local dev loop. The [language poster](https://www.mxcli.org/poster/mxcli-language-poster.html) puts all of mxcli and MDL on one page.
Expand Down
8 changes: 8 additions & 0 deletions cmd/mxcli/syntax/features_page.go
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,14 @@ LIST IMPACT OF htmlelement;
"-- reports that at check time, and a name that does not resolve is reported by\n" +
"-- `check --references` rather than failing the build with CE1613.\n" +
"-- The alternatives are the URL form above, or `ImageType: icon`.\n" +
"-- To show an image a user uploaded (an object of an entity extending\n" +
"-- System.Image), bind it to the object of the enclosing data view, list view\n" +
"-- or gallery. DefaultImage is shown while that object holds no image:\n" +
"DATAVIEW dvPhoto (DataSource: $Photo) {\n" +
" IMAGE imgPhoto (Image: $currentObject, DefaultImage: 'Module.Collection.ImageName')\n" +
"}\n" +
"-- This is the React-client way to show an image object; DYNAMICIMAGE below is\n" +
"-- CE0582 there (mendixlabs/mxcli#1476).\n" +
"-- A text-template property (ImageUrl, AlternativeText, a pluggable widget's\n" +
"-- headerCaption/title/…) takes TEXT, so a bare value renders the same string\n" +
"-- on every row. Bind it with the property's own `<Name>Params` companion:\n" +
Expand Down
11 changes: 11 additions & 0 deletions docs-site/src/language/widget-types.md
Original file line number Diff line number Diff line change
Expand Up @@ -367,8 +367,19 @@ DYNAMICIMAGE imgPhoto (DataSource: $Photo, Width: 200, Height: 150)

-- Generic image widget
IMAGE imgBanner (Width: 800, Height: 200)

-- An uploaded image: the object of the enclosing data view, list view or
-- gallery (entity must extend System.Image). Works in a React-client app,
-- where DYNAMICIMAGE is refused with CE0582
DATAVIEW dvPhoto (DataSource: $Photo) {
IMAGE imgPhoto (Image: $currentObject, DefaultImage: 'MyModule.Images.placeholder')
}
```

`Image:` on `IMAGE` is either a three-part image collection entry
(`'Module.Collection.ImageName'`) or `$currentObject`. `DefaultImage:` is the
fallback for the second, shown while the object holds no image.

**Image properties:**

| Property | Description | Example |
Expand Down
1 change: 1 addition & 0 deletions docs/01-project/MDL_QUICK_REFERENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -1127,6 +1127,7 @@ Respond in {{Language}}.$$,
| Create or modify | `create or modify image collection Module.Name [...];` | Preserves UUID — preferred for AI agents |
| Drop collection | `drop image collection [if exists] Module.Name;` | Removes collection and all embedded images |
| Show an image on a page | `image imgLogo (Image: 'Module.Collection.ImageName');` | Three-part name, like an icon reference. `describe image collection` lists the names |
| Show an uploaded image | `image imgPhoto (Image: $currentObject, DefaultImage: 'Module.Collection.ImageName');` | Inside a data view, list view or gallery over an entity extending `System.Image`; `DefaultImage` is optional (mendixlabs/mxcli#1476) |

An `image` widget's default source **is** an image collection entry, so a bare
`image imgLogo (...)` with no `Image:` builds into a model mxbuild refuses ("No
Expand Down
65 changes: 65 additions & 0 deletions mdl-examples/bug-tests/widgets-1476-image-object-binding.mdl
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
mdl 1;
-- mendixlabs/mxcli#1476: no MDL form bound the pluggable Image widget to an
-- image object, so a page in a React-client app could not show an image a user
-- uploaded. The legacy `dynamicimage` is refused there:
--
-- [error] [CE0582] "Widget dynamic image is not supported in React client."
--
-- and `Image:` could only name an image collection entry.
--
-- `Image: $currentObject` binds the widget to the object of the enclosing data
-- view, list view or gallery, whose entity must specialise System.Image.
-- The stored shape is Studio Pro's, measured on 11.12.1 with the Image widget
-- 1.5.0 by setting "Image source" to the data view's object and decoding the
-- unit before and after:
--
-- imageObject Image 'Atlas_Core.Content.Mendix' -> ''
-- EntityRef null -> DirectEntityRef 'Module.Photo'
-- defaultImageDynamic EntityRef null -> DirectEntityRef 'Module.Photo'
--
-- Nothing else in the unit changes. `DefaultImage:` is the fallback shown while
-- the object holds no image. The reference page set none, so its shape is
-- measured in the running app instead: written as a collection image on
-- `defaultImageDynamic` WITHOUT the EntityRef it renders (an object whose
-- HasContents is false shows the fallback); with the EntityRef beside it the
-- widget renders hidden with no src. Both shapes pass mx check.
--
-- Measured on a blank Mendix 11.12.1 app (React client), mxbuild 11.12.1:
--
-- before `image (Image: $currentObject)` check: MDL-WIDGET22 + MDL-WIDGET42
-- after exec + mx check 0 errors
-- after describe -> exec Unchanged page (both pages)
-- after a non-image entity refused by exec; mxbuild would
-- say CE7247 "Widget expects entity
-- of type System.Image." twice

create persistent entity MyFirstModule.Photo extends System.Image (
Caption: string(200)
);

-- The data view's object.
create or modify page MyFirstModule.Photo_View (
Title: 'Photo',
Layout: Atlas_Core.PopupLayout,
Params: ( $Photo: MyFirstModule.Photo )
) {
dataview Photo_View_Form (DataSource: $Photo) {
image Photo_View_Image (Image: $currentObject)
}
};

-- A list's row object, with a fallback for a row that has no image yet.
create or modify page MyFirstModule.Photo_Overview (
Title: 'Photos',
Layout: Atlas_Core.Atlas_Default
) {
listview Photo_Overview_List (DataSource: database from MyFirstModule.Photo) {
image Photo_Overview_Image (
Image: $currentObject,
DefaultImage: 'Atlas_Core.Content.Mendix',
WidthUnit: pixels,
Width: 120
)
dynamictext Photo_Overview_Caption (Content: '{1}', ContentParams: ({1} = Caption))
}
};
26 changes: 26 additions & 0 deletions mdl-examples/doctype-tests/03-page-examples.mdl
Original file line number Diff line number Diff line change
Expand Up @@ -2009,6 +2009,32 @@ create page PgTest.P042_Image
}
};

-- The image object in context (mendixlabs/mxcli#1476): an image a user
-- uploaded, held by an object whose entity extends System.Image. `Image:
-- $currentObject` binds the widget to the enclosing data view's or list's
-- object; `DefaultImage:` is shown while that object holds no image. The entity
-- is stored on `imageObject` (and on an unset `defaultImageDynamic`, as Studio
-- Pro does), and mxbuild refuses an entity that is not an image (CE7247).

create persistent entity PgTest.ProductPhoto extends System.Image (
Caption: String(200)
);

create page PgTest.P042b_ImageObject
(
title: 'Image Object',
layout: Atlas_Core.Atlas_Default
)
{
listview lvPhotos (DataSource: database from PgTest.ProductPhoto) {
image imgPhoto (
Image: $currentObject,
DefaultImage: Atlas_Core.Layout.logo,
WidthUnit: pixels, Width: 120
)
}
};

-- MARK: Custom Container

-- ============================================================================
Expand Down
10 changes: 10 additions & 0 deletions mdl/backend/mcp/widget.go
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,16 @@ func (w *mcpWidgetBuilder) SetImage(propertyKey, imageQN string) {
w.object[propertyKey] = imageQN
}

// SetImageObject: the MCP widget payload's image-object shape has not been
// measured, so the binding is reported unsupported rather than sent in a form
// Studio Pro might store differently.
func (w *mcpWidgetBuilder) SetImageObject(propertyKey, entityName string) {
if entityName == "" {
return
}
w.note("SetImageObject(" + propertyKey + ")")
}

func (w *mcpWidgetBuilder) SetDataSource(propertyKey string, ds pages.DataSource) {
if src := customWidgetXPathSource(ds); src != nil {
w.object[propertyKey] = src
Expand Down
5 changes: 5 additions & 0 deletions mdl/backend/mutation.go
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,11 @@ type WidgetObjectBuilder interface {
// `imageObject`) at an image collection entry, by its three-part qualified
// name `Module.Collection.Image`.
SetImage(propertyKey string, imageQN string)
// SetImageObject binds an image-typed property to the image object in
// context — the enclosing data container's object, an entity that
// specialises System.Image — by naming that entity. Studio Pro stores it as
// a DirectEntityRef on the property's WidgetValue with no collection image.
SetImageObject(propertyKey string, entityName string)
SetExpression(propertyKey string, value string)
SetDataSource(propertyKey string, ds pages.DataSource)
SetChildWidgets(propertyKey string, children []pages.Widget)
Expand Down
44 changes: 44 additions & 0 deletions mdl/backend/widgetobj/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,24 @@ func (ob *Builder) SetImage(propertyKey string, imageQN string) {
})
}

// SetImageObject binds an image-typed property to the image object in context
// (mendixlabs/mxcli#1476). Measured on Studio Pro 11.12.1 with Image widget
// 1.5.0: setting "Image source" to the data view's object stores, on the
// property's WidgetValue, an EntityRef that is a DirectEntityRef to the data
// view's entity and an empty Image.
//
// The EntityRef slot comes from the template; a template without one is left
// alone rather than given a key the document schema may not declare, the same
// rule setImageValue follows.
func (ob *Builder) SetImageObject(propertyKey string, entityName string) {
if entityName == "" {
return
}
ob.object = updateWidgetPropertyValue(ob.object, ob.propertyTypeIDs, propertyKey, func(val bson.D) bson.D {
return setImageObjectValue(val, entityName)
})
}

func (ob *Builder) SetSelection(propertyKey string, value string) {
if value == "" {
return
Expand Down Expand Up @@ -1200,6 +1218,32 @@ func setImageValue(val bson.D, imageQN string) bson.D {
return result
}

// setImageObjectValue writes the image-object binding onto a WidgetValue: a
// DirectEntityRef to entityName under EntityRef, and an empty Image, which is
// what Studio Pro leaves when a property moves from a collection image to the
// object in context.
func setImageObjectValue(val bson.D, entityName string) bson.D {
if entityName == "" {
return val
}
result := make(bson.D, 0, len(val))
for _, elem := range val {
switch elem.Key {
case "EntityRef":
result = append(result, bson.E{Key: "EntityRef", Value: bson.D{
{Key: "$ID", Value: bsonutil.NewIDBsonBinary()},
{Key: "$Type", Value: "DomainModels$DirectEntityRef"},
{Key: "Entity", Value: entityName},
}})
case "Image":
result = append(result, bson.E{Key: "Image", Value: ""})
default:
result = append(result, elem)
}
}
return result
}

func setDataSource(val bson.D, ds pages.DataSource) bson.D {
result := make(bson.D, 0, len(val))
for _, elem := range val {
Expand Down
Loading
Loading