Now.js Framework Documentation

Now.js Framework Documentation

Modal - ระบบ Modal Dialog

TH 27 Nov 2025 12:12

Modal - ระบบ Modal Dialog

เอกสารฉบับนี้อธิบาย Modal class ซึ่งเป็นคอมโพเนนต์สำหรับสร้างและจัดการ modal dialogs (กล่องโต้ตอบแบบป๊อปอัพ) ที่มีฟีเจอร์ครบถ้วน รองรับ animation, accessibility และ data binding

📋 สารบัญ

  1. ภาพรวม
  2. การติดตั้งและนำเข้า
  3. การใช้งานพื้นฐาน
  4. ตัวเลือกการกำหนดค่า
  5. Data Binding
  6. Lifecycle Callbacks
  7. Accessibility
  8. JavaScript API
  9. ตัวอย่างการใช้งาน
  10. API Reference
  11. แนวทางปฏิบัติที่ดี

ภาพรวม

Modal เป็น class สำหรับสร้าง modal dialogs ที่มีฟีเจอร์ครบถ้วน รองรับการแสดงผลแบบ overlay พร้อม backdrop, animation, keyboard navigation และ data binding

ฟีเจอร์หลัก

  • สร้าง Modal แบบ Programmatic: สร้าง modal ด้วย JavaScript ได้ทันที
  • Animation Support: รองรับ fade-in/fade-out animations
  • Backdrop Management: จัดการ backdrop อัตโนมัติผ่าน BackdropManager
  • Keyboard Support: ปิด modal ด้วยปุ่ม ESC
  • Focus Management: จัดการ focus และ focus trap อัตโนมัติ
  • Accessibility: รองรับ ARIA attributes และ inert property
  • Data Binding: ผูกข้อมูลกับ elements ใน modal ได้
  • Content Sanitization: ป้องกัน XSS ด้วยการ sanitize ข้อมูลอัตโนมัติ
  • Lifecycle Callbacks: รองรับ callbacks สำหรับแต่ละขั้นตอน
  • Element Cleanup: ทำความสะอาด elements อัตโนมัติเมื่อปิด modal

ใช้ Modal เมื่อ:

  • ต้องการสร้าง modal dialog แบบ programmatic
  • ต้องการ modal ที่มี animation และ accessibility ครบถ้วน
  • ต้องการผูกข้อมูลกับ modal content
  • ต้องการควบคุม modal ผ่าน JavaScript API

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

  • มี modal elements อยู่ใน HTML แล้ว (ใช้ ModalDataBinder แทน)
  • ต้องการ modal แบบง่ายๆ ไม่ต้องการ features พิเศษ
  • ต้องการ modal ที่ไม่มี backdrop

การติดตั้งและนำเข้า

Modal โหลดมาพร้อมกับ Now.js Framework และพร้อมใช้งานทันทีผ่าน window object:

// ไม่ต้อง import - พร้อมใช้งานทันที
console.log(window.Modal); // Modal class

สิ่งที่ต้องพึ่งพา

  • BackdropManager – สำหรับจัดการ backdrop (ทางเลือก)
  • FormManager – สำหรับ cleanup forms ใน modal (ทางเลือก)
  • ElementManager – สำหรับ cleanup elements ใน modal (ทางเลือก)

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

1. สร้าง Modal แบบง่าย

// สร้าง modal instance
const modal = new Modal({
  title: 'ยินดีต้อนรับ',
  content: '<p>นี่คือ modal แรกของคุณ!</p>'
});

// แสดง modal
modal.show();

2. สร้าง Modal พร้อม Options

const modal = new Modal({
  id: 'my-modal',
  title: 'ข้อมูลผู้ใช้',
  content: `
    <div class="user-info">
      <img data-modal-target="avatar" class="avatar">
      <h3 data-modal-target="name"></h3>
      <p data-modal-target="email"></p>
    </div>
  `,
  width: '500px',
  closeButton: true,
  backdrop: true,
  keyboard: true,
  animation: true
});

modal.show();

3. ปิด Modal

// ปิด modal
modal.hide();

// หรือกดปุ่ม ESC
// หรือคลิกที่ backdrop (ถ้าเปิดใช้งาน)

ตัวเลือกการกำหนดค่า

Modal รองรับ options เหล่านี้:

Options พื้นฐาน

Option Type Default Description
id string auto-generated ID ของ modal element
title string '' หัวข้อของ modal
content string|Node '' เนื้อหาของ modal (HTML string หรือ DOM node)
width string 'auto' ความกว้างของ modal
maxWidth string '90%' ความกว้างสูงสุด
height string 'auto' ความสูงของ modal
maxHeight string '90vh' ความสูงสูงสุด
className string '' CSS class เพิ่มเติม

Options การแสดงผล

Option Type Default Description
closeButton boolean true แสดงปุ่มปิด
animation boolean true เปิดใช้งาน animation
backdrop boolean true ปิด modal เมื่อคลิก backdrop
keyboard boolean true ปิด modal เมื่อกดปุ่ม ESC
focus boolean true จัดการ focus อัตโนมัติ

Options Backdrop

Option Type Default Description
backdropOpacity number 0.5 ความทึบของ backdrop
backdropColor string 'rgba(0,0,0,0.5)' สีของ backdrop

Lifecycle Callbacks

Option Type Description
onShow function เรียกก่อน modal แสดง
onShown function เรียกหลัง modal แสดงเสร็จ
onHide function เรียกก่อน modal ปิด
onHidden function เรียกหลัง modal ปิดเสร็จ

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

const modal = new Modal({
  id: 'confirm-modal',
  title: 'ยืนยันการลบ',
  content: '<p>คุณแน่ใจหรือไม่ที่จะลบรายการนี้?</p>',
  width: '400px',
  closeButton: true,
  backdrop: true,
  keyboard: true,
  animation: true,
  backdropOpacity: 0.7,
  className: 'modal-danger',

  onShow: function() {
    console.log('Modal กำลังแสดง...');
  },

  onShown: function() {
    console.log('Modal แสดงเสร็จแล้ว');
  },

  onHide: function() {
    console.log('Modal กำลังปิด...');
  },

  onHidden: function() {
    console.log('Modal ปิดเสร็จแล้ว');
  }
});

Data Binding

Modal รองรับการผูกข้อมูลกับ elements ภายใน modal ผ่าน data-modal-target attribute

การผูกข้อมูลพื้นฐาน

// สร้าง modal พร้อม data binding targets
const modal = new Modal({
  title: 'ข้อมูลผู้ใช้',
  content: `
    <div class="user-profile">
      <img data-modal-target="avatar" class="avatar">
      <h3 data-modal-target="name"></h3>
      <p data-modal-target="email"></p>
      <p data-modal-target="bio"></p>
    </div>
  `
});

// ผูกข้อมูล
modal.bindData({
  avatar: '/images/user.jpg',
  name: 'สมชาย ใจดี',
  email: 'somchai@example.com',
  bio: 'นักพัฒนา Software'
});

// แสดง modal
modal.show();

อัพเดทข้อมูล

// อัพเดทข้อมูลบางส่วน
modal.updateData({
  bio: 'Senior Software Developer'
});

// หรือใช้ method chaining
modal.updateData({
  name: 'สมชาย ใจดี (Admin)'
}).show();

อ่านข้อมูลที่ผูกไว้

// อ่านข้อมูลทั้งหมด
const data = modal.getData();
console.log(data);
// { avatar: '/images/user.jpg', name: 'สมชาย ใจดี', ... }

การผูกข้อมูลกับ Element Types ต่างๆ

Modal จะผูกข้อมูลตาม element type อัตโนมัติ:

const modal = new Modal({
  content: `
    <!-- Image: ตั้งค่า src -->
    <img data-modal-target="productImage">

    <!-- Link: ตั้งค่า href -->
    <a data-modal-target="productLink">ดูสินค้า</a>

    <!-- Input: ตั้งค่า value -->
    <input data-modal-target="productName">

    <!-- Text content (default) -->
    <h3 data-modal-target="title"></h3>
    <p data-modal-target="description"></p>

    <!-- HTML content (ใช้ data-modal-html) -->
    <div data-modal-target="richContent" data-modal-html></div>
  `
});

modal.bindData({
  productImage: '/images/product.jpg',
  productLink: '/products/123',
  productName: 'สินค้าตัวอย่าง',
  title: 'สินค้าใหม่',
  description: 'รายละเอียดสินค้า',
  richContent: '<strong>HTML</strong> content'
});

Content Sanitization

Modal จะ sanitize ข้อมูลอัตโนมัติเพื่อป้องกัน XSS:

// Text content - escape HTML อัตโนมัติ
modal.bindData({
  name: '<script>alert("XSS")</script>' // จะแสดงเป็น text ธรรมดา
});

// HTML content - ลบ scripts และ event handlers
modal.bindData({
  richContent: '<p onclick="alert()">Safe HTML</p>' // ลบ onclick ออก
});

// URL - validate protocols
modal.bindData({
  link: 'javascript:alert()' // จะเปลี่ยนเป็น '#'
});

Lifecycle Callbacks

Modal มี callbacks สำหรับแต่ละขั้นตอนของ lifecycle:

onShow - ก่อนแสดง Modal

const modal = new Modal({
  title: 'Loading...',
  content: '<div class="spinner"></div>',
  onShow: function() {
    console.log('Modal กำลังแสดง');
    // เตรียมข้อมูล, เริ่ม loading
  }
});

onShown - หลังแสดง Modal เสร็จ

const modal = new Modal({
  title: 'ข้อมูลผู้ใช้',
  content: '<div id="user-data"></div>',
  onShown: async function() {
    console.log('Modal แสดงเสร็จแล้ว');
    // โหลดข้อมูล, focus input
    const data = await fetchUserData();
    this.bindData(data);
  }
});

onHide - ก่อนปิด Modal

const modal = new Modal({
  title: 'แบบฟอร์ม',
  content: '<form>...</form>',
  onHide: function() {
    console.log('Modal กำลังปิด');
    // บันทึกข้อมูล, ยืนยันการปิด
    const hasUnsavedChanges = checkFormChanges();
    if (hasUnsavedChanges) {
      return confirm('มีการเปลี่ยนแปลงที่ยังไม่ได้บันทึก ต้องการปิดหรือไม่?');
    }
  }
});

onHidden - หลังปิด Modal เสร็จ

const modal = new Modal({
  title: 'สำเร็จ',
  content: '<p>บันทึกข้อมูลเรียบร้อย</p>',
  onHidden: function() {
    console.log('Modal ปิดเสร็จแล้ว');
    // Cleanup, redirect, refresh data
    window.location.reload();
  }
});

Accessibility

Modal มีฟีเจอร์ accessibility ครบถ้วน:

ARIA Attributes

// Modal จะตั้งค่า ARIA attributes อัตโนมัติ
const modal = new Modal({
  title: 'ข้อมูล',
  content: '<p>เนื้อหา</p>'
});

// จะสร้าง HTML ดังนี้:
// <div role="dialog" aria-modal="true" aria-hidden="false">
//   ...
// </div>

Inert Property

// Modal ใช้ inert property เพื่อป้องกัน interaction เมื่อซ่อน
modal.hide(); // modal.inert = true
modal.show(); // modal.inert = false

Focus Management

const modal = new Modal({
  title: 'แบบฟอร์ม',
  content: `
    <input type="text" id="name">
    <button>บันทึก</button>
  `,
  focus: true // เปิดใช้งาน focus management (default: true)
});

modal.show();
// จะ focus ที่ element แรกที่ focusable ได้อัตโนมัติ

Focus Trap

// Modal จะจับ focus ไว้ภายใน modal
// กด Tab จะวนไปที่ elements ใน modal เท่านั้น
// กด Shift+Tab จะวนกลับ

Keyboard Navigation

const modal = new Modal({
  keyboard: true // เปิดใช้งาน keyboard support (default: true)
});

// กด ESC เพื่อปิด modal

JavaScript API

Constructor

const modal = new Modal(options);

Parameters:

  • options (Object) - ตัวเลือกการกำหนดค่า modal

Returns:

  • Modal instance

show()

แสดง modal

modal.show();

Returns: void

hide()

ปิด modal

modal.hide();

Returns: void

setContent(content)

เปลี่ยนเนื้อหาของ modal

// HTML string
modal.setContent('<p>เนื้อหาใหม่</p>');

// DOM node
const element = document.createElement('div');
element.textContent = 'เนื้อหาใหม่';
modal.setContent(element);

// DocumentFragment
const fragment = document.createDocumentFragment();
// ... add nodes to fragment
modal.setContent(fragment);

Parameters:

  • content (string|Node|DocumentFragment) - เนื้อหาใหม่

Returns: void

setTitle(title)

เปลี่ยนหัวข้อของ modal

modal.setTitle('หัวข้อใหม่');

Parameters:

  • title (string) - หัวข้อใหม่

Returns: void

bindData(data)

ผูกข้อมูลกับ elements ใน modal

modal.bindData({
  name: 'John Doe',
  email: 'john@example.com'
});

Parameters:

  • data (Object) - ข้อมูลที่ต้องการผูก

Returns: Modal instance (สำหรับ method chaining)

updateData(data)

อัพเดทข้อมูลที่ผูกไว้

modal.updateData({
  email: 'newemail@example.com'
});

Parameters:

  • data (Object) - ข้อมูลที่ต้องการอัพเดท

Returns: Modal instance (สำหรับ method chaining)

getData()

อ่านข้อมูลที่ผูกไว้

const data = modal.getData();
console.log(data);

Returns: Object - ข้อมูลที่ผูกไว้

destroy()

ลบ modal ออกจาก DOM

modal.destroy();

Returns: void

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

1. Modal แสดงข้อมูลผู้ใช้

// สร้าง modal
const userModal = new Modal({
  id: 'user-modal',
  title: 'ข้อมูลผู้ใช้',
  content: `
    <div class="user-profile">
      <img data-modal-target="avatar" class="avatar">
      <div class="user-info">
        <h3 data-modal-target="name"></h3>
        <p data-modal-target="email"></p>
        <p data-modal-target="role"></p>
      </div>
    </div>
  `,
  width: '500px'
});

// ผูกข้อมูลและแสดง
userModal.bindData({
  avatar: '/images/users/john.jpg',
  name: 'John Doe',
  email: 'john@example.com',
  role: 'Administrator'
}).show();

2. Modal ยืนยันการลบ

function confirmDelete(itemName, onConfirm) {
  const modal = new Modal({
    title: 'ยืนยันการลบ',
    content: `
      <p>คุณแน่ใจหรือไม่ที่จะลบ "<strong>${itemName}</strong>"?</p>
      <p class="text-muted">การดำเนินการนี้ไม่สามารถย้อนกลับได้</p>
      <div class="modal-actions">
        <button class="btn btn-secondary" data-dismiss="modal">ยกเลิก</button>
        <button class="btn btn-danger" id="confirm-delete">ลบ</button>
      </div>
    `,
    width: '400px',
    backdrop: true,
    keyboard: true,
    onShown: function() {
      // Bind confirm button
      document.getElementById('confirm-delete').addEventListener('click', () => {
        onConfirm();
        this.hide();
      });

      // Bind cancel button
      document.querySelector('[data-dismiss="modal"]').addEventListener('click', () => {
        this.hide();
      });
    }
  });

  modal.show();
}

// ใช้งาน
confirmDelete('รายการสินค้า #123', () => {
  console.log('ลบรายการแล้ว');
  // ทำการลบจริง
});

3. Modal แสดงรูปภาพ

function showImageModal(imageUrl, title, description) {
  const modal = new Modal({
    title: title,
    content: `
      <div class="image-modal">
        <img data-modal-target="image" class="modal-image">
        <p data-modal-target="description" class="image-description"></p>
      </div>
    `,
    width: '800px',
    maxWidth: '90%',
    className: 'image-modal-container'
  });

  modal.bindData({
    image: imageUrl,
    description: description
  }).show();
}

// ใช้งาน
showImageModal(
  '/images/product.jpg',
  'สินค้าใหม่',
  'รายละเอียดสินค้า...'
);

4. Modal โหลดข้อมูลจาก API

async function showUserDetails(userId) {
  // สร้าง modal พร้อม loading state
  const modal = new Modal({
    title: 'กำลังโหลด...',
    content: '<div class="spinner">Loading...</div>',
    width: '600px'
  });

  modal.show();

  try {
    // โหลดข้อมูล
    const response = await fetch(`/api/users/${userId}`);
    const user = await response.json();

    // อัพเดทเนื้อหา
    modal.setTitle('ข้อมูลผู้ใช้');
    modal.setContent(`
      <div class="user-details">
        <img data-modal-target="avatar" class="avatar">
        <h3 data-modal-target="name"></h3>
        <p data-modal-target="email"></p>
        <p data-modal-target="bio"></p>
      </div>
    `);

    // ผูกข้อมูล
    modal.bindData(user.data);

  } catch (error) {
    modal.setTitle('เกิดข้อผิดพลาด');
    modal.setContent('<p class="error">ไม่สามารถโหลดข้อมูลได้</p>');
  }
}

// ใช้งาน
showUserDetails(123);

5. Modal แบบฟอร์ม

function showEditForm(userData) {
  const modal = new Modal({
    title: 'แก้ไขข้อมูล',
    content: `
      <form id="edit-form">
        <div class="form-group">
          <label>ชื่อ:</label>
          <input type="text" name="name" data-modal-target="name" class="form-control">
        </div>
        <div class="form-group">
          <label>อีเมล:</label>
          <input type="email" name="email" data-modal-target="email" class="form-control">
        </div>
        <div class="form-actions">
          <button type="submit" class="btn btn-primary">บันทึก</button>
          <button type="button" class="btn btn-secondary" data-dismiss>ยกเลิก</button>
        </div>
      </form>
    `,
    width: '500px',
    onShown: function() {
      const form = document.getElementById('edit-form');

      // Bind form submit
      form.addEventListener('submit', async (e) => {
        e.preventDefault();
        const formData = new FormData(form);
        const data = Object.fromEntries(formData);

        // บันทึกข้อมูล
        await saveUserData(data);
        this.hide();
      });

      // Bind cancel button
      form.querySelector('[data-dismiss]').addEventListener('click', () => {
        this.hide();
      });
    }
  });

  // ผูกข้อมูลเริ่มต้น
  modal.bindData(userData).show();
}

// ใช้งาน
showEditForm({
  name: 'John Doe',
  email: 'john@example.com'
});

API Reference

Properties

Property Type Description
id string ID ของ modal element
modal HTMLElement Modal element
dialog HTMLElement Modal dialog element
header HTMLElement Modal header element
body HTMLElement Modal body element
visible boolean สถานะการแสดงผล
boundData Object ข้อมูลที่ผูกไว้
backdropId string|null ID ของ backdrop

Methods

Constructor

new Modal(options)

Public Methods

  • show() - แสดง modal
  • hide() - ปิด modal
  • setContent(content) - เปลี่ยนเนื้อหา
  • setTitle(title) - เปลี่ยนหัวข้อ
  • bindData(data) - ผูกข้อมูล
  • updateData(data) - อัพเดทข้อมูล
  • getData() - อ่านข้อมูล
  • destroy() - ลบ modal

Private Methods

  • createModal() - สร้าง modal DOM
  • bindEvents() - ผูก event handlers
  • _cleanupModalElements() - ทำความสะอาด elements
  • _setElementContent(element, value, key) - ตั้งค่า element content
  • _sanitizeValue(value, context) - Sanitize ค่า
  • _sanitizeText(text) - Sanitize text
  • _sanitizeHTML(html) - Sanitize HTML
  • _sanitizeURL(url) - Sanitize URL

แนวทางปฏิบัติที่ดี

✅ ควรทำ

1. ใช้ Data Binding แทนการตั้งค่าโดยตรง

// ✅ ดี - ใช้ data binding
modal.bindData({
  name: userName,
  email: userEmail
});

// ❌ ไม่ดี - ตั้งค่าโดยตรง
modal.body.querySelector('.name').textContent = userName;
modal.body.querySelector('.email').textContent = userEmail;

2. ใช้ Lifecycle Callbacks

// ✅ ดี - ใช้ callbacks
const modal = new Modal({
  content: '<div id="data"></div>',
  onShown: async function() {
    const data = await fetchData();
    this.bindData(data);
  }
});

// ❌ ไม่ดี - โหลดข้อมูลก่อนแสดง modal
const data = await fetchData();
modal.bindData(data);
modal.show();

3. ทำความสะอาดเมื่อไม่ใช้แล้ว

// ✅ ดี - destroy เมื่อไม่ใช้
modal.destroy();

// ❌ ไม่ดี - ทิ้งไว้ใน DOM
// modal ยังคงอยู่ใน memory

4. ใช้ Method Chaining

// ✅ ดี - chain methods
modal.bindData(userData).show();

// ❌ ไม่ดี - แยกบรรทัด
modal.bindData(userData);
modal.show();

❌ ไม่ควรทำ

1. อย่าใส่ HTML ที่ไม่ปลอดภัยโดยตรง

// ❌ อันตราย - XSS vulnerability
modal.setContent(`<p>${userInput}</p>`);

// ✅ ปลอดภัย - ใช้ data binding
modal.setContent('<p data-modal-target="message"></p>');
modal.bindData({ message: userInput });

2. อย่าสร้าง Modal หลายตัวซ้อนกัน

// ❌ ไม่ดี - modal ซ้อน modal
modal1.show();
modal2.show(); // จะทับกัน

// ✅ ดี - ปิดก่อนเปิดใหม่
modal1.hide();
setTimeout(() => modal2.show(), 200);

3. อย่าลืมจัดการ Event Listeners

// ❌ ไม่ดี - memory leak
const modal = new Modal({
  onShown: function() {
    document.getElementById('btn').addEventListener('click', handler);
    // ไม่มีการ remove listener
  }
});

// ✅ ดี - cleanup listeners
const modal = new Modal({
  onShown: function() {
    const btn = document.getElementById('btn');
    btn.addEventListener('click', handler);
  },
  onHidden: function() {
    const btn = document.getElementById('btn');
    btn.removeEventListener('click', handler);
  }
});

💡 Tips

1. ใช้ CSS Classes สำหรับ Styling

const modal = new Modal({
  className: 'modal-large modal-primary',
  content: '...'
});

2. ใช้ Template Literals สำหรับ Content ที่ซับซ้อน

const modal = new Modal({
  content: `
    <div class="complex-content">
      <section>...</section>
      <section>...</section>
    </div>
  `
});

3. เก็บ Modal Instance ไว้ใช้ซ้ำ

// สร้างครั้งเดียว
const confirmModal = new Modal({
  title: 'ยืนยัน',
  content: '<p data-modal-target="message"></p>'
});

// ใช้ซ้ำได้
function confirm(message) {
  confirmModal.updateData({ message }).show();
}

เพิ่มเติม