kccf/.claude/skills/kccf-jsp-view/SKILL.md
ythong 80b5ed1da4 Initial commit - KCCF 문화원연합회 누리집
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-15 11:38:15 +09:00

7.2 KiB

name description
kccf-jsp-view KCCF 시스템의 JSP 화면을 작성하거나 수정한다. 목록/등록/상세/팝업 화면, JSP 레이아웃, Ajax 연동 작업 시 사용한다. '화면 수정', 'JSP 추가', '목록화면', '등록화면', '팝업' 관련 요청에 적용.

KCCF JSP 화면 작성 스킬

화면 위치 규칙

WEB-INF/jsp/
  mng/{module}/           ← 관리자(인증 필요) 화면
    list{Module}.jsp      ← 목록
    {module}Regist.jsp    ← 등록/수정
    select{Module}Popup.jsp ← 선택 팝업
  kccf/{module}/          ← 공개 화면
    list{Module}Find.jsp  ← 조회/검색
    popup/                ← 공개 팝업 (주소, 지도 등)

목록 화면 템플릿

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<c:set var="contextPath" value="${pageContext.request.contextPath}"/>

<!-- 검색 영역 -->
<form id="searchForm" name="searchForm" method="post">
    <input type="hidden" name="selMenuId" value="${searchVO.selMenuId}">
    <input type="hidden" name="pageNo" value="1">
    <!-- 검색 조건 필드들 -->
    <select name="searchAreaCode">
        <option value="">전체</option>
        <c:forEach var="item" items="${areaCodeList}">
            <option value="${item.code}" <c:if test="${searchVO.searchAreaCode eq item.code}">selected</c:if>>
                <c:out value="${item.codeNm}"/>
            </option>
        </c:forEach>
    </select>
    <input type="text" name="searchNm" value="<c:out value='${searchVO.searchNm}'/>">
    <button type="button" onclick="fnSearch(1)">검색</button>
</form>

<!-- 버튼 영역 -->
<div class="btn-area">
    <c:if test="${searchVO.canWrite}">
        <button type="button" onclick="fnGoDetail('')">등록</button>
    </c:if>
</div>

<!-- 목록 테이블 -->
<table>
    <thead>
        <tr>
            <th>번호</th><th>명칭</th><th>등록일</th>
        </tr>
    </thead>
    <tbody id="listBody">
        <tr><td colspan="3">조회 중...</td></tr>
    </tbody>
</table>
<div id="pagingDiv"></div>

<script>
var contextPath = "${contextPath}";
var selMenuId = "${searchVO.selMenuId}";

$(document).ready(function() { fnSearch(1); });

function fnSearch(pageNo) {
    $("#pageNo").val(pageNo);
    $.ajax({
        url: contextPath + "/sec/{module}/select{Module}ListAjax.do",
        type: "POST",
        data: $("#searchForm").serialize(),
        dataType: "json",
        success: function(result) {
            var list = result.data;
            var sVO = result.searchVO;
            var html = "";
            if (!list || list.length === 0) {
                html = "<tr><td colspan='3'>조회된 데이터가 없습니다.</td></tr>";
            } else {
                $.each(list, function(i, item) {
                    html += "<tr>";
                    html += "<td>" + item.rn + "</td>";
                    html += "<td><a href='javascript:fnGoDetail(\"" + item.{module}Id + "\")'>" + item.{module}Nm + "</a></td>";
                    html += "<td>" + (item.regDttm || "") + "</td>";
                    html += "</tr>";
                });
            }
            $("#listBody").html(html);
            // 페이징 처리 (기존 paging 유틸 사용)
        },
        error: function() { alert("목록 조회 중 오류가 발생했습니다."); }
    });
}

function fnGoDetail(id) {
    var params = $("#searchForm").serialize();
    params += "&{module}Id=" + id;
    location.href = contextPath + "/sec/{module}/{module}DetailPage.do?" + params;
}
</script>

등록/수정 화면 템플릿

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<c:set var="contextPath" value="${pageContext.request.contextPath}"/>

<form id="dataForm" name="dataForm" method="post"
      action="${contextPath}/sec/{module}/insert{Module}.do">
    <!-- 식별자 및 검색 조건 유지 -->
    <input type="hidden" name="{module}Id"    value="<c:out value='${{module}VO.{module}Id}'/>">
    <input type="hidden" name="selMenuId"     value="${searchVO.selMenuId}">
    <input type="hidden" name="pageNo"        value="${searchVO.pageNo}">
    <input type="hidden" name="searchAreaCode" value="${searchVO.searchAreaCode}">

    <!-- 입력 필드 -->
    <table>
        <tr>
            <th>명칭 <span class="required">*</span></th>
            <td>
                <input type="text" name="{module}Nm"
                       value="<c:out value='${{module}VO.{module}Nm}'/>" required>
            </td>
        </tr>
        <tr>
            <th>지역</th>
            <td>
                <select name="areaCode">
                    <option value="">선택</option>
                    <c:forEach var="item" items="${areaCodeList}">
                        <option value="${item.code}"
                            <c:if test="${{module}VO.areaCode eq item.code}">selected</c:if>>
                            <c:out value="${item.codeNm}"/>
                        </option>
                    </c:forEach>
                </select>
            </td>
        </tr>
    </table>

    <!-- 버튼 -->
    <div class="btn-area">
        <c:if test="${searchVO.canWrite}">
            <button type="button" onclick="fnSave()">저장</button>
            <c:if test="${not empty {module}VO.{module}Id}">
                <button type="button" onclick="fnDelete()">삭제</button>
            </c:if>
        </c:if>
        <button type="button" onclick="history.back()">목록</button>
    </div>
</form>

<script>
var contextPath = "${contextPath}";

function fnSave() {
    if (!$("#dataForm [name='{module}Nm']").val().trim()) {
        alert("명칭을 입력하세요."); return;
    }
    if (confirm("저장하시겠습니까?")) {
        $("#dataForm").submit();
    }
}

function fnDelete() {
    if (confirm("삭제하시겠습니까?")) {
        $("#dataForm").attr("action", contextPath + "/sec/{module}/delete{Module}.do").submit();
    }
}
</script>

팝업 화면 패턴

<!-- 선택 팝업 (부모 창에 값 전달) -->
<script>
function fnSelect(id, nm) {
    var opener = window.opener;
    opener.document.getElementById("targetId").value = id;
    opener.document.getElementById("targetNm").value = nm;
    window.close();
}
</script>

주소 검색 팝업 호출

function fnJusoPopup() {
    var url = contextPath + "/juso/jusoPopup.do";
    window.open(url, "jusoPopup", "width=570,height=420,scrollbars=yes");
}
// 주소팝업 콜백: fn_jusoPopup(roadAddr, zipCode, jibunAddr)

주의사항

  • XSS 방지: 출력은 반드시 <c:out value="${...}"/> 또는 fn:escapeXml() 사용
  • ${} 직접 HTML 출력 금지 (스크립트 컨텍스트 제외)
  • 신규 화면 작성 전 기존 유사 화면을 먼저 Read하여 스타일 클래스 통일
  • 참조 화면: WEB-INF/jsp/mng/bsns/ 디렉토리

참조

  • 주소팝업: WEB-INF/jsp/kccf/lcltt/popup/jusoPopup.jsp
  • 지도팝업: WEB-INF/jsp/kccf/lcltt/popup/mapPopup.jsp
  • Act 선택팝업: WEB-INF/jsp/mng/act/selectActListPopup.jsp
  • 사업 선택팝업: WEB-INF/jsp/mng/act/selectBsnsPopup.jsp