English | 한국어 | 日本語

glendix

Hello! This is glendix and it’s ever so brilliant! It’s a Gleam library for Mendix Pluggable Widgets.

You can write proper Mendix widgets using only Gleam — no JSX needed at all, how lovely is that!

React is handled by redraw/redraw_dom, TEA pattern by lustre, and glendix focuses on the Mendix bits.

What’s New in v3.0

v3.0 is a big one! We’ve ripped out the entire glendix/react layer (14 files!) and delegated React bindings to redraw and redraw_dom. We’ve also added a Lustre bridge so you can use the TEA pattern inside Mendix widgets.

What’s Changed Then

Migration Cheatsheet

Before (v2)After (v3)
import glendix/react.{type ReactElement}import redraw.{type Element}
import glendix/react.{type JsProps}import glendix/mendix.{type JsProps}
import glendix/react/htmlimport redraw/dom/html
import glendix/react/attributeimport redraw/dom/attribute
import glendix/react/eventimport redraw/dom/events
import glendix/react/hookimport redraw (hooks are in the main module)
import glendix/react/svgimport redraw/dom/svg
import glendix/react/refimport redraw/ref
react.text("hi")html.text("hi")
react.none()html.none()
react.fragment(children)redraw.fragment(children)
react.define_component(name, render)redraw.component_(name, render)
react.memo(component)redraw.memoize_(component)
hook.use_state(0)redraw.use_state(0)
hook.use_effect(fn, deps)redraw.use_effect(fn, deps)
react.component_el(comp, attrs, children)interop.component_el(comp, attrs, children)
react.when(cond, fn)case cond { True -> fn() False -> html.none() }

How to Put It In Your Project

Pop this into your gleam.toml:

# gleam.toml
[dependencies]
glendix = ">= 3.0.2 and < 4.0.0"

Peer Dependencies

Your widget project’s package.json needs these as well:

{
  "dependencies": {
    "react": "^19.0.0",
    "react-dom": "^19.0.0",
    "big.js": "^6.0.0"
  }
}

Let’s Get Started!

Here’s a dead simple widget — look how short it is!

import glendix/mendix.{type JsProps}
import redraw.{type Element}
import redraw/dom/attribute
import redraw/dom/html

pub fn widget(props: JsProps) -> Element {
  let name = mendix.get_string_prop(props, "sampleText")
  html.div([attribute.class("my-widget")], [
    html.text("Hello " <> name),
  ])
}

fn(JsProps) -> Element — that’s literally all a Mendix Pluggable Widget needs. Easy peasy!

Using Lustre TEA Pattern

If you prefer The Elm Architecture, use the Lustre bridge — your update and view functions are 100% standard Lustre:

import glendix/lustre as gl
import glendix/mendix.{type JsProps}
import lustre/effect
import lustre/element/html
import lustre/event
import redraw.{type Element}

type Model { Model(count: Int) }
type Msg { Increment }

fn update(model, msg) {
  case msg {
    Increment -> #(Model(model.count + 1), effect.none())
  }
}

fn view(model: Model) {
  html.div([], [
    html.button([event.on_click(Increment)], [
      html.text("Count: " <> int.to_string(model.count)),
    ]),
  ])
}

pub fn widget(_props: JsProps) -> Element {
  gl.use_tea(#(Model(0), effect.none()), update, view)
}

All the Modules

React & Rendering (via redraw)

ModuleWhat It Does
redrawComponents, hooks, fragments, context — the full React API in Gleam
redraw/dom/htmlHTML tags — div, span, input, text, none, and loads more
redraw/dom/attributeAttribute type + HTML attribute functions — class, id, style, and more
redraw/dom/eventsEvent handlers — on_click, on_change, on_input, with capture variants
redraw/dom/svgSVG elements — svg, path, circle, filter primitives, and more
redraw/domDOM utilities — create_portal, flush_sync, resource hints

glendix Bridges

ModuleWhat It Does
glendix/interopRenders external JS React components (from widget/binding) as redraw.Element
glendix/lustreLustre TEA bridge — use_tea, use_simple, render, embed
glendix/bindingFor using other people’s React components — just write bindings.json and you’re sorted!
glendix/widgetFor using .mpk widgets from the widgets/ folder — component, prop, editable_prop, action_prop
glendix/classicClassic (Dojo) widget wrapper — classic.render(widget_id, properties)
glendix/marketplaceSearch and download widgets from the Mendix Marketplace
glendix/defineInteractive TUI editor for widget property definitions

Mendix Bits

ModuleWhat It Does
glendix/mendixCore Mendix types (ValueStatus, ObjectItem, JsProps) + props accessors
glendix/mendix/editable_valueFor values you can change — value, set_value, set_text_value, display_value
glendix/mendix/actionFor doing actions — can_execute, execute, execute_if_can
glendix/mendix/dynamic_valueFor read-only values (expression attributes and that)
glendix/mendix/list_valueLists of data — items, set_filter, set_sort_order, reload
glendix/mendix/list_attributeTypes that go with lists — ListAttributeValue, ListActionValue, ListWidgetValue
glendix/mendix/selectionFor picking one thing or lots of things
glendix/mendix/referenceSingle association (ReferenceValue)
glendix/mendix/reference_setMultiple associations (ReferenceSetValue)
glendix/mendix/dateA wrapper for JS Date (months go from 1 in Gleam to 0 in JS automatically)
glendix/mendix/bigBig.js wrapper for precise numbers
glendix/mendix/fileFileValue and WebImage
glendix/mendix/iconWebIcon — Glyph, Image, IconFont
glendix/mendix/formatterValueFormatterformat and parse
glendix/mendix/filterFilterCondition builder
glendix/editor_configEditor helpers (Jint compatible)

JS Interop Bits

ModuleWhat It Does
glendix/js/arrayGleam List ↔ JS Array conversion
glendix/js/objectCreate objects, read/write/delete properties, call methods, new instances
glendix/js/jsonstringify and parse (parse returns a proper Result!)
glendix/js/promisePromise chaining (then_, map, catch_), all, race, resolve, reject
glendix/js/domDOM helpers — focus, blur, click, scroll_into_view, query_selector
glendix/js/timerset_timeout, set_interval, clear_timeout, clear_interval

Examples

Attribute Lists

This is how you make a button with attributes — it’s like a shopping list!

import redraw/dom/attribute
import redraw/dom/events
import redraw/dom/html

html.button(
  [
    attribute.class("btn btn-primary"),
    attribute.type_("submit"),
    attribute.disabled(False),
    events.on_click(fn(_event) { Nil }),
  ],
  [html.text("Submit")],
)

useState + useEffect

Here’s a counter! Every time you press the button, the number goes up by one — magic!

import gleam/int
import redraw
import redraw/dom/attribute
import redraw/dom/events
import redraw/dom/html

pub fn counter(_props) -> redraw.Element {
  let #(count, set_count) = redraw.use_state(0)

  redraw.use_effect(fn() { Nil }, Nil)

  html.div([], [
    html.button(
      [events.on_click(fn(_) { set_count(count + 1) })],
      [html.text("Count: " <> int.to_string(count))],
    ),
  ])
}

Reading and Writing Mendix Values

Here’s how you get values out of Mendix and do things with them:

import gleam/option.{None, Some}
import glendix/mendix.{type JsProps}
import glendix/mendix/editable_value as ev
import redraw.{type Element}
import redraw/dom/html

pub fn render_input(props: JsProps) -> Element {
  case mendix.get_prop(props, "myAttribute") {
    Some(attr) -> {
      let display = ev.display_value(attr)
      let editable = ev.is_editable(attr)
      // ...
    }
    None -> html.none()
  }
}

Using Other People’s React Components (Bindings)

You can use React libraries from npm without writing any .mjs files yourself — isn’t that ace!

1. Make a bindings.json file:

{
  "recharts": {
    "components": ["PieChart", "Pie", "Cell", "Tooltip", "Legend"]
  }
}

2. Install the package:

npm install recharts

3. Run gleam run -m glendix/install

4. Write a nice Gleam wrapper:

// src/chart/recharts.gleam
import glendix/binding
import glendix/interop
import redraw.{type Element}
import redraw/dom/attribute.{type Attribute}

fn m() { binding.module("recharts") }

pub fn pie_chart(attrs: List(Attribute), children: List(Element)) -> Element {
  interop.component_el(binding.resolve(m(), "PieChart"), attrs, children)
}

pub fn pie(attrs: List(Attribute), children: List(Element)) -> Element {
  interop.component_el(binding.resolve(m(), "Pie"), attrs, children)
}

5. Use it in your widget:

import chart/recharts
import redraw/dom/attribute

pub fn my_chart(data) -> redraw.Element {
  recharts.pie_chart(
    [attribute.attribute("width", 400), attribute.attribute("height", 300)],
    [
      recharts.pie(
        [attribute.attribute("data", data), attribute.attribute("dataKey", "value")],
        [],
      ),
    ],
  )
}

Using .mpk Widgets

You can put .mpk files in the widgets/ folder and use them like React components — how cool is that!

1. Pop your .mpk files into the widgets/ folder

2. Run gleam run -m glendix/install (it sorts out all the bindings for you!)

3. Have a look at the auto-generated src/widgets/*.gleam files:

// src/widgets/switch.gleam (made automatically!)
import glendix/mendix.{type JsProps}
import glendix/interop
import glendix/widget
import redraw.{type Element}
import redraw/dom/attribute

pub fn render(props: JsProps) -> Element {
  let boolean_attribute = mendix.get_prop_required(props, "booleanAttribute")
  let action = mendix.get_prop_required(props, "action")

  let comp = widget.component("Switch")
  interop.component_el(
    comp,
    [
      attribute.attribute("booleanAttribute", boolean_attribute),
      attribute.attribute("action", action),
    ],
    [],
  )
}

4. Use it in your widget:

You can pass Mendix props through directly, or create values from scratch using the widget prop helpers:

// Creating values from scratch (e.g. in Lustre TEA views)
import glendix/widget

widget.prop("caption", "Hello")                              // DynamicValue
widget.editable_prop("text", value, display, set_value)      // EditableValue
widget.action_prop("onClick", fn() { do_something() })       // ActionValue
import widgets/switch

switch.render(props)

Downloading Widgets from the Marketplace

You can search for widgets on the Mendix Marketplace and download them right from the terminal — it’s dead handy!

1. Put your Mendix PAT in .env:

MENDIX_PAT=your_personal_access_token

You can get a PAT from Mendix Developer Settings — click New Token under Personal Access Tokens. You’ll need the mx:marketplace-content:read permission.

2. Run this:

gleam run -m glendix/marketplace

3. Use the lovely interactive menu:

  ── Page 1/5+ ──

  [0] Star Rating (54611) v3.2.2 — Mendix
  [1] Switch (50324) v4.0.0 — Mendix
  ...

  Number: download | Search term: filter by name | n: next | p: previous | r: reset | q: quit

> 0              ← type a number to download it
> star           ← type a word to search
> 0,1,3          ← use commas to pick several at once

Build Scripts

CommandWhat It Does
gleam run -m glendix/installInstalls everything + makes bindings + generates widget files
gleam run -m glendix/marketplaceSearches and downloads widgets from the Marketplace
gleam run -m glendix/defineInteractive TUI editor for widget property definitions
gleam run -m glendix/buildMakes a production build (.mpk file)
gleam run -m glendix/devStarts a dev server (with HMR)
gleam run -m glendix/startConnects to a Mendix test project
gleam run -m glendix/lintChecks your code with ESLint
gleam run -m glendix/lint_fixFixes ESLint problems automatically
gleam run -m glendix/releaseMakes a release build

Why We Made It This Way

Thank You

glendix v3.0 is built on top of the brilliant redraw and lustre ecosystems. Cheers to both projects!

Licence

Blue Oak Model Licence 1.0.0

Search Document