- Published on
메타클래스 속성 편집을 모달에서 인라인 폼으로 옮긴 UX 회고
- Authors

- Name
- Hyo814
메타클래스 속성 편집을 모달에서 인라인 폼으로 옮긴 UX 회고
메타클래스 속성을 추가하거나 수정할 때마다 모달이 떴습니다. 작업을 끝내고 닫으면 트리에서 다음 노드를 찾고, 다시 모달을 열고… 같은 동선을 매번 반복하면서 "이건 모달이 아니라 인라인이어야 하는 작업" 이라는 확신이 들어 정리하게 된 이야기입니다.
1. 증상 — 모달이 모달의 본분과 어긋나 있었다
기존 메타클래스 관리 화면 구조:
[좌측] 메타클래스 트리
[우측] 선택된 메타클래스의 속성 리스트 + [속성 추가] 버튼
↓ [속성 추가] 클릭 시
[중앙] 모달 (트리/리스트 화면 위에 떠서 가림)
- 속성명, 키, 데이터 타입, 설명 입력
- 저장/취소 버튼
모달이 떠 있는 동안 트리도 가려지고, 좀 전에 봤던 다른 속성들도 가려졌어요. 사용자 입장에선 "방금 저 위에 있던 게 뭐였지?" 를 자주 떠올려야 했습니다.
게다가 동선이 반복적이었어요:
트리 노드 클릭 → 우측 속성 리스트 확인 → [추가] 클릭 → 모달 열림
→ 입력 → 저장 → 모달 닫힘 → 트리 다음 노드 클릭 → 또 모달 열기...
한 메타클래스에 속성이 5~10개씩 있다 보니 모달을 매번 여는 작업이 부담스러웠습니다.
2. 모달이 적절한가, 인라인이 적절한가
UX에서 모달과 인라인의 선택 기준을 다시 정리해보면:
| 적절한 경우 | 모달 | 인라인 |
|---|---|---|
| 이 작업이 끝나야만 다음으로 갈 수 있다 | ⭕ | |
| 컨텍스트(주변 정보)가 필요 없는 일회성 입력 | ⭕ | |
| 주변 정보를 보면서 입력하는 작업 | ⭕ | |
| 빈번하게 반복되는 입력/편집 | ⭕ | |
| 트리/리스트의 다른 항목과 비교하면서 작업 | ⭕ |
메타클래스 속성 편집은 후자 셋에 다 해당했습니다. 트리에서 형제 노드의 속성을 참고하면서, 같은 속성을 다른 클래스에도 적용할지 보면서 입력해야 하는 작업이에요.
같은 화면(분류체계 관리)에는 이미 인라인 패턴이 적용되어 있어서, 일관성 측면에서도 인라인 쪽이 맞았습니다.
3. 변경 — 우측 패널을 둘로 분할
[좌측] 메타클래스 트리
[우측 상단] 선택된 메타클래스의 속성 리스트
[우측 하단] 인라인 폼 영역 (속성 추가/편집)
- 리스트에서 [편집] 클릭 → 폼에 채워짐
- [+ 추가] 클릭 → 빈 폼 열림
- 저장 시 폼은 그대로 두고 리스트만 갱신
폼이 항상 화면에 있으니까, 사용자는 모달을 여닫는 비용 없이 연속 작업을 할 수 있게 됐습니다. 다른 속성을 보면서 입력할 수 있다는 점이 가장 컸어요.
4. 구현 측면에서의 변화
4.1 모달 마크업 제거, 인라인 마크업 추가
기존 모달은 별도 partial로 분리되어 있었고, JS로 띄우는 구조였어요. 모달 마크업과 띄우기 로직을 다 들어내고, 대신 우측 패널에 항상 보이는 폼 마크업을 추가했습니다.
4.2 JS 재작성 — 약 900줄 규모
metaClassTree.js의 모달 띄우기/닫기 로직과 폼 상태 관리 로직 전부를 새로 짰습니다. 줄 수로는 비슷하지만 구조가 완전히 달라졌어요.
// (요약) 변경 전: 모달 띄우기
function openModal(metaClassId, propertyId = null) {
const modal = $('#property-modal');
if (propertyId) {
// 편집 모드: 서버에서 가져와 폼 채우기
fetch(`/api/properties/${propertyId}/`).then(/*...*/);
} else {
// 추가 모드: 폼 초기화
}
modal.show();
}
// (요약) 변경 후: 인라인 폼 상태
function loadIntoForm(propertyId = null) {
const $form = $('#property-form');
if (propertyId) {
fetch(`/api/properties/${propertyId}/`).then(data => fillForm($form, data));
} else {
clearForm($form);
}
// 폼은 닫힐 일 없음. 항상 그 자리에 있음.
}
핵심은 "폼은 열고 닫는 게 아니라 항상 거기 있다" 가 된 점입니다. 상태 전이가 줄어들면서 코드가 단순해졌어요.
4.3 RDF 키 셀렉트 옵션 — JSON mock으로 시작
속성에 붙는 RDF 키 셀렉트박스가 새로 생겼는데, 백엔드 모델이 아직 없는 상태였습니다. 그래서 static/data/meta_class_modal_mock.json에 옵션을 두고 프론트에서 fetch해서 채우는 식으로 mockup을 먼저 적용했어요.
[
{ "ns": "rdf", "key": "type", "label": "rdf:type" },
{ "ns": "rdfs", "key": "label", "label": "rdfs:label" },
{ "ns": "rdfs", "key": "comment", "label": "rdfs:comment" },
{ "ns": "dc", "key": "title", "label": "dc:title" }
]
mockup이라는 사실을 화면에 명시하려고 옵션 옆에 "목업" 배지를 붙였습니다. "이건 아직 백엔드와 연결 안 되어 있다" 를 디자인이 자명하게 알려주도록.
5. 부수 결정 — 별도 추가/수정 페이지는 그대로 두기
리스트 페이지만 인라인 폼으로 바꿨고, /add, /<pk>/edit 같은 별도 페이지는 그대로 두었습니다. 이유:
- 그 페이지들은 별도 진입 경로(예: 외부 링크에서 직접 진입)에서 쓰이고 있어, 갑자기 없애면 깨질 가능성
- 리스트에서의 빈번한 편집이 모달의 본질적 문제였고, 별도 페이지의 단발 편집은 모달 문제가 없음
리팩토링은 "바꿔야 할 곳"과 "굳이 안 바꿔도 되는 곳"을 가르는 것이 중요한데, 이 케이스에서는 그 경계가 명확했습니다.
6. 결과와 회고
- 연속 편집의 마찰이 사라짐. 모달 여닫는 클릭 두 번이 없어진 것만으로도 체감 차이가 큼
- 컨텍스트를 보면서 입력하게 됨. 같은 트리의 형제 속성을 참고하면서 작성 가능
- 분류체계 화면과 패턴이 일관됨. 사용자가 한 번 학습한 동선이 다른 화면에도 적용됨
작업하면서 든 생각:
모달은 "방해해도 되는 경우"에만 쓰자. 사용자의 시선을 강제로 새 곳에 옮기는 도구라는 점에서, 빈번한 작업에는 부적합하다.
이번 작업의 시작은 "그냥 좀 불편하다"는 인상이었습니다. 그게 누적된 동선 비용이라는 걸 분석하고, 인라인 패턴이 더 맞는다는 걸 확신한 다음에 손댔어요. 모든 모달이 인라인으로 가야 하는 건 아니지만, "이 작업에 모달이 정말 맞는가?" 를 한 번 더 물어보는 습관은 들일 만합니다.