Now.js Framework Documentation

Now.js Framework Documentation

FileElementFactory - คอมโพเนนต์อัปโหลดไฟล์

TH 04 Sep 2026 01:40

FileElementFactory - คอมโพเนนต์อัปโหลดไฟล์

คู่มือสำหรับ FileElementFactory คอมโพเนนต์อัปโหลดไฟล์ที่มีความสามารถครบครัน รองรับการแสดงตัวอย่าง ลากวาง เรียงลำดับ และตรวจสอบความถูกต้อง

📋 สารบัญ

  1. ภาพรวม
  2. การติดตั้งและเตรียมการ
  3. การใช้งานพื้นฐาน
  4. Attributes ใน HTML
  5. ตัวเลือกการตั้งค่า
  6. ฟีเจอร์
  7. การตรวจสอบไฟล์
  8. ระบบแสดงตัวอย่าง
  9. ลากและวาง
  10. เรียงลำดับไฟล์
  11. ไฟล์ที่มีอยู่
  12. เหตุการณ์
  13. JavaScript API
  14. ตัวอย่างการใช้งาน
  15. Best Practices

ภาพรวม

FileElementFactory เป็น ElementFactory ที่เพิ่มความสามารถให้กับ file input element ด้วยฟีเจอร์ขั้นสูง เช่น การแสดงตัวอย่าง ลากและวาง การตรวจสอบไฟล์ การเรียงลำดับ และการทำงานร่วมกับ Form component ของ Now.js

ฟีเจอร์หลัก

  • ✅ แสดงตัวอย่างแบบเรียลไทม์: แสดงภาพตัวอย่างของไฟล์ที่อัปโหลด
  • ✅ ลากและวาง: อินเทอร์เฟซลากและวางที่ใช้งานง่าย
  • ✅ ตรวจสอบไฟล์: ตรวจสอบประเภทและขนาดไฟล์
  • ✅ เรียงลำดับไฟล์: จัดเรียงไฟล์ด้วยการลาก (ต้องใช้ Sortable.js)
  • ✅ หลายไฟล์: รองรับการอัปโหลดไฟล์เดียวและหลายไฟล์
  • ✅ ไฟล์ที่มีอยู่: โหลดและแสดงไฟล์ที่มีอยู่จากเซิร์ฟเวอร์
  • ✅ ลบไฟล์: ลบไฟล์ก่อนส่งฟอร์ม
  • ✅ ติดตามความคืบหน้า: แสดงความคืบหน้าการอัปโหลด
  • ✅ ทำงานร่วมกับฟอร์ม: ทำงานร่วมกับ FormManager ได้อย่างลงตัว
  • ✅ เริ่มต้นอัตโนมัติ: เพิ่มความสามารถให้ file input อัตโนมัติ

เมื่อไหร่ควรใช้ FileElementFactory

✅ ใช้ FileElementFactory เมื่อ:

  • ต้องการอัปโหลดไฟล์พร้อมแสดงตัวอย่าง
  • ต้องการฟีเจอร์ลากและวาง
  • ต้องการตรวจสอบประเภทและขนาดไฟล์
  • ต้องการแสดงไฟล์ที่อัปโหลดไว้แล้ว
  • ต้องการเรียงลำดับไฟล์
  • ต้องการทำงานร่วมกับฟอร์มอย่างลงตัว

❌ ไม่ควรใช้ FileElementFactory เมื่อ:

  • ต้องการ file input แบบธรรมดา (ใช้ <input type="file"> ปกติ)
  • ต้องการ logic การอัปโหลดแบบกำหนดเอง
  • ไม่ต้องการฟีเจอร์เพิ่มเติมใดๆ

การติดตั้งและเตรียมการ

FileElementFactory เป็นส่วนหนึ่งของ Now.js Framework และลงทะเบียนกับ ElementManager อัตโนมัติ

Dependencies

  • ElementFactory - คลาสพื้นฐาน
  • EventSystemManager - จัดการ event
  • FormError - แสดงข้อผิดพลาด
  • Sortable.js (ตัวเลือก) - สำหรับเรียงลำดับไฟล์
  • MediaViewer (ตัวเลือก) - สำหรับแสดงภาพแบบ modal

การลงทะเบียนอัตโนมัติ

// FileElementFactory ลงทะเบียนอัตโนมัติ
ElementManager.registerElement('file', FileElementFactory);

// <input type="file"> ทุกตัวจะได้รับการเพิ่มความสามารถอัตโนมัติ

การใช้งานพื้นฐาน

1. อัปโหลดไฟล์แบบธรรมดา

<input type="file"
       id="avatar"
       name="avatar"
       accept="image/*">

2. อัปโหลดไฟล์พร้อมแสดงตัวอย่าง

<input type="file"
       id="avatar"
       name="avatar"
       data-preview="true"
       accept="image/*">

3. หลายไฟล์พร้อมลากและวาง

<input type="file"
       id="files"
       name="files"
       multiple
       data-preview="true"
       data-drag-drop="true"
       accept="image/*,application/pdf">

4. ตัวอย่างครบครันพร้อมฟีเจอร์ทั้งหมด

<form data-form="upload">
  <label for="files">อัปโหลดไฟล์</label>
  <span class="form-control icon-upload">
    <input type="file"
           id="files"
           name="files"
           multiple
           data-preview="true"
           data-drag-drop="true"
           data-sortable="true"
           data-allow-remove-existing="true"
           data-file-reference="url"
           data-max-file-size="5242880"
           accept="image/*,application/pdf"
           placeholder="ลากไฟล์มาวางที่นี่ หรือคลิกเพื่อเลือก">
  </span>
</form>

Attributes ใน HTML

FileElementFactory รองรับ data attributes เหล่านี้สำหรับการตั้งค่า:

Attributes พื้นฐาน

Attribute ประเภท ค่าเริ่มต้น คำอธิบาย
data-preview boolean false เปิดใช้งานการแสดงตัวอย่างไฟล์
data-drag-drop boolean false เปิดใช้งานลากและวาง
data-sortable boolean false อนุญาตให้เรียงลำดับไฟล์
data-allow-remove-existing boolean false อนุญาตให้ลบไฟล์ที่มีอยู่
multiple boolean false อนุญาตให้เลือกหลายไฟล์
accept string * ประเภทไฟล์ที่อนุญาต (เช่น "image/*,application/pdf")
placeholder string - ข้อความ placeholder สำหรับ drop zone

Attributes สำหรับอ้างอิงไฟล์

Attribute ประเภท ค่าเริ่มต้น คำอธิบาย
data-file-reference string 'id' ชื่อฟิลด์สำหรับอ้างอิงไฟล์ (เช่น "url", "id")
data-attr string - ผูกกับแหล่งข้อมูล (เช่น "value:files")
data-files JSON/expression - ข้อมูลไฟล์ที่มีอยู่ จะเป็น static JSON หรือ expression ของ TemplateManager ก็ได้

ถ้า TemplateManager เป็นคนประมวลผล data-files, expression จะถูกประเมินใหม่แบบ reactive และ preview เดิมจะถูกแทนหรือล้างให้ตรงกับ state ล่าสุด ดูเพิ่มที่ template/data-files.md

Attributes สำหรับตรวจสอบ

Attribute ประเภท ค่าเริ่มต้น คำอธิบาย
data-max-file-size number 10485760 ขนาดไฟล์สูงสุดเป็น bytes (ค่าเริ่มต้น: 10MB)
required boolean false กำหนดให้ต้องอัปโหลดไฟล์

Attributes สำหรับเชื่อมต่อเซิร์ฟเวอร์

Attribute ประเภท ค่าเริ่มต้น คำอธิบาย
data-action-url string - API endpoint สำหรับจัดการไฟล์ (ลบ, เรียงลำดับ)

Attribute สำหรับ Preview Container

Attribute ประเภท ค่าเริ่มต้น คำอธิบาย
data-preview-container string - CSS selector สำหรับ preview container แบบกำหนดเอง

ตัวเลือกการตั้งค่า

เมื่อสร้าง instance ด้วย JavaScript:

const instance = FileElementFactory.create(element, {
  // แสดงตัวอย่าง
  preview: true,
  previewContainer: '.custom-preview',

  // ลากและวาง
  dragDrop: true,

  // เรียงลำดับ
  sortable: true,

  // จัดการไฟล์
  allowRemoveExisting: true,
  fileReference: 'url',

  // ตรวจสอบ
  maxFileSize: 5 * 1024 * 1024, // 5MB
  allowedMimeTypes: ['image/*', 'application/pdf'],

  // เชื่อมต่อเซิร์ฟเวอร์
  actionUrl: '/api/files',

  // ไฟล์ที่มีอยู่
  existingFiles: [
    { url: 'files/photo.jpg', name: 'photo.jpg' },
    { url: 'files/document.pdf', name: 'document.pdf' }
  ],

  // UI
  placeholder: 'ลากไฟล์มาวางที่นี่ หรือคลิกเพื่อเลือก',
  downloadEnabled: true,

  // Callbacks
  onChange: (files, element) => {
    console.log('ไฟล์เปลี่ยนแปลง:', files);
  },
  onError: (error) => {
    console.error('ข้อผิดพลาด:', error);
  }
});

ฟีเจอร์

1. แสดงตัวอย่างแบบเรียลไทม์

แสดงภาพย่อของไฟล์ที่อัปโหลด:

<input type="file"
       name="images"
       multiple
       data-preview="true"
       accept="image/*">

ความสามารถ:

  • ภาพย่อสำหรับไฟล์รูปภาพ
  • ไอคอนไฟล์สำหรับเอกสาร (PDF, Word, Excel ฯลฯ)
  • แสดงชื่อและขนาดไฟล์
  • ปุ่มลบสำหรับแต่ละไฟล์

2. ลากและวาง

เปิดใช้งานการอัปโหลดไฟล์แบบลากและวาง:

<input type="file"
       name="files"
       multiple
       data-drag-drop="true"
       placeholder="ลากไฟล์มาวางที่นี่ หรือคลิกเพื่อเลือก">

ความสามารถ:

  • พื้นที่วางไฟล์ที่มองเห็นชัดเจน
  • แสดงผลตอบรับเมื่อลากผ่าน
  • รองรับการวางหลายไฟล์
  • สำรองด้วยการคลิกเลือก

3. หลายไฟล์

อัปโหลดหลายไฟล์พร้อมกัน:

<input type="file"
       name="files"
       multiple
       data-preview="true">

4. ตรวจสอบไฟล์

ตรวจสอบประเภทและขนาดไฟล์:

<input type="file"
       name="avatar"
       accept="image/jpeg,image/png,image/webp"
       data-max-file-size="2097152">
<!-- ขนาดสูงสุด: 2MB -->

กฎการตรวจสอบ:

  • ตรวจสอบประเภทไฟล์ (MIME types และนามสกุล)
  • ตรวจสอบขนาดไฟล์
  • แสดงข้อผิดพลาดอัตโนมัติ
  • ทำงานร่วมกับฟอร์ม

5. เรียงลำดับไฟล์

จัดเรียงไฟล์ที่อัปโหลดด้วยการลาก (ต้องใช้ Sortable.js):

<input type="file"
       name="gallery"
       multiple
       data-preview="true"
       data-sortable="true"
       data-action-url="/api/files/sort">

ความสามารถ:

  • จุดจับสำหรับลากแต่ละไฟล์
  • แสดงผลตอบรับระหว่างลาก
  • บันทึกลำดับไปยังเซิร์ฟเวอร์อัตโนมัติ
  • ย้อนกลับเมื่อเกิดข้อผิดพลาด

6. ไฟล์ที่มีอยู่แล้ว

โหลดและแสดงไฟล์ที่มีอยู่จากเซิร์ฟเวอร์:

<input type="file"
       name="files"
       multiple
       data-preview="true"
       data-attr="value:files"
       data-file-reference="url"
       data-allow-remove-existing="true"
       data-action-url="/api/files">

รูปแบบ Response จากเซิร์ฟเวอร์:

{
  "data": {
    "files": [
      {
        "url": "uploads/photo.jpg",
        "name": "photo.jpg",
        "size": 1258291
      },
      {
        "url": "uploads/document.pdf",
        "name": "document.pdf",
        "size": 524288
      }
    ]
  }
}

การตรวจสอบไฟล์

ประเภทไฟล์ที่รองรับ

MIME types ที่อนุญาตตามค่าเริ่มต้น:

allowedMimeTypes: [
  'image/*',
  'application/pdf',
  'application/msword',
  'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
  'application/vnd.ms-excel',
  'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
  'text/plain',
  'application/zip',
  'application/x-zip-compressed'
]

การตรวจสอบแบบกำหนดเอง

<!-- รับเฉพาะรูปภาพ -->
<input type="file" accept="image/*">

<!-- รับเฉพาะรูปภาพบางประเภท -->
<input type="file" accept="image/jpeg,image/png,image/webp">

<!-- รับรูปภาพและ PDF -->
<input type="file" accept="image/*,application/pdf">

<!-- รับตามนามสกุล -->
<input type="file" accept=".jpg,.jpeg,.png,.pdf">

ตรวจสอบขนาดไฟล์

<!-- สูงสุด 5MB -->
<input type="file" data-max-file-size="5242880">

<!-- สูงสุด 10MB (ค่าเริ่มต้น) -->
<input type="file" data-max-file-size="10485760">

ข้อผิดพลาดการตรวจสอบ

ข้อผิดพลาดจะแสดงอัตโนมัติผ่าน FormError:

// ข้อความแสดงข้อผิดพลาดจะถูกแปลภาษา
"ขนาดไฟล์ต้องไม่เกิน {maxsize}"
"ประเภทไฟล์ไม่ได้รับอนุญาต"
"จำเป็นต้องเลือกไฟล์"

ระบบแสดงตัวอย่าง

แสดงตัวอย่างอัตโนมัติ

ตัวอย่างจะถูกสร้างอัตโนมัติเมื่อ data-preview="true":

<span class="form-control icon-upload">
  <input type="file"
         name="files"
         multiple
         data-preview="true">
</span>

HTML ที่สร้างขึ้น:

<span class="form-control icon-upload">
  <input type="file" name="files" multiple>
  <div class="file-preview">
    <div class="preview-item" data-file-name="photo.jpg">
      <span class="image-preview" style="background-image: url(...)"></span>
      <div class="file-info">photo.jpg (1.2 MB)</div>
      <button type="button" class="icon-delete" title="ลบไฟล์"></button>
    </div>
  </div>
</span>

Preview Container แบบกำหนดเอง

<input type="file"
       name="files"
       data-preview="true"
       data-preview-container="#custom-preview">

<div id="custom-preview"></div>

ความสามารถของการแสดงตัวอย่าง

  • ภาพย่อ: รูปภาพพื้นหลังสำหรับรูปถ่าย
  • ไอคอนไฟล์: ไอคอนสำหรับเอกสาร (PDF, Word, Excel, ZIP)
  • ข้อมูลไฟล์: แสดงชื่อและขนาด
  • ปุ่มลบ: ลบไฟล์ก่อนอัปโหลด
  • คลิกเพื่อดู: คลิกรูปภาพเพื่อเปิดใน modal (ต้องใช้ MediaViewer)

ลากและวาง

ลากและวางพื้นฐาน

<input type="file"
       name="files"
       multiple
       data-drag-drop="true"
       placeholder="ลากไฟล์มาวางที่นี่ หรือคลิกเพื่อเลือก">

Drop Zone ที่สร้างขึ้น

<div class="file-drop-zone">
  <input type="file" name="files" multiple>
  <div class="file-drop-content">
    <div class="file-drop-message">ลากไฟล์มาวางที่นี่ หรือคลิกเพื่อเลือก</div>
  </div>
  <div class="file-preview">
    <!-- รายการตัวอย่าง -->
  </div>
</div>

CSS Classes

Class เมื่อใช้ คำอธิบาย
file-drop-zone ตลอดเวลา Container ของ drop zone
drag-over ระหว่างลาก กำลังลากไฟล์ผ่าน
file-drop-content ตลอดเวลา พื้นที่เนื้อหาของ drop zone
file-drop-message ตลอดเวลา ข้อความของ drop zone

ตัวอย่างการจัดรูปแบบ

.file-drop-zone {
  border: 2px dashed #ccc;
  border-radius: 8px;
  padding: 20px;
  text-align: center;
  transition: all 0.3s;
}

.file-drop-zone.drag-over {
  border-color: #007bff;
  background-color: #f0f8ff;
}

.file-drop-message {
  color: #666;
  font-size: 16px;
  padding: 40px 20px;
}

เรียงลำดับไฟล์

เปิดใช้งานการเรียงลำดับ

ต้องใช้ไลบรารี Sortable.js:

<script src="https://cdn.jsdelivr.net/npm/sortablejs@latest/Sortable.min.js"></script>

<input type="file"
       name="gallery"
       multiple
       data-preview="true"
       data-sortable="true"
       data-action-url="/api/files/sort"
       data-file-reference="id">

เรียงลำดับฝั่งเซิร์ฟเวอร์

เมื่อผู้ใช้ลากเรียงลำดับไฟล์ จะส่ง POST request ไปยัง data-action-url:

Request:

{
  "action": "sort",
  "order": [
    { "id": "123", "position": 0 },
    { "id": "456", "position": 1 },
    { "id": "789", "position": 2 }
  ]
}

Response:

{
  "status": 200,
  "message": "Saved successfully"
}

ความสามารถของการเรียงลำดับ

  • จุดจับสำหรับลากแต่ละไฟล์
  • แสดงผลตอบรับระหว่างลาก
  • บันทึกไปยังเซิร์ฟเวอร์อัตโนมัติ
  • ย้อนกลับเมื่อเกิดข้อผิดพลาด
  • แจ้งเตือนเมื่อสำเร็จ

ไฟล์ที่มีอยู่

โหลดไฟล์ที่มีอยู่จาก API (แนะนำ)

ใช้ data-load-api ร่วมกับ FormManager เพื่อโหลดและแสดงไฟล์ที่มีอยู่อัตโนมัติ:

<form data-form="profile"
      data-load-api="/api/profile"
      data-load-query-params="true">
  <input type="file"
         id="avatar"
         name="avatar"
         data-preview="true"
         data-attr="value:avatar"
         data-file-reference="url"
         data-allow-remove-existing="true"
         data-action-url="/api/files"
         accept="image/*">
</form>

หลักการทำงาน:

  1. data-load-api - FormManager โหลดข้อมูลจาก API
  2. data-attr="value:avatar" - FormManager.setFormData จะ set ค่าจาก response.data.avatar
  3. FormManager ตรวจพบว่าเป็น file input จึง set data-files attribute แทน
  4. FileElementFactory.showExistingFiles() แสดง preview อัตโนมัติ

ถ้า block เดิมถูก bind ใหม่ในภายหลังด้วย record คนละตัว TemplateManager ก็สามารถขับ flow เดียวกันได้ตรงๆ ผ่าน data-files="existingAttachments"

กำหนดไฟล์ที่มีอยู่แบบ Static

ใช้ data-files attribute โดยตรงสำหรับไฟล์ที่มีอยู่แบบคงที่:

<input type="file"
       name="files"
       multiple
       data-preview="true"
       data-files='[{"url":"uploads/photo.jpg","name":"photo.jpg"}]'
       data-file-reference="url"
       data-allow-remove-existing="true"
       data-action-url="/api/files">

รูปแบบ Response จากเซิร์ฟเวอร์

{
  "data": {
    "avatar": [
      {
        "url": "uploads/photo.jpg",
        "name": "photo.jpg",
        "size": 1258291
      }
    ],
    "files": [
      {
        "id": "456",
        "url": "uploads/document.pdf",
        "name": "document.pdf",
        "size": 524288
      }
    ]
  }
}

หมายเหตุ: ต้องใช้ data-preview="true" เพื่อให้ FileElementFactory สร้าง preview container สำหรับแสดงไฟล์

ลบไฟล์ที่มีอยู่

เมื่อ data-allow-remove-existing="true" ผู้ใช้สามารถลบไฟล์ที่มีอยู่ได้:

[!IMPORTANT]
ปุ่มลบจะแสดงเฉพาะเมื่อมีทั้ง data-allow-remove-existing="true" และ data-action-url กำหนดไว้เท่านั้น

Request:

{
  "action": "delete",
  "url": "uploads/photo.jpg"
}

หมายเหตุ: Key ที่ส่งไป (เช่น url) จะตรงกับค่าใน data-file-reference ถ้าใช้ data-file-reference="id" จะส่ง "id": "123" แทน

Response:

{
  "status": 200,
  "message": "File deleted successfully"
}

ตัวอย่าง PHP Backend

<?php
/**
 * API สำหรับจัดการไฟล์ (ลบ/เรียงลำดับ)
 */
class FilesController extends \Kotchasan\ApiController
{
    public function handleAction(Request $request)
    {
        $this->validateCsrfToken($request);
        $login = $this->authenticateRequest($request);

        if (!$login) {
            return $this->errorResponse('Unauthorized', 401);
        }

        $action = $request->post('action')->filter('a-z');

        switch ($action) {
            case 'delete':
                return $this->deleteFile($request, $login);
            case 'sort':
                return $this->sortFiles($request, $login);
            default:
                return $this->errorResponse('Invalid action', 400);
        }
    }

    protected function deleteFile(Request $request, $login)
    {
        // รับค่าตาม data-file-reference (url หรือ id)
        $fileUrl = $request->post('url')->url();
        // หรือ $fileId = $request->post('id')->toInt();

        // ตรวจสอบสิทธิ์และลบไฟล์
        // ...

        return $this->successResponse('File deleted successfully');
    }

    protected function sortFiles(Request $request, $login)
    {
        $order = $request->post('order')->toArray();

        foreach ($order as $item) {
            // อัปเดตตำแหน่งในฐานข้อมูล
            // Model::updatePosition($item['id'], $item['position']);
        }

        return $this->successResponse('Saved successfully');
    }
}

หมายเหตุ: FileElementFactory จะใช้ ApiService.post() (ถ้ามี) เพื่อส่ง CSRF token อัตโนมัติ หรือ fallback ไปใช้ simpleFetch.post()

เหตุการณ์

FileElementFactory ส่งเหตุการณ์ผ่าน EventSystemManager:

ประเภทของเหตุการณ์

Event เมื่อเกิด Detail
file:change รายการไฟล์เปลี่ยน {elementId, files}

รับฟังเหตุการณ์

Now.on('file:change', (data) => {
  console.log('ไฟล์เปลี่ยน:', data.files);
  console.log('Element ID:', data.elementId);
});

การทำงานร่วมกับฟอร์ม

FileElementFactory ทำงานร่วมกับ FormManager:

  • ตรวจสอบอัตโนมัติเมื่อส่งฟอร์ม
  • ข้อมูลไฟล์ถูกรวมไปกับการส่งฟอร์ม
  • เชื่อมกับการแสดงข้อผิดพลาด

JavaScript API

รับ Instance

// จาก element
const instance = ElementManager.getInstance(element);

// จาก ID
const element = document.getElementById('files');
const instance = ElementManager.getInstance(element);

Instance Methods

// ล้างไฟล์ทั้งหมด
instance.clearFiles();

// ตรวจสอบไฟล์
const isValid = instance.validateFiles();

// อัปโหลดไฟล์
const result = await instance.upload({
  url: '/api/upload',
  data: { userId: 123 }
});

Static Methods

// ตรวจสอบไฟล์เดียว
await FileElementFactory.validateFile(file, config);

// จัดรูปแบบขนาดไฟล์
const size = FileElementFactory.formatFileSize(1258291);
// คืนค่า: "1.2 MB"

// รับ class ไอคอนไฟล์
const icon = FileElementFactory.getFileIcon('document.pdf');
// คืนค่า: "icon-pdf"

// ตรวจสอบประเภทไฟล์
const isValid = FileElementFactory.isValidFileType(file, ['image/*', 'application/pdf']);

เมธอด upload

const result = await instance.upload({
  url: '/api/upload',           // Upload URL
  data: {                        // Additional data
    userId: 123,
    category: 'photos'
  }
});

if (result.success) {
  console.log('Upload successful');
} else {
  console.error('Upload failed:', result.message);
}

ความสามารถ:

  • ติดตามความคืบหน้า
  • สร้าง FormData ให้เอง
  • แนบข้อมูลเพิ่มได้
  • จัดการข้อผิดพลาด
  • ทำงานแบบ Promise

ตัวอย่างการใช้งาน

ตัวอย่าง 1: อัปโหลดรูปโปรไฟล์

<form data-form="profile" method="POST" action="/api/profile">
  <label for="avatar">รูปโปรไฟล์</label>
  <span class="form-control icon-image">
    <input type="file"
           id="avatar"
           name="avatar"
           data-preview="true"
           accept="image/*"
           data-max-file-size="2097152"
           placeholder="เลือกรูปโปรไฟล์ของคุณ">
  </span>
  <button type="submit">บันทึกโปรไฟล์</button>
</form>

ตัวอย่าง 2: หลายไฟล์พร้อมลากและวาง

<form data-form="documents" method="POST" action="/api/documents">
  <label for="files">อัปโหลดเอกสาร</label>
  <span class="form-control icon-upload">
    <input type="file"
           id="files"
           name="files"
           multiple
           data-preview="true"
           data-drag-drop="true"
           accept="image/*,application/pdf"
           data-max-file-size="10485760"
           placeholder="ลากไฟล์มาวางที่นี่ หรือคลิกเพื่อเลือก">
  </span>
  <button type="submit">อัปโหลดไฟล์</button>
</form>

ตัวอย่าง 3: แกลเลอรีที่เรียงลำดับได้

<form data-form="gallery" data-load-api="/api/gallery">
  <label for="photos">แกลเลอรีรูปภาพ</label>
  <span class="form-control icon-image">
    <input type="file"
           id="photos"
           name="photos"
           multiple
           data-preview="true"
           data-drag-drop="true"
           data-sortable="true"
           data-allow-remove-existing="true"
           data-file-reference="id"
           data-action-url="/api/gallery"
           data-attr="value:photos"
           accept="image/*"
           placeholder="ลากรูปภาพมาวางที่นี่">
  </span>
  <button type="submit">บันทึกแกลเลอรี</button>
</form>

ตัวอย่างที่ 4: ใช้งานจากโค้ด

const element = document.getElementById('files');

const instance = FileElementFactory.create(element, {
  preview: true,
  dragDrop: true,
  sortable: true,
  maxFileSize: 5 * 1024 * 1024,
  allowedMimeTypes: ['image/*', 'application/pdf'],
  onChange: (files) => {
    console.log('Files selected:', files);
  },
  onError: (error) => {
    console.error('Error:', error);
  }
});

// Upload files
document.querySelector('button').addEventListener('click', async () => {
  const result = await instance.upload({
    url: '/api/upload',
    data: { category: 'documents' }
  });

  if (result.success) {
    alert('Upload successful!');
  }
});

Best Practices

1. ตรวจสอบประเภทไฟล์เสมอ

<!-- ✅ ดี: ระบุประเภทไฟล์ชัดเจน -->
<input type="file" accept="image/jpeg,image/png,image/webp">

<!-- ❌ ไม่ดี: ไม่มีการตรวจสอบ -->
<input type="file">

2. กำหนดขนาดไฟล์ที่เหมาะสม

<!-- ✅ ดี: จำกัด 5MB สำหรับรูปภาพ -->
<input type="file"
       accept="image/*"
       data-max-file-size="5242880">

<!-- ❌ ไม่ดี: ค่าเริ่มต้น 10MB อาจใหญ่เกินไป -->
<input type="file" accept="image/*">

3. ใช้ Preview เพื่อ UX ที่ดีขึ้น

<!-- ✅ ดี: แสดงตัวอย่าง -->
<input type="file"
       data-preview="true"
       accept="image/*">

<!-- ❌ ไม่ดี: ไม่มีผลตอบรับทางภาพ -->
<input type="file" accept="image/*">

4. เปิดใช้งานลากและวางสำหรับหลายไฟล์

<!-- ✅ ดี: ลากและวางสำหรับหลายไฟล์ -->
<input type="file"
       multiple
       data-drag-drop="true"
       data-preview="true">

<!-- ❌ ไม่ดี: หลายไฟล์แต่ไม่มีลากและวาง -->
<input type="file" multiple>

5. ใส่ Placeholder ที่ชัดเจน

<!-- ✅ ดี: คำแนะนำที่ชัดเจน -->
<input type="file"
       data-drag-drop="true"
       placeholder="ลากรูปภาพมาวางที่นี่ หรือคลิกเพื่อเลือก (สูงสุด 5MB)">

<!-- ❌ ไม่ดี: Placeholder ทั่วไป -->
<input type="file" data-drag-drop="true">

6. จัดการไฟล์ที่มีอยู่อย่างถูกต้อง

<!-- ✅ ดี: โหลดและอนุญาตให้ลบ -->
<input type="file"
       data-attr="value:files"
       data-file-reference="url"
       data-allow-remove-existing="true"
       data-action-url="/api/files">

<!-- ❌ ไม่ดี: ไม่มีวิธีจัดการไฟล์ที่มีอยู่ -->
<input type="file" data-attr="value:files">

7. ใช้การทำงานร่วมกับฟอร์ม

<!-- ✅ ดี: ทำงานร่วมกับฟอร์ม -->
<form data-form="upload" data-ajax-submit="true">
  <input type="file" name="files" required>
  <button type="submit">อัปโหลด</button>
</form>

<!-- ❌ ไม่ดี: จัดการด้วยตนเอง -->
<input type="file" id="files">
<button onclick="uploadFiles()">อัปโหลด</button>

8. ให้ผลตอบกลับแก่ผู้ใช้

// ✅ ดี: บอกผู้ใช้ว่าเกิดอะไรขึ้น
const instance = FileElementFactory.create(element, {
  onChange: (files) => {
    console.log(`${files.length} file(s) selected`);
  },
  onError: (error) => {
    alert('Error: ' + error.message);
  }
});

// ❌ ไม่ดี: ล้มเหลวแบบเงียบ
const instance = FileElementFactory.create(element, {});

รูปแบบที่ใช้บ่อย

รูปแบบที่ 1: รูปโปรไฟล์ผู้ใช้

<form data-form="profile">
  <div class="avatar-upload">
    <label for="avatar">Profile Photo</label>
    <span class="form-control">
      <input type="file"
             id="avatar"
             name="avatar"
             data-preview="true"
             data-attr="value:avatar"
             data-file-reference="url"
             accept="image/*"
             data-max-file-size="2097152">
    </span>
  </div>
</form>

รูปแบบที่ 2: อัปโหลดเอกสาร

<form data-form="documents">
  <label for="files">Upload Documents</label>
  <span class="form-control icon-upload">
    <input type="file"
           id="files"
           name="files"
           multiple
           data-preview="true"
           data-drag-drop="true"
           accept="application/pdf,application/msword"
           data-max-file-size="10485760"
           placeholder="Drag PDF or Word documents here">
  </span>
</form>

รูปแบบที่ 3: แกลเลอรีรูปภาพ

<form data-form="gallery" data-load-api="/api/gallery">
  <label for="images">Gallery Images</label>
  <span class="form-control">
    <input type="file"
           id="images"
           name="images"
           multiple
           data-preview="true"
           data-drag-drop="true"
           data-sortable="true"
           data-allow-remove-existing="true"
           data-file-reference="id"
           data-action-url="/api/gallery"
           data-attr="value:images"
           accept="image/*"
           placeholder="Drag images to upload">
  </span>
</form>

แก้ปัญหา

ไฟล์ไม่ถูกอัปโหลด

อาการ: เลือกไฟล์แล้วแต่ไม่ถูกอัปโหลด

วิธีแก้:

<!-- Ensure form has correct attributes -->
<form data-form="upload"
      method="POST"
      action="/api/upload"
      data-ajax-submit="true">
  <input type="file" name="files" multiple>
  <button type="submit">Upload</button>
</form>

ไม่แสดงตัวอย่าง

อาการ: ตัวอย่างไฟล์ไม่ปรากฏ

วิธีแก้:

<!-- Ensure data-preview is set -->
<input type="file" data-preview="true">

<!-- Or check if preview container exists -->
<input type="file"
       data-preview="true"
       data-preview-container="#preview">
<div id="preview"></div>

เรียงลำดับไม่ได้

อาการ: ลากสลับตำแหน่งไฟล์ไม่ได้

วิธีแก้:

<!-- 1. Include Sortable.js -->
<script src="https://cdn.jsdelivr.net/npm/sortablejs@latest/Sortable.min.js"></script>

<!-- 2. Enable sortable -->
<input type="file"
       data-sortable="true"
       data-action-url="/api/files/sort">

ไฟล์ที่ถูกต้องกลับไม่ผ่านการตรวจ

อาการ: ไฟล์ที่ควรผ่านถูกปฏิเสธ

วิธีแก้:

<!-- Check accept attribute -->
<input type="file" accept="image/*,application/pdf">

<!-- Check file size limit -->
<input type="file" data-max-file-size="10485760">

สรุป API

การตั้งค่า

interface FileElementConfig {
  // Display
  preview: boolean;
  previewContainer: string | null;
  placeholder: string | null;

  // Interaction
  dragDrop: boolean;
  sortable: boolean;
  allowRemoveExisting: boolean;
  downloadEnabled: boolean;

  // Validation
  maxFileSize: number;
  allowedMimeTypes: string[];

  // Data
  fileReference: string;
  existingFiles: FileData[];

  // Server
  actionUrl: string;

  // Callbacks
  onChange: (files: File[], element: HTMLElement) => void;
  onError: (error: Error) => void;
}

interface FileData {
  id?: string;
  url: string;
  name: string;
  size?: number;
}

เมธอด

class FileElementFactory {
  // Instance Methods
  clearFiles(): void;
  validateFiles(): boolean;
  upload(options: UploadOptions): Promise<UploadResult>;

  // Static Methods
  static validateFile(file: File, config: Config): Promise<boolean>;
  static formatFileSize(bytes: number): string;
  static getFileIcon(filename: string): string;
  static isValidFileType(file: File, acceptedTypes: string[]): boolean;
}

เอกสารที่เกี่ยวข้อง