Now.js Framework Documentation

Now.js Framework Documentation

ResponseHandler

TH 04 Dec 2025 06:21

ResponseHandler

ภาพรวม

ResponseHandler เป็นระบบจัดการ API Response แบบรวมศูนย์ที่ช่วยลดความซับซ้อนของโค้ด JavaScript โดยการให้เซิร์ฟเวอร์ส่ง "คำสั่ง" (actions) มาพร้อมกับ response แทนที่จะต้องเขียนโค้ดจัดการแต่ละ case ซ้ำๆ ในฝั่ง client

จุดเด่น:

  • ลดโค้ดซ้ำซ้อน - API ควบคุมการแสดงผลและพฤติกรรมได้โดยตรง
  • รองรับ action หลากหลายประเภท (notification, modal, redirect, DOM manipulation)
  • Hybrid Modal Approach - Frontend กำหนด template, Backend ส่งแค่ data (แยก concerns)
  • Template-Driven Modals - โหลดและ render template อัตโนมัติพร้อม data binding
  • สามารถสร้าง custom action handler ได้
  • รองรับการทำงานแบบ async/await
  • มีระบบ condition เพื่อควบคุมการทำงานของ action
  • มี Priority System สำหรับ modal configuration (API force > API suggest > Frontend default)
  • Security-first: ปิด eval โดย default

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

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

// ไม่ต้อง import - พร้อมใช้งานทันที
console.log(window.ResponseHandler); // อ็อบเจ็กต์ ResponseHandler ที่ผูกไว้กับ window

การกำหนดค่า

ตัวเลือก Configuration

ตัวเลือก ประเภท ค่าเริ่มต้น คำอธิบาย
debug Boolean false เปิด/ปิด debug logging
defaultRedirectDelay Number 1000 ระยะเวลาหน่วงการ redirect (ms)
defaultNotificationDuration Number 5000 ระยะเวลาแสดง notification (ms)
allowEval Boolean false อนุญาตให้ใช้ eval action (ไม่แนะนำ)
autoHandleFormResponses Boolean true จัดการ form response อัตโนมัติ

การ Initialize

// Auto-initialize (ใช้ค่า default)
ResponseHandler.init();

// Initialize พร้อม custom config
ResponseHandler.init({
  debug: true,
  defaultRedirectDelay: 2000,
  defaultNotificationDuration: 3000
});

โครงสร้าง API Response

รูปแบบพื้นฐาน

{
  "success": true,
  "message": "บันทึกข้อมูลสำเร็จ",
  "data": {...},
  "actions": [
    {
      "type": "notification",
      "level": "success",
      "message": "บันทึกข้อมูลเรียบร้อย"
    },
    {
      "type": "redirect",
      "url": "/dashboard",
      "delay": 1000
    }
  ]
}

รูปแบบ Action เดี่ยว

{
  "success": true,
  "type": "alert",
  "message": "ทำรายการสำเร็จ"
}

Action พร้อม Condition

{
  "type": "notification",
  "message": "มีข้อความใหม่",
  "condition": true
}

Methods

init(options)

เริ่มต้นการทำงานของ ResponseHandler

Parameters:

  • options (Object, optional): ตัวเลือก configuration

Returns: ResponseHandler

ตัวอย่าง:

ResponseHandler.init({
  debug: true,
  defaultRedirectDelay: 1500
});

process(response, context)

ประมวลผล API response และทำงานตาม actions ที่กำหนด

Parameters:

  • response (Object): API response object
  • context (Object, optional): Context object สำหรับส่งข้อมูลเพิ่มเติมให้ handlers

Returns: Promise<Object> - response object

ตัวอย่าง:

// ใช้กับ fetch API
const response = await fetch('/api/save', {
  method: 'POST',
  body: JSON.stringify(data)
});

const json = await response.json();
await ResponseHandler.process(json);

// ใช้พร้อม context
await ResponseHandler.process(json, {
  tableId: 'users-table',
  formId: 'user-form'
});

executeAction(action, context)

ทำงาน action เดี่ยว

Parameters:

  • action (Object): Action object
  • context (Object, optional): Context object

Returns: Promise<void>

ตัวอย่าง:

await ResponseHandler.executeAction({
  type: 'notification',
  level: 'success',
  message: 'บันทึกสำเร็จ'
});

registerHandler(type, handler)

ลงทะเบียน custom action handler

Parameters:

  • type (String): ชื่อ action type
  • handler (Function): Function สำหรับจัดการ action (action, context) => {}

Returns: void

ตัวอย่าง:

// สร้าง custom action
ResponseHandler.registerHandler('playSound', (action) => {
  const audio = new Audio(action.soundUrl);
  audio.volume = action.volume || 1.0;
  audio.play();
});

// ใช้งาน
await ResponseHandler.executeAction({
  type: 'playSound',
  soundUrl: '/sounds/success.mp3',
  volume: 0.5
});

executeCallback(fn, args)

เรียก callback function

Parameters:

  • fn (Function|String): Function หรือชื่อ function ใน window scope
  • args (Array, optional): Arguments สำหรับส่งให้ function

Returns: void

ตัวอย่าง:

// เรียก function โดยตรง
ResponseHandler.executeCallback(() => {
  console.log('Done!');
});

// เรียก function จากชื่อ
window.myCallback = (msg) => alert(msg);
ResponseHandler.executeCallback('myCallback', ['Hello!']);

emit(eventName, data)

ส่ง custom event

Parameters:

  • eventName (String): ชื่อ event
  • data (Any): ข้อมูลที่จะส่ง

Returns: void

ตัวอย่าง:

ResponseHandler.emit('data:updated', {
  id: 123,
  type: 'user'
});

Built-in Action Types

1. Notification

แสดง notification message

{
  "type": "notification",
  "level": "success",
  "message": "บันทึกข้อมูลสำเร็จ",
  "duration": 3000
}

Properties:

  • level: "success" | "info" | "warning" | "error"
  • message: ข้อความที่จะแสดง
  • duration: ระยะเวลาแสดง (ms)

2. Alert

แสดง browser alert dialog

{
  "type": "alert",
  "message": "กรุณากรอกข้อมูลให้ครบถ้วน"
}

3. Confirm

แสดง confirm dialog และเรียก callback เมื่อกด OK

{
  "type": "confirm",
  "message": "ต้องการลบข้อมูลใช่หรือไม่?",
  "onConfirm": "deleteRecord"
}

Properties:

  • message: ข้อความถาม
  • onConfirm: Function หรือชื่อ function ที่จะเรียกเมื่อ confirm

4. Modal (Hybrid Approach)

เปิด/ปิด modal dialog พร้อมรองรับ template loading และ data binding

Hybrid Modal Approach:
ResponseHandler รองรับ 3 แนวทางในการแสดง modal:

  1. Frontend-Driven (แนะนำ 90%) - Frontend กำหนด template ใน HTML
  2. API Suggestion - API แนะนำ template (ถ้า frontend ไม่ได้กำหนด)
  3. API Force Override - API บังคับใช้ template (กรณีพิเศษ 10%)

Priority System:

API Force (force: true)     [สูงสุด]
    ↓
API Suggest                  [กลาง]
    ↓
Frontend Config              [ต่ำ - Default]

แนวทาง 1: Frontend-Driven (แนะนำ) ⭐

HTML Configuration:

<table data-row-actions='{
  "edit": {
    "element": "button",
    "className": "btn btn-success icon-edit",
    "modal": {
      "template": "editprofile",
      "title": "Edit User Profile",
      "className": "large-modal"
    }
  }
}'>

API Response (Simple):

{
  "success": true,
  "data": {
    "id": 123,
    "name": "John Doe",
    "email": "john@example.com"
  },
  "options": {
    "provinces": [...],
    "genders": [...]
  }
}

ข้อดี:

  • ✅ Backend ไม่ต้องรู้จัก UI template
  • ✅ Frontend ควบคุม UX ได้เต็มที่
  • ✅ API เดียว ใช้ได้หลาย template
  • ✅ แก้ไข template ไม่ต้องแก้ backend

แนวทาง 2: API Suggestion

API แนะนำ template (ใช้ถ้า frontend ไม่ได้กำหนด):

{
  "success": true,
  "data": {...},
  "actions": [{
    "type": "modal",
    "template": "editprofile",
    "title": "Edit Profile"
  }]
}

แนวทาง 3: API Force Override

API บังคับใช้ template (กรณี role-based UI, security):

{
  "success": true,
  "data": {...},
  "actions": [{
    "type": "modal",
    "template": "editprofile_admin",
    "force": true,
    "title": "Admin: Edit User"
  }]
}

Template Loading Modes:

// Mode 1: Template name (auto-load from /templates/modals/)
{
  "type": "modal",
  "template": "editprofile"
}

// Mode 2: Template URL
{
  "type": "modal",
  "templateUrl": "/templates/custom/form.html"
}

// Mode 3: HTML content (ready to use)
{
  "type": "modal",
  "html": "<form>...</form>"
}

// Mode 4: Simple content (backward compatible)
{
  "type": "modal",
  "content": "<h2>Hello</h2>"
}

Actions:

  • show / open: เปิด modal
  • close / hide: ปิด modal

Properties:

  • template (string): ชื่อ template (โหลดจาก /templates/modals/{name}.html)
  • templateUrl (string): URL ของ template
  • html (string): HTML สำเร็จรูป
  • content (string): HTML content (เหมือน html)
  • dataKey (string): Path ไปยัง data ใน response (เช่น "data", "data.user")
  • optionsKey (string): Path ไปยัง options
  • title (string): ชื่อ modal
  • className (string): CSS class
  • width / maxWidth / height / maxHeight: ขนาด modal
  • closeButton (boolean): แสดงปุ่มปิด (default: true)
  • backdrop (boolean): แสดง backdrop (default: true)
  • keyboard (boolean): ปิดด้วย ESC (default: true)
  • buttons (array): ปุ่มใน footer
  • force (boolean): บังคับใช้ config นี้ (override frontend)
  • onShown / onHide (function): Callbacks

Auto-extraction:

  • data - ดึงจาก response.data.data อัตโนมัติ
  • options - ดึงจาก response.data.options อัตโนมัติ

State Management:

  • สร้าง modal state module ใน StateManager อัตโนมัติ
  • เข้าถึงได้ผ่าน StateManager.state.modal
  • มี formData, options, loading, errors

5. Redirect

เปลี่ยนเส้นทาง (URL)

{
  "type": "redirect",
  "url": "/dashboard",
  "delay": 1000,
  "target": "_self"
}

Special URLs:

  • "reload": โหลดหน้าปัจจุบันใหม่
  • "refresh": โหลดหน้าใหม่
  • "back": ย้อนกลับประวัติ

Properties:

  • url: URL ปลายทาง
  • delay: หน่วงเวลา (ms)
  • target: "_self" | "_blank" | "_parent" | "_top"

6. Update

อัปเดตเนื้อหาของ element

{
  "type": "update",
  "target": "#result",
  "content": "<p>ข้อมูลใหม่</p>",
  "method": "html"
}

Methods:

  • html: ตั้งค่า innerHTML
  • text: ตั้งค่า textContent
  • value: ตั้งค่า value (สำหรับ input)
  • append: เพิ่มต่อท้าย
  • prepend: เพิ่มไว้ข้างหน้า

7. Remove

ลบ element

{
  "type": "remove",
  "target": ".temp-message",
  "animate": true
}

Properties:

  • target: Selector หรือ element
  • animate: แสดง animation ก่อนลบ

8. Class

จัดการ CSS class

{
  "type": "class",
  "target": "#myDiv",
  "class": "active highlight",
  "method": "add"
}

Methods:

  • add: เพิ่ม class
  • remove: ลบ class
  • toggle: สลับ class

9. Attribute

ตั้งค่า/ลบ attribute

{
  "type": "attribute",
  "target": "#myInput",
  "name": "disabled",
  "value": true
}

Properties:

  • name: ชื่อ attribute
  • value: ค่า (ใช้ null เพื่อลบ attribute)

10. Event

ส่ง custom event

{
  "type": "event",
  "event": "data:updated",
  "target": "#dataTable",
  "data": {"id": 123},
  "bubbles": true
}

11. Callback

เรียก callback function

{
  "type": "callback",
  "function": "window.refreshTable",
  "args": [123, "user"]
}

12. Download

ดาวน์โหลดไฟล์

{
  "type": "download",
  "url": "/export/data.xlsx",
  "filename": "report-2024.xlsx"
}

13. Clipboard

คัดลอกข้อความไปยัง clipboard

{
  "type": "clipboard",
  "text": "https://example.com/share/123",
  "notify": true,
  "message": "คัดลอก URL แล้ว"
}

14. Scroll

เลื่อนไปยัง element

{
  "type": "scroll",
  "target": "#top",
  "behavior": "smooth",
  "block": "center"
}

15. Focus

โฟกัสที่ element

{
  "type": "focus",
  "target": "#emailInput",
  "select": true
}

Properties:

  • select: เลือกข้อความทั้งหมด (สำหรับ input)

16. Reload

โหลดส่วนประกอบใหม่

{
  "type": "reload",
  "target": "#users-table"
}

17. Eval (ไม่แนะนำ)

รันโค้ด JavaScript (ต้องเปิดใช้ allowEval)

{
  "type": "eval",
  "code": "console.log('Hello');"
}

⚠️ คำเตือน: ใช้เฉพาะเมื่อจำเป็นจริงๆ และควบคุม input อย่างเข้มงวด

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

1. การบันทึกข้อมูลพื้นฐาน

API Response (PHP):

<?php
echo json_encode([
    'success' => true,
    'message' => 'บันทึกข้อมูลสำเร็จ',
    'data' => ['id' => 123],
    'actions' => [
        [
            'type' => 'notification',
            'level' => 'success',
            'message' => 'บันทึกข้อมูลผู้ใช้เรียบร้อย'
        ],
        [
            'type' => 'redirect',
            'url' => '/users',
            'delay' => 1500
        ]
    ]
]);

JavaScript:

document.querySelector('#saveBtn').addEventListener('click', async () => {
  const formData = new FormData(document.querySelector('#userForm'));

  const response = await fetch('/api/users/save', {
    method: 'POST',
    body: formData
  });

  const json = await response.json();
  await ResponseHandler.process(json);
  // จะแสดง notification และ redirect อัตโนมัติ
});

2. Form Validation

API Response:

{
  "success": false,
  "message": "ข้อมูลไม่ถูกต้อง",
  "actions": [
    {
      "type": "notification",
      "level": "error",
      "message": "กรุณากรอกข้อมูลให้ครบถ้วน"
    },
    {
      "type": "class",
      "target": "#emailInput",
      "class": "error",
      "method": "add"
    },
    {
      "type": "focus",
      "target": "#emailInput",
      "select": true
    }
  ]
}

3. การลบข้อมูลพร้อม Confirm

JavaScript:

async function deleteUser(userId) {
  const response = await fetch(`/api/users/${userId}`, {
    method: 'DELETE'
  });

  const json = await response.json();
  await ResponseHandler.process(json);
}

API Response:

{
  "success": true,
  "actions": [
    {
      "type": "notification",
      "level": "success",
      "message": "ลบข้อมูลแล้ว"
    },
    {
      "type": "remove",
      "target": "#user-row-123",
      "animate": true
    },
    {
      "type": "event",
      "event": "user:deleted",
      "data": {"id": 123}
    }
  ]
}

4. Hybrid Modal - Frontend-Driven (แนะนำ)

ขั้นตอนที่ 1: กำหนด Modal Config ใน HTML

<table data-table="users"
       data-source="api/v1/users"
       data-row-actions='{
  "edit": {
    "element": "button",
    "className": "btn btn-success icon-edit",
    "title": "Edit",
    "modal": {
      "template": "editprofile",
      "title": "Edit User Profile",
      "className": "large-modal"
    }
  },
  "view": {
    "element": "button",
    "className": "btn icon-published1",
    "modal": {
      "template": "viewprofile",
      "className": "readonly-modal"
    }
  }
}'>
</table>

ขั้นตอนที่ 2: สร้าง Template (/templates/modals/editprofile.html)

<div class="modal-form-container">
  <form id="editProfileForm" class="form">
    <input type="hidden" name="id" data-model="id">

    <div class="form-group">
      <label for="name">Name</label>
      <input type="text" id="name" name="name"
             data-model="name" data-text="name" required>
    </div>

    <div class="form-group">
      <label for="email">Email</label>
      <input type="email" id="email" name="email"
             data-model="email" data-text="email">
    </div>

    <div class="form-group">
      <label for="province">Province</label>
      <select id="province" name="provinceID" data-model="provinceID">
        <option value="">Select Province</option>
        <template data-for="province in options.provinces">
          <option data-attr="value:province.id"
                  data-text="province.name"
                  data-attr="selected:provinceID==province.id">
          </option>
        </template>
      </select>
    </div>

    <div class="form-actions">
      <button type="submit" class="btn btn-primary">Save</button>
      <button type="button" class="btn btn-secondary"
              onclick="window.modal?.hide()">Cancel</button>
    </div>
  </form>
</div>

ขั้นตอนที่ 3: API Response (Simple)

// PHP API Endpoint
public function action(Request $request) {
    $action = $request->post('action')->toString();

    if ($action === 'edit') {
        $user = $db->first('user', [['id', $request->post('id')->toInt()]]);
        $provinces = $db->select('province')->execute()->fetchAll();

        // Simple response - Frontend controls modal
        return $this->successResponse([
            'data' => $user,
            'options' => [
                'provinces' => $provinces,
                'genders' => [
                    ['value' => 'm', 'label' => 'Male'],
                    ['value' => 'f', 'label' => 'Female']
                ]
            ]
        ]);
    }
}
{
  "success": true,
  "data": {
    "id": 123,
    "name": "John Doe",
    "email": "john@example.com",
    "provinceID": 10
  },
  "options": {
    "provinces": [
      {"id": 10, "name": "Bangkok"},
      {"id": 20, "name": "Chiang Mai"}
    ],
    "genders": [
      {"value": "m", "label": "Male"},
      {"value": "f", "label": "Female"}
    ]
  }
}

ผลลัพธ์:

  • ✅ Modal เปิดอัตโนมัติพร้อม template editprofile
  • ✅ ข้อมูล user ถูก bind เข้า form fields
  • ✅ Select options ถูกสร้างจาก options.provinces
  • ✅ Form พร้อมใช้งานทันที

5. Modal - API Force Override (Role-Based UI)

Use Case: Admin และ User ต้องใช้ template ต่างกัน

// PHP API
public function action(Request $request) {
    $user = $this->getUser();
    $targetUser = $db->first('user', [['id', $request->post('id')->toInt()]]);

    // Admin gets different template
    $template = ($user->status == 1) ? 'editprofile_admin' : 'editprofile';

    return $this->successResponse([
        'data' => $targetUser,
        'actions' => [[
            'type' => 'modal',
            'template' => $template,
            'force' => true,  // Override frontend config
            'title' => $user->status == 1 ? 'Admin: Edit User' : 'Edit Profile'
        ]]
    ]);
}

6. Modal - HTML Content (Backward Compatible)

API Response:

{
  "success": true,
  "data": {
    "name": "สมชาย ใจดี",
    "email": "somchai@example.com"
  },
  "actions": [
    {
      "type": "modal",
      "html": "<div class='user-detail'><h2>สมชาย ใจดี</h2><p>Email: somchai@example.com</p></div>",
      "className": "user-modal",
      "buttons": [
        {
          "text": "แก้ไข",
          "className": "button primary",
          "onclick": "editUser(123)"
        },
        {
          "text": "ปิด",
          "className": "button",
          "onclick": "modal.hide()"
        }
      ]
    }
  ]
}

5. Multiple Actions ต่อเนื่อง

API Response:

{
  "success": true,
  "actions": [
    {
      "type": "notification",
      "level": "success",
      "message": "อัปโหลดสำเร็จ"
    },
    {
      "type": "update",
      "target": "#fileList",
      "content": "<li>document.pdf</li>",
      "method": "append"
    },
    {
      "type": "class",
      "target": "#uploadBtn",
      "class": "loading",
      "method": "remove"
    },
    {
      "type": "event",
      "event": "file:uploaded",
      "data": {"filename": "document.pdf"}
    }
  ]
}

6. Custom Action Handler

JavaScript:

// ลงทะเบียน custom action
ResponseHandler.registerHandler('animateProgress', (action) => {
  const progressBar = document.querySelector(action.target);
  const targetValue = action.value;

  let current = 0;
  const interval = setInterval(() => {
    current += 5;
    progressBar.style.width = `${current}%`;
    progressBar.textContent = `${current}%`;

    if (current >= targetValue) {
      clearInterval(interval);
    }
  }, 50);
});

// ใช้งาน
await ResponseHandler.executeAction({
  type: 'animateProgress',
  target: '#progress',
  value: 75
});

API Response:

{
  "success": true,
  "actions": [
    {
      "type": "animateProgress",
      "target": "#uploadProgress",
      "value": 100
    },
    {
      "type": "notification",
      "level": "success",
      "message": "อัปโหลดเสร็จสิ้น"
    }
  ]
}

Best Practices สำหรับ Hybrid Modal

1. ใช้ Frontend-Driven เป็นหลัก (90%)

เหตุผล:

  • แยก UI logic จาก business logic
  • ง่ายต่อการ maintain (เปลี่ยน UI ไม่ต้องแก้ backend)
  • Developer frontend สามารถทำงานอิสระ
  • ลด coupling ระหว่าง frontend และ backend

ตัวอย่าง:

<!-- กำหนดทุกอย่างใน HTML -->
<button data-row-actions='{
  "edit": {
    "modal": {
      "template": "editprofile",
      "title": "แก้ไขข้อมูล",
      "className": "large-modal"
    }
  },
  "delete": {
    "modal": {
      "template": "confirm-delete",
      "title": "ยืนยันการลบ"
    }
  }
}'>Actions</button>
// API แค่ส่งข้อมูล
echo json_encode([
    'success' => true,
    'data' => $userData,
    'options' => $dropdowns
]);

2. ใช้ API Force Override เฉพาะกรณีพิเศษ (10%)

Use Cases ที่เหมาะสม:

  • Role-based UI (Admin ดู template แบบหนึ่ง, User ดูอีกแบบ)
  • Feature flags / A/B testing
  • Dynamic workflow (ขั้นตอนแตกต่างกันตามสถานะ)
  • Emergency override (ปิดระบบบางส่วนแบบ real-time)

ตัวอย่าง:

// กรณีพิเศษ: role-based
$template = $isAdmin ? 'admin-edit-profile' : 'user-edit-profile';

echo json_encode([
    'success' => true,
    'actions' => [
        [
            'type' => 'modal',
            'template' => $template,  // Force override
            'force' => true,
            'data' => $userData
        ]
    ]
]);

3. ใช้ Auto-Extraction สำหรับ Simple Cases

ข้อดี:

  • ลด boilerplate code
  • API response กระชับ
  • ทำงานได้ทันทีโดยไม่ต้อง config

ตัวอย่าง:

// Simple response - auto extract
echo json_encode([
    'success' => true,
    'data' => [
        'id' => 1,
        'name' => 'John',
        'email' => 'john@example.com'
    ],
    'options' => [
        'roles' => ['admin', 'user', 'guest'],
        'status' => ['active', 'inactive']
    ]
]);
// Frontend config - ระบบจะ extract data.data และ data.options อัตโนมัติ
<button data-row-actions='{
  "edit": {
    "modal": {
      "template": "editprofile"
    }
  }
}'>Edit</button>

4. ใช้ Explicit Data สำหรับ Complex Cases

Use Cases:

  • ข้อมูลซ้อน (nested objects/arrays)
  • ต้องการ transform data ก่อนส่ง modal
  • ข้อมูลมาจากหลาย source
  • ต้องการควบคุมโครงสร้างแน่นอน

ตัวอย่าง:

// Complex data structure
echo json_encode([
    'success' => true,
    'actions' => [
        [
            'type' => 'modal',
            'template' => 'advanced-editor',
            'data' => [
                'user' => $userData,
                'permissions' => $userPermissions,
                'history' => $recentActivity,
                'metadata' => [
                    'lastLogin' => $lastLogin,
                    'createdBy' => $creator
                ]
            ],
            'options' => [
                'roles' => $availableRoles,
                'departments' => $departments,
                'groups' => $groups
            ]
        ]
    ]
]);

5. จัดการ Error อย่างถูกต้อง

Pattern ที่แนะนำ:

try {
    // Validation
    if (empty($data['email'])) {
        throw new Exception('กรุณากรอกอีเมล');
    }

    // Business logic
    $result = $model->save($data);

    echo json_encode([
        'success' => true,
        'actions' => [
            ['type' => 'notification', 'level' => 'success', 'message' => 'บันทึกสำเร็จ'],
            ['type' => 'closeModal'],
            ['type' => 'reload', 'target' => '#users-table']
        ]
    ]);

} catch (Exception $e) {
    echo json_encode([
        'success' => false,
        'actions' => [
            [
                'type' => 'modal',
                'html' => '<div class="error-modal">' . htmlspecialchars($e->getMessage()) . '</div>',
                'title' => 'เกิดข้อผิดพลาด',
                'className' => 'error-modal'
            ]
        ]
    ]);
}

6. Template Organization

โครงสร้างที่แนะนำ:

templates/
├── modals/
│   ├── crud/              # CRUD operations
│   │   ├── edit-user.html
│   │   ├── create-user.html
│   │   └── delete-confirm.html
│   ├── forms/             # Complex forms
│   │   ├── advanced-search.html
│   │   └── bulk-import.html
│   ├── viewers/           # View-only modals
│   │   ├── user-profile.html
│   │   └── activity-log.html
│   └── common/            # Reusable components
│       ├── confirm.html
│       └── alert.html

Naming Convention:

  • ใช้ kebab-case: edit-user.html, confirm-delete.html
  • ชื่อบอกหน้าที่ชัด: user-advanced-search.html (ไม่ใช่ modal1.html)
  • Group ตาม feature: product-edit.html, product-create.html, product-delete.html

7. Data Binding Best Practices

✅ Good:

<!-- ใช้ data attributes สำหรับ dynamic content -->
<input type="text" data-model="name" />
<span data-text="email"></span>
<img data-attr="src:avatarUrl" alt="Avatar" />

<!-- Loop กับ conditional -->
<select data-model="roleID">
  <option
    data-for="role in options.roles"
    data-attr="value:role.id,selected:roleID==role.id"
    data-text="role.name">
  </option>
</select>

❌ Avoid:

<!-- ไม่ควร inline ค่าตรงๆ -->
<input type="text" value="<?= $name ?>" />
<span><?= $email ?></span>

<!-- ไม่ควร mix server-side และ client-side rendering -->
<select>
  <?php foreach ($roles as $role): ?>
    <option value="<?= $role['id'] ?>"><?= $role['name'] ?></option>
  <?php endforeach; ?>
</select>

8. State Management

ใช้ StateManager สำหรับ modal state:

// ใน template
const modal = StateManager.getModule('modal');

// Listen to changes
modal.watch('data.status', (newStatus) => {
  // Update UI based on status
});

// Update state
modal.set('data.notes', newNotes);

ข้อดี:

  • Reactive updates (เปลี่ยน state, UI update อัตโนมัติ)
  • Easy debugging (ดู state ทั้งหมดได้)
  • Undo/Redo implementation ได้ง่าย

9. Performance Optimization

Lazy Load Templates:

// กำหนด template path แทนที่จะโหลดทันที
ResponseHandler.config.templateBasePath = '/templates/modals/';

// Template จะโหลดตอนเปิด modal เท่านั้น
<button data-row-actions='{
  "edit": {
    "modal": {
      "template": "heavy-editor"  // โหลดแค่ตอนต้องใช้
    }
  }
}'>Edit</button>

Cache Templates:

// TemplateManager cache อัตโนมัติ
// เปิด modal ครั้งที่ 2+ จะเร็วกว่าครั้งแรก

10. Security Considerations

ทำ Validation ทั้ง Client และ Server:

// Client validation
async function submitForm(data) {
  if (!data.email || !data.email.includes('@')) {
    NotificationManager.error('กรุณากรอกอีเมลที่ถูกต้อง');
    return;
  }

  const response = await fetch('/api/save', {...});
  await ResponseHandler.process(response);
}
// Server validation (ต้องมี!)
if (!filter_var($data['email'], FILTER_VALIDATE_EMAIL)) {
    throw new Exception('รูปแบบอีเมลไม่ถูกต้อง');
}

Sanitize Output:

<!-- ใน template ใช้ data attributes (auto-escaped) -->
<div data-text="userInput"></div>

<!-- ถ้าต้องการ HTML ให้ระบุชัด -->
<div data-html="trustedHtml"></div>

ไม่ควร:

// ❌ ไม่ใช้ eval
{
  "type": "eval",
  "code": userInput  // Dangerous!
}

// ❌ ไม่ใช้ innerHTML กับ user input
element.innerHTML = userInput;

Common Pitfalls (Hybrid Modal)

1. ลืมตั้งค่า modalConfig

อาการ: Modal ไม่เปิด หรือเปิดแต่ไม่มีข้อมูล

สาเหตุ:

<!-- ❌ ขาด modal config -->
<button data-row-actions='{"edit": {}}'>Edit</button>

แก้ไข:

<!-- ✅ ระบุ modal config -->
<button data-row-actions='{
  "edit": {
    "modal": {
      "template": "editprofile"
    }
  }
}'>Edit</button>

2. Template Path ผิด

อาการ: Console error "Template not found"

สาเหตุ:

// ❌ template ชื่อผิดหรือไม่มีไฟล์
{
  "modal": {
    "template": "edit-profile"  // ไฟล์จริง: editprofile.html
  }
}

แก้ไข:

// ✅ ตรวจสอบชื่อไฟล์
{
  "modal": {
    "template": "editprofile"  // ตรงกับ /templates/modals/editprofile.html
  }
}

// หรือใช้ full path
{
  "modal": {
    "templateUrl": "/templates/modals/editprofile.html"
  }
}

3. Data Structure ไม่ตรง

อาการ: Form แสดงผลแต่ไม่มีข้อมูล

สาเหตุ:

// ❌ โครงสร้างไม่ตรงกับ auto-extraction
echo json_encode([
    'success' => true,
    'user' => $userData,      // ไม่ใช่ 'data'
    'dropdowns' => $options   // ไม่ใช่ 'options'
]);

แก้ไข:

// ✅ Option 1: ใช้ auto-extraction structure
echo json_encode([
    'success' => true,
    'data' => $userData,
    'options' => $options
]);

// ✅ Option 2: ระบุ data ชัดเจน
echo json_encode([
    'success' => true,
    'actions' => [
        [
            'type' => 'modal',
            'template' => 'editprofile',
            'data' => $userData,
            'options' => $options
        ]
    ]
]);

4. Modal ไม่ปิดหลัง Submit

อาการ: Submit แล้ว modal ยังเปิดอยู่

สาเหตุ:

// ❌ ลืม action closeModal
echo json_encode([
    'success' => true,
    'actions' => [
        ['type' => 'notification', 'message' => 'บันทึกสำเร็จ']
        // ขาด closeModal
    ]
]);

แก้ไข:

// ✅ เพิ่ม closeModal action
echo json_encode([
    'success' => true,
    'actions' => [
        ['type' => 'notification', 'level' => 'success', 'message' => 'บันทึกสำเร็จ'],
        ['type' => 'closeModal'],
        ['type' => 'reload', 'target' => '#users-table']
    ]
]);

5. Context ไม่ถูกส่งต่อ

อาการ: modalConfig ไม่ทำงาน

สาเหตุ:

// ❌ ส่ง request โดยไม่มี context
await HTTPManager.post('/api/save', data);

แก้ไข:

// ✅ TableManager จัดการ context ให้อัตโนมัติ
// แต่ถ้าเรียกเอง ต้องส่ง context
const modalConfig = {
  template: 'editprofile',
  title: 'แก้ไขข้อมูล'
};

const response = await HTTPManager.post('/api/save', data);
await ResponseHandler.process(response, { modalConfig });

6. force Flag ผิดที่

อาการ: Frontend config ถูก override ไม่ตรงตามต้องการ

สาเหตุ:

// ❌ ใช้ force โดยไม่จำเป็น
echo json_encode([
    'success' => true,
    'actions' => [
        [
            'type' => 'modal',
            'template' => 'editprofile',
            'force' => true  // ทำให้ frontend config ไม่ทำงาน
        ]
    ]
]);

แก้ไข:

// ✅ ใช้ force เฉพาะเมื่อจำเป็น
echo json_encode([
    'success' => true,
    'data' => $userData,
    'options' => $options
    // ไม่มี actions - ให้ frontend config ทำงาน
]);

// หรือใช้ suggest แทน
echo json_encode([
    'success' => true,
    'actions' => [
        [
            'type' => 'modal',
            'template' => 'editprofile',  // suggest (ไม่มี force)
            'data' => $userData
        ]
    ]
]);

State Properties

Property Type Description
state.initialized Boolean สถานะการ initialize
state.actionHandlers Map Collection ของ action handlers ทั้งหมด

ตัวอย่าง:

// ตรวจสอบว่า initialize แล้วหรือยัง
if (ResponseHandler.state.initialized) {
  console.log('Ready to use');
}

// ดูรายการ handlers ทั้งหมด
console.log(ResponseHandler.state.actionHandlers.keys());

Integration

ใช้กับ HTTPManager

const result = await HTTPManager.post('/api/save', data);
await ResponseHandler.process(result);

ใช้กับ TableManager

const table = TableManager.create({
  source: '/api/users',
  onActionComplete: async (response) => {
    await ResponseHandler.process(response);
  }
});

ใช้กับ Form Submit

document.querySelector('form').addEventListener('submit', async (e) => {
  e.preventDefault();

  const formData = new FormData(e.target);
  const response = await fetch('/api/submit', {
    method: 'POST',
    body: formData
  });

  const json = await response.json();
  await ResponseHandler.process(json, {
    form: e.target
  });
});

Best Practices

1. ใช้ Actions แทนโค้ดซ้ำซ้อน

❌ ไม่แนะนำ:

const response = await fetch('/api/save', ...);
const json = await response.json();

if (json.success) {
  NotificationManager.success('บันทึกสำเร็จ');
  setTimeout(() => {
    window.location.href = '/users';
  }, 1000);
} else {
  NotificationManager.error(json.message);
}

✅ แนะนำ:

const response = await fetch('/api/save', ...);
const json = await response.json();
await ResponseHandler.process(json);
// API Response
echo json_encode([
    'success' => true,
    'actions' => [
        ['type' => 'notification', 'level' => 'success', 'message' => 'บันทึกสำเร็จ'],
        ['type' => 'redirect', 'url' => '/users', 'delay' => 1000]
    ]
]);

2. ใช้ Context สำหรับ Shared Data

await ResponseHandler.process(response, {
  tableId: 'users-table',
  formId: 'user-form',
  currentUserId: 123
});

3. สร้าง Custom Actions สำหรับพฤติกรรมพิเศษ

// Action สำหรับ refresh table
ResponseHandler.registerHandler('refreshTable', async (action, context) => {
  const tableId = action.tableId || context.tableId;
  const table = document.querySelector(`#${tableId}`);

  if (table && table.tableManager) {
    await table.tableManager.reload();
  }
});

4. ใช้ Condition เพื่อควบคุมการทำงาน

{
  "type": "redirect",
  "url": "/admin",
  "condition": false  // ข้าม action นี้
}

5. เรียงลำดับ Actions อย่างมีเหตุผล

{
  "actions": [
    {"type": "notification", "message": "กำลังบันทึก..."},
    {"type": "update", "target": "#result", "content": "..."},
    {"type": "notification", "level": "success", "message": "บันทึกเสร็จ"},
    {"type": "redirect", "url": "/list", "delay": 1000}
  ]
}

ข้อผิดพลาดที่พบบ่อย

1. ลืม await

❌ ผิด:

ResponseHandler.process(response);  // ไม่รอให้เสร็จ
console.log('Done');  // จะทำงานทันที

✅ ถูก:

await ResponseHandler.process(response);
console.log('Done');  // จะทำงานหลัง actions เสร็จ

2. Action Type ผิด

// ❌ ไม่มี action type "noti"
{
  "type": "noti",
  "message": "Hello"
}

// ✅ ใช้ "notification"
{
  "type": "notification",
  "message": "Hello"
}

3. ไม่ระบุ target สำหรับ DOM Actions

// ❌ ขาด target
{
  "type": "update",
  "content": "Hello"
}

// ✅ มี target
{
  "type": "update",
  "target": "#result",
  "content": "Hello"
}

4. ใช้ eval โดยไม่เปิดใช้งาน

// ต้องเปิดก่อนใช้ (ไม่แนะนำ)
ResponseHandler.init({
  allowEval: true
});

Performance

1. ใช้ Batch Actions

แทนที่จะเรียก executeAction หลายครั้ง ให้ใช้ process กับ actions array:

// ✅ ดีกว่า
await ResponseHandler.process({
  actions: [action1, action2, action3]
});

// ❌ ช้ากว่า
await ResponseHandler.executeAction(action1);
await ResponseHandler.executeAction(action2);
await ResponseHandler.executeAction(action3);

2. ใช้ delay อย่างเหมาะสม

{
  "type": "redirect",
  "url": "/dashboard",
  "delay": 1000  // ให้เวลาผู้ใช้อ่าน notification
}

3. Cleanup เมื่อไม่ใช้

// ถ้าไม่ต้องการ auto-init
ResponseHandler.config.autoHandleFormResponses = false;

Security

1. ปิด eval (Default)

// ✅ ปลอดภัย - eval ปิดอยู่
ResponseHandler.config.allowEval = false;

2. Validate Actions ที่ API

// API ควรกรอง actions ที่อนุญาต
$allowedActions = ['notification', 'redirect', 'modal'];

foreach ($actions as $action) {
    if (!in_array($action['type'], $allowedActions)) {
        // ไม่ส่ง action ที่ไม่ปลอดภัย
        continue;
    }
}

3. Sanitize Content

// สำหรับ modal content หรือ update content
$content = htmlspecialchars($userInput, ENT_QUOTES, 'UTF-8');

4. ใช้ CSP Headers

Content-Security-Policy: script-src 'self' 'unsafe-inline'

5. ตรวจสอบ Callback Functions

ResponseHandler.registerHandler('callback', (action) => {
  const allowedFunctions = ['refreshTable', 'closeModal'];

  if (!allowedFunctions.includes(action.function)) {
    console.warn('Callback not allowed:', action.function);
    return;
  }

  // ทำงานต่อ...
});

Browser Compatibility

Browser Version Support
Chrome 60+ ✅ Full
Firefox 55+ ✅ Full
Safari 12+ ✅ Full
Edge 79+ ✅ Full
IE Not Supported

Features ที่ต้องการ:

  • ES6 (async/await, arrow functions, Map)
  • Fetch API
  • CustomEvent API
  • Clipboard API (สำหรับ clipboard action)

Polyfills:

<!-- สำหรับ browser เก่า -->
<script src="https://cdn.polyfill.io/v3/polyfill.min.js"></script>

API Reference

Configuration Properties

{
  debug: Boolean,
  defaultRedirectDelay: Number,
  defaultNotificationDuration: Number,
  allowEval: Boolean,
  autoHandleFormResponses: Boolean
}

Methods

Method Parameters Returns Description
init(options) options?: Object ResponseHandler เริ่มต้นการทำงาน
process(response, context) response: Object, context?: Object Promise<Object> ประมวลผล response
executeAction(action, context) action: Object, context?: Object Promise<void> ทำงาน action เดี่ยว
registerHandler(type, handler) type: String, handler: Function void ลงทะเบียน handler
executeCallback(fn, args) fn: Function\|String, args?: Array void เรียก callback
emit(eventName, data) eventName: String, data?: Any void ส่ง event

Action Types

Type Description Main Properties
notification แสดง notification level, message, duration
alert แสดง alert dialog message
confirm แสดง confirm dialog message, onConfirm
modal เปิด/ปิด modal action, content, buttons
redirect เปลี่ยนเส้นทาง url, delay, target
update อัปเดต element target, content, method
remove ลบ element target, animate
class จัดการ CSS class target, class, method
attribute ตั้งค่า attribute target, name, value
event ส่ง custom event event, target, data
callback เรียก function function, args
download ดาวน์โหลดไฟล์ url, filename
clipboard คัดลอกไปยัง clipboard text, notify
scroll เลื่อนไปยัง element target, behavior
focus โฟกัส element target, select
reload โหลดส่วนประกอบใหม่ target
eval รันโค้ด (ไม่แนะนำ) code

สรุป

ResponseHandler ช่วย:

  • ลดโค้ดซ้ำซ้อน - ให้ API ควบคุม UI behavior
  • มาตรฐานเดียวกัน - API response format เดียวกันทั้งโปรเจค
  • ยืดหยุ่น - สร้าง custom actions ได้
  • ปลอดภัย - Security-first design
  • ง่ายต่อการดูแล - เปลี่ยนพฤติกรรมที่เซิร์ฟเวอร์ ไม่ต้องแก้ไข JavaScript