컴포넌트/필드 컴포넌트

FileDropUpload (파일드롭 업로드)

VanillaFront 2026. 9. 14. 17:59

Va.FileDropUpload — 드래그앤드롭 파일 업로드 영역

드래그앤드롭이 주된 UI인 파일 업로드 컴포넌트. 사용자가 파일을 창 안에 끌어다 놓으면 자동으로 읽어 이벤트를 dispatch합니다. 파일 내용을 텍스트로 읽을지 DataURL(base64)로 읽을지 옵션으로 선택 가능.

  • 클래스: Va.FileDropUpload  va_component.js:12504
  • short name: fileDropUpload
  • 상속: Va.Component (PureField 아님!)
  • isContainer: true
  • 베이스 CSS: va-file-upload-div

1. 기본 사용

{
    tagName: 'fileDropUpload',
    text: '파일을 여기 놓으세요',
    fileType: 'binary',           // 'text' / 'binary'
    style: {
        width: '100%',
        height: '200px',
        border: '2px dashed gray',
        display: 'flex',
        alignItems: 'center',
        justifyContent: 'center'
    },
    onFiledrop: 'onFileDropped'
}

렌더 결과:

  • 안내 텍스트가 표시된 드롭 영역
  • 파일을 드래그하면 빨간 outline 표시
  • 드롭 시 파일 자동 읽기 → 이벤트 dispatch

2. File / ImageFile / FileDropUpload의 차이

항목Va.FileVa.ImageFileVa.FileDropUpload

UI 형태 표시 + 두 버튼 이미지 미리보기 + 필드 드롭 영역만
파일 선택 다이얼로그 ✓ (File 버튼) ✕ (드래그만)
드래그앤드롭 ✓ (핵심)
파일 내용 자동 읽기 ✕ (수동) ✓ (이미지만) ✓ (fileType 옵션)
미리보기 ✓ (이미지) ✕ (개발자가 처리)
파일 목록 UI ✓ (파일명) ✕ (자유 커스텀)
상속 PureField PureField Component

한 줄 요약: "드래그앤드롭 전용 파일 업로드 영역 — 커스텀 UI 자유도 높음."


3. 주요 속성

FileDropUpload 전용

속성기본값설명

fileType 'binary' 파일 읽기 방식 — 'text' / 'binary'
text 드롭 영역에 표시할 안내 텍스트
innerHTML HTML 안내 콘텐츠
disabled / readonly 상태

fileType 두 가지

값읽기 방식반환 형태언제

'text' FileReader.readAsText() 문자열 CSV, JSON, TXT 등 텍스트 파일
'binary' (기본) FileReader.readAsDataURL() DataURL (base64) 이미지, PDF, 바이너리

⚠️ 이름은 'binary'지만 실제로는 DataURL 형식 (base64 인코딩). 진짜 ArrayBuffer 원시 바이너리가 필요하면 별도 처리.

⚠️ fileType이 없거나 인식 안 되는 값이면 Va.Alert로 "fileType 설정이 없습니다" 표시 (va_component.js:12626-12630) — 명시 지정 권장.


4. 이벤트

이벤트시그니처발생 시점

filedrop (component, element, result, evt) 파일 드롭 후 읽기 완료. result가 텍스트 또는 DataURL
change (component, element, files, evt) 드롭과 동시에 dispatch. files는 FileList

⚠️ 두 이벤트가 함께 발생 — filedrop은 파일 내용, change는 파일 목록. 필요에 따라 선택.

filedrop 콜백 예시

onFileDropped(comp, el, result, evt) {
    // fileType: 'binary'면 result는 DataURL
    // 이미지로 표시하고 싶다면:
    document.getElementById('preview').src = result;

    // 또는 서버 전송용 FormData 생성
    const files = comp.fieldElement.files;
    const formData = new FormData();
    for (let i = 0; i < files.length; i++) {
        formData.append('files', files[i]);
    }
    UploadService.send(this, formData, ...);
}

change 콜백 예시 (파일 정보만 필요할 때)

onChange(comp, el, files, evt) {
    for (let i = 0; i < files.length; i++) {
        console.log(files[i].name, files[i].size);
    }
}

5. 메서드

상태

메서드설명

setDisabled(bool) / setReadonly(bool) 상태

표시

메서드설명

setText(text) 안내 텍스트 변경
setInnerHTML(html) HTML 안내 콘텐츠 변경

주목: 별도의 파일 조회 메서드가 없음. component.fieldElement.files에 직접 접근하거나 change 콜백에서 받아 사용.


6. 내부 구조

<div elname="element" class="va-file-upload-div" tabindex="0">
  <div elname="inner" class="file-upload-div-inner"
       style="width:100%; height:100%">
    <span elname="text">파일을 여기 놓으세요</span>
    <input elname="field" type="file" style="display:none">
  </div>
</div>

핵심 트릭:

  • <input type="file">은 숨겨져 있음 — 파일 선택 다이얼로그가 아니라 드롭 이벤트만 받음
  • 드롭 시 fieldElement.files에 직접 세팅 — 브라우저의 드롭 API로 받은 FileList를 input에 할당
  • 드래그 시 빨간 outline — 시각적 피드백

7. 드래그앤드롭 시각 피드백

내부적으로 세 이벤트를 처리:

innerElement.ondragover  = (e) => e.preventDefault();          // 드롭 허용
innerElement.ondragenter = () => outline = 'solid red 1px';   // 진입 시 빨간 테두리
innerElement.ondragleave = () => outline = '';                 // 벗어나면 제거
innerElement.ondrop      = async (e) => { /* 파일 처리 */ };

빨간 outline이 하드코딩되어 있음 — 테마 색상으로 바꾸려면 CSS 커스터마이즈 필요.


8. 브라우저 API 활용

파일 처리에 세 가지 API를 순서대로 시도:

  1. File System Access API (getAsFileSystemHandle) — 최신 API, Chromium 계열
  2. WebKit Entry API (webkitGetAsEntry) — 폴백
  3. 표준 File API (getAsFile) — 마지막 폴백

각 방식 모두 파일과 디렉토리를 구분하며, 디렉토리는 무시하고 파일만 처리합니다.


9. 언제 쓰나

FileDropUpload가 맞을 때

  • 드래그앤드롭 UI가 주된 방식 — 대량 업로드 화면, 파일 관리 도구
  • CSV/JSON 파일 파싱 (fileType: 'text')
  • 이미지·문서 미리보기 (fileType: 'binary' → DataURL)
  • 커스텀 UI가 필요한 업로드 영역 (자유도 높음)

다른 걸 쓸 때

  • 표준 파일 선택 다이얼로그 → Va.File / Va.FileButton
  • 이미지 미리보기 통합 → Va.ImageFile
  • 라벨 붙은 폼 필드 → Va.FileDropUploadField

10. 흔한 조합 예시

// 표준 (바이너리, DataURL 반환)
{
    tagName: 'fileDropUpload',
    text: '파일을 여기 놓으세요',
    fileType: 'binary',
    style: {
        width: '100%',
        height: '200px',
        border: '2px dashed var(--colorNeutralStroke)',
        borderRadius: '8px',
        display: 'flex',
        alignItems: 'center',
        justifyContent: 'center',
        cursor: 'pointer'
    },
    onFiledrop: 'onDrop'
}

// CSV 파싱용 (텍스트 읽기)
{
    tagName: 'fileDropUpload',
    text: 'CSV 파일을 놓으세요',
    fileType: 'text',
    onFiledrop: 'onCsvDrop'
}

// 아이콘 포함
{
    tagName: 'fileDropUpload',
    innerHTML: '<div style="text-align:center"><i class="ico_cloud_upload" style="font-size:48px"></i><br>파일을 드래그하거나 클릭하세요</div>',
    fileType: 'binary'
}

// 여러 파일 동시 드롭 처리
{
    tagName: 'fileDropUpload',
    text: '이미지 여러 개를 드래그',
    fileType: 'binary',
    onChange: 'onMultiFiles'
}

11. 실전 예 — CSV 파일 파싱

class CsvImport extends Va.View {
    onCsvDrop(comp, el, result, evt) {
        // result는 파일의 전체 텍스트 내용
        const lines = result.split('\n').filter(line => line.trim());
        const headers = lines[0].split(',');

        const data = [];
        for (let i = 1; i < lines.length; i++) {
            const values = lines[i].split(',');
            const row = {};
            headers.forEach((h, idx) => {
                row[h.trim()] = values[idx]?.trim() || '';
            });
            data.push(row);
        }

        // 그리드에 반영
        this.getRef('grid').setData(data);

        new Va.Alert({
            title: '완료',
            message: `${data.length}건 로드됨`
        }).show(this);
    }

    config() {
        return {
            tagName: 'page',
            tags: [{
                tagName: 'panel',
                tags: [
                    { tagName: 'h2', innerHTML: 'CSV 가져오기' },
                    {
                        tagName: 'fileDropUpload',
                        text: 'CSV 파일을 드래그하세요',
                        fileType: 'text',
                        style: {
                            width: '100%',
                            height: '150px',
                            border: '2px dashed gray',
                            borderRadius: '8px',
                            display: 'flex',
                            alignItems: 'center',
                            justifyContent: 'center',
                            marginBottom: '20px'
                        },
                        onFiledrop: 'onCsvDrop'
                    },
                    {
                        tagName: 'grid',
                        ref: 'grid',
                        columns: [/* 동적 */]
                    }
                ]
            }]
        };
    }
}

12. 실전 예 — 이미지 여러 개 업로드

class MultiImageUpload extends Va.View {
    onMultiFiles(comp, el, files, evt) {
        // files는 FileList — 여러 파일 처리
        const gallery = this.getRef('gallery');
        gallery.innerHTML = '';

        for (let i = 0; i < files.length; i++) {
            const file = files[i];

            // 이미지만
            if (!file.type.startsWith('image/')) continue;

            // 각 이미지 미리보기 생성
            const reader = new FileReader();
            reader.onload = (e) => {
                const img = document.createElement('img');
                img.src = e.target.result;
                img.style.width = '150px';
                img.style.margin = '4px';
                gallery.appendChild(img);
            };
            reader.readAsDataURL(file);
        }

        // 서버 전송
        this.uploadFiles(files);
    }

    uploadFiles(files) {
        const formData = new FormData();
        for (let i = 0; i < files.length; i++) {
            formData.append('images', files[i]);
        }
        this.showMasking();
        UploadService.send(this, formData, (view, ok) => {
            view.hideMasking();
        });
    }

    config() {
        return {
            tagName: 'page',
            tags: [{
                tagName: 'panel',
                tags: [
                    {
                        tagName: 'fileDropUpload',
                        text: '이미지 여러 개를 드래그하세요',
                        fileType: 'binary',
                        style: {
                            width: '100%',
                            height: '150px',
                            border: '2px dashed gray',
                            display: 'flex',
                            alignItems: 'center',
                            justifyContent: 'center'
                        },
                        onChange: 'onMultiFiles'      // change 이벤트로 전체 files 받기
                    },
                    {
                        tagName: 'div',
                        ref: 'gallery',
                        style: { display: 'flex', flexWrap: 'wrap', marginTop: '20px' }
                    }
                ]
            }]
        };
    }
}

포인트:

  • 여러 파일: files 배열을 for-loop로 처리
  • change 이벤트로 전체 목록 받음 (filedrop은 파일 하나씩 반복 호출)
  • 각 파일 미리보기는 FileReader로 개별 처리

13. 알아두면 좋을 주의사항

  1. fileType 반드시 지정 — 미지정 시 Va.Alert로 경고. 'text' / 'binary' 중 선택.
  2. 'binary'는 실제로 DataURL — 진짜 ArrayBuffer 필요하면 별도 FileReader.readAsArrayBuffer 처리.
  3. filedrop은 파일마다 반복 — 여러 파일 드롭 시 각 파일에 대해 dispatch. 전체 목록은 change로.
  4. 디렉토리는 자동 무시 — 폴더 드롭해도 파일만 처리.
  5. 파일 다이얼로그 없음 — 클릭으로 파일 선택 창이 안 열림. 필요하면 component.fieldElement.click() 수동 호출로 UI 추가.
  6. 빨간 outline 하드코딩 — 시각 피드백 색상 변경은 CSS 커스터마이즈.
  7. 레이아웃/스타일 개발자 책임 — 기본 스타일 최소. height, border 등 스타일을 직접 지정해야 UI가 보임.
  8. getFiles() 편의 메서드 없음  component.fieldElement.files 직접 접근.
  9. text/innerHTML 세팅 시 element에 직접 반영  this.element.textContent = text로. inner에 반영되는 게 아니라 element 전체가 텍스트로 대체됨 (다른 컴포넌트와 다른 동작). ⚠️ 이 때문에 innerElement가 덮여 파일 input이 사라질 수 있는 버그 위험. text/innerHTML 사용 시 주의.
  10. 접근성 취약  role="button"/aria-label 자동 세팅 없음. 스크린리더 대응 약함.
  11. 모바일 지원 제한 — 드래그앤드롭은 데스크톱 위주 UX.
  12. disabled 시 파일 input에 disable (오타)로 세팅  va_component.js:12591 disable(l 없음)로 표기. HTML 표준 disabled와 다름. 실질 disable 동작 확인 필요.

14. fileDropUpload vs imageFile vs file 선택

상황추천

드래그앤드롭 주 UI fileDropUpload
CSV/JSON 파싱 fileDropUpload + fileType: 'text'
이미지 미리보기 통합 imageFile
표준 파일 첨부 file / fileField
간단 첨부 버튼 fileButton
여러 파일 대량 관리 UI fileDropUpload + 커스텀 목록

"드래그앤드롭이 UI의 핵심이면 fileDropUpload" — 명확한 사용처.


참고

'컴포넌트 > 필드 컴포넌트' 카테고리의 다른 글

Display (디스플레이)  (0) 2026.09.14
FileDropUploadField (파일드롭 업로드 필드)  (0) 2026.09.14
ImageFile (이미지파일)  (1) 2026.09.14
FileField (파일필드)  (1) 2026.09.14
File (파일)  (0) 2026.09.13