Skip to content

feat: bind the image widget to an image object in context (Image: $currentObject) - #1482

Closed
tgolembiewski wants to merge 1 commit into
mendixlabs:mainfrom
tgolembiewski:feat/1476-image-object-binding
Closed

tgolembiewski wants to merge 1 commit into
mendixlabs:mainfrom
tgolembiewski:feat/1476-image-object-binding

Conversation

@tgolembiewski

Copy link
Copy Markdown
Contributor

Closes #1476

What does it do?

The pluggable Image widget (image, com.mendix.widget.web.image) can now show an
image a user uploaded: an object of an entity that extends System.Image, taken from
the enclosing data view, list view or gallery.

dataview Photo_View_Form (DataSource: $Photo) {
  image Photo_View_Image (Image: $currentObject, DefaultImage: 'Atlas_Core.Content.Mendix')
}

DefaultImage: is optional. It is the image shown while the object holds none.

Before this, Image: could only name an image collection entry. The legacy
dynamicimage gets CE0582 in a React-client app, so MDL had no way to show an uploaded
photo on a Mendix 11 page.

  • Writer. Image: $currentObject resolves to the enclosing data container's entity.
    It is written as Studio Pro writes it (see below), through a new
    WidgetObjectBuilder.SetImageObject. DefaultImage is mapped onto
    defaultImageDynamic in image.def.json and written as a plain collection
    image, without the EntityRef (see Measurements for why).
  • Describe. describe page prints Image: $currentObject and DefaultImage: back.
    Before, a Studio Pro page bound this way described as a bare image Photo_View_Image,
    which re-executes into "No image selected.". Stored shapes MDL has no spelling for (an
    association path, a page parameter) print a -- NOT re-executable note instead of a
    wrong $currentObject.
  • Refusals before the write. These are refused:
    • an entity that does not specialise System.Image (mxbuild would answer CE7247
      "Widget expects entity of type System.Image.", once per bound property);
    • a widget with no enclosing data container;
    • any object other than $currentObject. Studio Pro also offers a page parameter,
      but that stored shape has not been measured.
  • Check. MDL-WIDGET22 accepts the binding as a complete source. It also reports
    DefaultImage without a binding (Studio Pro hides it then) and a binding under another
    ImageType. MDL-WIDGET42 no longer calls $currentObject an expression on this
    property. check --references resolves DefaultImage like Image.

Studio Pro reference shape

Measured with Studio Pro 11.12.1 and Image widget 1.5.0:

  1. An mxcli-written page had image Photo_View_Image (Image: 'Atlas_Core.Content.Mendix')
    inside dataview (DataSource: $Photo), where Photo extends System.Image.
  2. In Studio Pro, "Image source" (Image type: Image) was set to the data view's object.
    The "Select Entity" dialog offered the page parameter and the data view; the data
    view was chosen.
  3. The unit was decoded before and after, with sorted keys. Exactly two values changed:
imageObject          Image:     'Atlas_Core.Content.Mendix' -> ''
                     EntityRef: null -> {$Type: 'DomainModels$DirectEntityRef', Entity: 'Module.Photo'}
defaultImageDynamic  EntityRef: null -> {$Type: 'DomainModels$DirectEntityRef', Entity: 'Module.Photo'}

No SourceVariable was stored, and datasource stays image. The reference page
set no default image, so this diff does not cover a chosen DefaultImage. That shape
was settled in the running app instead (below).

Repro on a blank app

mdl-examples/bug-tests/widgets-1476-image-object-binding.mdl runs on a new Mendix
11.12.1 app (React client). It creates MyFirstModule.Photo extends System.Image, a
popup page with the image in a data view, and an overview list with DefaultImage.

before (main) after
mxcli check of image (Image: $currentObject) MDL-WIDGET22 "names no image" + MDL-WIDGET42 "cannot hold the expression $currentObject" passes
exec + mx check — 0 errors
describe page → exec, both pages — Unchanged page
describe page of a Studio Pro page bound to the data view's object image Photo_View_Image (binding dropped) image Photo_View_Image (Image: $currentObject)

Measurements

  • Same unit as Studio Pro. The "before" page was taken and rewritten with the
    binding by this build. The unit was decoded with $IDs and TypePointers stripped
    and keys sorted. It is identical to Studio Pro's "after" unit, and diff prints
    nothing. mx check: 0 errors.
  • Round trip. describe → exec of that page reports Unchanged page. The stored
    unit compared with the same normalisation is identical.
  • Non-image entity. With the refusal stubbed out, mx check reports CE7247 twice,
    once for imageObject and once for defaultImageDynamic. With the fix, exec refuses
    the script before writing.
  • Runtime (run --local, Chromium via playwright-cli). This was a test app using
    the Marketplace File Uploader 2.5.0. Its product page had shown the photo through a
    JavaScript-computed URL stored in a String attribute. That image was replaced, by
    alter page, with listview (DataSource: $currentObject/Mod.Product_Photos) { image (Image: $currentObject) }; the overview grid got the same plus DefaultImage.
    mx check: 0 errors. A 40×40 PNG was uploaded through the File Uploader. The product
    page and the overview grid then each render
    <img src="/file?guid=…&changedDate=…&name=red40.png"> with naturalWidth 40, after
    a reload too. Console errors: 0.
  • Default image, in the running app. The photo's HasContents was set to false in
    the database and the page was reloaded:
    • With the fallback written as a collection image plus the DirectEntityRef
      (Studio Pro's shape for an unset fallback), the widget rendered
      class="… hidden" with no src.
    • With the collection image alone, it rendered
      <img src="/img/Atlas_Core$Content$Mendix.png?…"> with naturalWidth 512.
    • Restoring the EntityRef hid it again, so the EntityRef is the cause.
    • Both shapes pass mx check, so only the runtime tells them apart. The writer uses
      the second.
  • Every new test detects its fix. Each guard was stubbed out in turn, and the test
    written for it was run against the stub. 18 of 18 fail.

Testing

Run locally on macOS, with TMPDIR=/tmp/t and
MX_BINARY=~/.mxcli/mxbuild/11.12.1/modeler/mx (mxbuild 11.12.1; CI's 11.12.2 is
Linux-only):

Step Result
make build ok
make test 102 packages ok. cmd/mxcli/docker fails on two SyncJavaDependencies tests (#1396: the orphaned-daemon pipe hangs, then the package times out); both fail the same way on the base commit, and the rest of the package passes with them skipped
make lint-go (after git add -A) ok
make check-mdl ok
make check-findings ok
make check-wiki-pages ok
./scripts/check-tunnel-deps.sh ok
./scripts/check-skill-mdl.sh ./bin/mxcli .claude/skills/mendix ok
./scripts/check-skill-mdl.sh ./bin/mxcli docs-site/src ok
make test-integration 102 packages ok, including mdl/roundtrip (484 s) and mdl/executor (500 s). cmd/mxcli/docker again: the #1396 hang, plus TestMxBuildCmd_HungBuildIsKilled failing under the package's load; that test passes alone on this branch and on the base commit
govulncheck ./... 0 vulnerabilities in called code
mdbook build docs-site (mdBook 0.5.2) ok; its 6 warnings are in language/versions.md, untouched here

Mendix validation

  • Mendix 11.12.1, React client, Image widget 1.5.0.
  • The blank-app example passes mx check with 0 errors, and so does
    03-page-examples.mdl with the new P042b_ImageObject.
  • The same shape as a Studio Pro-authored page (diff above).
  • An uploaded image renders in the running app, and so does the default image for an
    object without contents.

Agentic Code Testing

  • Error messages are helpful for debugging. Each refusal names the remedy: wrap the
    widget in a data view over the object, or use an entity that extends
    System.Image.
  • Skills updated: create-page SKILL.md and reference/widgets.md, mxcli syntax page, docs-site/src/language/widget-types.md, MDL_QUICK_REFERENCE.md.
  • Tested with Claude Code in the dev container. Not done; the feature was exercised
    from the CLI on macOS.
  • Claude can generate correct MDL for this feature. Not measured in a separate
    session. The issue came from an agent session that tried three wrong variants;
    the syntax help and the MDL-WIDGET22 suggestion now name this form.

Not in this PR

  • An unknown ImageType is still stored as written. For example,
    ImageType: dynamicImage passes check and mx check but is not a value of the widget
    enumeration. This is a separate concern and will get a separate PR.
  • Binding through a page parameter or an association path (Image: $Photo,
    Image: $currentObject/Mod.Assoc) is refused. The Studio Pro shapes for these
    bindings have not been measured. A data view over the object is the supported route.
  • The MCP backend (mdl/backend/mcp) reports SetImageObject as unsupported
    rather than guessing its payload.
  • Describe does not check the stored entity. It prints Image: $currentObject for
    any stored DirectEntityRef without a source variable, and does not compare that
    entity with the enclosing container's. A Studio Pro page bound to a different
    image entity than its container would re-execute to the container's entity. This
    case has not been seen or measured.
  • The System.Image check runs at exec, not in check. Exec refuses before
    writing, but mxcli check --references does not yet report it.

…#1476)

MDL had no way to show an uploaded image (an object of a System.Image
specialisation) on a React-client page. `Image:` on the pluggable Image
widget could only name an image collection entry. The legacy `dynamicimage`
is CE0582 in React. `image i (Image: $currentObject)` failed check with
MDL-WIDGET22 and MDL-WIDGET42. Describe of a Studio Pro page bound this
way printed a bare `image i`, which re-executes into "No image selected."

`Image: $currentObject` now binds the widget to the enclosing data view's,
list view's or gallery's object. The stored shape is Studio Pro's.
Measured on 11.12.1 with Image widget 1.5.0 by setting "Image source" to
a data view's object and decoding the unit before and after, exactly two
values change:
  imageObject          Image '' and EntityRef DirectEntityRef{Entity}
  defaultImageDynamic  EntityRef DirectEntityRef{Entity}

`DefaultImage:` is the fallback on `defaultImageDynamic`, written as a
plain collection image. The Studio Pro page set no fallback, so this shape
was measured in the running app, on an object whose HasContents is false.
With the EntityRef beside the image, the widget renders hidden with no src.
Without it, the fallback renders. Both shapes pass mx check.

Results:
- Written by mxcli on the same page, the unit is identical to Studio
  Pro's once $IDs and TypePointers are stripped. mx check: 0 errors.
- describe -> exec reports "Unchanged page".
- In a running app, an image uploaded through the File Uploader renders
  as /file?guid=... with naturalWidth 40 for a 40-pixel PNG. With its
  contents cleared, the fallback renders instead.

These are refused before the write:
- an entity that is not an image (mxbuild: CE7247, once per bound
  property);
- a widget with no enclosing data container;
- a page parameter or an association path, whose stored shapes have not
  been measured.

Describe prints the binding back. A stored shape MDL cannot spell gets a
NOT re-executable note.
@github-actions

Copy link
Copy Markdown

AI Code Review

Review Summary

This PR implements the ability to bind the Image widget to an image object in context (Image: $currentObject) for Mendix 11 React-client apps, closing issue #1476. The implementation is thorough and follows all required patterns.

What Looks Good

  • Full-stack consistency: The feature properly wires through the complete pipeline:

    • AST: Uses existing ast.DataSourceV3 for $currentObject binding
    • Executor: Comprehensive changes in widget_image_object.go, validate_widget_image.go, widget_engine.go, and describe-related files
    • DESCRIBE roundtrip: Properly extracts and emits Image: $currentObject bindings, with correct handling of unreadable shapes (associations/parameters) via -- NOT re-executable notes
    • Documentation: Updated skills, widget references, quick reference, and changelog
  • MDL syntax design:

    • Uses

Automated review via OpenRouter (Nemotron Super 120B) — workflow source

@tgolembiewski

Copy link
Copy Markdown
Contributor Author

Closing this: #1476 was fixed on main by d9ec242 ("fix(pages): bind the Image widget to an uploaded image with Image: $currentObject", merged with #1503), with the same syntax and its own bug test, so nothing here is still needed. Thanks for picking it up.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

No MDL form binds the Image widget to an image object; dynamicimage fails CE0582 in React apps

1 participant