Skip to content

GG-O-BP/glendix

Repository files navigation

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 Mendix types/widgets/marketplace are delegated to mendraw.

What's New in v4.0

v4.0 delegates Mendix API types, widget bindings (.mpk), classic widgets, and marketplace functionality to mendraw. glendix now focuses purely on build tooling, external React component bindings, and the Lustre bridge.

What's Changed Then

  • Mendix types moved to mendraw: import glendix/mendiximport mendraw/mendix, all submodules (editable_value, action, list_value, etc.) now under mendraw/mendix/*
  • Interop moved to mendraw: import glendix/interopimport mendraw/interop
  • Widget moved to mendraw: import glendix/widgetimport mendraw/widget, TOML config [tools.glendix.widgets.*][tools.mendraw.widgets.*]
  • Classic moved to mendraw: import glendix/classicimport mendraw/classic
  • Marketplace moved to mendraw: gleam run -m glendix/marketplacegleam run -m mendraw/marketplace
  • glendix/binding stays: external React component bindings remain in glendix
  • glendix/lustre stays: Lustre TEA bridge remains in glendix

Migration Cheatsheet (v3 → v4)

Before (v3) After (v4)
import glendix/mendix.{type JsProps} import mendraw/mendix.{type JsProps}
import glendix/mendix/editable_value import mendraw/mendix/editable_value
import glendix/mendix/action import mendraw/mendix/action
import glendix/interop import mendraw/interop
import glendix/widget import mendraw/widget
import glendix/classic import mendraw/classic
gleam run -m glendix/marketplace gleam run -m mendraw/marketplace
[tools.glendix.widgets.X] [tools.mendraw.widgets.X]

How to Put It In Your Project

Pop this into your gleam.toml:

# gleam.toml
[dependencies]
glendix = ">= 4.0.0 and < 5.0.0"
mendraw = ">= 1.0.0 and < 2.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"
  }
}

big.js is only needed if your widget uses Decimal attributes. Skip it if you don't!

Let's Get Started!

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

import mendraw/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 mendraw/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)

Module What It Does
redraw Components, hooks, fragments, context — the full React API in Gleam
redraw/dom/html HTML tags — div, span, input, text, none, and loads more
redraw/dom/attribute Attribute type + HTML attribute functions — class, id, style, and more
redraw/dom/events Event handlers — on_click, on_change, on_input, with capture variants
redraw/dom/svg SVG elements — svg, path, circle, filter primitives, and more
redraw/dom DOM utilities — create_portal, flush_sync, resource hints

glendix Bridges

Module What It Does
glendix/lustre Lustre TEA bridge — use_tea, use_simple, render, embed
glendix/binding For using other people's React components — configure in gleam.toml [tools.glendix.bindings]
glendix/define Interactive TUI editor for widget property definitions

mendraw (Mendix API & Widgets)

Module What It Does
mendraw/mendix Core Mendix types (ValueStatus, ObjectItem, JsProps) + props accessors
mendraw/interop Renders external JS React components (from widget/binding) as redraw.Element
mendraw/widget For using .mpk widgets — auto-downloaded via gleam.tomlcomponent, prop, editable_prop, action_prop
mendraw/classic Classic (Dojo) widget wrapper — classic.render(widget_id, properties)
mendraw/marketplace Search and download widgets from the Mendix Marketplace

JS Interop Bits

Module What It Does
glendix/js/array Gleam List ↔ JS Array conversion
glendix/js/object Create objects, read/write/delete properties, call methods, new instances
glendix/js/json stringify and parse (parse returns a proper Result!)
glendix/js/promise Promise chaining (then_, map, catch_), all, race, resolve, reject
glendix/js/dom DOM helpers — focus, blur, click, scroll_into_view, query_selector
glendix/js/timer set_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 mendraw/mendix.{type JsProps}
import mendraw/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. Add bindings to gleam.toml:

[tools.glendix.bindings]
recharts = ["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 mendraw/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 use Marketplace widgets as React components — auto-downloaded via gleam.toml.

Register your widget in gleam.toml and run gleam run -m glendix/install:

[tools.mendraw.widgets.Charts]
version = "3.0.0"
# s3_id = "com/..."   ← if you have this, no auth needed!

It downloads to build/widgets/ cache and generates everything automatically.

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

// src/widgets/switch.gleam (made automatically!)
import mendraw/mendix.{type JsProps}
import mendraw/interop
import mendraw/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 mendraw/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 mendraw/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

Downloaded widgets are cached in build/widgets/ and automatically added to your gleam.toml — no need to commit .mpk files to source control!

Build Scripts

Command What It Does
gleam run -m glendix/install Installs deps + downloads TOML widgets + makes bindings + generates widget files
gleam run -m mendraw/marketplace Searches and downloads widgets from the Marketplace
gleam run -m glendix/define Interactive TUI editor for widget property definitions
gleam run -m glendix/build Makes a production build (.mpk file)
gleam run -m glendix/dev Starts a dev server (with HMR)
gleam run -m glendix/start Connects to a Mendix test project
gleam run -m glendix/lint Checks your code with ESLint
gleam run -m glendix/lint_fix Fixes ESLint problems automatically
gleam run -m glendix/release Makes a release build

Why We Made It This Way

  • Delegate, don't duplicate. React bindings belong to redraw. TEA belongs to lustre. Mendix types and widgets belong to mendraw. glendix only handles build tooling, external React component bindings, and the Lustre bridge.
  • Opaque types keep everything safe. JS values like JsProps and EditableValue are wrapped up in Gleam types so you can't accidentally do something wrong — the compiler catches it!
  • undefined turns into Option automatically. When JS gives us undefined or null, Gleam gets None. When there's a real value, it becomes Some(value). No faffing about!
  • Two rendering paths. Use redraw for direct React, or use the Lustre bridge for TEA — both output redraw.Element, so they compose freely.

Thank You

glendix v4.0 is built on top of the brilliant redraw, lustre, and mendraw ecosystems. Cheers to all projects!

Licence

Blue Oak Model Licence 1.0.0

Packages

 
 
 

Contributors