기본 콘텐츠로 건너뛰기

jQuery dynamicInput으로 파일·텍스트 입력 필드 동적 추가/삭제 구현하기

jQuery dynamicInput 플러그인으로 파일·텍스트 입력필드 동적 추가/삭제 구현하기

폼에 첨부파일이나 텍스트 입력 필드를 동적으로 추가/삭제해야 할 때, 매번 HTML을 복붙하거나 인덱스를 신경 쓰는 것은 상당히 번거롭습니다.
이 글에서는 jQuery로 구현한 dynamicInput 플러그인을 이용해서 , 버튼만으로 입력 필드를 편리하게 관리하는 방법을 정리해 보겠습니다.


1. dynamicInput 플러그인 개념

$.fn.dynamicInput은 jQuery 플러그인 형태로 구현된 함수로, 다음과 같은 기능을 제공합니다.

  • 파일 입력(type="file") 또는 텍스트 입력(type="text")을 동적으로 추가/삭제
  • 최대 개수(max) 설정으로 무한 추가 방지
  • 기존 첨부파일 리스트(viewData)를 보여주고, 삭제 버튼으로 논리 삭제 처리
  • 실제 삭제 대상은 hidden 필드 값으로 서버에 전송

기본 아이디어는 다음과 같습니다.

  1. 대상 영역에 플러그인을 초기화한다. ($("#addFileZone").dynamicInput({...}))
  2. 플러그인 내부에서 추가/삭제 버튼입력 필드 그룹을 자동으로 생성한다.
  3. 버튼 클릭 시, 설정에 따라 입력 필드 DOM을 추가하거나 삭제한다.

2. 개선된 dynamicInput 플러그인 코드

아래 코드는 파일 타입만 지원하던 기존 코드에서 확장해서, 첨부파일(file)과 텍스트(text) 모두 처리할 수 있도록 보완한 버전입니다.


$.fn.dynamicInput = function(opt){
  var t = $(this);                        // 플러그인을 적용할 대상 컨테이너
  var btnBody = $(document.createElement('span'));  // 추가/삭제 버튼 영역
  var base = $(document.createElement('span'));     // 기본 input 1개가 들어가는 영역
  var dynamicPanel = $(document.createElement('div')); // 추가로 붙는 input 들이 들어가는 영역

  $(dynamicPanel).addClass("dynamicPanel");

  var setting = {};
  var classNm = "";
  var addPossibleCnt = 0;  // 더 추가할 수 있는 개수

  // 버튼 생성
  var btn = function(){
    var addBtn = $(document.createElement('input'));
    var delBtn = $(document.createElement('input'));

    $(addBtn).prop("type", "button");
    $(delBtn).prop("type", "button");
    $(addBtn).val("추가");
    $(delBtn).val("삭제");

    $(addBtn).on("click", add);
    $(delBtn).on("click", del);

    $(btnBody).append(addBtn);
    $(btnBody).append(delBtn);

    // 기본 input 1개 생성
    $(base).append(create());
  };

  // input 추가
  var add = function(){
    if (addPossibleCnt > 0) {
      var div = $(document.createElement('div'));
      $(div).addClass(classNm);
      $(dynamicPanel).append($(div).append(create()));
      addPossibleCnt--;
      console.log("등록 가능 개수 : " + addPossibleCnt);
    } else {
      alert('더 이상 추가 할 수 없습니다.');
    }
  };

  // input 삭제
  var del = function(){
    // 동적으로 생성된 input이 1개 이상일 때만 삭제
    if ($("." + classNm, dynamicPanel).length > 0) {
      $("." + classNm + ":last", dynamicPanel).remove();
      addPossibleCnt++;
      console.log("등록 가능 개수 : " + addPossibleCnt);
    }
  };

  // input 엘리먼트 생성
  var create = function(){
    var inpt = $(document.createElement('input'));

    var n = setting["fileNm"];
    var tpe = setting["type"];
    var c = setting["class"];

    $(inpt).attr("type", tpe);
    $(inpt).attr("name", n);
    if (c) {
      $(inpt).addClass(c);
    }
    return inpt;
  };

  // 초기화
  var init = function(){
    t.append(base);
    t.append(btnBody);

    setting["fileNm"] = opt.filenm;
    setting["type"]   = opt.inptType === undefined ? "text" : opt.inptType;
    setting["class"]  = opt.cls;
    setting["max"]    = opt.max === undefined ? 3 : opt.max;

    // 동적 input 그룹에 사용할 class 이름
    classNm = "dynamicInpt" + opt.filenm;
    $(base).addClass(classNm);

    btn();

    // 기존 데이터 개수를 고려해서, 실제로 더 추가할 수 있는 개수 계산
    addPossibleCnt = setting["max"] - (opt.viewData === undefined ? 0 : opt.viewData.length);

    // 추가로 들어가는 영역
    t.append(dynamicPanel);

    // 파일 타입일 경우 기존 첨부파일 목록 렌더링
    if (opt.inptType === "file") {
      viewFile();
    }

    // 이미 max만큼 존재하면 기본 input은 숨김
    if (addPossibleCnt === 0) {
      $(base).hide();
    }
  };

  // 기존 첨부파일 목록 표시 및 삭제 처리
  var viewFile = function(){
    if (opt.viewData && opt.viewData.length > 0) {
      var viewDiv = $(document.createElement('div'));
      $(viewDiv).addClass('viewDiv');

      for (var i = 0; i < opt.viewData.length; i++) {
        var data = opt.viewData[i];

        var seqNm       = data.seqNm;       // PK 필드명
        var filename    = data.filename;
        var filerealname= data.filerealname;
        var filepath    = data.filepath;

        var params = encodeURIComponent(
          "filename=" + filename +
          "&filerealname=" + filerealname +
          "&filepath=" + filepath
        );

        var seq = data[seqNm];

        var panel  = $(document.createElement('div'));
        var down   = $(document.createElement('a'));
        var link   = opt.fileDownUrl + "?" + params;
        var hidden = $(document.createElement('input'));
        var deleteBtn = $(opt.deleteBtn);   // 외부에서 넘겨주는 삭제 버튼 템플릿

        $(hidden).prop("type", "hidden");
        $(hidden).val(seq);

        $(down).html(filename);
        $(down).attr("href", link);

        $(panel).append(hidden);
        $(panel).append(down);
        $(panel).append(deleteBtn);

        // 파일 삭제 버튼 클릭 시 처리
        var deleteFn = function(){
          if (confirm("삭제하시겠습니까?")) {
            var target = $(this).parent();
            $(":hidden", target).prop("name", "deleteFile"); // 서버로 삭제 대상 전송
            $(target).hide();
            addPossibleCnt++;
            console.log("등록 가능 개수 : " + addPossibleCnt);
          }
        };

        $(deleteBtn).click(deleteFn);
        $(viewDiv).append(panel);
      }

      t.append(viewDiv);
    }
  };

  // 플러그인 실행
  init();
  return this;
};

3. 파일 업로드 필드에 적용 예시

가장 많이 사용하는 케이스는 첨부파일 업로드 영역


<div id="addFileZone"></div>

$("#addFileZone").dynamicInput({
  filenm    : "addFile",     // input name
  inptType  : "file",        // 파일 업로드
  cls       : "",            // 추가로 줄 class (필요 없으면 빈 값)
  max       : 3,             // 최대 3개까지 추가
  viewData  : [],            // 기존 첨부파일 목록 (없으면 빈 배열)
  fileDownUrl : "/file/download.do", // 파일 다운로드 URL
  deleteBtn : "<button type='button' class='btn_del'>삭제</button>"
});

위와 같이 설정하면, 기본 파일 input + 추가 버튼 + 삭제 버튼이 자동으로 생성되고, 사용자는 “추가” 버튼으로 새 첨부파일 필드를 만들 수 있습니다.
“삭제” 버튼은 마지막으로 추가한 필드를 순차적으로 제거합니다.


4. 텍스트 입력 필드 동적 추가/삭제 예시

파일 대신 텍스트 입력을 여러 개 받는 경우(예: 태그, 옵션명, URL 리스트 등)에도 동일한 플러그인을 재사용할 수 있습니다.


<div id="tagZone"></div>

$("#tagZone").dynamicInput({
  filenm    : "tagName",   // input name
  inptType  : "text",      // 텍스트 타입
  cls       : "tag-input", // CSS 클래스
  max       : 5            // 최대 5개까지 입력 가능
  // viewData, fileDownUrl, deleteBtn 은 텍스트용이라면 생략 가능
});

텍스트 타입으로 사용하면 viewFile() 부분만 동작하지 않을 뿐, 추가/삭제 로직은 그대로 재사용됩니다.


5. dynamicInput 플러그인 설계 포인트 정리

  • 플러그인 형태로 만들어 재사용성과 유지보수성 향상
  • 옵션(opt)으로 filenm, inptType, cls, max 등을 받아서 유연하게 처리
  • 기존 첨부파일(viewData)은 실제 삭제가 아니라 hidden값을 deleteFile로 바꿔 서버에서 처리
  • addPossibleCnt를 이용해서 현재 추가 가능한 개수를 실시간으로 관리

실무에서 첨부파일 업로드나 반복 입력이 필요한 폼을 만들 때, 위와 같은 jQuery 플러그인을 한 번 만들어 두면 여러 프로젝트에서 그대로 가져다 쓸 수 있는 생산성 도구가 됩니다.

필요하다면, 이후에는 다음 기능도 쉽게 확장할 수 있습니다.

  • input 옆에 설명 텍스트/라벨 추가
  • drag & drop 업로드와 연동
  • 폼 검증 라이브러리와 연계하여 필수값 검증

이 글의 코드를 복사해서 프로젝트에 맞게 커스터마이징해 보시면, 동적 입력 필드 관리가 훨씬 수월해질 것입니다. 😉

댓글

이 블로그의 인기 게시물

Java Servlet Request Parameter 완전 정복 — GET/POST 모든 파라미터 확인 & 디버깅 예제 (Request Parameter 전체보기)

Java Servlet Request Parameter 완전 정복 — GET/POST 모든 파라미터 확인 & 디버깅 예제 Java Servlet Request Parameter 완전 정복 웹 애플리케이션에서 클라이언트로부터 전달되는 Request Parameter 를 확인하는 것은 필수입니다. 이 글에서는 Java Servlet 과 JSP 에서 GET/POST 요청 파라미터를 전체 출력하고 디버깅하는 방법을 다양한 예제와 함께 소개합니다. 1. 기본 예제: getParameterNames() 사용 Enumeration<String> params = request.getParameterNames(); System.out.println("----------------------------"); while (params.hasMoreElements()){ String name = params.nextElement(); System.out.println(name + " : " + request.getParameter(name)); } System.out.println("----------------------------"); 위 코드는 요청에 포함된 모든 파라미터 이름과 값을 출력하는 기본 방법입니다. 2. HTML Form과 연동 예제 <form action="CheckParamsServlet" method="post"> 이름: <input type="text" name="username"><br> 이메일: <input type="email" name="email"><b...

PostgreSQL 달력(일별,월별)

SQL 팁: GENERATE_SERIES로 일별, 월별 날짜 목록 만들기 SQL 팁: GENERATE_SERIES 로 일별, 월별 날짜 목록 만들기 데이터베이스에서 통계 리포트를 작성하거나 비어있는 날짜 데이터를 채워야 할 때, 특정 기간의 날짜 목록이 필요할 수 있습니다. PostgreSQL과 같은 데이터베이스에서는 GENERATE_SERIES 함수를 사용하여 이 작업을 매우 간단하게 처리할 수 있습니다. 1. 🗓️ 일별 날짜 목록 생성하기 2020년 1월 1일부터 12월 31일까지의 모든 날짜를 '1 day' 간격으로 생성하는 쿼리입니다. WITH date_series AS ( SELECT DATE(GENERATE_SERIES( TO_DATE('2020-01-01', 'YYYY-MM-DD'), TO_DATE('2020-12-31', 'YYYY-MM-DD'), '1 day' )) AS DATE ) SELECT DATE FROM date_series 이 쿼리는 WITH 절(CTE)을 사용하여 date_series 라는 임시 테이블을 만들고, GENERATE_SERIES 함수로 날짜를 채웁니다. 결과 (일별 출력) 2. 📅 월별 날짜 목록 생성하기 동일한 원리로, 간격을 '1 MONTH' 로 변경하면 월별 목록을 생성할 수 있습니다. TO...

CSS로 레이어 팝업 화면 가운데 정렬하는 방법 (top·left·transform 완전 정리)

레이어 팝업 센터 정렬, 이 코드만 알면 끝 (CSS 예제 포함) 이벤트 배너나 공지사항을 띄울 때 레이어 팝업(center 정렬) 을 깔끔하게 잡는 게 생각보다 어렵습니다. 화면 크기가 변해도 가운데에 고정되고, 모바일에서도 자연스럽게 보이게 하려면 position , top , left , transform 을 정확하게 이해해야 합니다. 이 글에서는 아래 내용을 예제로 정리합니다. 레이어 팝업(center 정렬)의 기본 개념 자주 사용하는 position: absolute / fixed 정렬 방식 질문에서 주신 스타일 top: 3.25%; left: 50%; transform: translateX(-50%) 의 의미 실무에서 바로 쓰는 반응형 레이어 팝업 HTML/CSS 예제 1. 레이어 팝업(center 정렬)이란? 레이어 팝업(레이어 팝업창) 은 새 창을 띄우는 것이 아니라, 현재 페이지 위에 div 레이어를 띄워서 공지사항, 광고, 이벤트 등을 보여주는 방식을 말합니다. 검색엔진(SEO) 입장에서도 같은 페이지 안에 HTML이 존재 하기 때문에 팝업 안의 텍스트도 정상적으로 인덱싱될 수 있습니다. 즉, “레이어 팝업 센터 정렬”, “레이어 팝업 만드는 방법”과 같이 관련 키워드를 적절히 넣어주면 검색 노출에 도움이 됩니다. 2. 질문에서 주신 레이어 팝업 스타일 분석 질문에서 주신 스타일은 다음과 같습니다. <div class="layer-popup" style="width:1210px; z-index:9001; position:absolute; top:3.25%; left:50%; transform:translateX(-50%);"> 레이어 팝업 내용 <...