Now.js Framework Documentation

Now.js Framework Documentation

ModalDataBinder - Declarative Modal Data Binding

TH 27 Nov 2025 12:14

ModalDataBinder - Declarative Modal Data Binding

เอกสารฉบับนี้อธิบาย ModalDataBinder ซึ่งเป็น helper สำหรับใช้งาน modal แบบ declarative ผ่าน HTML attributes พร้อมระบบ data binding, template evaluation และ gallery navigation

📋 สารบัญ

  1. ภาพรวม
  2. การติดตั้งและนำเข้า
  3. การใช้งานพื้นฐาน
  4. HTML Attributes
  5. Template Context Evaluation
  6. Gallery Mode
  7. การทำงานกับ Modal Elements
  8. JavaScript API
  9. ตัวอย่างการใช้งาน
  10. API Reference
  11. แนวทางปฏิบัติที่ดี

ภาพรวม

ModalDataBinder เป็น helper ที่ช่วยให้ใช้งาน modal แบบ declarative ผ่าน HTML attributes โดยไม่ต้องเขียน JavaScript เพิ่มเติม รองรับการผูกข้อมูลจาก template loops และ gallery navigation

ฟีเจอร์หลัก

  • Declarative API: ใช้งานผ่าน HTML attributes ได้ทันที
  • Data Binding: ผูกข้อมูลจาก template context อัตโนมัติ
  • Template Evaluation: ประเมินค่า expressions จาก template loops
  • Gallery Navigation: รองรับการนำทางแบบ gallery (prev/next)
  • Dual Mode: รองรับทั้ง pre-existing HTML modals และ Modal instances
  • Auto-initialization: เริ่มทำงานอัตโนมัติเมื่อหน้าโหลด
  • Event Delegation: ใช้ event delegation สำหรับ performance

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

ใช้ ModalDataBinder เมื่อ:

  • มี modal elements อยู่ใน HTML แล้ว
  • ต้องการผูกข้อมูลจาก template loops
  • ต้องการ gallery navigation (prev/next)
  • ต้องการใช้งานแบบ declarative ไม่ต้องเขียน JavaScript

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

  • ต้องการสร้าง modal แบบ programmatic (ใช้ Modal class แทน)
  • ต้องการควบคุมทุกรายละเอียดของ modal
  • ไม่มีข้อมูลที่ต้องผูก

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

ModalDataBinder โหลดมาพร้อมกับ Now.js Framework และเริ่มทำงานอัตโนมัติ:

// ไม่ต้อง import - เริ่มทำงานอัตโนมัติ
console.log(window.ModalDataBinder); // ModalDataBinder object

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

  • Modal – สำหรับสร้าง Modal instances (สำหรับ dynamic modals)
  • BackdropManager – สำหรับจัดการ backdrop
  • TemplateManager – สำหรับ template context evaluation (ทางเลือก)

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

1. Modal แบบง่าย

<!-- Modal Element -->
<div class="modal" id="simple-modal">
  <div class="modal-content">
    <button class="modal-close">&times;</button>
    <div class="modal-body">
      <h3 data-modal-target="title"></h3>
      <p data-modal-target="message"></p>
    </div>
  </div>
</div>

<!-- Trigger Button -->
<button data-modal="simple-modal"
        data-modal-bind="title:'สวัสดี', message:'ยินดีต้อนรับ'">
  เปิด Modal
</button>

2. Modal กับ Template Loop

<!-- Gallery Items -->
<div class="gallery">
  <div class="gallery-item"
       data-modal="image-modal"
       data-modal-bind="image:item.image, title:item.title, desc:item.desc"
       data-image="photo1.jpg"
       data-title="รูปที่ 1"
       data-desc="รายละเอียดรูปที่ 1">
    <img src="photo1.jpg">
  </div>

  <div class="gallery-item"
       data-modal="image-modal"
       data-modal-bind="image:item.image, title:item.title, desc:item.desc"
       data-image="photo2.jpg"
       data-title="รูปที่ 2"
       data-desc="รายละเอียดรูปที่ 2">
    <img src="photo2.jpg">
  </div>
</div>

<!-- Modal -->
<div class="modal" id="image-modal">
  <div class="modal-content">
    <button class="modal-close">&times;</button>
    <div class="modal-body">
      <img data-modal-target="image">
      <h3 data-modal-target="title"></h3>
      <p data-modal-target="desc"></p>
    </div>
  </div>
</div>
<!-- Gallery Items -->
<div class="gallery">
  <div class="gallery-item"
       data-modal="gallery-modal"
       data-modal-bind="image:item.image, title:item.title"
       data-modal-gallery="true"
       data-image="photo1.jpg"
       data-title="รูปที่ 1">
    <img src="photo1.jpg">
  </div>

  <!-- More items... -->
</div>

<!-- Gallery Modal -->
<div class="modal" id="gallery-modal">
  <div class="modal-content">
    <button class="modal-close">&times;</button>
    <div class="modal-body">
      <img data-modal-target="image">
      <h3 data-modal-target="title"></h3>
    </div>
    <div class="modal-controls">
      <button data-modal-nav="prev">‹ ก่อนหน้า</button>
      <button data-modal-nav="next">ถัดไป ›</button>
    </div>
  </div>
</div>

HTML Attributes

Trigger Attributes

Attribute Type Required Description
data-modal string ID ของ modal ที่ต้องการเปิด
data-modal-bind string Data binding expressions (คั่นด้วย comma)
data-modal-gallery boolean เปิดใช้งาน gallery mode
data-modal-gallery-loop boolean วนกลับเมื่อถึงท้าย (default: true)
Attribute Type Description
data-modal-target string ชื่อของ data key ที่จะผูก
data-modal-html boolean ใช้ innerHTML แทน textContent
Attribute Type Description
data-modal-nav string ทิศทางการนำทาง: "prev" หรือ "next"

ตัวอย่าง Data Binding Syntax

<!-- Simple values -->
<button data-modal="my-modal"
        data-modal-bind="name:'John', age:30">
  Open
</button>

<!-- From data attributes -->
<div data-modal="my-modal"
     data-modal-bind="title:item.title, desc:item.desc"
     data-title="My Title"
     data-desc="My Description">
  Click me
</div>

<!-- Mixed -->
<div data-modal="my-modal"
     data-modal-bind="name:item.name, status:'active', count:$index"
     data-name="John">
  Item
</div>

Template Context Evaluation

ModalDataBinder สามารถประเมินค่า expressions จาก template context:

Special Variables

Variable Description Example
$index Index ของ element ใน parent count:$index
$item Dataset ทั้งหมดของ element data:$item
item.property Property จาก dataset name:item.title

Expression Evaluation

<!-- Template Loop Example -->
<div data-for="product of products">
  <div class="product-card"
       data-modal="product-modal"
       data-modal-bind="
         id:item.id,
         name:item.name,
         price:item.price,
         index:$index
       "
       data-id="123"
       data-name="Product Name"
       data-price="999">
    <h3>{product.name}</h3>
  </div>
</div>

Data Attribute Mapping

<!-- Element with data attributes -->
<div data-title="My Title"
     data-description="My Description"
     data-image-url="/image.jpg"
     data-modal="my-modal"
     data-modal-bind="
       title:item.title,
       desc:item.description,
       image:item.imageUrl
     ">
  Click
</div>

Mapping Rules:

  • item.titledata-title
  • item.descriptiondata-description
  • item.imageUrldata-image-url (camelCase → kebab-case)

Gallery mode ช่วยให้สามารถนำทางระหว่าง items ได้:

<!-- Gallery Items -->
<div class="gallery">
  <div data-modal="gallery-modal"
       data-modal-gallery="true"
       data-modal-bind="image:item.image, title:item.title"
       data-image="photo1.jpg"
       data-title="Photo 1">
    <img src="photo1.jpg">
  </div>

  <div data-modal="gallery-modal"
       data-modal-gallery="true"
       data-modal-bind="image:item.image, title:item.title"
       data-image="photo2.jpg"
       data-title="Photo 2">
    <img src="photo2.jpg">
  </div>

  <div data-modal="gallery-modal"
       data-modal-gallery="true"
       data-modal-bind="image:item.image, title:item.title"
       data-image="photo3.jpg"
       data-title="Photo 3">
    <img src="photo3.jpg">
  </div>
</div>

<!-- Gallery Modal -->
<div class="modal" id="gallery-modal">
  <div class="modal-content">
    <button class="modal-close">&times;</button>
    <img data-modal-target="image" class="gallery-image">
    <h3 data-modal-target="title"></h3>

    <!-- Navigation Buttons -->
    <button class="modal-prev" data-modal-nav="prev">‹</button>
    <button class="modal-next" data-modal-nav="next">›</button>
  </div>
</div>
<!-- ไม่วนกลับเมื่อถึงท้าย -->
<div data-modal="gallery-modal"
     data-modal-gallery="true"
     data-modal-gallery-loop="false"
     data-modal-bind="...">
  Item
</div>
<!-- Previous Button -->
<button data-modal-nav="prev">ก่อนหน้า</button>

<!-- Next Button -->
<button data-modal-nav="next">ถัดไป</button>

<!-- Custom Styling -->
<button class="btn-prev" data-modal-nav="prev">
  <i class="icon-arrow-left"></i> ก่อนหน้า
</button>

ModalDataBinder รองรับ 2 โหมดการทำงาน:

1. Pre-existing HTML Modals

สำหรับ modal elements ที่มีอยู่แล้วใน HTML:

<!-- Modal ที่มีอยู่แล้วใน HTML -->
<div class="modal" id="my-modal">
  <div class="modal-content">
    <button class="modal-close">&times;</button>
    <div class="modal-body">
      <h3 data-modal-target="title"></h3>
      <p data-modal-target="message"></p>
    </div>
  </div>
</div>

<!-- Trigger -->
<button data-modal="my-modal"
        data-modal-bind="title:'Hello', message:'World'">
  Open Modal
</button>

คุณสมบัติ:

  • ใช้ modal element ที่มีอยู่แล้ว
  • แสดง/ซ่อนด้วย CSS class .show
  • จัดการ backdrop ผ่าน BackdropManager
  • ไม่มี animation (ใช้ CSS transitions)

2. Dynamic Modal Instances

สำหรับ modal ที่ไม่มี .modal class (จะสร้าง Modal instance):

<!-- Element ที่ไม่ใช่ modal -->
<div id="dynamic-modal">
  <h3 data-modal-target="title"></h3>
  <p data-modal-target="message"></p>
</div>

<!-- Trigger -->
<button data-modal="dynamic-modal"
        data-modal-bind="title:'Dynamic', message:'Modal'">
  Open Dynamic Modal
</button>

คุณสมบัติ:

  • สร้าง Modal instance อัตโนมัติ
  • มี animation support
  • มี accessibility features ครบถ้วน
  • มี lifecycle callbacks

JavaScript API

getInstance(modalId)

อ่าน modal instance หรือ reference

const modalRef = ModalDataBinder.state.modalInstances.get('my-modal');

if (modalRef.isPreExisting) {
  // Pre-existing modal
  console.log(modalRef.element);
} else {
  // Modal instance
  console.log(modalRef); // Modal instance
}

openModalWithData(modalId, data, options)

เปิด modal พร้อมข้อมูล

ModalDataBinder.openModalWithData('my-modal', {
  title: 'Hello',
  message: 'World'
}, {
  gallery: false
});

Parameters:

  • modalId (string) - ID ของ modal
  • data (Object) - ข้อมูลที่ต้องการผูก
  • options (Object) - ตัวเลือกเพิ่มเติม

updateModalData(modalId, data)

อัพเดทข้อมูลของ modal ที่เปิดอยู่

ModalDataBinder.updateModalData('my-modal', {
  message: 'Updated message'
});

Parameters:

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

closeModal(modalId)

ปิด modal

ModalDataBinder.closeModal('my-modal');

Parameters:

  • modalId (string) - ID ของ modal

นำทาง gallery

// ไปรูปถัดไป
ModalDataBinder.navigateGallery('gallery-modal', 'next');

// ไปรูปก่อนหน้า
ModalDataBinder.navigateGallery('gallery-modal', 'prev');

Parameters:

  • modalId (string) - ID ของ modal
  • direction (string) - 'prev' หรือ 'next'

getModalData(modalId)

อ่านข้อมูลของ modal

const data = ModalDataBinder.getModalData('my-modal');
console.log(data);

Parameters:

  • modalId (string) - ID ของ modal

Returns: Object | null

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

<!-- Product Grid -->
<div class="product-grid">
  <div class="product-card"
       data-modal="product-modal"
       data-modal-gallery="true"
       data-modal-bind="
         image:item.image,
         name:item.name,
         price:item.price,
         desc:item.description
       "
       data-image="/products/product1.jpg"
       data-name="สินค้า 1"
       data-price="999"
       data-description="รายละเอียดสินค้า 1">
    <img src="/products/product1.jpg">
    <h3>สินค้า 1</h3>
    <p class="price">฿999</p>
  </div>

  <div class="product-card"
       data-modal="product-modal"
       data-modal-gallery="true"
       data-modal-bind="
         image:item.image,
         name:item.name,
         price:item.price,
         desc:item.description
       "
       data-image="/products/product2.jpg"
       data-name="สินค้า 2"
       data-price="1,299"
       data-description="รายละเอียดสินค้า 2">
    <img src="/products/product2.jpg">
    <h3>สินค้า 2</h3>
    <p class="price">฿1,299</p>
  </div>
</div>

<!-- Product Modal -->
<div class="modal" id="product-modal">
  <div class="modal-content">
    <button class="modal-close">&times;</button>
    <div class="modal-body">
      <img data-modal-target="image" class="product-image">
      <h2 data-modal-target="name"></h2>
      <p class="price" data-modal-target="price"></p>
      <p data-modal-target="desc"></p>
      <button class="btn-add-cart">เพิ่มในตะกร้า</button>
    </div>
    <div class="modal-nav">
      <button data-modal-nav="prev">‹ ก่อนหน้า</button>
      <button data-modal-nav="next">ถัดไป ›</button>
    </div>
  </div>
</div>

2. User Profile Cards

<!-- User Cards -->
<div class="user-grid">
  <div class="user-card"
       data-modal="user-modal"
       data-modal-bind="
         avatar:item.avatar,
         name:item.name,
         email:item.email,
         role:item.role,
         bio:item.bio
       "
       data-avatar="/avatars/user1.jpg"
       data-name="สมชาย ใจดี"
       data-email="somchai@example.com"
       data-role="Developer"
       data-bio="Full-stack developer">
    <img src="/avatars/user1.jpg" class="avatar">
    <h3>สมชาย ใจดี</h3>
    <p>Developer</p>
  </div>

  <!-- More users... -->
</div>

<!-- User Modal -->
<div class="modal" id="user-modal">
  <div class="modal-content">
    <button class="modal-close">&times;</button>
    <div class="modal-body">
      <div class="user-profile">
        <img data-modal-target="avatar" class="avatar-large">
        <h2 data-modal-target="name"></h2>
        <p class="role" data-modal-target="role"></p>
        <p class="email" data-modal-target="email"></p>
        <p class="bio" data-modal-target="bio"></p>
      </div>
    </div>
  </div>
</div>

3. Image Lightbox

<!-- Image Grid -->
<div class="image-grid">
  <div class="image-item"
       data-modal="lightbox"
       data-modal-gallery="true"
       data-modal-bind="image:item.src, caption:item.caption"
       data-src="/images/photo1.jpg"
       data-caption="ภาพที่ 1">
    <img src="/images/thumb1.jpg">
  </div>

  <div class="image-item"
       data-modal="lightbox"
       data-modal-gallery="true"
       data-modal-bind="image:item.src, caption:item.caption"
       data-src="/images/photo2.jpg"
       data-caption="ภาพที่ 2">
    <img src="/images/thumb2.jpg">
  </div>

  <!-- More images... -->
</div>

<!-- Lightbox Modal -->
<div class="modal modal-lightbox" id="lightbox">
  <div class="modal-content">
    <button class="modal-close">&times;</button>
    <img data-modal-target="image" class="lightbox-image">
    <p class="caption" data-modal-target="caption"></p>
    <button class="lightbox-prev" data-modal-nav="prev">‹</button>
    <button class="lightbox-next" data-modal-nav="next">›</button>
  </div>
</div>

<style>
.modal-lightbox {
  background: rgba(0,0,0,0.9);
}
.lightbox-image {
  max-width: 90vw;
  max-height: 80vh;
  object-fit: contain;
}
.lightbox-prev,
.lightbox-next {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  font-size: 48px;
  color: white;
  background: none;
  border: none;
  cursor: pointer;
}
.lightbox-prev { left: 20px; }
.lightbox-next { right: 20px; }
</style>

4. Notification Details

<!-- Notification List -->
<div class="notification-list">
  <div class="notification-item"
       data-modal="notification-modal"
       data-modal-bind="
         title:item.title,
         message:item.message,
         time:item.time,
         type:item.type
       "
       data-title="การแจ้งเตือนใหม่"
       data-message="คุณมีข้อความใหม่จาก Admin"
       data-time="5 นาทีที่แล้ว"
       data-type="info">
    <span class="icon">🔔</span>
    <div class="content">
      <h4>การแจ้งเตือนใหม่</h4>
      <p>คุณมีข้อความใหม่...</p>
    </div>
  </div>

  <!-- More notifications... -->
</div>

<!-- Notification Modal -->
<div class="modal" id="notification-modal">
  <div class="modal-content">
    <button class="modal-close">&times;</button>
    <div class="modal-body">
      <h2 data-modal-target="title"></h2>
      <p class="time" data-modal-target="time"></p>
      <p class="message" data-modal-target="message"></p>
      <span class="badge" data-modal-target="type"></span>
    </div>
  </div>
</div>

5. Dynamic Content with Template

<!-- Template Loop -->
<div data-for="article of articles">
  <article class="article-card"
           data-modal="article-modal"
           data-modal-bind="
             title:item.title,
             author:item.author,
             date:item.date,
             content:item.content,
             tags:item.tags
           "
           data-title="{article.title}"
           data-author="{article.author}"
           data-date="{article.date}"
           data-content="{article.content}"
           data-tags="{article.tags}">
    <h3>{article.title}</h3>
    <p class="meta">โดย {article.author} • {article.date}</p>
  </article>
</div>

<!-- Article Modal -->
<div class="modal" id="article-modal">
  <div class="modal-content">
    <button class="modal-close">&times;</button>
    <div class="modal-body">
      <h1 data-modal-target="title"></h1>
      <p class="meta">
        โดย <span data-modal-target="author"></span>
        • <span data-modal-target="date"></span>
      </p>
      <div data-modal-target="content" data-modal-html></div>
      <div class="tags" data-modal-target="tags"></div>
    </div>
  </div>
</div>

API Reference

Configuration

ModalDataBinder.config = {
  debug: true,                    // เปิด debug logging
  galleryClass: 'modal-gallery',  // CSS class สำหรับ gallery
  bindAttribute: 'data-modal-bind',
  targetAttribute: 'data-modal-target',
  sanitize: true                  // เปิด sanitization
};

State

ModalDataBinder.state = {
  initialized: false,
  modalInstances: new Map(),  // modalId => Modal instance or {element, isPreExisting}
  galleryData: new Map()      // modalId => {items, currentIndex, loop}
};

Methods

Public Methods

  • init() - เริ่มต้น ModalDataBinder
  • openModalWithData(modalId, data, options) - เปิด modal พร้อมข้อมูล
  • updateModalData(modalId, data) - อัพเดทข้อมูล
  • closeModal(modalId) - ปิด modal
  • navigateGallery(modalId, direction) - นำทาง gallery
  • getModalData(modalId) - อ่านข้อมูล
  • destroy() - ทำลาย instances ทั้งหมด

Private Methods

  • bindModalTriggers() - ผูก click events
  • bindGalleryNavigation() - ผูก navigation events
  • handleModalTrigger(trigger) - จัดการ trigger click
  • parseBindData(bindStr) - แปลง bind string เป็น object
  • evaluateBindData(bindData, trigger) - ประเมินค่า expressions
  • findTemplateContext(element) - หา template context
  • evaluateExpression(expr, context, trigger) - ประเมินค่า expression
  • setupGallery(modalId, trigger, data) - ตั้งค่า gallery
  • _bindDataToElement(modalElement, data) - ผูกข้อมูลกับ element
  • _setElementContent(element, value, key) - ตั้งค่า content
  • _showPreExistingModal(modalElement) - แสดง pre-existing modal
  • _bindPreExistingModalEvents(modalElement) - ผูก events
  • log(...args) - Debug logging

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

✅ ควรทำ

1. ใช้ Data Attributes สำหรับข้อมูล

<!-- ✅ ดี - ใช้ data attributes -->
<div data-modal="my-modal"
     data-modal-bind="name:item.name, email:item.email"
     data-name="John"
     data-email="john@example.com">
  Click
</div>

<!-- ❌ ไม่ดี - hardcode ค่า -->
<div data-modal="my-modal"
     data-modal-bind="name:'John', email:'john@example.com'">
  Click
</div>

2. ใช้ Gallery Mode สำหรับ Collections

<!-- ✅ ดี - gallery mode -->
<div data-modal="gallery"
     data-modal-gallery="true"
     data-modal-bind="...">
  Item
</div>

<!-- ❌ ไม่ดี - แยก modal แต่ละ item -->
<div data-modal="item1-modal">Item 1</div>
<div data-modal="item2-modal">Item 2</div>

3. ใช้ Semantic HTML

<!-- ✅ ดี - semantic -->
<button data-modal="confirm-modal">Confirm</button>

<!-- ❌ ไม่ดี - div as button -->
<div data-modal="confirm-modal">Confirm</div>

❌ ไม่ควรทำ

1. อย่าใส่ HTML ใน Bind Expressions

<!-- ❌ อันตราย - XSS -->
<div data-modal="my-modal"
     data-modal-bind="content:'<script>alert(1)</script>'">
  Click
</div>

<!-- ✅ ปลอดภัย - ใช้ data attributes -->
<div data-modal="my-modal"
     data-modal-bind="content:item.content"
     data-content="Safe content">
  Click
</div>

2. อย่าใช้ Modal ID ซ้ำ

<!-- ❌ ไม่ดี - ID ซ้ำ -->
<div class="modal" id="my-modal">...</div>
<div class="modal" id="my-modal">...</div>

<!-- ✅ ดี - ID ไม่ซ้ำ -->
<div class="modal" id="modal-1">...</div>
<div class="modal" id="modal-2">...</div>

3. อย่าลืมปุ่มปิด

<!-- ❌ ไม่ดี - ไม่มีปุ่มปิด -->
<div class="modal" id="my-modal">
  <div class="modal-content">
    <p>Content</p>
  </div>
</div>

<!-- ✅ ดี - มีปุ่มปิด -->
<div class="modal" id="my-modal">
  <div class="modal-content">
    <button class="modal-close">&times;</button>
    <p>Content</p>
  </div>
</div>

💡 Tips

1. ใช้ Debug Mode

// เปิด debug logging
ModalDataBinder.config.debug = true;

// ดู logs ใน console
// [ModalDataBinder] Using pre-existing modal element: my-modal
// [ModalDataBinder] Opened modal my-modal with data {...}

2. ตรวจสอบ Modal State

// ดู modal instances
console.log(ModalDataBinder.state.modalInstances);

// ดู gallery data
console.log(ModalDataBinder.state.galleryData);

3. ใช้ CSS สำหรับ Animation

.modal {
  opacity: 0;
  transition: opacity 0.3s;
}

.modal.show {
  opacity: 1;
}

เพิ่มเติม