code-gen form-field 개발자 가이드¶
구버전 AnyBiz 모달·오프캔버스 HTML을 hera-v4
offcanvas구조로 자동 변환하는 코드 생성 도구 사용 가이드.
목차¶
1. 개요¶
_hera-code-gen의 form-field 프로파일은 구버전 AnyBiz 프로젝트의 모달/오프캔버스 HTML 파일을 입력받아, hera-v4 규격의 offcanvas HTML 파일을 자동 생성한다. 생성 후 부모 페이지에 오픈 버튼과 layout:insert fragment 삽입까지 자동으로 처리한다.
지원 모드¶
| 모드 | page-type 값 | 출력 파일 suffix | 설명 |
|---|---|---|---|
| 입력/수정 | input | (없음) | 사용자가 값을 입력하는 폼 |
| 조회 | view | -ro | 입력 컨트롤이 read-only span으로 변환됨 |
지원 레이아웃 구조¶
| 구조 | 원본 패턴 | 처리 방식 |
|---|---|---|
| TABLE_STY01 | <table class="table_sty01"> | table tr/th/td 기반 파싱 |
| DIV_MINU_FORM | .minu-form.row > .form-group | div row 기반 파싱 |
| MIXED | 두 구조 혼재 | 감지 순서에 따라 혼합 처리 |
| fluid 다단 컬럼 | .fluid.width-{N} 복수 | 너비 역산 → col-md-N col-lg-N 변환 |
지원 필드 타입¶
| 타입 | 변환 결과 |
|---|---|
text / number / tel | <input class="form-control form-control-sm"> |
select | <select class="form-select form-select-sm"> + th:each 바인딩 보존 |
textarea | <textarea class="form-control form-control-sm"> |
checkbox / radio | custom-control custom-checkbox/radio 마크업 |
group | 같은 td 안에 복수 컨트롤 (우편번호+주소 등) |
display | 입력 없는 표시 전용 텍스트 (span) |
display-table | <tbody id="..."> 기반 동적 그리드 헤더 보존 |
적용 시나리오¶
- 구버전 AnyBiz 입력 모달 → hera-v4 offcanvas 입력 폼으로 포팅
- 조회 전용 화면 별도 생성 (view 모드)
- 탭(
tab_wrapper) 구조 포함 화면 자동 처리 - 그리드
layout:insert포함 화면 (자동 보존)
2. 빠른 시작¶
5분 안에 첫 번째 offcanvas 파일을 생성하는 예시.
Step 1 — 설정 파일 열기¶
Step 2 — 대상 설정¶
code-generator:
mode: form-field
run-mode: output-module
source:
dir: "D:\\dev_home\\04.repo\\genexon\\anybiz5-erp-ssffin" # 원본 프로젝트 루트
domain-code: acm0101-m01 # 포팅할 원본 파일 기준 코드
target:
modal-type: offcanvas # offcanvas 고정 (현재 modal 미지원)
page-type: input # input | view
parent-page-id: acm0101 # 오픈 버튼을 삽입할 부모 페이지 ID
Step 3 — 프로파일 활성화 확인¶
Step 4 — 실행¶
_hera-code-gen AppRunner 실행
또는
Step 5 — 결과 확인¶
| 항목 | 위치 | 예시 |
|---|---|---|
| 생성된 offcanvas HTML | hera-webapp/src/main/resources/templates/ | acm/acm0101-o01.html |
| 부모 페이지 오픈 버튼 | 부모 페이지 .card-header.options | acm0101RegOffcanvasOpenBtn |
| 부모 페이지 insert | 부모 페이지 하단 | <th:block layout:insert="~{acm/acm0101-o01 :: offcanvas}"/> |
3. 단계별 적용 가이드¶
Step 1 — 원본 HTML 파일 위치 파악¶
도구는 아래 순서로 원본 파일을 탐색한다. 파일이 없으면 IllegalStateException으로 중단된다.
{sourceDir}/anybiz-webapp/src/main/resources/templates/{root3}/{domainCode}.html
{sourceDir}/src/main/resources/templates/{root3}/{domainCode}.html
{sourceDir}/templates/{root3}/{domainCode}.html
{sourceDir}/{root3}/{domainCode}.html
{sourceDir}/{domainCode}.html
root3은 domainCode 앞 3자.acm0101-m01→acm.
원본 파일 조건: - <div class="modal"> 또는 <div class="offcanvas">를 포함해야 한다. - 파일 인코딩은 UTF-8.
Step 2 — input 모드 생성¶
출력:
타겟 ID 규칙:
| 원본 버튼 | 생성 타겟 ID |
|---|---|
| 저장/등록/insert 버튼 있음 | acm0101RegOffcanvas |
| 수정/update 버튼 있음 | acm0101EditOffcanvas |
Step 3 — view 모드 생성¶
source:
domain-code: acm0101-m01 # 동일한 원본 파일 사용 가능
target:
page-type: view # ← 변경
parent-page-id: acm0101
출력:
view 모드 변환 규칙:
| 원본 컨트롤 | view 모드 출력 |
|---|---|
<input type="text"> | <span class="form-control-plaintext form-control-sm"> |
<select> | <span> (선택 텍스트) |
<textarea> | <span class="form-control-plaintext form-control-sm"> |
<input type="checkbox"> | 체크 여부 표시 span |
| 버튼 영역 | 닫기 버튼만 유지 |
Step 4 — 탭 구조 화면 처리¶
원본 HTML에 .tab_wrapper 클래스가 있으면 자동으로 탭 구조를 감지한다.
원본 구조 예시:
<div class="tab_wrapper">
<ul class="tab_list">
<li id="tab1">기본 정보</li>
<li id="tab2">추가 정보</li>
</ul>
<div class="content_wrapper">
<div class="tab_content">...</div>
<div class="tab_content">...</div>
</div>
</div>
생성 결과:
<div class="tx-wrap">
<div class="panel tabs-style-1">
<div class="tab-menu-heading">
<ul class="nav panel-tabs main-nav-line" role="tablist">
<li class="nav-item"><a class="nav-link active" ...>기본 정보</a></li>
<li class="nav-item"><a class="nav-link" ...>추가 정보</a></li>
</ul>
</div>
<div class="panel-body tabs-menu-body">
<div class="tab-content">
<div class="tab-pane active" ...>...</div>
<div class="tab-pane" ...>...</div>
</div>
</div>
</div>
</div>
별도 설정 없이 자동 감지된다.
Step 5 — 그리드 layout:insert 포함 화면¶
원본 HTML 안에 layout:insert가 있고 내용에 grid-wrapper가 포함된 경우, 자동으로 보존된다.
출력 결과 (form 태그 안에 주석으로 삽입):
직접 렌더링 대신 주석으로 보존하므로, 개발자가 수동으로 활성화해야 한다.
Step 6 — 부모 페이지 확인¶
도구 실행 후 parent-page-id에 지정한 HTML 파일에 아래 두 가지가 자동 삽입된다.
오픈 버튼 (.card-header.options .card-options.toolbar 안에 추가):
<button type="button" class="btn btn-action btn-sm"
id="acm0101RegOffcanvasOpenBtn"
data-bs-toggle="offcanvas"
data-bs-target="#acm0101RegOffcanvas"
aria-controls="acm0101RegOffcanvas">
{원본 제목}
</button>
layout:insert (부모 파일 하단에 추가):
이미 동일 ID의 버튼 또는 동일 insert 구문이 있으면 중복 삽입하지 않는다.
Step 7 — JS skeleton 완성¶
도구가 생성하는 JS는 함수 호출 구조만 있는 skeleton이다. 개발자가 직접 로직을 채운다.
생성된 skeleton:
document.addEventListener('DOMContentLoaded', () => {
// [ INIT ]
initAcm0101RegOffcanvas();
// [ ACTION ]
onClickSaveBtnAcm0101Btn();
});
// [ INIT ]
const initAcm0101RegOffcanvas = () => {
};
// [ ACTION ]
const onClickSaveBtnAcm0101Btn = () => {
$('#saveBtnAcm0101').on('click', evt => {
});
};
Thymeleaf 미해석 변수 처리:
원본의 th:if, 동적 option(th:each) 등은 live markup에 포함되지 않고 TODO 주석으로 보존된다.
<!-- TODO(ffv4): original th:if="${scrnAtrtMap.inpt == 'Y'}" -->
<button type="button" class="btn btn-action btn-sm" id="saveBtn">저장</button>
<!-- TODO(ffv4): original dynamic options
<option th:each="item : ${aplcDvsn}" th:text="${item.value}" th:value="${item.key}"></option>
-->
4. 핵심 패턴 & 안티패턴¶
출력 파일명 규칙¶
{root3}/{base}-{typeCode}{seq}[-ro].html
예시)
acm0101-m01 + offcanvas + input → acm/acm0101-o01.html
acm0101-m01 + offcanvas + view → acm/acm0101-o01-ro.html
acm0101-m02 + offcanvas + input → acm/acm0101-o02.html
| 변수 | 규칙 |
|---|---|
root3 | domainCode 앞 3자 (acm0101 → acm) |
base | domainCode에서 - 이전 (acm0101-m01 → acm0101) |
typeCode | offcanvas → o, modal → m |
seq | 원본 suffix 숫자 (m01 → 01) |
-ro | page-type: view 일 때만 부여 |
파일이 이미 존재하면
acm0101-o01(1).html형태로 sequence suffix를 붙인다.
타겟 ID 규칙¶
{base}{actionSuffix}{ModalType}
예시)
acm0101 + 저장 버튼 → acm0101RegOffcanvas
acm0101 + 수정 버튼 → acm0101EditOffcanvas
acm0101 + view 모드 → acm0101ViewOffcanvas
actionSuffix | 조건 |
|---|---|
Reg | 저장/등록/insert/submit 버튼 있음 |
Edit | 수정/update/modify 버튼 있음 |
View | page-type: view |
offcanvas 높이 계산¶
자동 계산이므로 별도 지정이 필요 없다. 생성 후 시각적으로 확인하고 필요 시 HTML에서 직접 수정한다.
✅ 권장 패턴¶
# ✅ input / view를 각각 별도 실행으로 두 파일 생성
# 1차 실행
target:
page-type: input
# 2차 실행
target:
page-type: view
<!-- ✅ 부모 페이지에 offcanvasScript fragment도 함께 선언 -->
<th:block layout:insert="~{acm/acm0101-o01 :: offcanvas}"/>
<th:block layout:insert="~{acm/acm0101-o01 :: offcanvasScript}"/>
❌ 안티패턴¶
<!-- ❌ 그리드 insert 주석을 그냥 두지 말고 개발자가 활성화해야 함 -->
<!-- <th:block layout:insert="~{.../grid-wrapper :: ...}"/> -->
<!-- ↑ 검토 후 주석 해제 또는 삭제 -->
// ❌ JS skeleton을 그대로 배포하지 말 것 — 반드시 로직 채우기
const initAcm0101RegOffcanvas = () => {
// TODO: 초기화 로직 작성 필요
};
5. 트러블슈팅¶
원본 HTML을 찾지 못함¶
증상
원인 및 해결
| 원인 | 확인 방법 | 해결 |
|---|---|---|
sourceDir 경로 오타 | 경로 직접 탐색 | yml에서 절대 경로 수정 |
파일명이 {domainCode}.html 형식 아님 | 파일 직접 확인 | domain-code를 실제 파일명(확장자 제외)으로 맞춤 |
root3 하위 디렉토리가 없음 | 디렉토리 구조 확인 | 원본 프로젝트 구조가 다른 경우 {sourceDir} 직접 아래에 파일 배치 |
필드가 누락됨¶
증상: 생성된 HTML에 원본 폼 필드 일부가 없음.
원인 및 해결
| 원인 | 확인 방법 | 해결 |
|---|---|---|
| hidden input으로 분류됨 | 원본 요소의 type="hidden" 또는 class="d-none" 확인 | 의도된 동작. hidden 섹션에서 확인 |
d-none 처리된 컨테이너 안에 있음 | 부모 요소 hidden 또는 d-none 확인 | 원본 HTML에서 해당 요소 이동 후 재실행 |
| 이미 처리된 동일 name/id가 있음 | spec 중복 필드 확인 | 원본에서 중복 name 속성 정리 |
탭이 인식되지 않음¶
증상: 탭 구조 원본인데 단일 카드로 생성됨.
원인 및 해결
| 원인 | 해결 |
|---|---|
탭 컨테이너 클래스가 .tab_wrapper가 아님 | 원본 HTML에서 클래스명 확인. .tab_wrapper가 맞는지 점검 |
<ul class="tab_list"> 구조가 다름 | .tab_list > li 또는 .modal_tab_list > li 형태인지 확인 |
부모 페이지 패치가 적용되지 않음¶
증상: 오픈 버튼 또는 layout:insert가 삽입되지 않음.
원인 및 해결
| 원인 | 해결 |
|---|---|
parent-page-id 설정 누락 | yml에서 parent-page-id 값 확인 |
| 부모 페이지 파일이 없거나 경로가 다름 | webappTemplatesDir 기준으로 {parentPageId}.html 파일 존재 확인 |
| 동일 버튼 ID가 이미 존재 | 의도된 동작 (중복 삽입 방지). 기존 버튼 확인 |
.card-header.options .card-options.toolbar 구조 없음 | 부모 페이지의 card 구조 확인. 해당 위치가 없으면 수동 삽입 필요 |
offcanvas 높이가 너무 낮거나 높음¶
증상: 생성된 offcanvas가 내용을 잘라내거나 과도하게 큼.
원인: 자동 계산식(row 수 × 42px + 200px)이 실제 컨텐츠와 맞지 않는 경우.
해결: 생성된 HTML에서 style 속성을 직접 수정한다.
<!-- 생성 결과 -->
<div class="offcanvas offcanvas-end offcanvas-round"
style="height:494px; max-height:calc(100vh - 215px); width:auto; margin-top:48px">
<!-- 직접 수정 -->
<div class="offcanvas offcanvas-end offcanvas-round"
style="height:600px; max-height:calc(100vh - 215px); width:680px; margin-top:48px">
프로파일이 form-field로 활성화되지 않음¶
증상: 실행해도 다른 모드로 동작하거나 아무것도 생성되지 않음.
확인:
active주석 처리 여부와 오타를 확인한다.
6. 레퍼런스 링크¶
내부 문서¶
| 문서 | 위치 |
|---|---|
| Plan 문서 | docs/01-plan/features/code-gen-form-field.plan.md |
| Design 문서 | docs/02-design/features/code-gen-form-field.design.md |
주요 소스 파일¶
| 파일 | 위치 | 역할 |
|---|---|---|
FormFieldWorkflow | _hera-code-gen/.../formfield/FormFieldWorkflow.java | 전체 실행 흐름 진입점 |
FormFieldGenerator | _hera-code-gen/.../formfield/FormFieldGenerator.java | HTML 파싱 → Spec 변환 |
FormFieldRenderer | _hera-code-gen/.../formfield/FormFieldRenderer.java | Spec → HTML 렌더링 |
FormFieldParentPagePatcher | _hera-code-gen/.../formfield/FormFieldParentPagePatcher.java | 부모 페이지 자동 patch |
FormFieldSpec | _hera-code-gen/.../formfield/FormFieldSpec.java | 중간 표현 데이터 모델 |
| 설정 파일 | _hera-code-gen/src/main/resources/application-codegen-form-field.yml | 실행 설정 |
관련 가이드¶
| 가이드 | URL |
|---|---|
| hera-v4 업로드 가이드 | https://heradocs.dandisoft.co.kr/manuals/hera-v4-업로드-가이드/ |