콘텐츠로 이동

code-gen form-field 개발자 가이드

구버전 AnyBiz 모달·오프캔버스 HTML을 hera-v4 offcanvas 구조로 자동 변환하는 코드 생성 도구 사용 가이드.


목차

  1. 개요
  2. 빠른 시작
  3. 단계별 적용 가이드
  4. 핵심 패턴 & 안티패턴
  5. 트러블슈팅
  6. 레퍼런스 링크

1. 개요

_hera-code-genform-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 — 설정 파일 열기

_hera-code-gen/src/main/resources/application-codegen-form-field.yml

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 — 프로파일 활성화 확인

# application.yml
spring:
  profiles:
    active: form-field  # ← 이 값이어야 함

Step 4 — 실행

_hera-code-gen AppRunner 실행

또는

./gradlew :_hera-code-gen:bootRun

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-m01acm.

원본 파일 조건: - <div class="modal"> 또는 <div class="offcanvas">를 포함해야 한다. - 파일 인코딩은 UTF-8.


Step 2 — input 모드 생성

source:
  domain-code: acm0101-m01
target:
  page-type: input
  parent-page-id: acm0101

출력:

hera-webapp/src/main/resources/templates/acm/acm0101-o01.html

타겟 ID 규칙:

원본 버튼 생성 타겟 ID
저장/등록/insert 버튼 있음 acm0101RegOffcanvas
수정/update 버튼 있음 acm0101EditOffcanvas

Step 3 — view 모드 생성

source:
  domain-code: acm0101-m01    # 동일한 원본 파일 사용 가능
target:
  page-type: view              # ← 변경
  parent-page-id: acm0101

출력:

hera-webapp/src/main/resources/templates/acm/acm0101-o01-ro.html  ← -ro suffix 자동 부여

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 태그 안에 주석으로 삽입):

<!-- <th:block layout:insert="~{sys/sys0101-grid :: grid-wrapper}"/> -->

직접 렌더링 대신 주석으로 보존하므로, 개발자가 수동으로 활성화해야 한다.


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 (부모 파일 하단에 추가):

<th:block layout:insert="~{acm/acm0101-o01 :: offcanvas}"/>

이미 동일 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자 (acm0101acm)
base domainCode에서 - 이전 (acm0101-m01acm0101)
typeCode offcanvas → o, modal → m
seq 원본 suffix 숫자 (m0101)
-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 높이 계산

height = (form-table tr 수 × 42px) + 200px
max-height = calc(100vh - 215px)

자동 계산이므로 별도 지정이 필요 없다. 생성 후 시각적으로 확인하고 필요 시 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}"/>
// ✅ 원본 domainCode는 원본 파일명 기준으로 그대로 입력
domain-code: acm0101-m01   // 원본 파일: .../acm/acm0101-m01.html

❌ 안티패턴

# ❌ modal-type: modal 은 현재 미지원 (예외 발생)
target:
  modal-type: modal
# ❌ 원본 파일이 없는 경로 지정 시 5개 경로 모두 탐색 후 실패
source:
  dir: "D:\\잘못된\\경로"
  domain-code: acm0101-m01
<!-- ❌ 그리드 insert 주석을 그냥 두지 말고 개발자가 활성화해야 함 -->
<!-- <th:block layout:insert="~{.../grid-wrapper :: ...}"/> -->
<!-- ↑ 검토 후 주석 해제 또는 삭제 -->
// ❌ JS skeleton을 그대로 배포하지 말 것 — 반드시 로직 채우기
const initAcm0101RegOffcanvas = () => {
    // TODO: 초기화 로직 작성 필요
};

5. 트러블슈팅

원본 HTML을 찾지 못함

증상

IllegalStateException: [FORM-FIELD] source html not found: [...]

원인 및 해결

원인 확인 방법 해결
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로 활성화되지 않음

증상: 실행해도 다른 모드로 동작하거나 아무것도 생성되지 않음.

확인:

# application.yml
spring:
  profiles:
    active: 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-업로드-가이드/