create-mendix-widget-gleam 1.0.1 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/index.mjs +36 -10
- package/template/src/widget/__widget_name__.gleam +2 -5
- package/template/src/widget/mendix/action.gleam +41 -0
- package/template/src/widget/mendix/dynamic_value.gleam +30 -0
- package/template/src/widget/mendix/editable_value.gleam +73 -0
- package/template/src/widget/mendix/file.gleam +34 -0
- package/template/src/widget/mendix/filter.gleam +109 -0
- package/template/src/widget/mendix/formatter.gleam +18 -0
- package/template/src/widget/mendix/icon.gleam +34 -0
- package/template/src/widget/mendix/list_attribute.gleam +57 -0
- package/template/src/widget/mendix/list_value.gleam +106 -0
- package/template/src/widget/mendix/reference.gleam +47 -0
- package/template/src/widget/mendix/selection.gleam +31 -0
- package/template/src/widget/mendix.gleam +65 -0
- package/template/src/widget/mendix_ffi.mjs +373 -0
- package/template/src/widget/react.gleam +5 -1
package/package.json
CHANGED
package/src/index.mjs
CHANGED
|
@@ -130,7 +130,7 @@ Gleam 언어로 Mendix Pluggable Widget을 개발하여 "Hello World"를 화면
|
|
|
130
130
|
## Tech Stack
|
|
131
131
|
|
|
132
132
|
- **Gleam** → JavaScript 컴파일 (target: javascript)
|
|
133
|
-
- **Gleam FFI** (\`@external\` 어노테이션 +
|
|
133
|
+
- **Gleam FFI** (\`@external\` 어노테이션 + FFI 파일) — React API와 Mendix API를 Gleam에서 직접 호출
|
|
134
134
|
- **Mendix Pluggable Widget** (React 19)
|
|
135
135
|
- **Package Manager**: ${pm} (npm 의존성은 \`gleam run -m scripts/install\`로 설치)
|
|
136
136
|
- **Build**: \`@mendix/pluggable-widgets-tools\` (Rollup 기반)
|
|
@@ -141,7 +141,12 @@ Gleam 언어로 Mendix Pluggable Widget을 개발하여 "Hello World"를 화면
|
|
|
141
141
|
src/
|
|
142
142
|
widget/ # 핵심 Gleam 코드 (개발자가 작업하는 곳)
|
|
143
143
|
${names.snakeCase}.gleam # 위젯 메인 모듈
|
|
144
|
-
|
|
144
|
+
react_ffi.mjs # React FFI 어댑터 (React 원시 함수)
|
|
145
|
+
mendix_ffi.mjs # Mendix FFI 어댑터 (Mendix 런타임 타입 접근)
|
|
146
|
+
react.gleam # 핵심 타입 + createElement + fragment/text/none
|
|
147
|
+
react/ # React 모듈 (prop, hook, event, html)
|
|
148
|
+
mendix.gleam # Mendix 핵심 타입 + Props 접근자
|
|
149
|
+
mendix/ # Mendix API 모듈 (editable_value, action, list_value 등)
|
|
145
150
|
editor_config.gleam # Studio Pro 속성 패널 설정
|
|
146
151
|
scripts/ # 빌드/개발 스크립트 (gleam run -m으로 실행)
|
|
147
152
|
cmd.gleam # 셸 명령어 실행 유틸리티
|
|
@@ -167,9 +172,9 @@ docs/
|
|
|
167
172
|
## Build Pipeline
|
|
168
173
|
|
|
169
174
|
\`\`\`
|
|
170
|
-
[src/widget
|
|
175
|
+
[src/widget/*.gleam] + [src/widget/react_ffi.mjs] + [src/widget/mendix_ffi.mjs]
|
|
171
176
|
↓ gleam run -m scripts/build
|
|
172
|
-
[build/dev/javascript/${names.snakeCase}/widget
|
|
177
|
+
[build/dev/javascript/${names.snakeCase}/widget/*.mjs]
|
|
173
178
|
↓ src/${names.pascalCase}.js (브릿지)가 import
|
|
174
179
|
↓ Rollup (pluggable-widgets-tools build:web)
|
|
175
180
|
[dist/1.0.0/mendix.${names.lowerCase}.${names.pascalCase}.mpk]
|
|
@@ -192,9 +197,27 @@ gleam format # Gleam 코드 포맷팅
|
|
|
192
197
|
|
|
193
198
|
## Gleam FFI Convention
|
|
194
199
|
|
|
195
|
-
- FFI
|
|
196
|
-
- \`@external(javascript, "./<
|
|
197
|
-
-
|
|
200
|
+
- FFI는 도메인별로 분리: \`react_ffi.mjs\` (React 원시 함수), \`mendix_ffi.mjs\` (Mendix 런타임 타입 접근)
|
|
201
|
+
- \`react.gleam\`/\`mendix.gleam\`에서 \`@external(javascript, "./<ffi>.mjs", "<function>")\` 형식으로 바인딩
|
|
202
|
+
- \`react/*.gleam\`에서 \`@external(javascript, "../react_ffi.mjs", "<function>")\` 형식으로 바인딩
|
|
203
|
+
- \`mendix/*.gleam\`에서 \`@external(javascript, "../mendix_ffi.mjs", "<function>")\` 형식으로 바인딩
|
|
204
|
+
- FFI 파일에는 API 래핑만 작성. 위젯 로직은 반드시 Gleam으로 작성
|
|
205
|
+
- \`mendix_ffi.mjs\`에서 JS \`undefined\`/\`null\` ↔ Gleam \`Option\` 변환 자동 처리
|
|
206
|
+
|
|
207
|
+
## Mendix API Modules
|
|
208
|
+
|
|
209
|
+
- \`mendix.gleam\` — \`ValueStatus\`, \`ObjectItem\`, Props 접근자 (\`get_prop\`, \`get_string_prop\`)
|
|
210
|
+
- \`mendix/editable_value.gleam\` — \`EditableValue\` (편집 가능한 값)
|
|
211
|
+
- \`mendix/action.gleam\` — \`ActionValue\` (마이크로플로우/나노플로우 실행)
|
|
212
|
+
- \`mendix/dynamic_value.gleam\` — \`DynamicValue\` (동적 읽기 전용 값)
|
|
213
|
+
- \`mendix/list_value.gleam\` — \`ListValue\`, \`FilterCondition\`, \`SortInstruction\`
|
|
214
|
+
- \`mendix/list_attribute.gleam\` — \`ListAttributeValue\` 등 리스트 연결 타입
|
|
215
|
+
- \`mendix/selection.gleam\` — 단일/다중 선택
|
|
216
|
+
- \`mendix/reference.gleam\` — 참조 관계 값
|
|
217
|
+
- \`mendix/file.gleam\` — 파일/이미지
|
|
218
|
+
- \`mendix/icon.gleam\` — 아이콘
|
|
219
|
+
- \`mendix/formatter.gleam\` — 값 포맷팅/파싱
|
|
220
|
+
- \`mendix/filter.gleam\` — 필터 조건 빌더
|
|
198
221
|
|
|
199
222
|
## Mendix Widget Conventions
|
|
200
223
|
|
|
@@ -205,7 +228,7 @@ gleam format # Gleam 코드 포맷팅
|
|
|
205
228
|
## Code Style
|
|
206
229
|
|
|
207
230
|
- Gleam 파일: \`gleam format\` 사용
|
|
208
|
-
- FFI 파일(
|
|
231
|
+
- FFI 파일(\`react_ffi.mjs\`, \`mendix_ffi.mjs\`): API 노출만 담당, 비즈니스 로직 금지
|
|
209
232
|
- 한국어 주석 사용
|
|
210
233
|
`;
|
|
211
234
|
|
|
@@ -268,7 +291,10 @@ gleam format # 코드 포맷팅
|
|
|
268
291
|
src/
|
|
269
292
|
widget/ # Gleam 위젯 코드
|
|
270
293
|
${names.snakeCase}.gleam # 메인 위젯 모듈
|
|
271
|
-
|
|
294
|
+
react_ffi.mjs # React FFI 어댑터
|
|
295
|
+
mendix_ffi.mjs # Mendix FFI 어댑터
|
|
296
|
+
react.gleam + react/ # React 모듈 계층
|
|
297
|
+
mendix.gleam + mendix/ # Mendix API 모듈 계층
|
|
272
298
|
editor_config.gleam # Studio Pro 속성 패널
|
|
273
299
|
scripts/ # 빌드/개발 스크립트
|
|
274
300
|
${names.pascalCase}.js # Mendix 브릿지 진입점
|
|
@@ -278,7 +304,7 @@ src/
|
|
|
278
304
|
## 기술 스택
|
|
279
305
|
|
|
280
306
|
- **Gleam** → JavaScript 컴파일
|
|
281
|
-
- **Gleam FFI** — React API 직접 바인딩
|
|
307
|
+
- **Gleam FFI** — React/Mendix API 직접 바인딩
|
|
282
308
|
- **Mendix Pluggable Widget** (React 19)
|
|
283
309
|
- **${pm}** — 패키지 매니저
|
|
284
310
|
|
|
@@ -1,17 +1,14 @@
|
|
|
1
1
|
// Mendix Pluggable Widget - "Hello World"
|
|
2
2
|
// React 함수형 컴포넌트: fn(JsProps) -> ReactElement
|
|
3
3
|
|
|
4
|
+
import widget/mendix
|
|
4
5
|
import widget/react.{type JsProps, type ReactElement}
|
|
5
6
|
import widget/react/html
|
|
6
7
|
import widget/react/prop
|
|
7
8
|
|
|
8
|
-
// Props에서 문자열 속성값 추출
|
|
9
|
-
@external(javascript, "./react_ffi.mjs", "get_string_prop")
|
|
10
|
-
fn get_string_prop(props: JsProps, key: String) -> String
|
|
11
|
-
|
|
12
9
|
/// 위젯 메인 함수 - Mendix 런타임이 React 컴포넌트로 호출
|
|
13
10
|
pub fn widget(props: JsProps) -> ReactElement {
|
|
14
|
-
let sample_text = get_string_prop(props, "sampleText")
|
|
11
|
+
let sample_text = mendix.get_string_prop(props, "sampleText")
|
|
15
12
|
html.div(prop.new() |> prop.class("widget-hello-world"), [
|
|
16
13
|
react.text("Hello " <> sample_text),
|
|
17
14
|
])
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// Mendix ActionValue 타입 — 실행 가능한 액션 (마이크로플로우, 나노플로우 등)
|
|
2
|
+
|
|
3
|
+
import gleam/option.{type Option, None, Some}
|
|
4
|
+
|
|
5
|
+
// === 타입 ===
|
|
6
|
+
|
|
7
|
+
pub type ActionValue
|
|
8
|
+
|
|
9
|
+
// === 접근자 ===
|
|
10
|
+
|
|
11
|
+
/// 실행 가능 여부
|
|
12
|
+
@external(javascript, "../mendix_ffi.mjs", "get_action_can_execute")
|
|
13
|
+
pub fn can_execute(action: ActionValue) -> Bool
|
|
14
|
+
|
|
15
|
+
/// 현재 실행 중인지 여부
|
|
16
|
+
@external(javascript, "../mendix_ffi.mjs", "get_action_is_executing")
|
|
17
|
+
pub fn is_executing(action: ActionValue) -> Bool
|
|
18
|
+
|
|
19
|
+
// === 메서드 ===
|
|
20
|
+
|
|
21
|
+
/// 액션 실행
|
|
22
|
+
@external(javascript, "../mendix_ffi.mjs", "action_execute")
|
|
23
|
+
pub fn execute(action: ActionValue) -> Nil
|
|
24
|
+
|
|
25
|
+
// === 편의 함수 (순수 Gleam) ===
|
|
26
|
+
|
|
27
|
+
/// 실행 가능할 때만 실행
|
|
28
|
+
pub fn execute_if_can(action: ActionValue) -> Nil {
|
|
29
|
+
case can_execute(action) {
|
|
30
|
+
True -> execute(action)
|
|
31
|
+
False -> Nil
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/// Option(ActionValue)를 받아 실행 가능하면 실행
|
|
36
|
+
pub fn execute_action(action: Option(ActionValue)) -> Nil {
|
|
37
|
+
case action {
|
|
38
|
+
Some(a) -> execute_if_can(a)
|
|
39
|
+
None -> Nil
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// Mendix DynamicValue 타입 — 동적으로 계산되는 읽기 전용 값
|
|
2
|
+
// 사용: 표현식 속성 (expression property)
|
|
3
|
+
|
|
4
|
+
import gleam/option.{type Option}
|
|
5
|
+
import widget/mendix.{type ValueStatus}
|
|
6
|
+
|
|
7
|
+
// === 타입 ===
|
|
8
|
+
|
|
9
|
+
pub type DynamicValue
|
|
10
|
+
|
|
11
|
+
// === 접근자 ===
|
|
12
|
+
|
|
13
|
+
@external(javascript, "../mendix_ffi.mjs", "get_status")
|
|
14
|
+
fn get_status_raw(dv: DynamicValue) -> String
|
|
15
|
+
|
|
16
|
+
/// 현재 상태 (Available, Loading, Unavailable)
|
|
17
|
+
pub fn status(dv: DynamicValue) -> ValueStatus {
|
|
18
|
+
mendix.to_value_status(get_status_raw(dv))
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/// 현재 값 (undefined → None)
|
|
22
|
+
@external(javascript, "../mendix_ffi.mjs", "get_dynamic_value")
|
|
23
|
+
pub fn value(dv: DynamicValue) -> Option(a)
|
|
24
|
+
|
|
25
|
+
// === 편의 함수 (순수 Gleam) ===
|
|
26
|
+
|
|
27
|
+
/// 값이 사용 가능한지 확인
|
|
28
|
+
pub fn is_available(dv: DynamicValue) -> Bool {
|
|
29
|
+
status(dv) == mendix.Available
|
|
30
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// Mendix EditableValue 타입 — 편집 가능한 값의 접근자와 메서드
|
|
2
|
+
// 사용: 텍스트, 숫자, 날짜 등 Mendix 속성 편집
|
|
3
|
+
|
|
4
|
+
import gleam/option.{type Option}
|
|
5
|
+
import widget/mendix.{type ValueStatus}
|
|
6
|
+
import widget/mendix/formatter.{type ValueFormatter}
|
|
7
|
+
|
|
8
|
+
// === 타입 ===
|
|
9
|
+
|
|
10
|
+
pub type EditableValue
|
|
11
|
+
|
|
12
|
+
// === 접근자 ===
|
|
13
|
+
|
|
14
|
+
@external(javascript, "../mendix_ffi.mjs", "get_status")
|
|
15
|
+
fn get_status_raw(ev: EditableValue) -> String
|
|
16
|
+
|
|
17
|
+
/// 현재 상태 (Available, Loading, Unavailable)
|
|
18
|
+
pub fn status(ev: EditableValue) -> ValueStatus {
|
|
19
|
+
mendix.to_value_status(get_status_raw(ev))
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/// 현재 값 (undefined → None)
|
|
23
|
+
@external(javascript, "../mendix_ffi.mjs", "get_editable_value")
|
|
24
|
+
pub fn value(ev: EditableValue) -> Option(a)
|
|
25
|
+
|
|
26
|
+
/// 읽기 전용 여부
|
|
27
|
+
@external(javascript, "../mendix_ffi.mjs", "get_editable_read_only")
|
|
28
|
+
pub fn read_only(ev: EditableValue) -> Bool
|
|
29
|
+
|
|
30
|
+
/// 유효성 검사 메시지 (없으면 None)
|
|
31
|
+
@external(javascript, "../mendix_ffi.mjs", "get_editable_validation")
|
|
32
|
+
pub fn validation(ev: EditableValue) -> Option(String)
|
|
33
|
+
|
|
34
|
+
/// 표시용 문자열 (포맷팅 적용된 값)
|
|
35
|
+
@external(javascript, "../mendix_ffi.mjs", "get_editable_display_value")
|
|
36
|
+
pub fn display_value(ev: EditableValue) -> String
|
|
37
|
+
|
|
38
|
+
/// 값 포매터
|
|
39
|
+
@external(javascript, "../mendix_ffi.mjs", "get_editable_formatter")
|
|
40
|
+
pub fn get_formatter(ev: EditableValue) -> ValueFormatter
|
|
41
|
+
|
|
42
|
+
/// 가능한 값 목록 (열거형 속성 등)
|
|
43
|
+
@external(javascript, "../mendix_ffi.mjs", "get_editable_universe")
|
|
44
|
+
pub fn universe(ev: EditableValue) -> Option(List(a))
|
|
45
|
+
|
|
46
|
+
// === 메서드 ===
|
|
47
|
+
|
|
48
|
+
/// 값 설정 (None → undefined 전달)
|
|
49
|
+
@external(javascript, "../mendix_ffi.mjs", "editable_set_value")
|
|
50
|
+
pub fn set_value(ev: EditableValue, value: Option(a)) -> Nil
|
|
51
|
+
|
|
52
|
+
/// 텍스트 값 직접 설정 (파싱은 Mendix가 처리)
|
|
53
|
+
@external(javascript, "../mendix_ffi.mjs", "editable_set_text_value")
|
|
54
|
+
pub fn set_text_value(ev: EditableValue, text: String) -> Nil
|
|
55
|
+
|
|
56
|
+
/// 유효성 검사기 설정 (None → 검사기 제거)
|
|
57
|
+
@external(javascript, "../mendix_ffi.mjs", "editable_set_validator")
|
|
58
|
+
pub fn set_validator(
|
|
59
|
+
ev: EditableValue,
|
|
60
|
+
validator: Option(fn(Option(a)) -> Option(String)),
|
|
61
|
+
) -> Nil
|
|
62
|
+
|
|
63
|
+
// === 편의 함수 (순수 Gleam) ===
|
|
64
|
+
|
|
65
|
+
/// 값이 사용 가능한지 확인
|
|
66
|
+
pub fn is_available(ev: EditableValue) -> Bool {
|
|
67
|
+
status(ev) == mendix.Available
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/// 편집 가능한 상태인지 확인 (사용 가능 + 읽기 전용 아님)
|
|
71
|
+
pub fn is_editable(ev: EditableValue) -> Bool {
|
|
72
|
+
is_available(ev) && !read_only(ev)
|
|
73
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// Mendix FileValue / WebImage 타입 — 파일 및 이미지 속성
|
|
2
|
+
// FileValue: 일반 파일, WebImage: 이미지 (altText 추가)
|
|
3
|
+
|
|
4
|
+
import gleam/option.{type Option}
|
|
5
|
+
|
|
6
|
+
// === 타입 ===
|
|
7
|
+
|
|
8
|
+
pub type FileValue
|
|
9
|
+
|
|
10
|
+
pub type WebImage
|
|
11
|
+
|
|
12
|
+
// === FileValue 접근자 ===
|
|
13
|
+
|
|
14
|
+
/// 파일 URI
|
|
15
|
+
@external(javascript, "../mendix_ffi.mjs", "get_file_uri")
|
|
16
|
+
pub fn uri(f: FileValue) -> String
|
|
17
|
+
|
|
18
|
+
/// 파일 이름 (없으면 None)
|
|
19
|
+
@external(javascript, "../mendix_ffi.mjs", "get_file_name")
|
|
20
|
+
pub fn name(f: FileValue) -> Option(String)
|
|
21
|
+
|
|
22
|
+
// === WebImage 접근자 (FileValue 확장) ===
|
|
23
|
+
|
|
24
|
+
/// 이미지 URI
|
|
25
|
+
@external(javascript, "../mendix_ffi.mjs", "get_file_uri")
|
|
26
|
+
pub fn image_uri(img: WebImage) -> String
|
|
27
|
+
|
|
28
|
+
/// 이미지 파일 이름 (없으면 None)
|
|
29
|
+
@external(javascript, "../mendix_ffi.mjs", "get_file_name")
|
|
30
|
+
pub fn image_name(img: WebImage) -> Option(String)
|
|
31
|
+
|
|
32
|
+
/// 이미지 대체 텍스트 (없으면 None)
|
|
33
|
+
@external(javascript, "../mendix_ffi.mjs", "get_image_alt_text")
|
|
34
|
+
pub fn alt_text(img: WebImage) -> Option(String)
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
// Mendix Filter 조건 빌더 — mendix/filters/builders 래핑
|
|
2
|
+
// 사용: ListValue의 필터 조건 프로그래매틱 구성
|
|
3
|
+
|
|
4
|
+
import widget/mendix/list_value.{type FilterCondition}
|
|
5
|
+
|
|
6
|
+
// === 타입 ===
|
|
7
|
+
|
|
8
|
+
/// 필터 표현식 (attribute, literal, association, empty)
|
|
9
|
+
pub type ValueExpression
|
|
10
|
+
|
|
11
|
+
// === Boolean 조합 ===
|
|
12
|
+
|
|
13
|
+
/// AND 조합 (모든 조건 충족)
|
|
14
|
+
@external(javascript, "../mendix_ffi.mjs", "filter_and")
|
|
15
|
+
pub fn and_(conditions: List(FilterCondition)) -> FilterCondition
|
|
16
|
+
|
|
17
|
+
/// OR 조합 (하나 이상 충족)
|
|
18
|
+
@external(javascript, "../mendix_ffi.mjs", "filter_or")
|
|
19
|
+
pub fn or_(conditions: List(FilterCondition)) -> FilterCondition
|
|
20
|
+
|
|
21
|
+
/// NOT 부정
|
|
22
|
+
@external(javascript, "../mendix_ffi.mjs", "filter_not")
|
|
23
|
+
pub fn not_(condition: FilterCondition) -> FilterCondition
|
|
24
|
+
|
|
25
|
+
// === 동등 비교 ===
|
|
26
|
+
|
|
27
|
+
@external(javascript, "../mendix_ffi.mjs", "filter_equals")
|
|
28
|
+
pub fn equals(a: ValueExpression, b: ValueExpression) -> FilterCondition
|
|
29
|
+
|
|
30
|
+
@external(javascript, "../mendix_ffi.mjs", "filter_not_equal")
|
|
31
|
+
pub fn not_equal(a: ValueExpression, b: ValueExpression) -> FilterCondition
|
|
32
|
+
|
|
33
|
+
// === 크기 비교 ===
|
|
34
|
+
|
|
35
|
+
@external(javascript, "../mendix_ffi.mjs", "filter_greater_than")
|
|
36
|
+
pub fn greater_than(a: ValueExpression, b: ValueExpression) -> FilterCondition
|
|
37
|
+
|
|
38
|
+
@external(javascript, "../mendix_ffi.mjs", "filter_greater_than_or_equal")
|
|
39
|
+
pub fn greater_than_or_equal(
|
|
40
|
+
a: ValueExpression,
|
|
41
|
+
b: ValueExpression,
|
|
42
|
+
) -> FilterCondition
|
|
43
|
+
|
|
44
|
+
@external(javascript, "../mendix_ffi.mjs", "filter_less_than")
|
|
45
|
+
pub fn less_than(a: ValueExpression, b: ValueExpression) -> FilterCondition
|
|
46
|
+
|
|
47
|
+
@external(javascript, "../mendix_ffi.mjs", "filter_less_than_or_equal")
|
|
48
|
+
pub fn less_than_or_equal(
|
|
49
|
+
a: ValueExpression,
|
|
50
|
+
b: ValueExpression,
|
|
51
|
+
) -> FilterCondition
|
|
52
|
+
|
|
53
|
+
// === 문자열 검색 ===
|
|
54
|
+
|
|
55
|
+
@external(javascript, "../mendix_ffi.mjs", "filter_contains")
|
|
56
|
+
pub fn contains(a: ValueExpression, b: ValueExpression) -> FilterCondition
|
|
57
|
+
|
|
58
|
+
@external(javascript, "../mendix_ffi.mjs", "filter_starts_with")
|
|
59
|
+
pub fn starts_with(a: ValueExpression, b: ValueExpression) -> FilterCondition
|
|
60
|
+
|
|
61
|
+
@external(javascript, "../mendix_ffi.mjs", "filter_ends_with")
|
|
62
|
+
pub fn ends_with(a: ValueExpression, b: ValueExpression) -> FilterCondition
|
|
63
|
+
|
|
64
|
+
// === 날짜 비교 ===
|
|
65
|
+
|
|
66
|
+
@external(javascript, "../mendix_ffi.mjs", "filter_day_equals")
|
|
67
|
+
pub fn day_equals(a: ValueExpression, b: ValueExpression) -> FilterCondition
|
|
68
|
+
|
|
69
|
+
@external(javascript, "../mendix_ffi.mjs", "filter_day_not_equal")
|
|
70
|
+
pub fn day_not_equal(a: ValueExpression, b: ValueExpression) -> FilterCondition
|
|
71
|
+
|
|
72
|
+
@external(javascript, "../mendix_ffi.mjs", "filter_day_greater_than")
|
|
73
|
+
pub fn day_greater_than(
|
|
74
|
+
a: ValueExpression,
|
|
75
|
+
b: ValueExpression,
|
|
76
|
+
) -> FilterCondition
|
|
77
|
+
|
|
78
|
+
@external(javascript, "../mendix_ffi.mjs", "filter_day_greater_than_or_equal")
|
|
79
|
+
pub fn day_greater_than_or_equal(
|
|
80
|
+
a: ValueExpression,
|
|
81
|
+
b: ValueExpression,
|
|
82
|
+
) -> FilterCondition
|
|
83
|
+
|
|
84
|
+
@external(javascript, "../mendix_ffi.mjs", "filter_day_less_than")
|
|
85
|
+
pub fn day_less_than(a: ValueExpression, b: ValueExpression) -> FilterCondition
|
|
86
|
+
|
|
87
|
+
@external(javascript, "../mendix_ffi.mjs", "filter_day_less_than_or_equal")
|
|
88
|
+
pub fn day_less_than_or_equal(
|
|
89
|
+
a: ValueExpression,
|
|
90
|
+
b: ValueExpression,
|
|
91
|
+
) -> FilterCondition
|
|
92
|
+
|
|
93
|
+
// === 표현식 생성 ===
|
|
94
|
+
|
|
95
|
+
/// 속성 참조 표현식 (속성 ID로 지정)
|
|
96
|
+
@external(javascript, "../mendix_ffi.mjs", "filter_attribute")
|
|
97
|
+
pub fn attribute(id: String) -> ValueExpression
|
|
98
|
+
|
|
99
|
+
/// 연관 관계 참조 표현식 (연관 ID로 지정)
|
|
100
|
+
@external(javascript, "../mendix_ffi.mjs", "filter_association")
|
|
101
|
+
pub fn association(id: String) -> ValueExpression
|
|
102
|
+
|
|
103
|
+
/// 리터럴 값 표현식
|
|
104
|
+
@external(javascript, "../mendix_ffi.mjs", "filter_literal")
|
|
105
|
+
pub fn literal(value: a) -> ValueExpression
|
|
106
|
+
|
|
107
|
+
/// 빈 값 표현식
|
|
108
|
+
@external(javascript, "../mendix_ffi.mjs", "filter_empty")
|
|
109
|
+
pub fn empty() -> ValueExpression
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// Mendix ValueFormatter 타입 — 값 포맷팅/파싱
|
|
2
|
+
// 사용: EditableValue, ListAttributeValue 등의 포매터
|
|
3
|
+
|
|
4
|
+
import gleam/option.{type Option}
|
|
5
|
+
|
|
6
|
+
// === 타입 ===
|
|
7
|
+
|
|
8
|
+
pub type ValueFormatter
|
|
9
|
+
|
|
10
|
+
// === 메서드 ===
|
|
11
|
+
|
|
12
|
+
/// 값을 표시 문자열로 포맷팅 (None → 빈 값 포맷)
|
|
13
|
+
@external(javascript, "../mendix_ffi.mjs", "formatter_format")
|
|
14
|
+
pub fn format(fmt: ValueFormatter, value: Option(a)) -> String
|
|
15
|
+
|
|
16
|
+
/// 문자열을 값으로 파싱 (실패 시 Error(Nil))
|
|
17
|
+
@external(javascript, "../mendix_ffi.mjs", "formatter_parse")
|
|
18
|
+
pub fn parse(fmt: ValueFormatter, text: String) -> Result(Option(a), Nil)
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// Mendix WebIcon 타입 — 아이콘 (글리프, 이미지, 아이콘 폰트)
|
|
2
|
+
// Studio Pro에서 설정한 아이콘 속성
|
|
3
|
+
|
|
4
|
+
// === 타입 ===
|
|
5
|
+
|
|
6
|
+
pub type WebIcon
|
|
7
|
+
|
|
8
|
+
pub type IconType {
|
|
9
|
+
Glyph
|
|
10
|
+
Image
|
|
11
|
+
IconFont
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// === 접근자 ===
|
|
15
|
+
|
|
16
|
+
@external(javascript, "../mendix_ffi.mjs", "get_icon_type")
|
|
17
|
+
fn get_icon_type_raw(icon: WebIcon) -> String
|
|
18
|
+
|
|
19
|
+
/// 아이콘 종류 (Glyph, Image, IconFont)
|
|
20
|
+
pub fn icon_type(icon: WebIcon) -> IconType {
|
|
21
|
+
case get_icon_type_raw(icon) {
|
|
22
|
+
"glyph" -> Glyph
|
|
23
|
+
"image" -> Image
|
|
24
|
+
_ -> IconFont
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/// 아이콘 CSS 클래스 (Glyph, IconFont에서 사용)
|
|
29
|
+
@external(javascript, "../mendix_ffi.mjs", "get_icon_class")
|
|
30
|
+
pub fn icon_class(icon: WebIcon) -> String
|
|
31
|
+
|
|
32
|
+
/// 아이콘 이미지 URL (Image에서 사용)
|
|
33
|
+
@external(javascript, "../mendix_ffi.mjs", "get_icon_url")
|
|
34
|
+
pub fn icon_url(icon: WebIcon) -> String
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// Mendix List-linked 타입 — ListValue의 아이템별 접근자
|
|
2
|
+
// ListAttributeValue, ListActionValue, ListExpressionValue, ListWidgetValue
|
|
3
|
+
|
|
4
|
+
import gleam/option.{type Option}
|
|
5
|
+
import widget/mendix.{type ObjectItem}
|
|
6
|
+
import widget/mendix/formatter.{type ValueFormatter}
|
|
7
|
+
import widget/react.{type ReactElement}
|
|
8
|
+
|
|
9
|
+
// === 타입 ===
|
|
10
|
+
|
|
11
|
+
pub type ListAttributeValue
|
|
12
|
+
|
|
13
|
+
pub type ListActionValue
|
|
14
|
+
|
|
15
|
+
pub type ListExpressionValue
|
|
16
|
+
|
|
17
|
+
pub type ListWidgetValue
|
|
18
|
+
|
|
19
|
+
// === 아이템별 값 접근 (공용 FFI: list_type_get) ===
|
|
20
|
+
|
|
21
|
+
/// 특정 아이템의 속성 값 가져오기 (EditableValue 반환)
|
|
22
|
+
@external(javascript, "../mendix_ffi.mjs", "list_type_get")
|
|
23
|
+
pub fn get_attribute(attr: ListAttributeValue, item: ObjectItem) -> a
|
|
24
|
+
|
|
25
|
+
/// 특정 아이템의 액션 가져오기 (Option(ActionValue) 반환)
|
|
26
|
+
@external(javascript, "../mendix_ffi.mjs", "list_type_get")
|
|
27
|
+
pub fn get_action(action: ListActionValue, item: ObjectItem) -> Option(a)
|
|
28
|
+
|
|
29
|
+
/// 특정 아이템의 표현식 값 가져오기 (DynamicValue 반환)
|
|
30
|
+
@external(javascript, "../mendix_ffi.mjs", "list_type_get")
|
|
31
|
+
pub fn get_expression(expr: ListExpressionValue, item: ObjectItem) -> a
|
|
32
|
+
|
|
33
|
+
/// 특정 아이템의 위젯 렌더링 가져오기 (ReactElement 반환)
|
|
34
|
+
@external(javascript, "../mendix_ffi.mjs", "list_type_get")
|
|
35
|
+
pub fn get_widget(widget: ListWidgetValue, item: ObjectItem) -> ReactElement
|
|
36
|
+
|
|
37
|
+
// === ListAttributeValue 메타데이터 ===
|
|
38
|
+
|
|
39
|
+
/// 속성 ID
|
|
40
|
+
@external(javascript, "../mendix_ffi.mjs", "get_list_attr_id")
|
|
41
|
+
pub fn attr_id(attr: ListAttributeValue) -> String
|
|
42
|
+
|
|
43
|
+
/// 정렬 가능 여부
|
|
44
|
+
@external(javascript, "../mendix_ffi.mjs", "get_list_attr_sortable")
|
|
45
|
+
pub fn attr_sortable(attr: ListAttributeValue) -> Bool
|
|
46
|
+
|
|
47
|
+
/// 필터링 가능 여부
|
|
48
|
+
@external(javascript, "../mendix_ffi.mjs", "get_list_attr_filterable")
|
|
49
|
+
pub fn attr_filterable(attr: ListAttributeValue) -> Bool
|
|
50
|
+
|
|
51
|
+
/// 속성 타입 문자열 ("String", "Integer", "DateTime" 등)
|
|
52
|
+
@external(javascript, "../mendix_ffi.mjs", "get_list_attr_type")
|
|
53
|
+
pub fn attr_type(attr: ListAttributeValue) -> String
|
|
54
|
+
|
|
55
|
+
/// 속성 포매터
|
|
56
|
+
@external(javascript, "../mendix_ffi.mjs", "get_list_attr_formatter")
|
|
57
|
+
pub fn attr_formatter(attr: ListAttributeValue) -> ValueFormatter
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
// Mendix ListValue 타입 — 데이터 소스에서 가져온 객체 목록
|
|
2
|
+
// 사용: 데이터 그리드, 리스트 뷰 등
|
|
3
|
+
|
|
4
|
+
import gleam/option.{type Option}
|
|
5
|
+
import widget/mendix.{type ObjectItem, type ValueStatus}
|
|
6
|
+
|
|
7
|
+
// === 타입 ===
|
|
8
|
+
|
|
9
|
+
pub type ListValue
|
|
10
|
+
|
|
11
|
+
pub type FilterCondition
|
|
12
|
+
|
|
13
|
+
pub type SortInstruction
|
|
14
|
+
|
|
15
|
+
pub type SortDirection {
|
|
16
|
+
Asc
|
|
17
|
+
Desc
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// === 접근자 ===
|
|
21
|
+
|
|
22
|
+
@external(javascript, "../mendix_ffi.mjs", "get_status")
|
|
23
|
+
fn get_status_raw(lv: ListValue) -> String
|
|
24
|
+
|
|
25
|
+
/// 현재 상태 (Available, Loading, Unavailable)
|
|
26
|
+
pub fn status(lv: ListValue) -> ValueStatus {
|
|
27
|
+
mendix.to_value_status(get_status_raw(lv))
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/// 아이템 목록 (로딩 중이면 None)
|
|
31
|
+
@external(javascript, "../mendix_ffi.mjs", "get_list_items")
|
|
32
|
+
pub fn items(lv: ListValue) -> Option(List(ObjectItem))
|
|
33
|
+
|
|
34
|
+
/// 현재 오프셋
|
|
35
|
+
@external(javascript, "../mendix_ffi.mjs", "get_list_offset")
|
|
36
|
+
pub fn offset(lv: ListValue) -> Int
|
|
37
|
+
|
|
38
|
+
/// 현재 페이지 크기
|
|
39
|
+
@external(javascript, "../mendix_ffi.mjs", "get_list_limit")
|
|
40
|
+
pub fn limit(lv: ListValue) -> Int
|
|
41
|
+
|
|
42
|
+
/// 더 많은 아이템이 있는지 (불확실하면 None)
|
|
43
|
+
@external(javascript, "../mendix_ffi.mjs", "get_list_has_more_items")
|
|
44
|
+
pub fn has_more_items(lv: ListValue) -> Option(Bool)
|
|
45
|
+
|
|
46
|
+
/// 전체 아이템 수 (요청하지 않았으면 None)
|
|
47
|
+
@external(javascript, "../mendix_ffi.mjs", "get_list_total_count")
|
|
48
|
+
pub fn total_count(lv: ListValue) -> Option(Int)
|
|
49
|
+
|
|
50
|
+
/// 현재 정렬 순서
|
|
51
|
+
@external(javascript, "../mendix_ffi.mjs", "get_list_sort_order")
|
|
52
|
+
pub fn sort_order(lv: ListValue) -> List(SortInstruction)
|
|
53
|
+
|
|
54
|
+
/// 현재 필터 조건 (없으면 None)
|
|
55
|
+
@external(javascript, "../mendix_ffi.mjs", "get_list_filter")
|
|
56
|
+
pub fn filter(lv: ListValue) -> Option(FilterCondition)
|
|
57
|
+
|
|
58
|
+
// === 메서드 ===
|
|
59
|
+
|
|
60
|
+
/// 오프셋 설정 (페이지네이션)
|
|
61
|
+
@external(javascript, "../mendix_ffi.mjs", "list_set_offset")
|
|
62
|
+
pub fn set_offset(lv: ListValue, offset: Int) -> Nil
|
|
63
|
+
|
|
64
|
+
/// 페이지 크기 설정
|
|
65
|
+
@external(javascript, "../mendix_ffi.mjs", "list_set_limit")
|
|
66
|
+
pub fn set_limit(lv: ListValue, limit: Int) -> Nil
|
|
67
|
+
|
|
68
|
+
/// 필터 조건 설정 (None → 필터 해제)
|
|
69
|
+
@external(javascript, "../mendix_ffi.mjs", "list_set_filter")
|
|
70
|
+
pub fn set_filter(lv: ListValue, filter: Option(FilterCondition)) -> Nil
|
|
71
|
+
|
|
72
|
+
/// 정렬 순서 설정
|
|
73
|
+
@external(javascript, "../mendix_ffi.mjs", "list_set_sort_order")
|
|
74
|
+
pub fn set_sort_order(lv: ListValue, order: List(SortInstruction)) -> Nil
|
|
75
|
+
|
|
76
|
+
/// 데이터 새로고침
|
|
77
|
+
@external(javascript, "../mendix_ffi.mjs", "list_reload")
|
|
78
|
+
pub fn reload(lv: ListValue) -> Nil
|
|
79
|
+
|
|
80
|
+
/// 전체 아이템 수 요청 (True → 요청, False → 해제)
|
|
81
|
+
@external(javascript, "../mendix_ffi.mjs", "list_request_total_count")
|
|
82
|
+
pub fn request_total_count(lv: ListValue, need: Bool) -> Nil
|
|
83
|
+
|
|
84
|
+
// === SortInstruction 빌더 ===
|
|
85
|
+
|
|
86
|
+
@external(javascript, "../mendix_ffi.mjs", "make_sort_instruction")
|
|
87
|
+
fn make_sort_raw(id: String, asc: Bool) -> SortInstruction
|
|
88
|
+
|
|
89
|
+
/// 정렬 명령 생성
|
|
90
|
+
pub fn sort(id: String, direction: SortDirection) -> SortInstruction {
|
|
91
|
+
make_sort_raw(id, direction == Asc)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
@external(javascript, "../mendix_ffi.mjs", "get_sort_id")
|
|
95
|
+
pub fn sort_id(instr: SortInstruction) -> String
|
|
96
|
+
|
|
97
|
+
@external(javascript, "../mendix_ffi.mjs", "get_sort_asc")
|
|
98
|
+
fn sort_asc_raw(instr: SortInstruction) -> Bool
|
|
99
|
+
|
|
100
|
+
/// 정렬 방향 조회
|
|
101
|
+
pub fn sort_direction(instr: SortInstruction) -> SortDirection {
|
|
102
|
+
case sort_asc_raw(instr) {
|
|
103
|
+
True -> Asc
|
|
104
|
+
False -> Desc
|
|
105
|
+
}
|
|
106
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// Mendix Reference 타입 — 연관 관계 (Association) 값
|
|
2
|
+
// ReferenceValue: 단일 참조, ReferenceSetValue: 다중 참조
|
|
3
|
+
// 둘 다 ModifiableValue 패턴을 따름
|
|
4
|
+
|
|
5
|
+
import gleam/option.{type Option}
|
|
6
|
+
|
|
7
|
+
// === 타입 ===
|
|
8
|
+
|
|
9
|
+
pub type ReferenceValue
|
|
10
|
+
|
|
11
|
+
pub type ReferenceSetValue
|
|
12
|
+
|
|
13
|
+
// === ReferenceValue (단일 참조) ===
|
|
14
|
+
|
|
15
|
+
/// 참조 값 (없으면 None)
|
|
16
|
+
@external(javascript, "../mendix_ffi.mjs", "get_modifiable_value")
|
|
17
|
+
pub fn value(ref: ReferenceValue) -> Option(a)
|
|
18
|
+
|
|
19
|
+
/// 참조 설정 (None → 참조 해제)
|
|
20
|
+
@external(javascript, "../mendix_ffi.mjs", "modifiable_set_value")
|
|
21
|
+
pub fn set_value(ref: ReferenceValue, value: Option(a)) -> Nil
|
|
22
|
+
|
|
23
|
+
/// 읽기 전용 여부
|
|
24
|
+
@external(javascript, "../mendix_ffi.mjs", "get_modifiable_read_only")
|
|
25
|
+
pub fn read_only(ref: ReferenceValue) -> Bool
|
|
26
|
+
|
|
27
|
+
/// 유효성 검사 메시지 (없으면 None)
|
|
28
|
+
@external(javascript, "../mendix_ffi.mjs", "get_modifiable_validation")
|
|
29
|
+
pub fn validation(ref: ReferenceValue) -> Option(String)
|
|
30
|
+
|
|
31
|
+
// === ReferenceSetValue (다중 참조) ===
|
|
32
|
+
|
|
33
|
+
/// 참조 목록 (없으면 None)
|
|
34
|
+
@external(javascript, "../mendix_ffi.mjs", "get_modifiable_value")
|
|
35
|
+
pub fn multi_value(rset: ReferenceSetValue) -> Option(List(a))
|
|
36
|
+
|
|
37
|
+
/// 참조 목록 설정 (None → 전체 해제)
|
|
38
|
+
@external(javascript, "../mendix_ffi.mjs", "modifiable_set_value")
|
|
39
|
+
pub fn set_multi_value(rset: ReferenceSetValue, value: Option(List(a))) -> Nil
|
|
40
|
+
|
|
41
|
+
/// 읽기 전용 여부
|
|
42
|
+
@external(javascript, "../mendix_ffi.mjs", "get_modifiable_read_only")
|
|
43
|
+
pub fn multi_read_only(rset: ReferenceSetValue) -> Bool
|
|
44
|
+
|
|
45
|
+
/// 유효성 검사 메시지 (없으면 None)
|
|
46
|
+
@external(javascript, "../mendix_ffi.mjs", "get_modifiable_validation")
|
|
47
|
+
pub fn multi_validation(rset: ReferenceSetValue) -> Option(String)
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// Mendix Selection 타입 — 단일/다중 선택
|
|
2
|
+
// 사용: 데이터 그리드 선택, 리스트 선택 등
|
|
3
|
+
|
|
4
|
+
import gleam/option.{type Option}
|
|
5
|
+
import widget/mendix.{type ObjectItem}
|
|
6
|
+
|
|
7
|
+
// === 타입 ===
|
|
8
|
+
|
|
9
|
+
pub type SelectionSingleValue
|
|
10
|
+
|
|
11
|
+
pub type SelectionMultiValue
|
|
12
|
+
|
|
13
|
+
// === 단일 선택 ===
|
|
14
|
+
|
|
15
|
+
/// 현재 선택된 아이템 (없으면 None)
|
|
16
|
+
@external(javascript, "../mendix_ffi.mjs", "get_selection_single")
|
|
17
|
+
pub fn selection(sel: SelectionSingleValue) -> Option(ObjectItem)
|
|
18
|
+
|
|
19
|
+
/// 선택 설정 (None → 선택 해제)
|
|
20
|
+
@external(javascript, "../mendix_ffi.mjs", "set_selection_single")
|
|
21
|
+
pub fn set_selection(sel: SelectionSingleValue, item: Option(ObjectItem)) -> Nil
|
|
22
|
+
|
|
23
|
+
// === 다중 선택 ===
|
|
24
|
+
|
|
25
|
+
/// 현재 선택된 아이템 목록
|
|
26
|
+
@external(javascript, "../mendix_ffi.mjs", "get_selection_multi")
|
|
27
|
+
pub fn selections(sel: SelectionMultiValue) -> List(ObjectItem)
|
|
28
|
+
|
|
29
|
+
/// 선택 목록 설정
|
|
30
|
+
@external(javascript, "../mendix_ffi.mjs", "set_selection_multi")
|
|
31
|
+
pub fn set_selections(sel: SelectionMultiValue, items: List(ObjectItem)) -> Nil
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// Mendix Pluggable Widget API - 핵심 타입 + JsProps 접근자
|
|
2
|
+
// ValueStatus, ObjectItem 타입과 props 접근 유틸리티
|
|
3
|
+
|
|
4
|
+
import gleam/option.{type Option}
|
|
5
|
+
import widget/react.{type JsProps}
|
|
6
|
+
|
|
7
|
+
// === ValueStatus ===
|
|
8
|
+
|
|
9
|
+
pub type ValueStatus {
|
|
10
|
+
Available
|
|
11
|
+
Unavailable
|
|
12
|
+
Loading
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
pub fn to_value_status(status: String) -> ValueStatus {
|
|
16
|
+
case status {
|
|
17
|
+
"available" -> Available
|
|
18
|
+
"loading" -> Loading
|
|
19
|
+
_ -> Unavailable
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// === ObjectItem ===
|
|
24
|
+
|
|
25
|
+
pub type ObjectItem
|
|
26
|
+
|
|
27
|
+
@external(javascript, "./mendix_ffi.mjs", "get_object_id")
|
|
28
|
+
pub fn object_id(item: ObjectItem) -> String
|
|
29
|
+
|
|
30
|
+
// === JsProps 접근자 ===
|
|
31
|
+
|
|
32
|
+
/// Mendix props에서 값 추출 (undefined → None)
|
|
33
|
+
@external(javascript, "./mendix_ffi.mjs", "get_mendix_prop")
|
|
34
|
+
pub fn get_prop(props: JsProps, key: String) -> Option(a)
|
|
35
|
+
|
|
36
|
+
/// Mendix props에서 항상 존재하는 값 추출
|
|
37
|
+
@external(javascript, "./mendix_ffi.mjs", "get_mendix_prop_required")
|
|
38
|
+
pub fn get_prop_required(props: JsProps, key: String) -> a
|
|
39
|
+
|
|
40
|
+
/// Mendix props에서 문자열 속성값 추출 (undefined → "")
|
|
41
|
+
@external(javascript, "./react_ffi.mjs", "get_string_prop")
|
|
42
|
+
pub fn get_string_prop(props: JsProps, key: String) -> String
|
|
43
|
+
|
|
44
|
+
/// Mendix props에서 키 존재 확인
|
|
45
|
+
@external(javascript, "./react_ffi.mjs", "has_prop")
|
|
46
|
+
pub fn has_prop(props: JsProps, key: String) -> Bool
|
|
47
|
+
|
|
48
|
+
// === Status 접근 (status 속성을 가진 모든 Mendix 객체) ===
|
|
49
|
+
|
|
50
|
+
@external(javascript, "./mendix_ffi.mjs", "get_status")
|
|
51
|
+
fn get_status_raw(obj: a) -> String
|
|
52
|
+
|
|
53
|
+
pub fn get_status(obj: a) -> ValueStatus {
|
|
54
|
+
to_value_status(get_status_raw(obj))
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// === Option 변환 유틸리티 (FFI 경계 처리) ===
|
|
58
|
+
|
|
59
|
+
/// JS 값을 Gleam Option으로 변환 (undefined/null → None)
|
|
60
|
+
@external(javascript, "./mendix_ffi.mjs", "to_option")
|
|
61
|
+
pub fn to_option(value: a) -> Option(a)
|
|
62
|
+
|
|
63
|
+
/// Gleam Option을 JS 값으로 변환 (None → undefined)
|
|
64
|
+
@external(javascript, "./mendix_ffi.mjs", "from_option")
|
|
65
|
+
pub fn from_option(option: Option(a)) -> a
|
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
// Mendix Pluggable Widget API FFI 어댑터
|
|
2
|
+
// Mendix 런타임 타입의 속성 접근과 메서드 호출을 Gleam에 노출
|
|
3
|
+
import { Some, None } from "../../gleam_stdlib/gleam/option.mjs";
|
|
4
|
+
import { Ok, Error as GleamError, toList } from "../gleam.mjs";
|
|
5
|
+
|
|
6
|
+
// === JS undefined ↔ Gleam Option 변환 ===
|
|
7
|
+
|
|
8
|
+
export function to_option(value) {
|
|
9
|
+
return value !== undefined && value !== null ? new Some(value) : new None();
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function from_option(option) {
|
|
13
|
+
return option instanceof Some ? option[0] : undefined;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// === JsProps 접근 ===
|
|
17
|
+
|
|
18
|
+
export function get_mendix_prop(props, key) {
|
|
19
|
+
return to_option(props[key]);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function get_mendix_prop_required(props, key) {
|
|
23
|
+
return props[key];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// === ValueStatus / ObjectItem ===
|
|
27
|
+
|
|
28
|
+
export function get_status(obj) {
|
|
29
|
+
return obj.status;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function get_object_id(item) {
|
|
33
|
+
return item.id;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// === EditableValue ===
|
|
37
|
+
|
|
38
|
+
export function get_editable_value(obj) {
|
|
39
|
+
return to_option(obj.value);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function get_editable_read_only(obj) {
|
|
43
|
+
return obj.readOnly;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function get_editable_validation(obj) {
|
|
47
|
+
return to_option(obj.validation);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function get_editable_display_value(obj) {
|
|
51
|
+
return obj.displayValue;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function get_editable_formatter(obj) {
|
|
55
|
+
return obj.formatter;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function get_editable_universe(obj) {
|
|
59
|
+
return to_option(obj.universe ? toList(obj.universe) : undefined);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function editable_set_value(obj, option) {
|
|
63
|
+
obj.setValue(from_option(option));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function editable_set_text_value(obj, text) {
|
|
67
|
+
obj.setTextValue(text);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function editable_set_validator(obj, option_fn) {
|
|
71
|
+
if (option_fn instanceof Some) {
|
|
72
|
+
const gleam_fn = option_fn[0];
|
|
73
|
+
obj.setValidator((value) => {
|
|
74
|
+
const result = gleam_fn(to_option(value));
|
|
75
|
+
return from_option(result);
|
|
76
|
+
});
|
|
77
|
+
} else {
|
|
78
|
+
obj.setValidator(undefined);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// === ActionValue ===
|
|
83
|
+
|
|
84
|
+
export function get_action_can_execute(a) {
|
|
85
|
+
return a.canExecute;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function get_action_is_executing(a) {
|
|
89
|
+
return a.isExecuting;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function action_execute(a) {
|
|
93
|
+
a.execute();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// === DynamicValue ===
|
|
97
|
+
|
|
98
|
+
export function get_dynamic_value(obj) {
|
|
99
|
+
return to_option(obj.value);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// === ListValue ===
|
|
103
|
+
|
|
104
|
+
export function get_list_items(lv) {
|
|
105
|
+
return to_option(lv.items ? toList(lv.items) : undefined);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function get_list_offset(lv) {
|
|
109
|
+
return lv.offset;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function get_list_limit(lv) {
|
|
113
|
+
return lv.limit;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function get_list_has_more_items(lv) {
|
|
117
|
+
return to_option(lv.hasMoreItems);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function get_list_total_count(lv) {
|
|
121
|
+
return to_option(lv.totalCount);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function get_list_sort_order(lv) {
|
|
125
|
+
return toList(lv.sortOrder || []);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export function get_list_filter(lv) {
|
|
129
|
+
return to_option(lv.filter);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export function list_set_offset(lv, offset) {
|
|
133
|
+
lv.setOffset(offset);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function list_set_limit(lv, limit) {
|
|
137
|
+
lv.setLimit(limit);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export function list_set_filter(lv, option) {
|
|
141
|
+
lv.setFilter(from_option(option));
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export function list_set_sort_order(lv, gleam_list) {
|
|
145
|
+
lv.setSortOrder(gleam_list.toArray());
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export function list_reload(lv) {
|
|
149
|
+
lv.reload();
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export function list_request_total_count(lv, need) {
|
|
153
|
+
lv.requestTotalCount(need);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// === List-linked 타입 (공용) ===
|
|
157
|
+
|
|
158
|
+
export function list_type_get(list_type, item) {
|
|
159
|
+
return list_type.get(item);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// === ListAttributeValue 메타데이터 ===
|
|
163
|
+
|
|
164
|
+
export function get_list_attr_id(attr) {
|
|
165
|
+
return attr.id;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export function get_list_attr_sortable(attr) {
|
|
169
|
+
return attr.sortable;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export function get_list_attr_filterable(attr) {
|
|
173
|
+
return attr.filterable;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export function get_list_attr_type(attr) {
|
|
177
|
+
return attr.type;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export function get_list_attr_formatter(attr) {
|
|
181
|
+
return attr.formatter;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// === Selection ===
|
|
185
|
+
|
|
186
|
+
export function get_selection_single(sel) {
|
|
187
|
+
return to_option(sel.selection);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export function get_selection_multi(sel) {
|
|
191
|
+
return toList(sel.selection || []);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export function set_selection_single(sel, option) {
|
|
195
|
+
sel.setSelection(from_option(option));
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export function set_selection_multi(sel, gleam_list) {
|
|
199
|
+
sel.setSelection(gleam_list.toArray());
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// === ModifiableValue / Reference ===
|
|
203
|
+
|
|
204
|
+
export function get_modifiable_value(obj) {
|
|
205
|
+
return to_option(obj.value);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export function modifiable_set_value(obj, option) {
|
|
209
|
+
obj.setValue(from_option(option));
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export function get_modifiable_read_only(obj) {
|
|
213
|
+
return obj.readOnly;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export function get_modifiable_validation(obj) {
|
|
217
|
+
return to_option(obj.validation);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// === FileValue / WebImage ===
|
|
221
|
+
|
|
222
|
+
export function get_file_uri(f) {
|
|
223
|
+
return f.uri;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export function get_file_name(f) {
|
|
227
|
+
return to_option(f.name);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export function get_image_alt_text(img) {
|
|
231
|
+
return to_option(img.altText);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// === WebIcon ===
|
|
235
|
+
|
|
236
|
+
export function get_icon_type(icon) {
|
|
237
|
+
return icon.type;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
export function get_icon_class(icon) {
|
|
241
|
+
return icon.iconClass || "";
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
export function get_icon_url(icon) {
|
|
245
|
+
return icon.iconUrl || "";
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// === Formatter ===
|
|
249
|
+
|
|
250
|
+
export function formatter_format(fmt, option_value) {
|
|
251
|
+
return fmt.format(from_option(option_value));
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
export function formatter_parse(fmt, text) {
|
|
255
|
+
const result = fmt.parse(text);
|
|
256
|
+
if (result.valid) {
|
|
257
|
+
return new Ok(to_option(result.value));
|
|
258
|
+
} else {
|
|
259
|
+
return new GleamError(undefined);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// === SortInstruction ===
|
|
264
|
+
|
|
265
|
+
export function make_sort_instruction(id, asc) {
|
|
266
|
+
return { id, asc };
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
export function get_sort_id(instr) {
|
|
270
|
+
return instr.id;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
export function get_sort_asc(instr) {
|
|
274
|
+
return instr.asc;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// === Filter 빌더 (mendix/filters/builders 래핑) ===
|
|
278
|
+
// Mendix 런타임에서 제공하는 외부 모듈 (Rollup에서 external 처리)
|
|
279
|
+
import * as filters from "mendix/filters/builders";
|
|
280
|
+
|
|
281
|
+
// Boolean 조합
|
|
282
|
+
export function filter_and(conditions) {
|
|
283
|
+
return filters.and(...conditions.toArray());
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
export function filter_or(conditions) {
|
|
287
|
+
return filters.or(...conditions.toArray());
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
export function filter_not(condition) {
|
|
291
|
+
return filters.not(condition);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// 동등 비교
|
|
295
|
+
export function filter_equals(a, b) {
|
|
296
|
+
return filters.equals(a, b);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
export function filter_not_equal(a, b) {
|
|
300
|
+
return filters.notEqual(a, b);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// 크기 비교
|
|
304
|
+
export function filter_greater_than(a, b) {
|
|
305
|
+
return filters.greaterThan(a, b);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
export function filter_greater_than_or_equal(a, b) {
|
|
309
|
+
return filters.greaterThanOrEqual(a, b);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
export function filter_less_than(a, b) {
|
|
313
|
+
return filters.lessThan(a, b);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
export function filter_less_than_or_equal(a, b) {
|
|
317
|
+
return filters.lessThanOrEqual(a, b);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// 문자열 검색
|
|
321
|
+
export function filter_contains(a, b) {
|
|
322
|
+
return filters.contains(a, b);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
export function filter_starts_with(a, b) {
|
|
326
|
+
return filters.startsWith(a, b);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
export function filter_ends_with(a, b) {
|
|
330
|
+
return filters.endsWith(a, b);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// 날짜 비교
|
|
334
|
+
export function filter_day_equals(a, b) {
|
|
335
|
+
return filters.dayEquals(a, b);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
export function filter_day_not_equal(a, b) {
|
|
339
|
+
return filters.dayNotEqual(a, b);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
export function filter_day_greater_than(a, b) {
|
|
343
|
+
return filters.dayGreaterThan(a, b);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
export function filter_day_greater_than_or_equal(a, b) {
|
|
347
|
+
return filters.dayGreaterThanOrEqual(a, b);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
export function filter_day_less_than(a, b) {
|
|
351
|
+
return filters.dayLessThan(a, b);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
export function filter_day_less_than_or_equal(a, b) {
|
|
355
|
+
return filters.dayLessThanOrEqual(a, b);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// 표현식 생성
|
|
359
|
+
export function filter_attribute(id) {
|
|
360
|
+
return filters.attribute(id);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
export function filter_association(id) {
|
|
364
|
+
return filters.association(id);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
export function filter_literal(value) {
|
|
368
|
+
return filters.literal(value);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
export function filter_empty() {
|
|
372
|
+
return filters.empty();
|
|
373
|
+
}
|
|
@@ -23,7 +23,11 @@ pub type Props
|
|
|
23
23
|
|
|
24
24
|
/// 범용 HTML 요소 생성
|
|
25
25
|
@external(javascript, "./react_ffi.mjs", "create_element")
|
|
26
|
-
pub fn el(
|
|
26
|
+
pub fn el(
|
|
27
|
+
tag: String,
|
|
28
|
+
props: Props,
|
|
29
|
+
children: List(ReactElement),
|
|
30
|
+
) -> ReactElement
|
|
27
31
|
|
|
28
32
|
/// props 없이 자식만으로 요소 생성
|
|
29
33
|
@external(javascript, "./react_ffi.mjs", "create_element_no_props")
|