Now.js Framework Documentation
ResponseHandler
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 objectcontext(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 objectcontext(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 typehandler(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 scopeargs(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): ชื่อ eventdata(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:
- Frontend-Driven (แนะนำ 90%) - Frontend กำหนด template ใน HTML
- API Suggestion - API แนะนำ template (ถ้า frontend ไม่ได้กำหนด)
- 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: เปิด modalclose/hide: ปิด modal
Properties:
template(string): ชื่อ template (โหลดจาก/templates/modals/{name}.html)templateUrl(string): URL ของ templatehtml(string): HTML สำเร็จรูปcontent(string): HTML content (เหมือน html)dataKey(string): Path ไปยัง data ใน response (เช่น "data", "data.user")optionsKey(string): Path ไปยัง optionstitle(string): ชื่อ modalclassName(string): CSS classwidth/maxWidth/height/maxHeight: ขนาด modalcloseButton(boolean): แสดงปุ่มปิด (default: true)backdrop(boolean): แสดง backdrop (default: true)keyboard(boolean): ปิดด้วย ESC (default: true)buttons(array): ปุ่มใน footerforce(boolean): บังคับใช้ config นี้ (override frontend)onShown/onHide(function): Callbacks
Auto-extraction:
data- ดึงจากresponse.data.dataอัตโนมัติoptions- ดึงจากresponse.data.optionsอัตโนมัติ
State Management:
- สร้าง
modalstate 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: ตั้งค่า innerHTMLtext: ตั้งค่า textContentvalue: ตั้งค่า value (สำหรับ input)append: เพิ่มต่อท้ายprepend: เพิ่มไว้ข้างหน้า
7. Remove
ลบ element
{
"type": "remove",
"target": ".temp-message",
"animate": true
}Properties:
target: Selector หรือ elementanimate: แสดง animation ก่อนลบ
8. Class
จัดการ CSS class
{
"type": "class",
"target": "#myDiv",
"class": "active highlight",
"method": "add"
}Methods:
add: เพิ่ม classremove: ลบ classtoggle: สลับ class
9. Attribute
ตั้งค่า/ลบ attribute
{
"type": "attribute",
"target": "#myInput",
"name": "disabled",
"value": true
}Properties:
name: ชื่อ attributevalue: ค่า (ใช้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.htmlNaming 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