컴포넌트/필드 컴포넌트

FileDropUploadField (파일드롭 업로드 필드)

VanillaFront 2026. 9. 14. 18:03

Va.FileDropUploadField — 라벨 + 드래그앤드롭 파일 업로드

Va.FileDropUpload가 순수 드롭 영역이라면, Va.FileDropUploadField는 그 위에 폼 라벨·필수 표시·검증 메시지를 얹은 완성 폼 필드입니다. Field 계열 아키텍처 그대로, 내부에 Va.FileDropUpload를 소유하는 Composition 구조. 폼 안에서 드래그앤드롭 업로드가 필요할 때 정석.

  • 클래스: Va.FileDropUploadField  va_component.js:12653
  • short name: fileDropUploadField
  • 상속: Va.Field (다른 Field 형제들과 같음)
  • 내부 컴포넌트: Va.FileDropUpload 인스턴스 (fieldComponent)
  • isContainer: true
  • 베이스 CSS: va-field

1. 기본 사용

{
    tagName: 'fileDropUploadField',
    label: '첨부파일',
    text: '파일을 여기 놓으세요',
    fileDropUpload: {                 // ← 내부 세부 옵션
        fileType: 'binary'
    },
    required: true,
    onFiledrop: 'onFileDropped'
}

라벨 + 검증 + 드롭 영역이 한 번에 세팅.


2. FileDropUpload / 다른 Field와의 차이

항목Va.FileDropUploadVa.FileDropUploadFieldVa.FileField

폼 라벨
검증 메시지
필수 표시(별표)
info 툴팁
UI 형태 드롭 영역 드롭 영역 (내부 위임) 파일 표시 + 두 버튼
드래그앤드롭
fileType 옵션 ✓ (fileDropUpload 통해)
파일 다이얼로그 지원

한 줄 요약: "폼 안 라벨 붙은 드래그앤드롭 업로드 필드."


3. 주요 속성

FileDropUpload 전용 (계승)

속성기본값설명

text 드롭 영역 안내 텍스트
innerHTML HTML 안내 콘텐츠

⚠️ fileType은 직접 옵션에 없음 — 내부 FileDropUpload 옵션이라 fileDropUpload 옵션 객체로 전달해야 합니다:

{
    tagName: 'fileDropUploadField',
    label: 'CSV 업로드',
    fileDropUpload: {
        fileType: 'text'          // 여기서 지정
    }
}

fileType 없으면 내부 FileDropUpload의 기본값 'binary'가 적용.

라벨 관련 (Field 상속)

속성설명

label 폼 라벨
labelPosition top / bottom / left / right
labelWidth 라벨 폭
noLabel 폼 라벨 숨김
infoButton info 아이콘
required 필수 표시

검증

속성설명

validation {state, size, message}
validationState success / warning / error
validationMessage 메시지

세부 커스터마이즈 (fileDropUpload 옵션 키)

{
    tagName: 'fileDropUploadField',
    label: '이미지',
    fileDropUpload: {                    // ← 내부 FileDropUpload에 직접 전달
        fileType: 'binary',
        text: '이미지를 드래그하세요'
    }
}

각 Field 계열 옵션 키:

  • FileField → file
  • ImageFileField → (있다면) imageFile
  • FileDropUploadField → fileDropUpload

4. 스타일 관련 특이사항

생성자에서 흥미로운 코드가 있습니다:

let fieldOption = {...option};
fieldOption.style = null;     // style 제거
fieldOption.layout = null;    // layout 제거

⚠️ fieldOption이 실제로는 활용 안 되는 로직 — 이 후 optionField를 별도로 만들어 사용. 데드 코드일 가능성. 다만 style/layout이 내부 컴포넌트로 전파되지 않도록 하려던 의도로 보입니다.

함의: style/layout 옵션은 Field 껍데기에만 적용되고, 안의 드롭 영역에는 별도 스타일을 fieldDropUpload 옵션 통해 전달해야 할 수도 있음. 실제 렌더링 결과 확인 후 조정 필요.


5. 이벤트

FileDropUpload의 이벤트를 재발화:

이벤트시그니처발생 시점

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

filedrop 콜백 예시

onFileDropped(field, el, result, evt) {
    // result는 fileType에 따라 텍스트 또는 DataURL
    const files = field.fieldComponent.fieldElement.files;
    console.log('드롭된 파일:', files);

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

주의: Field 표준 이벤트(focus/blur/click 등)는 재발화 코드에 없음. 필요하면 component.fieldComponent에 직접 리스너.


6. 메서드

상태 (Field 상속)

메서드설명

setDisabled(bool) / getDisabled() 비활성화
setReadOnly(bool) / setReadonly(bool) 읽기 전용
setLabel(label) 폼 라벨 변경
setSize(size) 크기

검증

메서드설명

setValidation(state, message) 검증 표시
clearValidation() 검증 해제

주의: 파일 조회 편의 메서드가 별도로 없음. getFiles() 같은 메서드도 명시적으로 없어, 파일은 component.fieldComponent.fieldElement.files로 접근.


7. 내부 구조

<div elname="element" class="va-field [vertical|horizontal]" field="true">
  <div elname="inner" class="field-inner">
    <div elname="labelDiv" class="label-div">
      <label cpname="label">첨부파일 <span class="required">*</span></label>
    </div>
    <div elname="comment" class="field-comment"></div>
    <div elname="fieldDiv" class="field-div">
      <div cpname="field" class="va-file-upload-div"        ← 내부 Va.FileDropUpload
           tabindex="0">
        <div class="file-upload-div-inner"
             style="width:100%; height:100%">
          <span>파일을 여기 놓으세요</span>
          <input type="file" style="display:none">
        </div>
      </div>
    </div>
  </div>
  <div elname="validationDiv" style="display:none">
    <div class="va-validation">...</div>
  </div>
</div>

8. 언제 쓰나

FileDropUploadField가 맞을 때

  • 폼 안 드래그앤드롭 업로드 — 첨부 자료, 이미지 등록 폼
  • 라벨·필수·검증이 필요한 드롭 UI
  • CSV/JSON 가져오기 폼 (fileType: 'text')
  • 이미지·바이너리 업로드 폼 (fileType: 'binary')

다른 걸 쓸 때

  • 라벨 없이 인라인 → Va.FileDropUpload
  • 표준 파일 선택 다이얼로그 → Va.FileField
  • 이미지 미리보기 통합 → Va.ImageFileField
  • 간단 첨부 버튼 → Va.FileButton

9. 흔한 조합 예시

// 표준 (바이너리)
{
    tagName: 'fileDropUploadField',
    label: '첨부파일',
    text: '파일을 여기 놓으세요',
    fileDropUpload: { fileType: 'binary' },
    required: true,
    onFiledrop: 'onDrop'
}

// CSV 파싱용
{
    tagName: 'fileDropUploadField',
    label: 'CSV 파일',
    text: 'CSV 파일을 드래그하세요',
    fileDropUpload: { fileType: 'text' },
    infoButton: {
        tooltip: 'UTF-8 인코딩된 CSV 파일만 지원'
    },
    onFiledrop: 'onCsvDrop'
}

// 좌측 라벨
{
    tagName: 'fileDropUploadField',
    label: '자료 첨부',
    labelPosition: 'left',
    labelWidth: 120,
    text: '파일을 놓으세요',
    fileDropUpload: {
        fileType: 'binary',
        style: {                              // 드롭 영역 스타일
            width: '100%',
            height: '150px',
            border: '2px dashed gray',
            borderRadius: '8px',
            display: 'flex',
            alignItems: 'center',
            justifyContent: 'center'
        }
    }
}

// 아이콘 포함
{
    tagName: 'fileDropUploadField',
    label: '이미지 업로드',
    fileDropUpload: {
        innerHTML: '<div style="text-align:center"><i class="ico_cloud_upload" style="font-size:48px"></i><br>이미지를 드래그하세요</div>',
        fileType: 'binary'
    }
}

10. 실전 예 — CSV 가져오기 폼

class ProductImport extends Va.View {
    onCsvDrop(field, el, result, evt) {
        // result는 CSV 텍스트 전체
        try {
            const lines = result.split('\n').filter(l => l.trim());
            const headers = lines[0].split(',').map(h => h.trim());

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

            if (data.length === 0) {
                field.setValidation('error', '데이터가 없습니다');
                return;
            }
            field.clearValidation();

            this.getRef('grid').setData(data);
            this.getRef('summary').setValue(`${data.length}건 로드됨`);

        } catch (e) {
            field.setValidation('error', 'CSV 파싱 실패: ' + e.message);
        }
    }

    onImport(btn, el, evt) {
        const data = this.getRef('grid').getData();
        if (data.length === 0) {
            new Va.Alert({ title: '알림', message: '먼저 CSV를 로드하세요' }).show(this);
            return;
        }

        ProductService.bulkImport(this, { products: data }, (view, ok) => {
            if (ok) new Va.Alert({ title: '완료', message: '가져오기 완료' }).show(view);
        });
    }

    config() {
        return {
            tagName: 'page',
            tags: [{
                tagName: 'panel',
                tags: [
                    { tagName: 'h2', innerHTML: '상품 대량 가져오기' },
                    {
                        tagName: 'fileDropUploadField',
                        ref: 'csv',
                        label: 'CSV 파일',
                        text: 'CSV 파일을 여기 놓으세요',
                        fileDropUpload: {
                            fileType: 'text',
                            style: {
                                width: '100%',
                                height: '150px',
                                border: '2px dashed var(--colorNeutralStroke)',
                                borderRadius: '8px',
                                display: 'flex',
                                alignItems: 'center',
                                justifyContent: 'center',
                                cursor: 'pointer'
                            }
                        },
                        required: true,
                        onFiledrop: 'onCsvDrop'
                    },
                    {
                        tagName: 'inputField',
                        ref: 'summary',
                        label: '로드 상태',
                        readonly: true
                    },
                    {
                        tagName: 'grid',
                        ref: 'grid',
                        columns: [/* 동적 */],
                        style: { height: '400px' }
                    },
                    {
                        tagName: 'button',
                        text: '가져오기 실행',
                        appearance: 'primary',
                        onClick: 'onImport'
                    }
                ]
            }]
        };
    }
}

흐름:

  1. 사용자가 CSV 파일 드롭 → filedrop 이벤트
  2. 텍스트 파싱해 그리드에 표시
  3. 검증 실패 시 인라인 에러 메시지
  4. "가져오기" 버튼으로 서버 전송

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

  1. fileType은 fileDropUpload 옵션으로 전달 — Field 옵션엔 없음. 필수 지정 아니면 'binary' 기본.
  2. style/layout은 Field 껍데기에 적용 — 드롭 영역 스타일은 fileDropUpload.style로 별도 전달.
  3. Field 표준 이벤트(focus/blur 등) 재발화 없음  filedrop/change만. 필요하면 fieldComponent에 직접.
  4. getFiles() 편의 메서드 없음  component.fieldComponent.fieldElement.files 직접 접근.
  5. filedrop 이벤트는 파일마다 반복 — 여러 파일 드롭 시 각 파일에 대해 dispatch. 전체 목록은 change로.
  6. 디렉토리 자동 무시 — 폴더 드롭해도 파일만 처리.
  7. 파일 다이얼로그 없음 — 클릭으로 파일 선택 창이 안 열림. 표준 파일 선택도 필요하면 FileField 병행.
  8. change 시 검증 자동 리셋 없음 — 필요하면 콜백에서 명시적 clearValidation().
  9. 레이아웃/스타일 개발자 책임 — 기본 스타일 최소. 드롭 영역 크기·테두리 명시 지정.
  10. 모바일 지원 제한 — 드래그앤드롭은 데스크톱 위주.
  11. 접근성 취약 — 스크린리더 대응 약함. 라벨 명시 권장.
  12. fieldOption 관련 데드 코드 — 소스에 미사용 로직 있음. 실제 동작에 영향 없음.

12. fileDropUploadField vs fileField vs imageFileField 선택

상황추천

폼 안 드래그앤드롭 업로드 fileDropUploadField
CSV/JSON 가져오기 폼 fileDropUploadField + fileType: 'text'
표준 파일 첨부 폼 fileField
이미지 미리보기 폼 imageFileField (있다면)
간단 첨부 (버튼만) fileField
인라인 (라벨 없이) fileDropUpload

참고

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

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