본문 바로가기

Javascript

[ Javascript ] 다중 파일 업로드 (Frontend)

 

 

 

기능정리

더보기
  • 일반 파일과 이미지파일을 따로 처리한다.
  • 일반파일은 파일명으로 표시하고 이미지파일은 썸네일로 표시한다.
  • 여러개의 파일을 업로드 한다.
  • 한번에 여러개의 파일을 선택할 수 있다.
  • 추가로 파일을 선택할 수 있다.
  • 등록했던 파일을 삭제할 수 있다.
  • 파일이름이 같은 경우 중복해서 등록할 수 없다.
  • 일괄등록. 모든 파일을 선택한 후에 서버에 전송한다.
  • ajax로 파일 업로드한다.

 

 

 

 

 

리팩터링 기준

더보기
  • depth를 3이 넘지 않도록 구현.
  • 함수(또는 메서드)가 한 가지 일만 하도록 최대한 작게 만들기. ( 길이가 15라인을 넘어가지 않게 한다. )
  • else 예약어를 쓰지 않기. ( if 조건절에서 값을 return 하도록 구현하면 else를 사용하지 않아도 된다.)
  • (참조) https://zzang9ha.tistory.com/307

 

 

 

 

 

 

HTML 

더보기

 

  •  일반 파일 업로드 버튼과 이미지파일 업로드 버튼이 2가지가 있다. 
	<div id="uploadFilesBox" class="mb-3">
		<label for="uploadFiles" class="btn">파일 여러개 업로드</label>
		<input type="file" multiple="multiple" id="uploadFiles" name="uploadFiles" style="display:none;" />
	</div>
	<br>

	<div id="uploadImagesBox" class="mb-3">
			<label for="uploadImages" class="btn" >이미지 업로드</label>
			<input  type="file" multiple="multiple" id="uploadImages" name="uploadImages" style="display:none;" >
	</div>
	<br>

	<input type="button" onclick="fileUploadFormSubmit();" value="등록" />

 

 

 

 

 

 

 

파일 업로드 버튼에 이벤트 등록

더보기

 

  •  파일 업로드 버튼 id 값과 컨테이너 태그의 id값을 인자값으로 전달한다. 
KurtFile.addEventFileTagAppend( "uploadFiles" , "uploadFilesBox" );
KurtFile.addEventFileTagAppend( "uploadImages" , "uploadImagesBox" );

 

 

 

 

 

 

File Class 로직

더보기

 

  • 파일 태그에 변경이 감지되면 실행된다.
  • 기존 등록된 파일 검증.
  • 버튼 종류에 따라 일반 파일 또는 이미지 파일 로직 실행.
  • 삭제 버튼 및 삭제 이벤트 추가.
  • ajax 업로드용 fileList 객체 관리.
    /* File 제어 공통 */

    let KurtFile = {
        uploadFiles : [] ,
        uploadImages : [] ,
    };


    KurtFile.addEventFileTagAppend = (fileTagId , fileContainerId ) => {

        const fileVo = {
            fileContainerId : fileContainerId ,
            fileTagId : fileTagId ,
            $file :  document.getElementById(fileTagId) ,
            fileName : ( fileTagId == "uploadFiles" )? "uploadFiles" : "uploadImages" ,
            fileBoxId : ( fileTagId == "uploadFiles" )? "uploadFileBox" : "uploadImageBox" ,
            fileBoxNum : 0 ,
            paramFileList :  ( fileTagId == "uploadFiles" )? KurtFile.uploadFiles : KurtFile.uploadImages ,
            isUploadFiles : fileTagId == 'uploadFiles' ,
        };



        fileVo.$file.onchange = () => {
            [...fileVo.$file.files].forEach( (file , index )=>{
                if( isSameParamFile(file) ) return;
                ( fileVo.isUploadFiles )? fileAppend(file) : imageAppend(file);
            } );
            fileVo.$file.value = "";
        };


        const isSameParamFile = (file) =>{
            const result = fileVo.paramFileList.find(paramFile =>{ return paramFile.name == file.name }) ;
            if( result != undefined ) alert(`${file.name}는 업로드된 파일이거나 파일 이름이 같습니다.`);
            return result != undefined;
        }



        /* File Append 로직  */

        const fileAppend = ( file ) => {
            const $fileBox = fileBoxTagMake();
            KurtFile.uploadFiles.push(file);
            $fileBox.append( makeFileShowTag(file) );
            const $fileDelete = deleteTag(fileVo.fileName);
            addEventRemoveFile( $fileDelete , file , $fileBox.getAttribute("id") );
            $fileBox.append( $fileDelete );
            fileBoxTagAppend( $fileBox );
        };

        const makeFileShowTag = (file) => {
            const $div = document.createElement("div");
            $div.textContent = file.name;
            return $div;
        };



        /* image Append 로직 */

        const imageAppend = ( file ) => {
            let fileReader = reader(file);
            fileReader.onload = (e) => {
                const fileReader = e.target;
                const $fileBox = fileBoxTagMake();
                KurtFile.uploadImages.push(file);
                $fileBox.append(makeImgShowTag(fileReader.result));
                const $imageDelete = deleteTag(fileVo.fileName);
                addEventRemoveFile( $imageDelete , file , $fileBox.getAttribute("id") );
                $fileBox.append($imageDelete);
                fileBoxTagAppend( $fileBox );
            }
        };

        const reader = (file) => {
            const reader = new FileReader();
            reader.readAsDataURL(file);
            return reader;
        };

        const makeImgShowTag = (fileSrc) => {
            const $img = document.createElement("img");
            $img.setAttribute("class","imageShow");
            $img.src = fileSrc;
            return $img;
        };



        /* 공통 로직 */

        const fileBoxTagMake = () => {
            const $fileBox = document.createElement("div");
            $fileBox.setAttribute("id" , fileVo.fileBoxId+fileVo.fileBoxNum++);
            $fileBox.setAttribute("class" , 'fileBox');
            return $fileBox;
        }

        const fileBoxTagAppend = ($fileBoxTag) => {
            const $fileContainer = document.getElementById(fileVo.fileContainerId);
            $fileContainer.append($fileBoxTag);
        };

        const deleteTag = ( fileName , fileBoxId ) => {
            const $fileDelete = document.createElement("img");
            $fileDelete.setAttribute("class",`${fileName}_remove_btn remove_btn` );
            return $fileDelete;
        };

        const addEventRemoveFile = ( $fileDelete , file , fileBoxId  )=>{
            $fileDelete.addEventListener('click',()=>{
                deleteParamFile(file);
                deleteFileBox(fileBoxId);
            });
        };

        const deleteParamFile = (file) => {
            fileVo.paramFileList.forEach((paramFile,index)=>{
                if( paramFile.name == file.name ) fileVo.paramFileList.splice(index , 1);
            });
        }

        const deleteFileBox = (fileBoxId) => {
            const $fileBox = document.getElementById(fileBoxId);
            $fileBox.remove();
        }

    }

 

 

 

 

 

 

 

 

파일 업로드 전송 로직

더보기

 

fileList를 ajax로 전송한다. 

    function fileUploadFormSubmit(){

        const form = new FormData();
        // form.append( "title", document.getElementById('title').value );
        // form.append( "content", document.getElementById('content').value );
        KurtFile.uploadFiles.forEach((file)=>{ form.append( "uploadFiles", file ); });
        KurtFile.uploadImages.forEach((file)=>{ form.append( "uploadImages", file ); });

        const url = new URL(window.location.href);
        const urlParams = url.searchParams;
        const page = ( urlParams.get('page') )?? 1;

        $.ajax({
            url : `/boards?page=${page}`
            , type : "POST"
            , processData : false
            , contentType : false
            , data : form
            , dataType : 'html'
            , success:function(response) {
                $('html').html(response);
            }
        });
    }

 

 

 

 

 

 

 

테스트용 전체 코드 ( .html )

더보기

 

<!DOCTYPE html>
<html>
<head>
	<style>
		.fileBox { position : relative; width:100%; display: flex; }
		.imageShow { width: 230px; }
		.remove_btn{
			background-image: url("https://w.namu.la/s/096ff2aacd1068d19f89d10f45bbf1db309d3f66b5d23c07ef113ab8a1ef72e8e1902b0b49ceb816c0b5cf894e82859704b9b059925448f42ac3c07dfcfafa1f011d5365a3e62c8da036f9d80cf76e4e8cba00f41fecf0a8a5bf82679f461b36");;
			background-size: 30px; width:30px; height:30px; cursor:pointer;
		}
		.uploadFile_remove_btn {  }
		.uploadImage_remove_btn { position: absolute; top: 0px; left : 200px; }
		.btn{
			border: 1px solid #757575; background-color: #EEEEEE;
			font-size: 12px; padding: 2px; cursor: pointer;
		}


	</style>
</head>
<body>


	<div id="uploadFilesBox" class="mb-3">
		<label for="uploadFiles" class="btn">파일 여러개 업로드</label>
		<input type="file" multiple="multiple" id="uploadFiles" name="uploadFiles" style="display:none;" />
	</div>
	<br>

	<div id="uploadImagesBox" class="mb-3">
			<label for="uploadImages" class="btn" >이미지 업로드</label>
			<input  type="file" multiple="multiple" id="uploadImages" name="uploadImages" style="display:none;" >
	</div>
	<br>

	<input type="button" onclick="fileUploadFormSubmit();" value="등록" />



	<script>
		/* File 제어 공통 */

		let KurtFile = {
			uploadFiles : [] ,
			uploadImages : [] ,
		};


		KurtFile.addEventFileTagAppend = (fileTagId , fileContainerId ) => {

			const fileVo = {
				fileContainerId : fileContainerId ,
				fileTagId : fileTagId ,
				$file :  document.getElementById(fileTagId) ,
				fileName : ( fileTagId == "uploadFiles" )? "uploadFiles" : "uploadImages" ,
				fileBoxId : ( fileTagId == "uploadFiles" )? "uploadFileBox" : "uploadImageBox" ,
				fileBoxNum : 0 ,
				paramFileList :  ( fileTagId == "uploadFiles" )? KurtFile.uploadFiles : KurtFile.uploadImages ,
				isUploadFiles : fileTagId == 'uploadFiles' ,
			};



			fileVo.$file.onchange = () => {
				[...fileVo.$file.files].forEach( (file , index )=>{
					if( isSameParamFile(file) ) return;
					( fileVo.isUploadFiles )? fileAppend(file) : imageAppend(file);
				} );
				fileVo.$file.value = "";
			};


			const isSameParamFile = (file) =>{
				const result = fileVo.paramFileList.find(paramFile =>{ return paramFile.name == file.name }) ;
				if( result != undefined ) alert(`${file.name}는 업로드된 파일이거나 파일 이름이 같습니다.`);
				return result != undefined;
			}



			/* File Append 로직  */

			const fileAppend = ( file ) => {
				const $fileBox = fileBoxTagMake();
				KurtFile.uploadFiles.push(file);
				$fileBox.append( makeFileShowTag(file) );
				const $fileDelete = deleteTag(fileVo.fileName);
				addEventRemoveFile( $fileDelete , file , $fileBox.getAttribute("id") );
				$fileBox.append( $fileDelete );
				fileBoxTagAppend( $fileBox );
			};

			const makeFileShowTag = (file) => {
				const $div = document.createElement("div");
				$div.textContent = file.name;
				return $div;
			};



			/* image Append 로직 */

			const imageAppend = ( file ) => {
				let fileReader = reader(file);
				fileReader.onload = (e) => {
					const fileReader = e.target;
					const $fileBox = fileBoxTagMake();
					KurtFile.uploadImages.push(file);
					$fileBox.append(makeImgShowTag(fileReader.result));
					const $imageDelete = deleteTag(fileVo.fileName);
					addEventRemoveFile( $imageDelete , file , $fileBox.getAttribute("id") );
					$fileBox.append($imageDelete);
					fileBoxTagAppend( $fileBox );
				}
			};

			const reader = (file) => {
				const reader = new FileReader();
				reader.readAsDataURL(file);
				return reader;
			};

			const makeImgShowTag = (fileSrc) => {
				const $img = document.createElement("img");
				$img.setAttribute("class","imageShow");
				$img.src = fileSrc;
				return $img;
			};



			/* 공통 로직 */

			const fileBoxTagMake = () => {
				const $fileBox = document.createElement("div");
				$fileBox.setAttribute("id" , fileVo.fileBoxId+fileVo.fileBoxNum++);
				$fileBox.setAttribute("class" , 'fileBox');
				return $fileBox;
			}

			const fileBoxTagAppend = ($fileBoxTag) => {
				const $fileContainer = document.getElementById(fileVo.fileContainerId);
				$fileContainer.append($fileBoxTag);
			};

			const deleteTag = ( fileName , fileBoxId ) => {
				const $fileDelete = document.createElement("img");
				$fileDelete.setAttribute("class",`${fileName}_remove_btn remove_btn` );
				return $fileDelete;
			};

			const addEventRemoveFile = ( $fileDelete , file , fileBoxId  )=>{
				$fileDelete.addEventListener('click',()=>{
					deleteParamFile(file);
					deleteFileBox(fileBoxId);
				});
			};

			const deleteParamFile = (file) => {
				fileVo.paramFileList.forEach((paramFile,index)=>{
					if( paramFile.name == file.name ) fileVo.paramFileList.splice(index , 1);
				});
			}

			const deleteFileBox = (fileBoxId) => {
				const $fileBox = document.getElementById(fileBoxId);
				$fileBox.remove();
			}

		}







		function fileUploadFormSubmit(){

			const form = new FormData();
			form.append( "title", document.getElementById('title').value );
			form.append( "content", document.getElementById('content').value );
			KurtFile.uploadFiles.forEach((file)=>{ form.append( "uploadFiles", file ); });
			KurtFile.uploadImages.forEach((file)=>{ form.append( "uploadImages", file ); });

			const url = new URL(window.location.href);
			const urlParams = url.searchParams;
			const page = ( urlParams.get('page') )?? 1;

			$.ajax({
				url : `/boards?page=${page}`
				, type : "POST"
				, processData : false
				, contentType : false
				, data : form
				, dataType : 'html'
				, success:function(response) {
					$('html').html(response);
				}
			});
		}





		KurtFile.addEventFileTagAppend( "uploadFiles" , "uploadFilesBox" );
		KurtFile.addEventFileTagAppend( "uploadImages" , "uploadImagesBox" );


	</script>
	
</body>
</html>

 

 

 

 

 

 

 

 

 

 

 

 

반응형

'Javascript' 카테고리의 다른 글

[ Javascript ] getElement와 querySelector의 차이. (HTLMCollection과 NodeList의 차이)  (0) 2022.07.11
[ Sweetalert ]  (0) 2021.11.20
[ Javascript ]  (0) 2021.11.16