Current section
Files
Jump to
Current section
Files
README.md
# glendix
Gleam FFI bindings for React and Mendix Pluggable Widget API.
**JSX μμ΄, μμ GleamμΌλ‘ Mendix Pluggable Widgetμ μμ±νλ€.**
## Installation
```toml
# gleam.toml
[dependencies]
glendix = { path = "../glendix" }
```
> Hex ν¨ν€μ§ λ°°ν¬ μ κΉμ§λ λ‘컬 κ²½λ‘λ‘ μ°Έμ‘°ν©λλ€.
### Peer Dependencies
μμ ― νλ‘μ νΈμ `package.json`μ λ€μμ΄ νμν©λλ€:
```json
{
"dependencies": {
"react": "^19.0.0",
"big.js": "^6.0.0"
}
}
```
## Quick Start
```gleam
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 μμ) |
| `glendix/binding` | μΈλΆ React μ»΄ν¬λνΈ λ°μΈλ© β `.mjs` μμ΄ `bindings.json`λ§μΌλ‘ μ¬μ© |
### 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 νμ΄νλΌμΈ
```gleam
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
```gleam
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 μ½κΈ°/μ°κΈ°
```gleam
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()
}
}
```
### μ‘°κ±΄λΆ λ λλ§
```gleam
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)])
})
```
### μΈλΆ React μ»΄ν¬λνΈ μ¬μ© (λ°μΈλ©)
`.mjs` νμΌ μμ± μμ΄ μΈλΆ React λΌμ΄λΈλ¬λ¦¬λ₯Ό μ¬μ©ν©λλ€.
**1. `bindings.json` μμ±:**
```json
{
"recharts": {
"components": ["PieChart", "Pie", "Cell", "Tooltip", "Legend"]
}
}
```
**2. `gleam run -m glendix/install` μ€ν** (λ°μΈλ© μλ μμ±)
**3. μμ GleamμΌλ‘ μ¬μ©:**
```gleam
import glendix/binding
import glendix/react
import glendix/react/prop
fn recharts() { binding.module("recharts") }
pub fn my_chart(data) -> react.ReactElement {
react.component(
binding.resolve(recharts(), "PieChart"),
prop.new() |> prop.int("width", 400) |> prop.int("height", 300),
[
react.component(
binding.resolve(recharts(), "Pie"),
prop.new() |> prop.any("data", data) |> prop.string("dataKey", "value"),
[],
),
],
)
}
```
## Build Scripts
glendixμ λ΄μ₯λ λΉλ μ€ν¬λ¦½νΈλ‘, μμ ― νλ‘μ νΈμμ λ³λ μ€ν¬λ¦½νΈ νμΌ μμ΄ `gleam run -m`μΌλ‘ μ€ννλ€.
| λͺ
λ Ήμ΄ | μ€λͺ
|
|--------|------|
| `gleam run -m glendix/install` | μμ‘΄μ± μ€μΉ + λ°μΈλ© μμ± (PM μλ κ°μ§) |
| `gleam run -m glendix/build` | νλ‘λμ
λΉλ (.mpk μμ±) |
| `gleam run -m glendix/dev` | κ°λ° μλ² (HMR, port 3000) |
| `gleam run -m glendix/start` | Mendix ν
μ€νΈ νλ‘μ νΈ μ°λ |
| `gleam run -m glendix/lint` | ESLint μ€ν |
| `gleam run -m glendix/lint_fix` | ESLint μλ μμ |
| `gleam run -m glendix/release` | λ¦΄λ¦¬μ¦ λΉλ |
ν¨ν€μ§ λ§€λμ λ lock νμΌ κΈ°λ°μΌλ‘ μλ κ°μ§λλ€:
- `pnpm-lock.yaml` β pnpm
- `bun.lockb` / `bun.lock` β bun
- κΈ°λ³Έκ° β npm
## 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 λΉλ
binding.gleam β μΈλΆ React μ»΄ν¬λνΈ λ°μΈλ© API
binding_ffi.mjs β λ°μΈλ© FFI (install μ μλ κ΅μ²΄)
cmd.gleam β μ
Έ λͺ
λ Ήμ΄ μ€ν + PM κ°μ§ + λ°μΈλ© μμ±
cmd_ffi.mjs β Node.js child_process + fs FFI + λ°μΈλ© μμ±
build.gleam β λΉλ μ€ν¬λ¦½νΈ
dev.gleam β κ°λ° μλ² μ€ν¬λ¦½νΈ
start.gleam β Mendix μ°λ μ€ν¬λ¦½νΈ
install.gleam β μμ‘΄μ± μ€μΉ + λ°μΈλ© μμ± μ€ν¬λ¦½νΈ
release.gleam β λ¦΄λ¦¬μ¦ λΉλ μ€ν¬λ¦½νΈ
lint.gleam β ESLint μ€ν¬λ¦½νΈ
lint_fix.gleam β ESLint μλ μμ μ€ν¬λ¦½νΈ
```
## Design Principles
- **FFIλ μμ μ΄λν°μΌ λΏμ΄λ€.** `react_ffi.mjs`λ React μμ ν¨μ, `mendix_ffi.mjs`λ Mendix λ°νμ νμ
μ κ·Όμλ₯Ό λ
ΈμΆν λΏ, λΉμ¦λμ€ λ‘μ§μ μ λΆ GleamμΌλ‘ μμ±νλ€.
- **Opaque typeμΌλ‘ νμ
μμ μ± λ³΄μ₯.** `ReactElement`, `JsProps`, `EditableValue` λ± JS κ°μ Gleamμ opaque typeμΌλ‘ κ°μΈ μλͺ»λ μ κ·Όμ μ»΄νμΌ νμμ μ°¨λ¨νλ€.
- **`undefined` β `Option` μλ λ³ν.** FFI κ²½κ³μμ JS `undefined`/`null`μ Gleam `None`μΌλ‘, κ°μ΄ μμΌλ©΄ `Some(value)`μΌλ‘ λ³νλλ€.
- **νμ΄νλΌμΈ API.** Propsλ `prop.new() |> prop.class("x") |> prop.on_click(handler)` ν¨ν΄μΌλ‘ ꡬμ±νλ€.
- **Gleam νν = JS λ°°μ΄.** `#(a, b)` = `[a, b]`μ΄λ―λ‘ `useState`μ λ°νκ°κ³Ό μ§μ νΈνλλ€.
## License
Apache-2.0