glendix
Gleam FFI bindings for React and Mendix Pluggable Widget API.
JSX 없이, 순수 Gleam으로 Mendix Pluggable Widget을 작성한다.
Installation
# gleam.toml
[dependencies]
glendix = { path = "../glendix" }
Hex 패키지 배포 전까지는 로컬 경로로 참조합니다.
Peer Dependencies
위젯 프로젝트의 package.json에 다음이 필요합니다:
{
"dependencies": {
"react": "^19.0.0",
"big.js": "^6.0.0"
}
}
Quick Start
import glendix/mendix
import glendix/react.{type JsProps, type ReactElement}
import glendix/react/html
import glendix/react/prop
pub fn widget(props: JsProps) -> ReactElement {
let name = mendix.get_string_prop(props, "sampleText")
html.div(prop.new() |> prop.class("my-widget"), [
react.text("Hello " <> name),
])
}
fn(JsProps) -> ReactElement — 이것이 Mendix Pluggable Widget의 전부입니다.
Modules
React
| Module | Description |
|---|---|
glendix/react | 핵심 타입 (ReactElement, JsProps, Props) + el, fragment, text, none, when, when_some |
glendix/react/prop | Props 파이프라인 빌더 — prop.new() |> prop.class("x") |> prop.on_click(handler) |
glendix/react/hook | React Hooks — use_state, use_effect, use_memo, use_callback, use_ref |
glendix/react/event | 이벤트 타입 + target_value, prevent_default, key |
glendix/react/html | HTML 태그 편의 함수 — div, span, input, button 등 (순수 Gleam, FFI 없음) |
Mendix
| Module | Description |
|---|---|
glendix/mendix | 핵심 타입 (ValueStatus, ObjectItem) + JsProps 접근자 (get_prop, get_string_prop) |
glendix/mendix/editable_value | 편집 가능한 값 — value, set_value, set_text_value, display_value |
glendix/mendix/action | 액션 실행 — can_execute, execute, execute_if_can |
glendix/mendix/dynamic_value | 동적 읽기 전용 값 (표현식 속성) |
glendix/mendix/list_value | 리스트 데이터 — items, set_filter, set_sort_order, reload |
glendix/mendix/list_attribute | 리스트 아이템별 접근 — ListAttributeValue, ListActionValue, ListWidgetValue |
glendix/mendix/selection | 단일/다중 선택 |
glendix/mendix/reference | 연관 관계 (단일 참조, 다중 참조) |
glendix/mendix/date | JS Date opaque 래퍼 (월: Gleam 1-based ↔ JS 0-based 자동 변환) |
glendix/mendix/big | Big.js 고정밀 십진수 래퍼 (compare → gleam/order.Order) |
glendix/mendix/file | FileValue, WebImage |
glendix/mendix/icon | WebIcon — Glyph, Image, IconFont |
glendix/mendix/formatter | ValueFormatter — format, parse |
glendix/mendix/filter | FilterCondition 빌더 — and_, or_, equals, contains, attribute, literal |
Examples
Props 파이프라인
import glendix/react/prop
let props =
prop.new()
|> prop.class("btn btn-primary")
|> prop.string("type", "submit")
|> prop.bool("disabled", False)
|> prop.on_click(fn(_event) { Nil })
useState + useEffect
import glendix/react
import glendix/react/hook
import glendix/react/html
import glendix/react/prop
pub fn counter(_props) -> react.ReactElement {
let #(count, set_count) = hook.use_state(0)
hook.use_effect_once(fn() {
// 마운트 시 한 번 실행
Nil
})
html.div_([
html.button(
prop.new() |> prop.on_click(fn(_) { set_count(count + 1) }),
[react.text("Count: " <> int.to_string(count))],
),
])
}
Mendix EditableValue 읽기/쓰기
import gleam/option.{None, Some}
import glendix/mendix
import glendix/mendix/editable_value as ev
pub fn render_input(props: react.JsProps) -> react.ReactElement {
case mendix.get_prop(props, "myAttribute") {
Some(attr) -> {
let display = ev.display_value(attr)
let editable = ev.is_editable(attr)
// ...
}
None -> react.none()
}
}
조건부 렌더링
import glendix/react
import glendix/react/html
// Bool 기반
react.when(is_visible, fn() {
html.div_([react.text("Visible!")])
})
// Option 기반
react.when_some(maybe_user, fn(user) {
html.span_([react.text(user.name)])
})
Architecture
glendix/
react.gleam ← 핵심 타입 + createElement
react_ffi.mjs ← React 원시 함수 (얇은 어댑터)
react/
prop.gleam ← Props 빌더
hook.gleam ← React Hooks
event.gleam ← 이벤트 타입
html.gleam ← HTML 태그 (순수 Gleam)
mendix.gleam ← Mendix 핵심 타입 + Props 접근자
mendix_ffi.mjs ← Mendix 런타임 타입 접근 (얇은 어댑터)
mendix/
editable_value.gleam ← EditableValue
action.gleam ← ActionValue
dynamic_value.gleam ← DynamicValue
list_value.gleam ← ListValue + Sort + Filter
list_attribute.gleam ← List-linked 타입
selection.gleam ← Selection
reference.gleam ← Reference
date.gleam ← JS Date 래퍼
big.gleam ← Big.js 래퍼
file.gleam ← File / Image
icon.gleam ← Icon
formatter.gleam ← ValueFormatter
filter.gleam ← FilterCondition 빌더
Design Principles
- FFI는 얇은 어댑터일 뿐이다.
react_ffi.mjs는 React 원시 함수,mendix_ffi.mjs는 Mendix 런타임 타입 접근자를 노출할 뿐, 비즈니스 로직은 전부 Gleam으로 작성한다. - Opaque type으로 타입 안전성 보장.
ReactElement,JsProps,EditableValue등 JS 값을 Gleam의 opaque type으로 감싸 잘못된 접근을 컴파일 타임에 차단한다. undefined↔Option자동 변환. FFI 경계에서 JSundefined/null은 GleamNone으로, 값이 있으면Some(value)으로 변환된다.- 파이프라인 API. Props는
prop.new() |> prop.class("x") |> prop.on_click(handler)패턴으로 구성한다. - Gleam 튜플 = JS 배열.
#(a, b)=[a, b]이므로useState의 반환값과 직접 호환된다.
License
Apache-2.0