본문 바로가기

Stack/Lowcode

[Mendix] Datagrid2 버리고 HTML+JS 위젯으로 갈아탄 삽질기

반응형
[Mendix] Datagrid2 버리고 HTML+JS 위젯으로 갈아탄 삽질기
MENDIX TROUBLESHOOTING

[Mendix] Datagrid2 버리고 HTML+JS 위젯으로 갈아탄 삽질기

Mendix 기본 위젯의 한계를 넘기 위해 Java Action + HTML Snippet 조합으로 커스텀 목록 위젯을 만들면서 만난 7가지 에러와 해결 과정을 정리합니다.


1. 목표 — 왜 Datagrid2를 버리려 했나

점검 결과를 관리하는 페이지에서 Mendix 기본 제공 Datagrid2를 사용하고 있었다. 그런데 요구사항이 쌓일수록 기본 위젯만으로 구현하기 어려운 것들이 늘어났다.

요구사항Datagrid2로 가능?비고
행 클릭 → 커스텀 팝업 상세보기△ 제한적기본 팝업 레이아웃 자유도 부족
역할별 편집 권한 분기 (3단계)Admin / 담당자 / 일반 구분 필요
행 확장 — 요약 + 상세 컬럼 토글행 내 ▶ 클릭으로 펼치기
상태 탭 필터 (조치예정 / 지연 / 완료)MF 호출 매번 필요
조치기한 지난 행 색상 강조조건부 스타일 지원 없음
사진 필드 Ctrl+V 붙여넣기CKEditor HTML을 그대로 표시 필요

결국 Datagrid2를 걷어내고 HTML/JavaScript Snippet 위젯 2개로 전체 목록+팝업을 직접 구현하기로 했다.

2. 설계한 아키텍처

전체 데이터 흐름

[Java Action] DS_GetIssueList_JSON
└→ Mendix DB XPath 조회 → StringBuilder로 JSON 직렬화 → String 반환

[Microflow Wrapper] MF_DS_GetIssueList_JSON
└→ Call JA → Return $ReturnValue

[Browser] mx.data.action("Inspection.MF_DS_GetIssueList_JSON")
└→ JSON.parse → DOM 렌더링

[HTML Snippet #1] <style> + HTML 마크업
[HTML Snippet #2] JavaScript 로직 (태그 없이 순수 JS)
💡 Widget 배치 순서가 중요 HTML Snippet #1(마크업)이 반드시 #2(JS) 에 있어야 한다. JS에서 getElementById로 DOM을 참조하기 때문에 마크업이 먼저 렌더링되어야 한다.

권한 분기 구조

역할점검정보 탭불합리정보 탭불합리조치 탭저장 버튼
관리자✏ 편집✏ 편집✏ 편집표시
조치담당자👁 읽기전용👁 읽기전용✏ 편집표시
일반👁 읽기전용👁 읽기전용👁 읽기전용숨김

3. 마주친 문제들 한눈에 보기

#에러 / 증상원인해결
1 package org.json does not exist Mendix 런타임에 org.json 라이브러리 없음 StringBuilder 직접 JSON 구성
2 retrieveXPathQuery 시그니처 불일치 Mendix 버전별 오버로드 차이 5-arg → 4-arg 시그니처로 교체
3 SyntaxError: Unexpected token '<' HTML Snippet에 <script> 태그 포함 태그 제거, 순수 JS만 붙여넣기
4 does not exist — mx.data.action이 JA 직접 호출 불가 mx.data.action은 Microflow만 호출 가능 JA 감싸는 Microflow Wrapper 추가
5 Association 필드 값이 전부 undefined 연결 엔티티 속성명 'Name' 불일치 ('column1'이 실제명) 속성명을 Studio Pro에서 직접 확인 후 교체
6 정렬 시 Can't find attribute 에러 오타: InspectionDetectionDate → 실제명은 IssueDetectionDate 실제 Attribute명으로 수정
7 팝업 상단이 Mendix 내비바에 가려짐 모달 position:fixed; top:0이 내비바 뒤로 들어감 JS로 내비바 높이 동적 측정 → padding-top 적용

4. 문제별 상세 해결법

① org.json 라이브러리 없음

Mendix 런타임에 org.json이 없어서 JSONArray, JSONObject import가 전부 실패했다.

⚠️ 외부 라이브러리를 추가하지 말 것 Mendix 프로젝트에 JAR을 임의로 추가하면 배포 환경에서 ClassPath 충돌이 발생할 수 있다. JDK 기본 라이브러리만 사용하는 것이 안전하다.

해결: StringBuilder로 JSON을 직접 조립하고, 특수문자 처리용 헬퍼를 추가했다.

// org.json 대체: StringBuilder 직접 JSON 구성
private String escapeJson(String s) {
    if (s == null) return "";
    return s.replace("\\", "\\\\")
            .replace("\"", "\\\"")
            .replace("\n", "\\n")
            .replace("\r", "\\r");
}

private void appendStr(StringBuilder sb, String key, String val, boolean last) {
    sb.append("\"").append(key).append("\":\"")
      .append(escapeJson(val)).append("\"");
    if (!last) sb.append(",");
}

② retrieveXPathQuery 시그니처 불일치

작성한 코드가 7-arg 오버로드를 사용했는데, Mendix 10.x에는 해당 시그니처가 없다.

// ❌ 잘못된 시그니처 (Mendix 10에 없음)
Core.retrieveXPathQuery(ctx, xpath, -1, 0, new HashMap<>(), "attr", false);

// ✅ 올바른 시그니처
Map<String, String> sortMap = new HashMap<>();
sortMap.put("IssueDetectionDate", "desc");
List<IMendixObject> issues = Core.retrieveXPathQuery(ctx, xpath, -1, 0, sortMap);

③ HTML Snippet에서 <script> 태그 오류

Mendix HTML/JavaScript Snippet 위젯은 Content Type을 HTML로 설정하면 <script> 태그를 그대로 문자열로 파싱하려 해서 Syntax Error가 발생한다.

위젯Content Type<style><script>
Widget #1 (HTML)HTML✅ 포함❌ 없어야 함
Widget #2 (JS)JavaScript❌ 태그 없이 JS 코드만

④ mx.data.action이 Java Action 직접 호출 불가

mx.data.action("Inspection.DS_GetIssueList_JSON", ...)을 호출했더니 "does not exist" 에러. mx.data.actionMicroflow만 호출한다.

해결: JA를 감싸는 얇은 Microflow Wrapper 하나를 만들면 끝.

[Microflow] MF_DS_GetIssueList_JSON
Return type: String
└→ [Call Java Action] DS_GetIssueList_JSON → $ReturnValue
└→ [End Event] Return: $ReturnValue
✅ 빠른 연결 테스트 팁 JA 작성 전에 Microflow에서 하드코딩 JSON 문자열을 return해서 JS → MF 연결 자체가 동작하는지 먼저 확인하면 문제 격리가 쉽다.

⑤ Association 속성명 불일치

JA에서 연결 엔티티의 표시값을 읽을 때 "Name"으로 썼는데, 실제 엔티티 Attribute명이 "column1"이었다.

// ❌ 엔티티에 'Name' 속성이 없어서 에러
getAssocValue(ctx, obj, "...R_InspectionRegulation", "Inspection.R_InspectionRegulation", "Name");

// ✅ Studio Pro에서 실제 속성명 확인 후 적용
getAssocValue(ctx, obj, "...R_InspectionRegulation", "Inspection.R_InspectionRegulation", "column1");
⚠️ 확인 방법 Studio Pro → Domain Model → 해당 엔티티 더블클릭 → Attributes 탭에서 String 타입 속성명 확인. Mendix 기준정보(Reference) 엔티티는 대부분 column1을 쓰는 경우가 많다.

⑥ Attribute 오타 (정렬 키)

정렬 키로 "InspectionDetectionDate"를 썼는데 실제 엔티티 속성명은 "IssueDetectionDate"였다. 런타임에서 정렬 시점에 터짐.

// ❌
sortMap.put("InspectionDetectionDate", "desc");

// ✅ 실제 Attribute 물리명 확인 후
sortMap.put("IssueDetectionDate", "desc");

⑦ 팝업 모달이 Mendix 내비바에 가려짐

모달 backdrop을 position: fixed; top: 0; inset: 0으로 설정했더니 Mendix 상단 내비바(높이 약 60px)가 모달 위에 올라와 헤더가 가려졌다.

// JS에서 내비바 실제 높이를 동적으로 읽어 padding-top 적용
function getNavbarHeight() {
  const selectors = ['.mx-navbar', 'nav.navbar', 'header'];
  for (const sel of selectors) {
    const el = document.querySelector(sel);
    if (el) return Math.ceil(el.getBoundingClientRect().bottom);
  }
  return 60; // fallback
}

// 모달 열 때
document.getElementById('eil-backdrop').style.paddingTop
  = getNavbarHeight() + 'px';
💡 z-index도 같이 올릴 것 backdrop z-index를 9999 이상으로 설정해야 Mendix 내부 요소들 위에 표시된다.

5. 핵심 코드 구조

JA — Association 값 읽기 헬퍼

private String getAssocValue(IContext ctx, IMendixObject obj,
    String assocName, String targetEntity, String attrName) {
  try {
    IMendixIdentifier id = (IMendixIdentifier) obj.getValue(ctx, assocName);
    if (id == null) return "";
    IMendixObject ref = Core.retrieveId(ctx, id);
    if (ref == null) return "";
    Object val = ref.getValue(ctx, attrName);
    return val == null ? "" : val.toString();
  } catch (Exception e) {
    return "";
  }
}

JS — Microflow 호출 (mock fallback 포함)

function loadIssues() {
  if (typeof mx !== 'undefined') {
    mx.data.action({
      params: { actionname: 'Inspection.MF_DS_GetIssueList_JSON' },
      callback: function(result) {
        try {
          allData = JSON.parse(result);
          renderTable();
        } catch(e) {
          console.error('JSON 파싱 실패', e);
          loadMock(); // fallback
        }
      },
      error: function(e) {
        console.error('MF 호출 실패', e);
        loadMock();
      }
    });
  } else {
    loadMock(); // Mendix 런타임 외부 (개발 미리보기)
  }
}

JS — 권한 분기 로직

function getCurrentUserRole() {
  if (typeof mx === 'undefined') return 'admin'; // 개발 모드 → 관리자
  const user = mx.session.getUserName ? mx.session.getUserName() : '';
  if (user === 'MxAdmin') return 'admin';
  const roles = mx.session.getUserRoles ? mx.session.getUserRoles() : [];
  if (roles.some(r => r.includes('Administrator') || r.includes('Admin'))) return 'admin';
  return 'user'; // 일반 유저; 조치담당자 판단은 IssueClearPersonId 비교로
}

function openModal(row) {
  const role = getCurrentUserRole();
  const isMyTask = (row.IssueClearPersonId === currentUser);
  const canEditAll   = (role === 'admin');
  const canEditClear = (role === 'admin' || isMyTask);
  // ...탭별 readonly 속성 토글
}

JS — 사진 필드 (contenteditable — CKEditor HTML 렌더링)

// 저장된 HTML을 innerHTML로 바로 렌더링 (이미지 포함)
function setPhotoArea(fieldId, htmlContent, readonly) {
  const el = document.getElementById(fieldId);
  if (!el) return;
  el.innerHTML = htmlContent || '';
  el.contentEditable = readonly ? 'false' : 'true';
}

// 저장 시 HTML 추출
function getPhotoHtml(fieldId) {
  const el = document.getElementById(fieldId);
  return el ? el.innerHTML : '';
}

6. 교훈 — 삽질에서 배운 것

1

mx.data.action은 Microflow만 호출한다

Java Action은 직접 호출 불가. 반드시 Microflow Wrapper로 감싸야 한다. 문서에 명시가 약하다.

2

HTML Snippet에 <script> 태그 넣지 말 것

Content Type이 JavaScript인 위젯에는 태그 없이 JS 코드만 붙여야 한다. 태그가 들어가면 Syntax Error.

3

라이브러리 의존 없이 JDK만 써라

Mendix 런타임에 외부 JSON 라이브러리가 없다. StringBuilder로 직접 구성하면 의존성 문제 없이 깔끔하다.

4

Association 속성명은 반드시 Studio Pro에서 확인

관행적으로 'Name'을 쓰면 십중팔구 에러. Domain Model 열고 Attributes 탭에서 실제 String 속성명을 확인해야 한다.

5

모달과 내비바는 반드시 높이 동적 측정

고정 px로 내비바 높이를 때려 넣으면 환경마다 다르다. JS로 getBoundingClientRect().bottom을 읽어서 적용해야 정확하다.

6

mock fallback 먼저 붙이고 개발하라

Mendix 없는 환경에서도 typeof mx === 'undefined' 분기로 목업 데이터로 fallback되게 해두면 프론트 개발 속도가 훨씬 빠르다.

반응형