Now.js Framework Documentation
FormManager - ระบบจัดการฟอร์มและการตรวจสอบความถูกต้อง
FormManager - ระบบจัดการฟอร์มและการตรวจสอบความถูกต้อง
เอกสารฉบับนี้อธิบาย FormManager ซึ่งเป็นระบบจัดการฟอร์มแบบครบวงจรของ Now.js Framework ครอบคลุมการตั้งค่า การตรวจสอบความถูกต้อง การส่งข้อมูลแบบ AJAX การอัปโหลดไฟล์ และการผสานระบบความปลอดภัย
📋 สารบัญ
- ภาพรวม
- การติดตั้งและนำเข้า
- การเริ่มต้นใช้งาน
- การตั้งค่าฟอร์ม
- การตรวจสอบความถูกต้องของฟอร์ม
- การส่งฟอร์ม
- การส่งแบบ AJAX
- การอัปโหลดไฟล์
- ข้อมูลฟอร์ม
- การคงค่าฟิลด์
- การผสานระบบความปลอดภัย
- ตัวอย่างการใช้งาน
- เอกสารอ้างอิง API
- แนวทางปฏิบัติที่แนะนำ
- ปัญหาที่พบบ่อย
- เอกสารที่เกี่ยวข้อง
- สัญญาอนุญาต
- ข้อมูลอัปเดตล่าสุด
ภาพรวม
FormManager เป็นระบบจัดการฟอร์มแบบครบวงจร รองรับการตรวจสอบความถูกต้อง การส่งข้อมูลทั้งแบบดั้งเดิมและ AJAX การอัปโหลดไฟล์ รวมถึงมาตรการด้านความปลอดภัยที่จำเป็น
ฟีเจอร์หลัก
- ✅ การเริ่มต้นอัตโนมัติ: ค้นหาและตั้งค่าฟอร์มที่มี
data-formให้พร้อมใช้งาน - ✅ การตรวจสอบความถูกต้อง: รองรับ validation ทั้งในตัวและแบบกำหนดเอง
- ✅ การส่งแบบ AJAX: ส่งคำร้องด้วย AJAX พร้อมรอผลลัพธ์และจัดการตอบกลับ
- ✅ การอัปโหลดไฟล์: รองรับการอัปโหลดไฟล์พร้อมแถบความคืบหน้า
- ✅ การจำค่าฟิลด์: บันทึกค่าฟิลด์ไว้ใน localStorage หรือคุกกี้ตามต้องการ
- ✅ ป้องกัน CSRF: แทรกและตรวจสอบ CSRF token อัตโนมัติ
- ✅ จำกัดจำนวนการส่ง: ป้องกันการส่งฟอร์มถี่เกินไปด้วย rate limiting
- ✅ ป้องกันการส่งซ้ำ: ปิดปุ่มส่งชั่วคราวเพื่อกันการ double submit
- ✅ ตัวตรวจสอบแบบกำหนดเอง: เพิ่ม validator ใหม่ได้ตาม requirement
- ✅ การจัดการข้อผิดพลาด: รวมศูนย์การจัดการ error และข้อความตอบกลับ
- ✅ ข้อความสำเร็จ/ผิดพลาด: แสดงผลลัพธ์ให้ผู้ใช้เข้าใจได้ทันที
- ✅ จัดการการเปลี่ยนเส้นทาง: ควบคุม redirect หลังส่งฟอร์มได้ละเอียด
- ✅ จำผู้ใช้: รองรับฟีเจอร์จดจำชื่อผู้ใช้สำหรับฟอร์มล็อกอิน
- ✅ จำ URL ปลายทาง: เก็บ URL ที่ผู้ใช้ตั้งใจจะเข้าก่อนล็อกอินและเติมให้อัตโนมัติ
เมื่อควรเลือกใช้ FormManager
✅ แนะนำให้ใช้เมื่อ:
- ต้องการระบบจัดการฟอร์มที่ตั้งค่าได้จาก HTML data attributes
- ต้องการ validation ที่ยืดหยุ่นและการส่งข้อมูลแบบ AJAX
- จำเป็นต้องอัปโหลดไฟล์พร้อมแสดงความคืบหน้า
- ต้องรวมระบบความปลอดภัย เช่น CSRF และ rate limiting
- ต้องการบันทึกค่าฟิลด์เพื่อประสบการณ์ผู้ใช้ที่ต่อเนื่อง
❌ พิจารณาทางเลือกอื่นเมื่อ:
- ฟอร์มมีความเรียบง่ายมากและไม่ต้องตรวจสอบความถูกต้อง
- ต้องการส่งข้อมูลแบบรีเฟรชหน้าแบบดั้งเดิมโดยไม่มีการประมวลผลเพิ่มเติม
การติดตั้งและนำเข้า
FormManager โหลดมาพร้อมกับ Now.js Framework และพร้อมใช้งานทันทีผ่าน window object:
// ไม่ต้อง import - พร้อมใช้งานทันที
console.log(window.FormManager); // อ็อบเจ็กต์ FormManager ที่ผูกไว้กับ windowการเริ่มต้นใช้งาน
การตั้งค่าพื้นฐาน
// เริ่มต้นใช้งาน FormManager
await FormManager.init({
debug: false,
ajaxSubmit: true,
autoValidate: true,
resetAfterSubmit: false,
preventDoubleSubmit: true,
showLoadingOnSubmit: true,
validateOnInput: true,
validateOnBlur: true
});
console.log('FormManager พร้อมใช้งานแล้ว!');ตัวอย่างฟอร์มพื้นฐาน
<!-- ฟอร์มพื้นฐานที่มี data-form (จำเป็นสำหรับ auto-init) -->
<form data-form="contact" method="POST" action="/api/contact">
<div>
<label>ชื่อ:</label>
<input type="text" name="name" required>
</div>
<div>
<label>อีเมล:</label>
<input type="email" name="email" required>
</div>
<div>
<label>ข้อความ:</label>
<textarea name="message" required minlength="10"></textarea>
</div>
<button type="submit">ส่งข้อความ</button>
</form>ลำดับการทำงานของฟอร์ม
ผู้ใช้กรอกข้อมูลในฟอร์ม
↓
กดปุ่มส่งฟอร์ม
↓
┌────────────────────┐
│ ยกเลิกการส่งดั้งเดิม │
│ - ป้องกันการส่งฟอร์มของเบราว์เซอร์ │
└─────────┬──────────┘
↓
┌────────────────────┐
│ ตรวจสอบความปลอดภัย │
│ - ตรวจสอบ rate limit │
│ - ตรวจสอบ CSRF token │
└─────────┬──────────┘
↓
┌────────────────────┐
│ ตรวจสอบฟอร์ม │
│ - ตรวจสอบฟิลด์ที่ต้องกรอก │
│ - ตรวจสอบรูปแบบข้อมูล │
│ - ตรวจสอบกฎที่กำหนดเอง │
└─────────┬──────────┘
↓
ผ่านหรือไม่? ─── ไม่ผ่าน ──> แสดงข้อผิดพลาด
│
ผ่าน
↓
┌────────────────────┐
│ รวบรวมข้อมูล │
│ - รวมข้อมูลเป็น FormData │
│ - สร้างอ็อบเจ็กต์ JSON │
│ - แนบไฟล์ (ถ้ามี) │
└─────────┬──────────┘
↓
┌────────────────────┐
│ ส่งคำร้อง (AJAX) │
│ - แสดงสถานะกำลังส่ง │
│ - ส่งคำร้องไปยังเซิร์ฟเวอร์ │
│ - จัดการผลลัพธ์ที่ตอบกลับ │
└─────────┬──────────┘
↓
┌────────────────────┐
│ ประมวลผลผลลัพธ์ │
│ - แจ้งผลสำเร็จ │
│ - จัดการข้อผิดพลาด │
│ - เปลี่ยนเส้นทางตามที่กำหนด │
└────────────────────┘การตั้งค่าฟอร์ม
1. การใช้ Data Attributes
<form
data-form="login"
data-ajax-submit="true"
data-auto-validate="true"
data-confirm="คุณต้องการส่งข้อมูลนี้หรือไม่?"
data-reset-after-submit="false"
data-prevent-double-submit="true"
data-show-loading-on-submit="true"
data-redirect="/dashboard"
data-success-message="เข้าสู่ระบบสำเร็จ!"
data-error-message="เข้าสู่ระบบไม่สำเร็จ">
<!-- ฟิลด์ของฟอร์ม -->
</form>หมายเหตุ: data-confirm จะแสดง confirmation dialog ก่อนส่งฟอร์ม
2. การตั้งค่าความปลอดภัย
<form
data-form="secure"
data-csrf="true"
data-csrf-token="..."
data-rate-limit="true"
data-rate-limit-limit="5"
data-rate-limit-window="60"
data-validation="true"
data-sanitize-input="true">
<!-- ฟิลด์ของฟอร์ม -->
</form>3. การตั้งค่าการตรวจสอบความถูกต้อง
<form
data-form="validated"
data-validate-on-submit="true"
data-validate-on-input="true"
data-validate-on-blur="true"
data-validate-only-dirty="true"
data-auto-focus-error="true"
data-auto-scroll-to-error="true">
<!-- ฟิลด์ของฟอร์ม -->
</form>4. การแสดงผลข้อผิดพลาด
<form
data-form="errors"
data-show-errors-inline="true"
data-show-errors-in-notification="true"
data-error-container=".error-container"
data-error-class="error"
data-auto-clear-errors="true"
data-auto-clear-errors-delay="5000">
<!-- ฟิลด์ของฟอร์ม -->
</form>5. การแสดงผลเมื่อสำเร็จ
<form
data-form="success"
data-success-redirect="/thank-you"
data-success-message="ส่งฟอร์มสำเร็จ!"
data-show-success-inline="true"
data-show-success-in-notification="true"
data-success-container=".success-container">
<!-- ฟิลด์ของฟอร์ม -->
</form>การตรวจสอบความถูกต้องของฟอร์ม
1. ตัวตรวจสอบที่มีมาให้
<!-- ต้องกรอก -->
<input
type="text"
name="name"
required
data-error-required="กรุณากรอกชื่อของคุณ">
<!-- อีเมล -->
<input
type="email"
name="email"
required
data-error-email="กรุณากรอกอีเมลให้ถูกต้อง">
<!-- URL -->
<input
type="url"
name="website"
data-error-url="กรุณากรอก URL ที่ถูกต้อง">
<!-- ตัวเลข -->
<input
type="number"
name="age"
data-error-number="กรุณากรอกตัวเลขที่ถูกต้อง">
<!-- ค่า Min/Max -->
<input
type="number"
name="age"
min="18"
max="100"
data-error-min="อายุต้องไม่ต่ำกว่า 18 ปี"
data-error-max="อายุต้องไม่เกิน 100 ปี">
<!-- ความยาวขั้นต่ำ/สูงสุด -->
<input
type="text"
name="username"
minlength="3"
maxlength="20"
data-error-minlength="ชื่อผู้ใช้ต้องยาวอย่างน้อย 3 ตัวอักษร"
data-error-maxlength="ชื่อผู้ใช้ต้องไม่เกิน 20 ตัวอักษร">
<!-- รูปแบบ -->
<input
type="text"
name="phone"
pattern="^[0-9]{10}$"
data-error-pattern="กรุณากรอกหมายเลขโทรศัพท์ 10 หลัก">
<!-- ตรวจสอบให้ตรงกัน (ยืนยันรหัสผ่าน) -->
<input
type="password"
name="password"
id="password">
<input
type="password"
name="confirm_password"
data-validate-match="password"
data-error-match="รหัสผ่านทั้งสองไม่ตรงกัน">2. ตัวตรวจสอบแบบกำหนดเอง
// ลงทะเบียนตัวตรวจสอบที่กำหนดเอง
FormManager.registerValidator('username', (value, element) => {
// อนุญาตเฉพาะตัวอักษรและขีดล่าง
return /^[a-zA-Z0-9_]+$/.test(value);
}, 'ชื่อผู้ใช้ต้องประกอบด้วยตัวอักษร ตัวเลข หรือขีดล่างเท่านั้น');HTML
<input
type="text"
name="username"
data-validate-username="true"
data-error-username="รูปแบบชื่อผู้ใช้ไม่ถูกต้อง">3. ตัวตรวจสอบแบบอะซิงโครนัส
// ลงทะเบียนตัวตรวจสอบแบบอะซิงโครนัส
FormManager.registerValidator('unique-email', async (value, element) => {
const response = await fetch(`/api/check-email?email=${value}`);
const data = await response.json();
return data.available;
}, 'อีเมลนี้ถูกใช้ไปแล้ว');<input
type="email"
name="email"
data-validate-unique-email="true"
data-error-unique-email="อีเมลนี้ถูกลงทะเบียนไว้แล้ว">4. ฟังก์ชันตรวจสอบแบบกำหนดเอง
<input
type="text"
name="code"
data-validate-fn="validateCode">
<script>
async function validateCode(value, field, instance) {
// เขียนเงื่อนไขตรวจสอบเอง
const response = await fetch(`/api/validate-code?code=${value}`);
const result = await response.json();
if (!result.valid) {
return result.message || 'รหัสไม่ถูกต้อง';
}
return true;
}
</script>5. เหตุการณ์ที่เกี่ยวข้องกับการตรวจสอบ
// รับฟังเหตุการณ์การตรวจสอบ
// การตรวจสอบระดับฟิลด์
document.addEventListener('form:field:change', (e) => {
const { formId, field, name, value } = e.detail;
console.log(`ฟิลด์ ${name} เปลี่ยนค่าเป็น:`, value);
});
// การตรวจสอบทั้งฟอร์ม
document.addEventListener('form:validate', (e) => {
const { formId, isValid, errors } = e.detail;
if (isValid) {
console.log('ฟอร์มผ่านการตรวจสอบ');
} else {
console.log('พบข้อผิดพลาดในการตรวจสอบ:', errors);
}
});
// กรณีตรวจสอบไม่ผ่าน
document.addEventListener('form:validation:failed', (e) => {
const { formId, errors } = e.detail;
console.log('การตรวจสอบไม่ผ่าน:', errors);
});การส่งฟอร์ม
1. การส่งแบบดั้งเดิม
<!-- ส่งแบบไม่ใช้ AJAX -->
<form
data-form="traditional"
data-ajax-submit="false"
method="POST"
action="/submit">
<input type="text" name="name" required>
<button type="submit">ส่ง</button>
</form>2. การส่งแบบ AJAX
<!-- การส่งแบบ AJAX (ค่าเริ่มต้น) -->
<form
data-form="ajax"
data-ajax-submit="true"
data-action="/api/submit"
data-method="POST">
<input type="text" name="name" required>
<button type="submit">ส่ง</button>
</form>3. การกำหนด HTTP Method เอง
<!-- ส่งคำร้องแบบ PUT -->
<form
data-form="update"
data-method="PUT"
data-action="/api/users/123">
<input type="text" name="name">
<button type="submit">อัปเดต</button>
</form>
<!-- ส่งคำร้องแบบ DELETE -->
<form
data-form="delete"
data-method="DELETE"
data-action="/api/users/123">
<button type="submit">ลบ</button>
</form>4. เหตุการณ์ที่เกี่ยวข้องกับการส่งฟอร์ม
// ก่อนส่งฟอร์ม
document.addEventListener('form:submitting', (e) => {
const { formId, form } = e.detail;
console.log('กำลังส่งฟอร์ม:', formId);
});
// หลังส่งสำเร็จ
document.addEventListener('form:submitted', (e) => {
const { formId, response } = e.detail;
console.log('ส่งฟอร์มเรียบร้อย:', formId);
console.log('ผลลัพธ์ที่ได้รับ:', response);
});
// เมื่อเกิดข้อผิดพลาด
document.addEventListener('form:error', (e) => {
const { formId, response, error } = e.detail;
console.log('เกิดข้อผิดพลาดในฟอร์ม:', formId);
console.log('รายละเอียด:', error || response);
});5. ป้องกันการส่งซ้ำ
<!-- ป้องกันการกดส่งซ้ำ -->
<form
data-form="secure"
data-prevent-double-submit="true"
data-double-submit-timeout="2000">
<!-- ป้องกันไม่ให้ส่งซ้ำภายใน 2 วินาที -->
<button type="submit">ส่ง</button>
</form>การส่งแบบ AJAX
1. การส่งข้อมูลแบบ JSON
<form data-form="json" data-action="/api/users">
<input type="text" name="name" value="John">
<input type="email" name="email" value="john@example.com">
<button type="submit">ส่ง</button>
</form>ข้อมูลที่ส่งออกไป:
{
"name": "John",
"email": "john@example.com",
"_token": "csrf_token_here"
}2. การส่งข้อมูลแบบ FormData
<form data-form="upload" enctype="multipart/form-data">
<input type="file" name="file">
<input type="text" name="title">
<button type="submit">อัปโหลด</button>
</form>รูปแบบที่ส่ง: multipart/form-data
3. การจัดการผลลัพธ์ที่ตอบกลับ
// โครงสร้างการตอบกลับจากเซิร์ฟเวอร์
{
"success": true,
"message": "ดำเนินการสำเร็จ",
"data": {
"user": {...},
"token": "..."
},
"redirectUrl": "/dashboard"
}
// หรือกรณีเกิดข้อผิดพลาด
{
"success": false,
"message": "การตรวจสอบไม่ผ่าน",
"errors": {
"email": "อีเมลนี้ถูกใช้แล้ว",
"password": "รหัสผ่านคาดว่าปลอดภัยไม่เพียงพอ"
}
}4. การประมวลผลผลลัพธ์เพิ่มเติม
// รับฟังผลลัพธ์จากการส่งฟอร์ม
document.addEventListener('form:submitted', async (e) => {
const { formId, response } = e.detail;
if (response.success) {
// จัดการกรณีสำเร็จ
if (response.data) {
console.log('ข้อมูลผู้ใช้:', response.data.user);
}
// กรณีต้องการเปลี่ยนเส้นทางเอง
if (response.redirectUrl) {
await Router.navigate(response.redirectUrl);
}
} else {
// จัดการข้อผิดพลาด
if (response.errors) {
Object.entries(response.errors).forEach(([field, message]) => {
console.log(`ฟิลด์ ${field}:`, message);
});
}
}
});5. Response Actions Format
FormManager รองรับการประมวลผล actions จากเซิร์ฟเวอร์ผ่าน ResponseHandler โดยใช้รูปแบบ single action object (ไม่ใช่ array):
รูปแบบที่ถูกต้อง:
// ✅ ถูกต้อง - Single action object
return $this->successResponse([
'data' => [...],
'actions' => [
'type' => 'reload',
'closeModal' => true,
'reload' => '[data-table="users"]'
]
], 'บันทึกสำเร็จ');รูปแบบที่ไม่ถูกต้อง:
// ❌ ผิด - Array of actions (ไม่รองรับ)
return $this->successResponse([
'data' => [...],
'actions' => [
['type' => 'notification', 'message' => 'Success'],
['type' => 'closeModal'],
['type' => 'reload']
]
]);Action Types ที่รองรับ:
1. Reload Table
'actions' => [
'type' => 'reload',
'closeModal' => true, // ปิด modal ก่อน reload
'reload' => '[data-table="table-name"]' // selector ของตาราง
]2. Redirect
'actions' => [
'type' => 'redirect',
'url' => '/dashboard', // URL ปลายทาง
'closeModal' => true // optional
]3. Close Modal Only
'actions' => [
'type' => 'closeModal'
]4. Show Notification
'actions' => [
'type' => 'notification',
'message' => 'ดำเนินการสำเร็จ',
'level' => 'success' // success, error, warning, info
]5. Combined Actions
// แสดง toast + ปิด modal + reload table
'actions' => [
'type' => 'reload',
'message' => 'บันทึกสำเร็จ', // แสดง toast อัตโนมัติ
'closeModal' => true,
'reload' => '[data-table="users"]'
]Flow การทำงาน:
1. Server ส่ง response พร้อม actions
↓
2. FormManager รับ response
↓
3. ส่งต่อไปยัง ResponseHandler.process()
↓
4. ResponseHandler ประมวลผล actions:
- แสดง toast (ถ้ามี message)
- ปิด modal (ถ้ามี closeModal: true)
- Reload table (ถ้ามี reload)
- Redirect (ถ้ามี url)การอัปโหลดไฟล์
1. อัปโหลดไฟล์เดี่ยว
<form data-form="upload" data-action="/api/upload">
<input type="file" name="avatar" accept="image/*" required>
<button type="submit">อัปโหลด</button>
</form>2. อัปโหลดหลายไฟล์
<form data-form="multi-upload" data-action="/api/upload">
<input type="file" name="files" multiple accept="image/*">
<button type="submit">อัปโหลดไฟล์</button>
</form>3. แสดงความคืบหน้าการอัปโหลด
<form data-form="upload-progress" data-action="/api/upload">
<input type="file" name="file">
<!-- แถบความคืบหน้า (สร้างให้อัตโนมัติ) -->
<div class="upload-progress" style="display:none">
<div class="progress">
<div class="progress-bar" role="progressbar"></div>
</div>
<div class="progress-text"></div>
</div>
<button type="submit">อัปโหลด</button>
</form>4. เหตุการณ์ระหว่างอัปโหลด
// ติดตามความคืบหน้าการอัปโหลด
document.addEventListener('form:upload-progress', (e) => {
const { loaded, total, percent } = e.detail;
console.log(`อัปโหลดไปแล้ว: ${percent}%`);
console.log(`ข้อมูลที่ส่งแล้ว: ${loaded} / ทั้งหมด: ${total}`);
// อัปเดต UI ที่สร้างเอง
updateProgressBar(percent);
});5. การตรวจสอบไฟล์ก่อนอัปโหลด
<input
type="file"
name="avatar"
accept="image/jpeg,image/png"
data-max-size="5242880"
data-error-file-size="ไฟล์ต้องมีขนาดไม่เกิน 5MB"
data-error-file-type="อนุญาตเฉพาะไฟล์ JPEG และ PNG">
<script>
// ตัวตรวจสอบไฟล์แบบกำหนดเอง
FormManager.registerValidator('file-size', (value, element, param) => {
if (!element.files || element.files.length === 0) return true;
const maxSize = parseInt(param);
const file = element.files[0];
return file.size <= maxSize;
}, 'ขนาดไฟล์เกินกว่าที่กำหนด');
</script>ข้อมูลฟอร์ม
1. ดึงข้อมูลจากฟอร์ม
// เข้าถึงอินสแตนซ์ของฟอร์ม
const form = FormManager.getInstance('contact');
// ดึงค่าฟอร์มในรูปแบบ JSON
const data = FormManager.getValues('contact');
console.log(data);
// { name: "John", email: "john@example.com" }
// หรืออ้างอิงจาก element โดยตรง
const formElement = document.querySelector('form[data-form="contact"]');
const data2 = FormManager.getValues(formElement);2. ตั้งค่าข้อมูลในฟอร์ม
// เข้าถึงอินสแตนซ์ของฟอร์ม
const instance = FormManager.getInstance('profile');
// กำหนดค่าฟิลด์ต่างๆ
instance.elements.get('name').value = 'John Doe';
instance.elements.get('email').value = 'john@example.com';
instance.state.data.name = 'John Doe';
instance.state.data.email = 'john@example.com';
// กระตุ้นให้เกิดเหตุการณ์ change
instance.elements.get('name').dispatchEvent(new Event('change'));3. รีเซ็ตฟอร์ม
// รีเซ็ตฟอร์มกลับสู่สถานะเริ่มต้น
const instance = FormManager.getInstance('contact');
FormManager.resetForm(instance);
// หรือผ่าน element โดยตรง
const formElement = document.querySelector('form[data-form="contact"]');
const instance2 = FormManager.getInstanceByElement(formElement);
FormManager.resetForm(instance2);4. ตรวจสอบสถานะของฟอร์ม
const instance = FormManager.getInstance('contact');
// ตรวจสอบว่ามีการแก้ไขข้อมูลหรือไม่
console.log(instance.state.modified); // true/false
// ตรวจสอบว่าฟอร์มผ่านการตรวจสอบหรือไม่
console.log(instance.state.valid); // true/false
// ตรวจสอบว่าฟอร์มกำลังส่งอยู่หรือไม่
console.log(instance.state.submitting); // true/false
// ดึงรายการข้อผิดพลาด
console.log(instance.state.errors);
// { email: "อีเมลไม่ถูกต้อง", password: "สั้นเกินไป" }
// จำนวนครั้งที่ส่งฟอร์ม
console.log(instance.state.submitCount); // 2
// เวลาในการส่งฟอร์มครั้งล่าสุด (timestamp)
console.log(instance.state.lastSubmitTime); // 1635789012345การคงค่าฟิลด์
1. บันทึกลง localStorage
<form data-form="preferences">
<!-- บันทึกลง localStorage อัตโนมัติ -->
<input
type="text"
name="username"
data-persist="true"
data-persist-on="submit">
<select
name="language"
data-persist="true"
data-persist-on="submit">
<option value="en">English</option>
<option value="th">ไทย</option>
</select>
<button type="submit">บันทึก</button>
</form>2. บันทึกลงคุกกี้
<form data-form="settings">
<!-- บันทึกลงคุกกี้ -->
<input
type="checkbox"
name="remember"
data-persist="cookie"
data-persist-ttl-days="30">
<button type="submit">บันทึก</button>
</form>3. ตั้งชื่อคีย์เอง
<input
type="text"
name="email"
data-persist="true"
data-persist-key="user_email_pref">4. กำหนดอายุข้อมูล (TTL)
<!-- หมดอายุหลังจาก 7 วัน -->
<input
type="text"
name="search"
data-persist="true"
data-persist-ttl-days="7">5. จำฉันไว้ (สำหรับฟอร์มล็อกอิน)
<form data-form="login" data-action="/api/auth/login">
<input type="text" name="username" required>
<input type="password" name="password" required>
<!-- ช่องเลือกเพื่อจำชื่อผู้ใช้ -->
<label>
<input type="checkbox" name="remember" id="remember">
จำฉันไว้
</label>
<button type="submit">เข้าสู่ระบบ</button>
</form>พฤติกรรม:
- เมื่อเลือก: จะบันทึกชื่อผู้ใช้ไว้ใน localStorage
- ครั้งถัดไปจะเติมชื่อผู้ใช้อัตโนมัติ
- ไม่บันทึกรหัสผ่าน เพื่อความปลอดภัย
การผสานระบบความปลอดภัย
1. การป้องกัน CSRF
<form data-form="secure" data-csrf="true">
<!-- ระบบจะเพิ่ม CSRF token ให้อัตโนมัติ -->
<input type="hidden" name="_token" value="...">
<input type="text" name="data">
<button type="submit">ส่ง</button>
</form>การตรวจจับอัตโนมัติ:
<!-- อ่าน CSRF token จาก meta tag -->
<meta name="csrf-token" content="your_csrf_token">
<!-- หรือจากคุกกี้ -->
<!-- คุกกี้ XSRF-TOKEN -->2. การจำกัดความถี่ในการส่ง
<form
data-form="contact"
data-rate-limit="true"
data-rate-limit-limit="5"
data-rate-limit-window="60">
<!-- ส่งได้สูงสุด 5 ครั้งใน 60 วินาที -->
<button type="submit">ส่ง</button>
</form>จัดการเมื่อถูกจำกัด:
document.addEventListener('form:rateLimit', (e) => {
const { formId, rateLimitResult } = e.detail;
const { retryAfter } = rateLimitResult;
showNotification(
`ส่งคำร้องมากเกินไป กรุณาลองใหม่ในอีก ${retryAfter} วินาที`,
'warning'
);
});3. การทำความสะอาดข้อมูลก่อนส่ง
<form
data-form="secure"
data-sanitize-input="true">
<!-- ระบบจะทำความสะอาดข้อมูลก่อนส่ง -->
<input type="text" name="comment">
<button type="submit">ส่ง</button>
</form>4. เหตุการณ์ด้านความปลอดภัย
// เมื่อเกิดข้อผิดพลาดเกี่ยวกับ CSRF
document.addEventListener('security:error', (e) => {
const { message, status, code } = e.detail;
if (code === 'csrf_invalid') {
console.log('การตรวจสอบ CSRF ไม่ผ่าน');
// รีเฟรช CSRF token ใหม่
}
});
// เมื่อรีเฟรช CSRF สำเร็จ
document.addEventListener('csrf:refreshed', (e) => {
const { token } = e.detail;
console.log('ได้รับ CSRF token ใหม่:', token);
});ตัวอย่างการใช้งาน
1. ฟอร์มเข้าสู่ระบบ
<form
data-form="login"
data-action="/api/auth/login"
data-method="POST"
data-ajax-submit="true"
data-use-intended-url="true"
data-redirect="/dashboard"
data-auto-fill-intended-url="true">
<div class="form-group">
<label>อีเมล:</label>
<input
type="email"
name="username"
required
autofocus
data-error-required="กรุณากรอกอีเมลของคุณ">
</div>
<div class="form-group">
<label>รหัสผ่าน:</label>
<input
type="password"
name="password"
required
minlength="6"
data-error-required="กรุณากรอกรหัสผ่าน"
data-error-minlength="รหัสผ่านต้องมีอย่างน้อย 6 ตัวอักษร">
</div>
<div class="form-group">
<label>
<input type="checkbox" name="remember" id="remember">
จำฉันไว้
</label>
</div>
<!-- Intended URL (auto-filled) -->
<input type="hidden" name="intended_url">
<button type="submit">เข้าสู่ระบบ</button>
</form>
<script>
// จัดการเมื่อเข้าสู่ระบบสำเร็จ
document.addEventListener('form:submitted', async (e) => {
const { formId, response } = e.detail;
if (formId === 'login' && response.success) {
// AuthManager จะจัดการเก็บโทเค็นให้เอง
showNotification('เข้าสู่ระบบสำเร็จ!', 'success');
// FormManager จะเปลี่ยนหน้าให้เองตามที่ตั้งค่าไว้
}
});
// จัดการกรณีเข้าสู่ระบบไม่สำเร็จ
document.addEventListener('form:error', (e) => {
const { formId, response } = e.detail;
if (formId === 'login') {
if (response.status === 401) {
showNotification('ข้อมูลเข้าสู่ระบบไม่ถูกต้อง', 'error');
} else {
showNotification(response.message || 'เข้าสู่ระบบไม่สำเร็จ', 'error');
}
}
});
</script>2. ฟอร์มลงทะเบียน
<form
data-form="register"
data-action="/api/auth/register"
data-redirect="/welcome"
data-success-message="สร้างบัญชีสำเร็จ!">
<div class="form-group">
<label>ชื่อ:</label>
<input
type="text"
name="name"
required
minlength="2"
data-error-required="กรุณากรอกชื่อของคุณ"
data-error-minlength="ชื่อต้องมีอย่างน้อย 2 ตัวอักษร">
</div>
<div class="form-group">
<label>อีเมล:</label>
<input
type="email"
name="email"
required
data-validate-unique-email="true"
data-error-required="กรุณากรอกอีเมลของคุณ"
data-error-email="กรุณากรอกอีเมลให้ถูกต้อง"
data-error-unique-email="อีเมลนี้ถูกลงทะเบียนไว้แล้ว">
</div>
<div class="form-group">
<label>ชื่อผู้ใช้:</label>
<input
type="text"
name="username"
required
minlength="3"
maxlength="20"
pattern="^[a-zA-Z0-9_]+$"
data-validate-unique-username="true"
data-error-required="กรุณาเลือกชื่อผู้ใช้"
data-error-pattern="ชื่อผู้ใช้ต้องเป็นตัวอักษร ตัวเลข หรือขีดล่าง"
data-error-unique-username="ชื่อผู้ใช้นี้ถูกใช้แล้ว">
</div>
<div class="form-group">
<label>รหัสผ่าน:</label>
<input
type="password"
name="password"
id="password"
required
minlength="8"
data-validate-strong-password="true"
data-error-required="กรุณากรอกรหัสผ่าน"
data-error-minlength="รหัสผ่านต้องมีอย่างน้อย 8 ตัวอักษร"
data-error-strong-password="รหัสผ่านต้องมีตัวพิมพ์ใหญ่ ตัวพิมพ์เล็ก ตัวเลข และอักขระพิเศษ">
</div>
<div class="form-group">
<label>ยืนยันรหัสผ่าน:</label>
<input
type="password"
name="confirm_password"
required
data-validate-match="password"
data-error-required="กรุณายืนยันรหัสผ่าน"
data-error-match="รหัสผ่านทั้งสองไม่ตรงกัน">
</div>
<div class="form-group">
<label>
<input
type="checkbox"
name="terms"
required
data-error-required="กรุณายอมรับเงื่อนไขการใช้งาน">
ฉันยอมรับ <a href="/terms">ข้อกำหนดการให้บริการ</a>
</label>
</div>
<button type="submit">สร้างบัญชี</button>
</form>
<script>
// ตัวตรวจสอบเพิ่มเติมสำหรับฟอร์มลงทะเบียน
FormManager.registerValidator('unique-email', async (value) => {
const response = await fetch(`/api/check-email?email=${value}`);
const data = await response.json();
return data.available;
}, 'อีเมลนี้ถูกลงทะเบียนไว้แล้ว');
FormManager.registerValidator('unique-username', async (value) => {
const response = await fetch(`/api/check-username?username=${value}`);
const data = await response.json();
return data.available;
}, 'ชื่อผู้ใช้นี้ถูกใช้แล้ว');
FormManager.registerValidator('strong-password', (value) => {
// ต้องประกอบด้วย: ตัวพิมพ์ใหญ่ ตัวพิมพ์เล็ก ตัวเลข และอักขระพิเศษ
const hasUpper = /[A-Z]/.test(value);
const hasLower = /[a-z]/.test(value);
const hasNumber = /[0-9]/.test(value);
const hasSpecial = /[!@#$%^&*]/.test(value);
return hasUpper && hasLower && hasNumber && hasSpecial;
}, 'รหัสผ่านต้องมีตัวพิมพ์ใหญ่ ตัวพิมพ์เล็ก ตัวเลข และอักขระพิเศษ');
</script>3. ฟอร์มติดต่อ
<form
data-form="contact"
data-action="/api/contact"
data-reset-after-submit="true"
data-success-message="ขอบคุณค่ะ! เราจะติดต่อกลับโดยเร็ว"
data-show-success-in-notification="true">
<div class="row">
<div class="col-md-6">
<label>ชื่อ:</label>
<input
type="text"
name="name"
required
data-error-required="กรุณากรอกชื่อของคุณ">
</div>
<div class="col-md-6">
<label>อีเมล:</label>
<input
type="email"
name="email"
required
data-error-required="กรุณากรอกอีเมลของคุณ"
data-error-email="กรุณากรอกอีเมลให้ถูกต้อง">
</div>
</div>
<div class="form-group">
<label>หัวข้อ:</label>
<select name="subject" required data-error-required="กรุณาเลือกหัวข้อที่ต้องการติดต่อ">
<option value="">-- เลือกหัวข้อ --</option>
<option value="general">สอบถามทั่วไป</option>
<option value="support">ต้องการความช่วยเหลือทางเทคนิค</option>
<option value="sales">ติดต่อฝ่ายขาย</option>
<option value="feedback">ข้อเสนอแนะ</option>
</select>
</div>
<div class="form-group">
<label>ข้อความ:</label>
<textarea
name="message"
rows="5"
required
minlength="10"
maxlength="1000"
data-error-required="กรุณากรอกข้อความของคุณ"
data-error-minlength="ข้อความต้องมีอย่างน้อย 10 ตัวอักษร"
data-error-maxlength="ข้อความต้องไม่เกิน 1,000 ตัวอักษร"></textarea>
<div class="form-text">
<span id="charCount">0</span> / 1,000 ตัวอักษร
</div>
</div>
<button type="submit">ส่งข้อความ</button>
</form>
<script>
// ตัวนับจำนวนตัวอักษร
const textarea = document.querySelector('textarea[name="message"]');
const charCount = document.getElementById('charCount');
textarea.addEventListener('input', () => {
charCount.textContent = textarea.value.length;
if (textarea.value.length > 1000) {
charCount.style.color = 'red';
} else {
charCount.style.color = '';
}
});
</script>4. ฟอร์มอัปเดตโปรไฟล์
<form
data-form="profile"
data-action="/api/profile"
data-method="PUT"
data-success-message="อัปเดตโปรไฟล์สำเร็จ!">
<div class="form-group">
<label>รูปโปรไฟล์:</label>
<input
type="file"
name="avatar"
accept="image/*"
data-max-size="5242880"
data-error-file-size="ไฟล์ต้องมีขนาดไม่เกิน 5MB">
<div class="current-avatar">
<img id="avatarPreview" src="/uploads/avatar.jpg" alt="ภาพโปรไฟล์">
</div>
</div>
<div class="form-group">
<label>ชื่อ:</label>
<input
type="text"
name="name"
value="John Doe"
required>
</div>
<div class="form-group">
<label>อีเมล:</label>
<input
type="email"
name="email"
value="john@example.com"
required>
</div>
<div class="form-group">
<label>ประวัติย่อ:</label>
<textarea
name="bio"
rows="4"
maxlength="500">นักพัฒนาเว็บและนักออกแบบ</textarea>
</div>
<div class="form-group">
<label>เว็บไซต์:</label>
<input
type="url"
name="website"
value="https://johndoe.com"
data-error-url="กรุณากรอก URL ให้ถูกต้อง">
</div>
<button type="submit">อัปเดตโปรไฟล์</button>
</form>
<script>
// แสดงตัวอย่างรูปก่อนอัปโหลด
const avatarInput = document.querySelector('input[name="avatar"]');
const avatarPreview = document.getElementById('avatarPreview');
avatarInput.addEventListener('change', (e) => {
const file = e.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = (event) => {
avatarPreview.src = event.target.result;
};
reader.readAsDataURL(file);
}
});
// จัดการเมื่ออัปเดตสำเร็จ
document.addEventListener('form:submitted', (e) => {
const { formId, response } = e.detail;
if (formId === 'profile' && response.success) {
// ปรับ UI ด้วยข้อมูลใหม่
if (response.data.avatar) {
avatarPreview.src = response.data.avatar;
}
}
});
</script>5. ฟอร์มหลายขั้นตอน
<form
data-form="wizard"
data-action="/api/onboarding"
data-validate-on-submit="false">
<!-- ขั้นที่ 1: ข้อมูลส่วนตัว -->
<div class="step" data-step="1">
<h3>ขั้นที่ 1: ข้อมูลส่วนบุคคล</h3>
<div class="form-group">
<label>ชื่อ:</label>
<input type="text" name="name" required>
</div>
<div class="form-group">
<label>อีเมล:</label>
<input type="email" name="email" required>
</div>
<button type="button" class="btn-next">ถัดไป</button>
</div>
<!-- ขั้นที่ 2: ข้อมูลบัญชี -->
<div class="step" data-step="2" style="display:none">
<h3>ขั้นที่ 2: ตั้งค่าบัญชี</h3>
<div class="form-group">
<label>ชื่อผู้ใช้:</label>
<input type="text" name="username" required>
</div>
<div class="form-group">
<label>รหัสผ่าน:</label>
<input type="password" name="password" id="password" required>
</div>
<div class="form-group">
<label>ยืนยันรหัสผ่าน:</label>
<input
type="password"
name="confirm_password"
data-validate-match="password"
required>
</div>
<button type="button" class="btn-prev">ย้อนกลับ</button>
<button type="button" class="btn-next">ถัดไป</button>
</div>
<!-- ขั้นที่ 3: การตั้งค่าความชอบ -->
<div class="step" data-step="3" style="display:none">
<h3>ขั้นที่ 3: การตั้งค่าความชอบ</h3>
<div class="form-group">
<label>ภาษา:</label>
<select name="language">
<option value="en">English</option>
<option value="th">ไทย</option>
</select>
</div>
<div class="form-group">
<label>การแจ้งเตือน:</label>
<label>
<input type="checkbox" name="notifications[]" value="email">
อีเมล
</label>
<label>
<input type="checkbox" name="notifications[]" value="sms">
SMS
</label>
</div>
<button type="button" class="btn-prev">ย้อนกลับ</button>
<button type="submit">เสร็จสิ้น</button>
</div>
<!-- ตัวบอกความคืบหน้า -->
<div class="progress-steps">
<div class="step-indicator active" data-step="1">1</div>
<div class="step-indicator" data-step="2">2</div>
<div class="step-indicator" data-step="3">3</div>
</div>
</form>
<script>
let currentStep = 1;
const totalSteps = 3;
// ปุ่มถัดไป
document.querySelectorAll('.btn-next').forEach(btn => {
btn.addEventListener('click', async () => {
// ตรวจสอบขั้นตอนปัจจุบัน
const stepElement = document.querySelector(`.step[data-step="${currentStep}"]`);
const fields = stepElement.querySelectorAll('input, select, textarea');
let valid = true;
for (const field of fields) {
const instance = FormManager.getInstance('wizard');
const isValid = await FormManager.validateField(instance, field);
if (!isValid) {
valid = false;
break;
}
}
if (valid && currentStep < totalSteps) {
// ซ่อนขั้นตอนปัจจุบัน
stepElement.style.display = 'none';
// แสดงขั้นตอนถัดไป
currentStep++;
document.querySelector(`.step[data-step="${currentStep}"]`).style.display = 'block';
// อัปเดตตัวบอกความคืบหน้า
document.querySelector(`.step-indicator[data-step="${currentStep}"]`).classList.add('active');
}
});
});
// ปุ่มย้อนกลับ
document.querySelectorAll('.btn-prev').forEach(btn => {
btn.addEventListener('click', () => {
if (currentStep > 1) {
// ซ่อนขั้นตอนปัจจุบัน
document.querySelector(`.step[data-step="${currentStep}"]`).style.display = 'none';
// แสดงขั้นตอนก่อนหน้า
currentStep--;
document.querySelector(`.step[data-step="${currentStep}"]`).style.display = 'block';
// อัปเดตตัวบอกความคืบหน้า
document.querySelector(`.step-indicator[data-step="${currentStep + 1}"]`).classList.remove('active');
}
});
});
</script>6. ฟิลด์แบบไดนามิก
<form data-form="dynamic" data-action="/api/contacts">
<div id="contactsContainer">
<div class="contact-row">
<input type="text" name="contacts[0][name]" placeholder="ชื่อ" required>
<input type="email" name="contacts[0][email]" placeholder="อีเมล" required>
<button type="button" class="btn-remove" style="display:none">ลบ</button>
</div>
</div>
<button type="button" id="addContact">เพิ่มผู้ติดต่อ</button>
<button type="submit">ส่ง</button>
</form>
<script>
let contactIndex = 1;
document.getElementById('addContact').addEventListener('click', () => {
const container = document.getElementById('contactsContainer');
const row = document.createElement('div');
row.className = 'contact-row';
row.innerHTML = `
<input type="text" name="contacts[${contactIndex}][name]" placeholder="ชื่อ" required>
<input type="email" name="contacts[${contactIndex}][email]" placeholder="อีเมล" required>
<button type="button" class="btn-remove">ลบ</button>
`;
container.appendChild(row);
contactIndex++;
// แสดงปุ่มลบ
document.querySelectorAll('.btn-remove').forEach(btn => {
btn.style.display = 'inline-block';
});
// สแกนฟอร์มใหม่เพื่อเตรียมฟิลด์ที่เพิ่มขึ้น
FormManager.scan(container);
});
// ลบผู้ติดต่อ
document.addEventListener('click', (e) => {
if (e.target.classList.contains('btn-remove')) {
e.target.closest('.contact-row').remove();
// ซ่อนปุ่มลบเมื่อเหลือเพียง 1 รายการ
const rows = document.querySelectorAll('.contact-row');
if (rows.length === 1) {
rows[0].querySelector('.btn-remove').style.display = 'none';
}
}
});
</script>เอกสารอ้างอิง API
Methods
init(options)
เริ่มต้น FormManager พร้อมตัวเลือกการตั้งค่า
พารามิเตอร์:
options(Object) - ตัวเลือกการตั้งค่า
ค่าที่ส่งกลับ: Promise<FormManager>
ตัวอย่าง:
await FormManager.init({
debug: false,
ajaxSubmit: true,
autoValidate: true
});initForm(form)
เริ่มต้นฟอร์มตามที่ระบุ
พารามิเตอร์:
form(HTMLFormElement) - อิลิเมนต์ฟอร์ม
ค่าที่ส่งกลับ: Object - อินสแตนซ์ของฟอร์ม
ตัวอย่าง:
const formElement = document.querySelector('form[data-form="contact"]');
const instance = FormManager.initForm(formElement);getInstance(formId)
ดึงอินสแตนซ์ของฟอร์มด้วยรหัส
พารามิเตอร์:
formId(string) - ค่าจากแอตทริบิวต์ data-form
ค่าที่ส่งกลับ: Object|undefined - อินสแตนซ์ของฟอร์ม
ตัวอย่าง:
const instance = FormManager.getInstance('contact');
console.log(instance.state.valid);getInstanceByElement(formElement)
ดึงอินสแตนซ์จากอิลิเมนต์ฟอร์ม
พารามิเตอร์:
formElement(HTMLFormElement) - อิลิเมนต์ฟอร์ม
ค่าที่ส่งกลับ: Object|null - อินสแตนซ์ของฟอร์ม
ตัวอย่าง:
const form = document.querySelector('form');
const instance = FormManager.getInstanceByElement(form);getValues(identifier)
ดึงข้อมูลฟอร์มในรูปแบบอ็อบเจ็กต์ปกติ
พารามิเตอร์:
identifier(string|HTMLFormElement|Object) - รหัสฟอร์ม อิลิเมนต์ หรืออินสแตนซ์
ค่าที่ส่งกลับ: Object|null - ข้อมูลฟอร์ม
ตัวอย่าง:
const data = FormManager.getValues('contact');
console.log(data);
// { name: "John", email: "john@example.com" }validateForm(instance)
ตรวจสอบความถูกต้องของฟอร์มทั้งชุด
พารามิเตอร์:
instance(Object) - อินสแตนซ์ของฟอร์ม
ค่าที่ส่งกลับ: Promise<boolean> - เป็นจริงเมื่อข้อมูลถูกต้อง
ตัวอย่าง:
const instance = FormManager.getInstance('contact');
const isValid = await FormManager.validateForm(instance);
if (isValid) {
console.log('ฟอร์มผ่านการตรวจสอบ');
} else {
console.log('พบข้อผิดพลาดในการตรวจสอบ:', instance.state.errors);
}validateField(instance, field)
ตรวจสอบความถูกต้องของฟิลด์เดี่ยว
พารามิเตอร์:
instance(Object) - อินสแตนซ์ของฟอร์มfield(HTMLElement) - อิลิเมนต์ฟิลด์
ค่าที่ส่งกลับ: Promise<boolean> - เป็นจริงเมื่อฟิลด์ถูกต้อง
ตัวอย่าง:
const instance = FormManager.getInstance('contact');
const emailField = document.querySelector('input[name="email"]');
const isValid = await FormManager.validateField(instance, emailField);registerValidator(name, fn, defaultMessage)
ลงทะเบียนตัวตรวจสอบแบบกำหนดเอง
พารามิเตอร์:
name(string) - ชื่อตัวตรวจสอบfn(Function) - ฟังก์ชันตรวจสอบdefaultMessage(string) - ข้อความผิดพลาดเริ่มต้น
ค่าที่ส่งกลับ: void
ตัวอย่าง:
FormManager.registerValidator('username', (value, element) => {
return /^[a-zA-Z0-9_]+$/.test(value);
}, 'ชื่อผู้ใช้ต้องประกอบด้วยตัวอักษร ตัวเลข หรือขีดล่างเท่านั้น');resetForm(instance)
รีเซ็ตฟอร์มกลับสู่สถานะเริ่มต้น
พารามิเตอร์:
instance(Object) - อินสแตนซ์ของฟอร์ม
ค่าที่ส่งกลับ: void
ตัวอย่าง:
const instance = FormManager.getInstance('contact');
FormManager.resetForm(instance);destroyForm(instance)
ยกเลิกอินสแตนซ์ของฟอร์มและทำความสะอาดทรัพยากร
พารามิเตอร์:
instance(Object) - อินสแตนซ์ของฟอร์ม
ค่าที่ส่งกลับ: void
ตัวอย่าง:
const instance = FormManager.getInstance('contact');
FormManager.destroyForm(instance);scan(container)
สแกนหาฟอร์มภายในคอนเทนเนอร์และเริ่มต้นการทำงาน
พารามิเตอร์:
container(HTMLElement) - คอนเทนเนอร์ (ค่าเริ่มต้น: document)
ค่าที่ส่งกลับ: Array<HTMLFormElement> - ฟอร์มที่พบ
ตัวอย่าง:
// สแกนทั้งเอกสาร
FormManager.scan();
// สแกนเฉพาะคอนเทนเนอร์
const modal = document.getElementById('modal');
FormManager.scan(modal);แนวทางปฏิบัติที่แนะนำ
1. ระบุแอตทริบิวต์ data-form เสมอ
<!-- ✅ ดี - ระบุว่าฟอร์มนี้ต้องให้ FormManager จัดการ -->
<form data-form="contact">
<!-- ฟิลด์ของฟอร์ม -->
</form>
<!-- ❌ ไม่ดี - จะไม่ถูกเริ่มต้น -->
<form>
<!-- FormManager จะไม่จัดการฟอร์มนี้ -->
</form>2. ระบุข้อความผิดพลาดให้ชัดเจน
<!-- ✅ ดี - กำหนดข้อความผิดพลาดเฉพาะเจาะจง -->
<input
type="email"
name="email"
required
data-error-required="กรุณากรอกอีเมลของคุณ"
data-error-email="กรุณากรอกอีเมลให้ถูกต้อง">
<!-- ❌ ไม่ดี - ใช้ข้อความทั่วไป -->
<input type="email" name="email" required>3. ใช้ตัวตรวจสอบให้เหมาะสม
<!-- ✅ ดี - เลือกตัวตรวจสอบที่ตรงกับข้อมูล -->
<input
type="email"
name="email"
required
data-validate-email="true">
<input
type="url"
name="website"
data-validate-url="true">
<!-- ❌ ไม่ดี - ไม่ตรวจสอบเลย -->
<input type="text" name="email">
<input type="text" name="website">4. จัดการผลลัพธ์ให้ครบทั้งสำเร็จและผิดพลาด
// ✅ ดี - รองรับทั้งกรณีสำเร็จและผิดพลาด
document.addEventListener('form:submitted', (e) => {
const { formId, response } = e.detail;
if (response.success) {
showSuccess(response.message);
} else {
showError(response.message);
}
});
document.addEventListener('form:error', (e) => {
const { formId, error } = e.detail;
showError(error.message);
});
// ❌ ไม่ดี - จัดการเฉพาะกรณีสำเร็จ
document.addEventListener('form:submitted', () => {
showSuccess('ดำเนินการเรียบร้อย!');
});5. ทำความสะอาดฟอร์มไดนามิกเมื่อไม่ใช้งาน
// ✅ ดี - ทำลายอินสแตนซ์ก่อนนำออก
function removeForm(formElement) {
FormManager.destroyFormByElement(formElement);
formElement.remove();
}
// ❌ ไม่ดี - ลบอิลิเมนต์อย่างเดียว
function removeForm(formElement) {
formElement.remove(); // อาจเกิด memory leak
}Auto-Load Form Data from API
FormManager รองรับการโหลดข้อมูลจาก API อัตโนมัติเมื่อเปิดฟอร์ม โดยใช้ URL parameters เป็นตัวกำหนด
พื้นฐาน: Single Parameter
<!-- URL: /form.html?id=1 -->
<form data-form="edit-user"
data-action="/api/v1/users"
data-method="PUT"
data-load-url="/api/v1/users/{id}"
data-auto-load="true">
<!-- Developer ต้องสร้าง input fields เอง -->
<input type="hidden" name="id" data-from-url="id" />
<div class="form-group">
<label>Name</label>
<input type="text" name="name" required />
</div>
<div class="form-group">
<label>Email</label>
<input type="email" name="email" required />
</div>
<div class="form-group">
<label>Phone</label>
<input type="tel" name="phone" />
</div>
<button type="submit">Update User</button>
</form>Flow:
- FormManager ตรวจจับ
data-auto-load="true" - อ่าน
data-load-url="/api/v1/users/{id}" - หา input ที่มี
data-from-url="id"→ ดึงค่าจาก URL param?id=1 - แทนที่
{id}ใน URL →/api/v1/users/1 - Request
GET /api/v1/users/1 - Populate form fields ตาม
nameattribute
API Response:
{
"success": true,
"data": {
"id": 1,
"name": "John Doe",
"email": "john@example.com",
"phone": "0812345678"
}
}Multiple Parameters
<!-- URL: /form.html?userId=5&addressId=12 -->
<form data-form="edit-address"
data-action="/api/v1/users/{userId}/addresses"
data-method="PUT"
data-load-url="/api/v1/users/{userId}/addresses/{addressId}"
data-auto-load="true">
<!-- Developer ต้องสร้าง input fields เอง - ไม่ต้องกังวลเรื่อง duplicate -->
<input type="hidden" name="userId" data-from-url="userId" />
<input type="hidden" name="addressId" data-from-url="addressId" />
<div class="form-group">
<label>Street</label>
<input type="text" name="street" required />
</div>
<div class="form-group">
<label>City</label>
<input type="text" name="city" required />
</div>
<div class="form-group">
<label>Postal Code</label>
<input type="text" name="zipcode" />
</div>
<button type="submit">Update Address</button>
</form>Result:
- Load from:
GET /api/v1/users/5/addresses/12 - Submit to:
PUT /api/v1/users/5/addresses/12
Optional Parameters with Defaults
<!-- URL: /form.html?reportId=5 หรือ ?reportId=5&version=2&lang=en -->
<form data-form="edit-report"
data-load-url="/api/v1/reports/{reportId}?version={version}&lang={lang}"
data-auto-load="true">
<input type="hidden" name="reportId" data-from-url="reportId" data-required="true" />
<input type="hidden" name="version" data-from-url="version" data-default="latest" />
<input type="hidden" name="lang" data-from-url="lang" data-default="th" />
<div class="form-group">
<label>Report Title</label>
<input type="text" name="title" required />
</div>
<div class="form-group">
<label>Content</label>
<textarea name="content" rows="10"></textarea>
</div>
<button type="submit">Save Report</button>
</form>URL Examples:
?reportId=5→GET /api/v1/reports/5?version=latest&lang=th?reportId=5&version=2→GET /api/v1/reports/5?version=2&lang=th?reportId=5&version=2&lang=en→GET /api/v1/reports/5?version=2&lang=en
Data Attributes สำหรับ Auto-Load
บน form element:
| Attribute | Type | Description | Example |
|---|---|---|---|
data-auto-load |
Boolean | เปิดใช้งาน auto-load | "true" |
data-load-url |
String | URL pattern (รองรับ {param}) |
"/api/users/{id}" |
data-load-method |
String | HTTP method สำหรับโหลด (default: GET) | "GET" |
data-on-load-error |
String | Callback function เมื่อโหลดผิดพลาด | "handleLoadError" |
บน input element:
| Attribute | Type | Description | Example |
|---|---|---|---|
data-from-url |
String | ชื่อ URL parameter ที่จะดึงค่า | "userId" |
data-default |
String | ค่า default ถ้าไม่มีใน URL | "latest" |
data-required |
Boolean | บังคับต้องมีค่า (ถ้าไม่มีจะไม่ load) | "true" |
Error Handling
<form data-load-url="/api/v1/users/{userId}/orders/{orderId}"
data-auto-load="true"
data-on-load-error="handleLoadError">
<input type="hidden" name="userId" data-from-url="userId" data-required="true" />
<input type="hidden" name="orderId" data-from-url="orderId" data-required="true" />
<!-- form fields -->
</form>
<script>
function handleLoadError(error) {
if (error.code === 'MISSING_REQUIRED_PARAM') {
NotificationManager.error('กรุณาระบุ User ID และ Order ID ใน URL');
} else if (error.code === 'NOT_FOUND') {
NotificationManager.error('ไม่พบข้อมูลที่ต้องการแก้ไข');
setTimeout(() => {
window.location.href = '/orders';
}, 2000);
} else {
NotificationManager.error('เกิดข้อผิดพลาด: ' + error.message);
}
}
</script>Error Codes:
MISSING_REQUIRED_PARAM- ขาด parameter ที่มีdata-required="true"NOT_FOUND- API ตอบกลับ 404UNAUTHORIZED- API ตอบกลับ 401/403NETWORK_ERROR- ปัญหาการเชื่อมต่อINVALID_RESPONSE- API response format ไม่ถูกต้อง
ตัวอย่างแบบสมบูรณ์: Edit Order Item
<!DOCTYPE html>
<html>
<head>
<title>Edit Order Item</title>
<link rel="stylesheet" href="/Now/css/Now.css">
</head>
<body>
<!-- URL: /edit-order-item.html?orderId=1001&itemId=3 -->
<div class="container">
<h1>Edit Order Item</h1>
<form data-form="edit-order-item"
data-action="/api/v1/orders/{orderId}/items"
data-method="PUT"
data-load-url="/api/v1/orders/{orderId}/items/{itemId}"
data-auto-load="true"
data-ajax-submit="true"
data-on-load-error="handleLoadError">
<!-- Hidden fields - Developer สร้างเอง -->
<input type="hidden" name="orderId" data-from-url="orderId" data-required="true" />
<input type="hidden" name="itemId" data-from-url="itemId" data-required="true" />
<div class="form-group">
<label>Product Name</label>
<input type="text" name="productName" readonly class="readonly" />
</div>
<div class="form-group">
<label>Quantity *</label>
<input type="number" name="quantity" min="1" required />
</div>
<div class="form-group">
<label>Price *</label>
<input type="number" name="price" step="0.01" min="0" required />
</div>
<div class="form-group">
<label>Discount (%)</label>
<input type="number" name="discount" min="0" max="100" value="0" />
</div>
<div class="form-actions">
<button type="button" class="button" onclick="history.back()">Cancel</button>
<button type="submit" class="button primary">Update Item</button>
</div>
</form>
</div>
<script src="/Now/Now.js"></script>
<script>
// Initialize FormManager
await FormManager.init({
debug: true,
ajaxSubmit: true
});
// Error handler
function handleLoadError(error) {
console.error('Load error:', error);
if (error.code === 'MISSING_REQUIRED_PARAM') {
NotificationManager.error('Missing order ID or item ID in URL');
setTimeout(() => window.location.href = '/orders', 2000);
} else if (error.code === 'NOT_FOUND') {
NotificationManager.error('Order item not found');
setTimeout(() => window.location.href = '/orders', 2000);
} else {
NotificationManager.error('Failed to load data: ' + error.message);
}
}
// Success handler
document.addEventListener('form:submitted', (e) => {
const { formId, response } = e.detail;
if (formId === 'edit-order-item' && response.success) {
NotificationManager.success('Order item updated successfully');
setTimeout(() => {
window.location.href = '/orders?id=' + response.data.orderId;
}, 1000);
}
});
</script>
</body>
</html>API Endpoint (PHP):
// GET /api/v1/orders/{orderId}/items/{itemId}
public function show(Request $request) {
$orderId = $request->get('orderId')->toInt();
$itemId = $request->get('itemId')->toInt();
$item = $this->db->table('order_items')
->join('products', 'products.id', 'order_items.product_id')
->where([
['order_items.id', $itemId],
['order_items.order_id', $orderId]
])
->select(
'order_items.*',
'products.name as productName'
)
->first();
if (!$item) {
return $this->errorResponse('Order item not found', 404);
}
return $this->successResponse([
'data' => $item
]);
}
// PUT /api/v1/orders/{orderId}/items/{itemId}
public function update(Request $request) {
$orderId = $request->get('orderId')->toInt();
$itemId = $request->post('itemId')->toInt();
$quantity = $request->post('quantity')->toInt();
$price = $request->post('price')->toFloat();
$discount = $request->post('discount', 0)->toFloat();
// Validation
if ($quantity < 1) {
return $this->errorResponse('Quantity must be at least 1', 400);
}
// Update
$updated = $this->db->update('order_items', [
['id', $itemId],
['order_id', $orderId]
], [
'quantity' => $quantity,
'price' => $price,
'discount' => $discount,
'updated_at' => date('Y-m-d H:i:s')
]);
if ($updated) {
return $this->successResponse([
'data' => [
'orderId' => $orderId,
'itemId' => $itemId
],
'actions' => [
'type' => 'reload',
'closeModal' => true,
'reload' => '[data-table="orders"]'
]
], 'อัพเดทสำเร็จ');
}
return $this->errorResponse('Failed to update', 500);
}สรุป Auto-Load Features
✅ Developer Control - Developer สร้าง input fields เอง (ไม่มี auto-generation)
✅ Multiple Parameters - รองรับหลาย URL parameters
✅ Optional Parameters - รองรับ default values
✅ Error Handling - Error codes และ callbacks ที่ชัดเจน
✅ Path + Query Params - รองรับทั้ง /users/{id} และ ?version={v}
✅ Validation - ตรวจสอบ required parameters ก่อน load
✅ Integration - ทำงานร่วมกับ ResponseHandler และ AJAX submit
All Form Data Attributes Reference
Core Attributes
| Attribute | Type | Default | Description |
|---|---|---|---|
data-form |
String | - | Required - Form ID สำหรับ identify |
data-ajax-submit |
Boolean | true |
ส่งฟอร์มแบบ AJAX |
data-action |
String | form.action |
URL endpoint สำหรับส่งข้อมูล |
data-method |
String | POST |
HTTP method (GET, POST, PUT, DELETE, PATCH) |
data-confirm |
String | - | ข้อความยืนยันก่อนส่งฟอร์ม |
Validation Attributes
| Attribute | Type | Default | Description |
|---|---|---|---|
data-auto-validate |
Boolean | true |
Validate ก่อนส่งฟอร์ม |
data-validate-on-input |
Boolean | true |
Validate ขณะพิมพ์ |
data-validate-on-blur |
Boolean | true |
Validate เมื่อออกจาก field |
data-validate-only-dirty |
Boolean | true |
Validate เฉพาะ field ที่มีการแก้ไข |
data-auto-focus-error |
Boolean | true |
Focus ไปที่ error field แรก |
Auto-Load Attributes
| Attribute | Type | Default | Description |
|---|---|---|---|
data-auto-load |
Boolean | false |
เปิดใช้งาน auto-load จาก API |
data-load-url |
String | - | URL pattern สำหรับโหลด (รองรับ {param}) |
data-load-method |
String | GET |
HTTP method สำหรับโหลด |
data-on-load-error |
String | - | Callback function เมื่อโหลดผิดพลาด |
Security Attributes
| Attribute | Type | Default | Description |
|---|---|---|---|
data-csrf |
Boolean | true |
เปิดใช้ CSRF protection |
data-csrf-token |
String | auto | CSRF token (auto-detect ถ้าไม่ระบุ) |
data-rate-limit |
Boolean | true |
เปิดใช้ rate limiting |
data-sanitize-input |
Boolean | true |
ทำ input sanitization |
UI/UX Attributes
| Attribute | Type | Default | Description |
|---|---|---|---|
data-prevent-double-submit |
Boolean | true |
ป้องกันการส่งซ้ำ |
data-show-loading-on-submit |
Boolean | true |
แสดง loading state |
data-loading-text |
String | "Processing..." |
ข้อความขณะ processing |
data-reset-after-submit |
Boolean | false |
Reset ฟอร์มหลังส่งสำเร็จ |
data-auto-clear-errors |
Boolean | true |
ล้าง errors อัตโนมัติ |
data-auto-clear-errors-delay |
Number | 5000 |
ระยะเวลาก่อนล้าง errors (ms) |
Response Handling Attributes
| Attribute | Type | Default | Description |
|---|---|---|---|
data-redirect |
String | - | URL สำหรับ redirect หลังส่งสำเร็จ |
data-redirect-delay |
Number | 1000 |
ระยะเวลาก่อน redirect (ms) |
data-success-message |
String | auto | ข้อความแสดงเมื่อสำเร็จ |
data-error-message |
String | auto | ข้อความแสดงเมื่อผิดพลาด |
data-show-errors-inline |
Boolean | true |
แสดง errors ใกล้ field |
data-show-errors-in-notification |
Boolean | false |
แสดง errors ใน notification |
File Upload Attributes
| Attribute | Type | Default | Description |
|---|---|---|---|
data-max-file-size |
Number | - | ขนาดไฟล์สูงสุด (bytes) |
data-allowed-extensions |
String | - | นามสกุลไฟล์ที่อนุญาต (comma-separated) |
data-show-progress |
Boolean | true |
แสดง upload progress |
Input Element Attributes (Auto-Load)
| Attribute | Type | Default | Description |
|---|---|---|---|
data-from-url |
String | - | ชื่อ URL parameter ที่จะดึงค่า |
data-default |
String | - | ค่า default ถ้าไม่มีใน URL |
data-required |
Boolean | false |
บังคับต้องมีค่า (ถ้าไม่มีจะไม่ load) |
ปัญหาที่พบบ่อย
1. ลืมเพิ่มแอตทริบิวต์ data-form
<!-- ❌ ไม่ดี - จะไม่ถูกเริ่มต้น -->
<form action="/submit" method="POST">
<input type="text" name="name">
<button type="submit">ส่ง</button>
</form>
<!-- ✅ ดี - FormManager จะเริ่มต้นให้ -->
<form data-form="contact" action="/submit" method="POST">
<input type="text" name="name">
<button type="submit">ส่ง</button>
</form>2. ไม่จัดการข้อผิดพลาด
// ❌ ไม่ดี - ไม่มีการจัดการข้อผิดพลาด
document.addEventListener('form:submitted', () => {
showSuccess('ส่งเรียบร้อย!');
});
// ✅ ดี - ตรวจสอบผลลัพธ์ก่อนแสดงข้อความ
document.addEventListener('form:submitted', (e) => {
const { response } = e.detail;
if (response.success) {
showSuccess(response.message);
} else {
showError(response.message);
}
});3. ไม่ตรวจสอบก่อนทำงานพิเศษ
// ❌ ไม่ดี - ส่งข้อมูลโดยไม่ตรวจสอบก่อน
document.querySelector('button.custom').addEventListener('click', async () => {
const data = FormManager.getValues('contact');
await sendData(data);
});
// ✅ ดี - ตรวจสอบก่อนทุกครั้ง
document.querySelector('button.custom').addEventListener('click', async () => {
const instance = FormManager.getInstance('contact');
const isValid = await FormManager.validateForm(instance);
if (isValid) {
const data = FormManager.getValues('contact');
await sendData(data);
}
});4. ตั้งค่าการส่งฟอร์มไม่สอดคล้องกัน
<!-- ❌ ไม่ดี - การตั้งค่าขัดกัน -->
<form
data-form="mixed"
data-ajax-submit="true"
action="/submit"
method="POST">
<!-- จะส่งแบบ AJAX แต่ไม่ใช้ action/method ที่ตั้งไว้ -->
</form>
<!-- ✅ ดี - ตั้งค่าให้สอดคล้อง -->
<form
data-form="ajax"
data-ajax-submit="true"
data-action="/api/submit"
data-method="POST">
<!-- ชัดเจนว่าใช้การส่งแบบ AJAX -->
</form>เอกสารที่เกี่ยวข้อง
- SecurityManager.md - การป้องกัน CSRF และการจำกัดความถี่
- AuthManager.md - การผสานงานกับระบบยืนยันตัวตน
- RouterManager.md - การนำทางหลังส่งฟอร์ม
- ResponseHandler.md - การประมวลผลผลลัพธ์จากเซิร์ฟเวอร์
- NotificationManager.md - ระบบแจ้งเตือนผู้ใช้