jQuery dynamicInput 플러그인으로 파일·텍스트 입력필드 동적 추가/삭제 구현하기
폼에 첨부파일이나 텍스트 입력 필드를 동적으로 추가/삭제해야 할 때,
매번 HTML을 복붙하거나 인덱스를 신경 쓰는 것은 상당히 번거롭습니다.
이 글에서는 jQuery로 구현한 dynamicInput 플러그인을 이용해서
, 버튼만으로 입력 필드를 편리하게 관리하는 방법을 정리해 보겠습니다.
1. dynamicInput 플러그인 개념
$.fn.dynamicInput은 jQuery 플러그인 형태로 구현된 함수로, 다음과 같은 기능을 제공합니다.
- 파일 입력(
type="file") 또는 텍스트 입력(type="text")을 동적으로 추가/삭제 - 최대 개수(max) 설정으로 무한 추가 방지
- 기존 첨부파일 리스트(
viewData)를 보여주고, 삭제 버튼으로 논리 삭제 처리 - 실제 삭제 대상은
hidden필드 값으로 서버에 전송
기본 아이디어는 다음과 같습니다.
- 대상 영역에 플러그인을 초기화한다. (
$("#addFileZone").dynamicInput({...})) - 플러그인 내부에서 추가/삭제 버튼과 입력 필드 그룹을 자동으로 생성한다.
- 버튼 클릭 시, 설정에 따라 입력 필드 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 업로드와 연동
- 폼 검증 라이브러리와 연계하여 필수값 검증
이 글의 코드를 복사해서 프로젝트에 맞게 커스터마이징해 보시면, 동적 입력 필드 관리가 훨씬 수월해질 것입니다. 😉
댓글
댓글 쓰기