2024.10.10 (Day 23) - File / Web APIs

반응형

 

server.zip
0.67MB

 

File 핸들링 제약

• 브라우저에서 실행되는 프론트 웹 애플리케이션에서 사용자 컴퓨터에 저장되있는 파일을 핸들링하는 것은 일차적으로 금지되어 있다.

유저가 브라우저에서 선택한 파일에 한해서만 자바스크립트로 읽어 들일 수 있고 서버에 업로드가 가능하다.

사용자가 선택한 파일이란 이후에 살펴볼 <input type=”file”>을 통해 유저가 선택한 파일이거나

아니면유저가 드래그&드랍으로 웹 페이지에 추가시킨 파일을 의미한다.

 

<input type=”file”>

• 사용자가 애플리케이션에 파일을 추가하기 위한 가장 기본적인 방법은 태그를 이용하는 방법이다.

 

multiple 속성

• multiple 속성이 추가되면 사용자가 파일 선택 다이얼로그에서 여러 파일을 선택할 수 있다.

▼ accept 속성

• accept 속성을 이용해 사용자가 선택할 파일의 타입을 지정할 수 있다.

 

  <div>
    <input type="file" id="fileInput1"/>
  </div>
  <div>
    <input type="file" id="fileInput2" multiple/>
  </div>
  <div>
    <input type="file" accept="image/*"/>
    <input type="file" accept=".txt, .jpg"/>
  </div>
  <ul id="results"></ul>
"use strict"

let fileNode1 = document.getElementById('fileInput1')
let fileNode2 = document.getElementById('fileInput2')
let resultNode = document.getElementById('results')

function handleFile(e){
  //기존 화면 출력 결과 지움
  while(resultNode.firstChild){
    resultNode.removeChild(resultNode.firstChild)
  }
  //유저가 선택한 파일 정보 획득
  let files = e.target.files //FileList - 여러개 선택이 가능함
  if(files.length !== 0){
    for(let file of files){//files 의 객체 갯수 만큼 반복적으로 for loop 실행
      //for loop 가 한번씩 실행되면서 files 안의 File 객체가 file 에 대입
      let item = document.createElement('li')
      item.innerHTML = `file name : ${file.name}, file size : ${file.size}, modified : ${new Date(file.lastModified)}`
      resultNode.appendChild(item)
    }
  }
}

fileNode1.addEventListener('change', handleFile)
fileNode2.addEventListener('change', handleFile)

 

File

• 파일 이용의 기본은 파일명, 파일의 저장시간 혹은 파일 사이즈 정보를 획득하는 것이다.

이를 위해 제공하는 API 가 File 과 FileList 이며

File 은 사용자가 선택한 파일 하나를 표현하며 File 객체 여러 개가 FileList 에 담겨서 이용되고

사용자가 선택한 파일 정보 획득은 대부분 에서 사용자가 파일을 선택한 순간 이루어지며

이를 위해 change 이벤트를 제공한다.

 

• 사용자가 파일을 하나 선택하든 여러 개 선택하든 모든 파일은 File 객체로 만들어지면

File 객체들이FileList 에 담겨서 전달되는데 File 객체의 name, size, lastModified 프로퍼티로 파일의 이름, 사이즈, 저장시간을 획득하여 화면에 출력시킨다.

 

▼ 파일 내용 읽기

• 파일의 내용을 읽어서 화면에 출력시켜야 한다면 FileReader API 를 사용한다.

• FileReader 는 파일 내용을 읽기 위해서 제공되는 API 로 다음의 함수를 제공한다.

  • readAsText() : 파일의 내용을 텍스트로 읽음
  • readAsDataURL() : 파일의 내용을 읽어 base64 로 인코딩해 반환
  • readAsArrayBuffer() : 파일의 내용을 배열 버퍼로 읽음
  • readAsBinaryString() : 파일의 내용을 바이너리 문자열로 읽음

• 이미지를 읽어 <img> 태그를 이용해 화면에 출력할 때는 readAsDataURL() 을 사용하며

readAsDataURL() 는 읽은 데이터를 base64로 인코딩하여 반환하는데

이 값을 그대로 <img> 태그의 src 에지정하면 이미지가 출력된다.

또한 readAsArrayBuffer() 는 버퍼 개념을 적용해 작게 나누어 읽을 때 유용하며

스트림으로 서버에 업로드 할 때 유용하다.

 

• FileReader 로 파일을 비동기로 읽기 때문에 읽은 내용은 이벤트를 등록하여 이벤트 콜백함수를 통해 획득한다.

다음은 FileReader 에서 제공하는 이벤트이다.

  • load : 파일 읽기 작업이 완료된 순간의 이벤트
  • error : 에러가 발생한 순간의 이벤트
  • abort : abort() 함수가 호출되어 읽기 작업이 취소된 순간의 이벤트
  • progress : 파일을 읽는 동안 주기적으로 발생하는 이벤트

 

<body>
  <input type="file" id="filepicker" multiple />
  <ul id="results"></ul>
  <script src="main.js"></script>
</body>
"use strict"

let fileNode = document.getElementById('filepicker')
let resultNode = document.getElementById('results')

fileNode.addEventListener('change', handleFile)

function handleFile(e){
  while(resultNode.firstChild){
    resultNode.removeChild(resultNode.firstChild)
  }

  let files = fileNode.files 
  if(files.length !== 0){
    for(let file of files){
      //파일의 내용을 읽어서 화면에 출력하고자 한다
      //텍스트 파일과 이미지 파일의 읽고 출력하는 코드가 상이해진다
      //유저가 선택한 파일의 타입에 따라 다른 코드가 실행되게 함
      if(file.type.startsWith('text')){
        handleTextFile(file)
      }else if(file.type.startsWith('image')){
        handleImageFile(file)
      }
    }
  }
}

function handleTextFile(file){
  let liNode = document.createElement('li')
  let nameNode = document.createElement('h3')
  nameNode.innerHTML = file.name 
  liNode.appendChild(nameNode)

  //유저가 선택한 파일을 읽기. File 에서 FileReader 
  let reader = new FileReader()

  reader.onload = function(e){
    //파일을 다 읽은 순간.. 
    let pNode = document.createElement('p')
    //파일의 엔터(\n) 도 데이터이다. html 에서는 태그에 의해서만 UI 효과
    //\n 을 <br>로
    //문자열.split('구분자') => 전체 문자열을 구분자로 나누어 배열로 리턴.
    //배열.join(구분자) => 배열의 데이터를 구분자로 연결해서 하나의 문자열로 리턴
    pNode.innerHTML = e.target.result.split('\n').join('<br/>')
    liNode.appendChild(pNode)
    resultNode.appendChild(liNode)
  }
  reader.onerror = function(e){
    //읽다가 에러가 발생한 경우
    //항상 IO(file, network) 은 에러 가능성
    let pNode = document.createElement('p')
    pNode.innerHTML = '파일 읽기에 실패 했습니다.'
    liNode.appendChild(pNode)
    resultNode.appendChild(liNode)
  }

  reader.readAsText(file)//읽기 시작. 내용은 이벤트 콜백함수
}

function handleImageFile(file){
  let liNode = document.createElement('li')
  let nameNode = document.createElement('h3')
  nameNode.innerHTML = file.name
  liNode.appendChild(nameNode)

  let reader = new FileReader()

  reader.onload = function(e){
    let imgNode = document.createElement('img')
    imgNode.setAttribute('src', e.target.result)
    imgNode.setAttribute('width', '100')
    liNode.appendChild(imgNode)
    resultNode.appendChild(liNode)
  }

  reader.onerror = function(e){
    //읽다가 에러가 발생한 경우
    //항상 IO(file, network) 은 에러 가능성
    let pNode = document.createElement('p')
    pNode.innerHTML = '파일 읽기에 실패 했습니다.'
    liNode.appendChild(pNode)
    resultNode.appendChild(liNode)
  }
  reader.readAsDataURL(file)//base64 로 인코딩된 문자열로 읽는다 ==>base64 문자열을
  //<img> 태그로 출력이 가능함
}

 

 

File - FormData

• 파일 업로드를 위해 여러가지 방법이 있지만 많이 사용하는 방법 중 하나가 FormData 로 파일을 담아 Ajax통신을 통해 업로드 하는 방법이있다.

 

• FormData 는 자바스크립트 API 이며 서버에 넘겨야 하는 데이터를 키-값 형식으로 표현하기 위해서 제공되는 API 이며FormData 에 다양한 데이터를 추가할 수 있으며 파일 또한 FormData 에 추가해 서버에 전송할 수 있다.

이렇게 만들어진 FormData 를 Ajax 통신을 통해 서버에 전송한다.

  <form id="form" action="#">
    <!-- file 이외에 upload 시에 일반 데이터 같이 전송  -->
     title <input type="text" name="title" id="title"/><br/>
     <input type="file" name="file" multiple id="filepicker"/><br/>
     <button id="button">업로드</button>
  </form>
"use strict"

let button = document.getElementById('button')
let fileNode = document.getElementById('filepicker')
let titleNode = document.getElementById('title')

async function upload(e) {
  //form 의 기본 이벤트 처리되지 않게 함
  e.preventDefault()

  let title = titleNode.value 
  let files = fileNode.files 

  if(files.length !== 0){
    //FormData 로 서버에 전송할 데이터를 구성
    //File 이외에 일반 데이터도 FormData 에 추가 가능
    let formData = new FormData()
    for(let file of files){
      formData.append('file', file)//'file' 추가된 데이터 식별자
    }
    formData.append('title', title)

    //file upload - post 방식
    let resp = await axios.post('http://localhost:8000/upload', formData)
    if(resp.data.status === 200){
      alert('upload ok')
    }
  }
}

button.addEventListener('click', upload)

 

File – drag & drop

• 웹 페이지에 드래그 & 드랍으로 파일을 추가해서 업로드 하는 방법을 이용한다.

• 사용자의 드래그 & 드랍을 위한 태그를 준비한 후 이곳에 드래그 & 드랍 이벤트를 등록하여 이벤트 콜백함수에서 사용자가 추가한 파일 정보를 추출해 업로드를 진행한다.

 

<body>
  <div id="drop_zone" ondrop="dropHandler(event)" ondragover="dragOverHandler(event)">
    <p>drop zone</p>
  </div>
  <script src="main.js"></script>
</body>
</html>
"use strict"

async function upload(files){
  if(files.length !== 0){
    let formData = new FormData()
    for(let file of files){
      formData.append('file', file)
    }
    let resp = await axios.post('http://localhost:8000/upload', formData)
    if(resp.data.status === 200){
      alert('upload ok')
    }
  }
}

function dropHandler(e){
  //브라우저의 기본 이벤트 처리 금지 
  e.preventDefault()
  //drop 한 파일 정보를 추출해서 upload 함수 호출.
  upload(e.dataTransfer.files)
}
function dragOverHandler(e){
  e.preventDefault()
}

 

반응형