Now.js Framework Documentation

Now.js Framework Documentation

FormError

TH 31 Oct 2025 01:38

FormError

ภาพรวม

FormError เป็นระบบจัดการข้อผิดพลาดที่ครอบคลุมสำหรับฟอร์มใน Now.js Framework มันให้ชุดเครื่องมือที่สมบูรณ์สำหรับการแสดง จัดการ และล้างข้อผิดพลาดการตรวจสอบและข้อความสำเร็จในฟอร์ม ระบบรองรับการจัดการข้อผิดพลาดทั้งระดับฟิลด์และระดับฟอร์มพร้อมการจัดการโฟกัสอัตโนมัติ การเลื่อน คุณสมบัติการเข้าถึง และฟังก์ชันล้างอัตโนมัติที่ปรับแต่งได้

คุณสมบัติหลัก

  • การแสดงข้อผิดพลาดระดับฟิลด์ - แสดงข้อผิดพลาดการตรวจสอบข้างๆ ฟิลด์ของฟอร์ม
  • การแสดงข้อความทั่วไป - แสดงข้อผิดพลาดหรือข้อความสำเร็จระดับฟอร์ม
  • การจัดการโฟกัสอัตโนมัติ - โฟกัสไปยังฟิลด์ที่มีข้อผิดพลาดแรกโดยอัตโนมัติ
  • การเลื่อนอัจฉริยะ - เลื่อนไปยังฟิลด์ที่มีข้อผิดพลาดพร้อม offset ที่ปรับได้
  • การรองรับการเข้าถึง - แอตทริบิวต์ ARIA สำหรับ screen reader (aria-invalid, aria-errormessage)
  • ล้างข้อความอัตโนมัติ - ล้างข้อความอัตโนมัติหลังจากหน่วงเวลาที่กำหนด
  • การคืนค่าเนื้อหาเดิม - เก็บรักษาและคืนค่าเนื้อหาเดิมของ container
  • ระดับการตั้งค่าหลายแบบ - การตั้งค่าแบบ global และเฉพาะฟอร์ม
  • การผสานรวมระบบเหตุการณ์ - ปล่อยเหตุการณ์สำหรับการดำเนินการข้อผิดพลาดทั้งหมด
  • การตรวจหา Container แบบยืดหยุ่น - ตรวจหาหรือระบุ container ข้อผิดพลาด/สำเร็จอัตโนมัติ

กรณีการใช้งาน

  • การตรวจสอบฟอร์ม - แสดงข้อผิดพลาดการตรวจสอบแบบ inline สำหรับฟิลด์ฟอร์ม
  • ข้อผิดพลาดจาก Response เซิร์ฟเวอร์ - แสดงข้อความข้อผิดพลาดจาก response ของ API
  • การแจ้งเตือนความสำเร็จ - แสดงข้อความสำเร็จหลังจากส่งฟอร์ม
  • ฟอร์มหลายขั้นตอน - จัดการข้อผิดพลาดในฟอร์มหลายขั้นตอน
  • การตรวจสอบแบบเรียลไทม์ - แสดง/ล้างข้อผิดพลาดเมื่อผู้ใช้พิมพ์หรือออกจากฟิลด์
  • การปฏิบัติตามมาตรฐานการเข้าถึง - ทำให้ฟอร์มเข้าถึงได้ด้วยแอตทริบิวต์ ARIA ที่เหมาะสม

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

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

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

เริ่มต้นอย่างรวดเร็ว

<!-- ฟอร์มพร้อม container ข้อผิดพลาด -->
<form id="loginForm">
  <div class="form-control">
    <label for="email">อีเมล</label>
    <input type="email" id="email" name="email">
    <div id="result_email" class="comment"></div>
  </div>

  <div class="form-control">
    <label for="password">รหัสผ่าน</label>
    <input type="password" id="password" name="password">
    <div id="result_password" class="comment"></div>
  </div>

  <div id="form-message" class="form-message"></div>
</form>

<script>
  // แสดงข้อผิดพลาดฟิลด์
  FormError.showFieldError('email', 'กรุณากรอกอีเมลที่ถูกต้อง');

  // แสดงข้อผิดพลาดทั่วไป
  FormError.showGeneralError('ข้อมูลเข้าสู่ระบบไม่ถูกต้อง', document.getElementById('loginForm'));

  // ล้างข้อผิดพลาดฟิลด์เฉพาะ
  FormError.clearFieldError('email');

  // ล้างข้อผิดพลาดทั้งหมด
  FormError.clearAll();
</script>

การแสดงข้อความสำเร็จ

<script>
  // แสดงข้อความสำเร็จ
  FormError.showSuccess('เข้าสู่ระบบสำเร็จ! กำลังเปลี่ยนเส้นทาง...', document.getElementById('loginForm'));

  // ล้างอัตโนมัติหลัง 3 วินาที
  setTimeout(() => {
    FormError.clearSuccess(document.getElementById('loginForm'));
  }, 3000);
</script>

การตั้งค่า

การตั้งค่าแบบ Global

ตั้งค่าพฤติกรรมเริ่มต้นสำหรับฟอร์มทั้งหมด:

FormError.configure({
  // คลาส CSS สำหรับฟิลด์ที่ไม่ผ่านการตรวจสอบ (ค่าเริ่มต้น: 'invalid')
  errorClass: 'has-error',

  // คลาส CSS สำหรับข้อความข้อผิดพลาด (ค่าเริ่มต้น: 'error')
  errorMessageClass: 'error-text',

  // โฟกัสฟิลด์ข้อผิดพลาดตัวแรกโดยอัตโนมัติ (ค่าเริ่มต้น: true)
  autoFocus: true,

  // เลื่อนไปยังข้อผิดพลาดตัวแรกโดยอัตโนมัติ (ค่าเริ่มต้น: true)
  autoScroll: true,

  // ระยะเยื้องในการเลื่อน (พิกเซล) (ค่าเริ่มต้น: 100)
  scrollOffset: 120,

  // เปิดโหมดดีบัก (ค่าเริ่มต้น: false)
  debug: true,

  // แสดงข้อผิดพลาดถัดจากฟิลด์โดยตรง (ค่าเริ่มต้น: true)
  showErrorsInline: true,

  // แสดงข้อผิดพลาดผ่านระบบแจ้งเตือน (ค่าเริ่มต้น: false)
  showErrorsInNotification: false,

  // ล้างข้อผิดพลาดอัตโนมัติหลังหน่วงเวลา (ค่าเริ่มต้น: true)
  autoClearErrors: true,

  // ระยะเวลาล้างอัตโนมัติ (มิลลิวินาที) (ค่าเริ่มต้น: 5000)
  autoClearErrorsDelay: 3000,

  // ID ของ container ข้อผิดพลาดเริ่มต้น (ค่าเริ่มต้น: 'form-message')
  defaultErrorContainer: 'global-error',

  // ID ของ container ข้อความสำเร็จเริ่มต้น (ค่าเริ่มต้น: 'form-message')
  defaultSuccessContainer: 'global-success'
});

การตั้งค่าเฉพาะฟอร์ม

FormError จะรับค่าการตั้งค่าจากอินสแตนซ์ของ FormManager โดยอัตโนมัติ:

<form
  data-form="registrationForm"
  data-error-container="registration-errors"
  data-success-container="registration-success"
  data-auto-focus="true"
  data-auto-scroll="false">
  <!-- ฟิลด์ของฟอร์ม -->
</form>

<script>
  // FormError จะใช้การตั้งค่าเฉพาะฟอร์มเมื่อแสดงข้อผิดพลาด
  const form = document.querySelector('[data-form="registrationForm"]');

  // ใช้ค่า container สำหรับข้อผิดพลาดที่กำหนดไว้ในฟอร์ม
  FormError.showGeneralError('การลงทะเบียนล้มเหลว', form);
</script>

การตั้งค่า Priority

  1. การตั้งค่าเฉพาะฟอร์ม (จากอินสแตนซ์ FormManager)
  2. การตั้งค่าแบบ global (จาก FormError.config)
  3. การตั้งค่าเริ่มต้น (ค่าที่ฝังอยู่ในโค้ด)

การจัดการข้อผิดพลาดของฟิลด์

แสดงข้อผิดพลาดของฟิลด์

แสดงข้อผิดพลาดการตรวจสอบสำหรับฟิลด์เฉพาะ:

// ตาม ID ของฟิลด์
FormError.showFieldError('email', 'ต้องระบุอีเมล');

// ตามชื่อฟิลด์
FormError.showFieldError('username', 'ชื่อผู้ใช้ต้องมีอย่างน้อย 6 ตัวอักษร');

// มีข้อความผิดพลาดหลายรายการ (แสดงรายการแรก)
FormError.showFieldError('password', [
  'ต้องระบุรหัสผ่าน',
  'รหัสผ่านต้องมีอย่างน้อย 8 ตัวอักษร',
  'รหัสผ่านต้องมีตัวเลขอย่างน้อย 1 ตัว'
]);

// ด้วยบริบทฟอร์ม
const form = document.getElementById('myForm');
FormError.showFieldError('email', 'อีเมลไม่ถูกต้อง', form);

// พร้อมตัวเลือกเพิ่มเติม
FormError.showFieldError('email', 'อีเมลไม่ถูกต้อง', form, {
  focus: false,  // ไม่ต้องโฟกัสอัตโนมัติ
  scroll: false  // ไม่ต้องเลื่อนอัตโนมัติ
});

โครงสร้าง Container ข้อผิดพลาด

FormError คาดหวังโครงสร้าง HTML นี้สำหรับข้อผิดพลาดของฟิลด์:

<div class="form-control">
  <label for="fieldName">ป้ายกำกับฟิลด์</label>
  <input type="text" id="fieldName" name="fieldName">

  <!-- container ข้อความข้อผิดพลาด (ต้องมี id="result_{fieldName}") -->
  <div id="result_fieldName" class="comment"></div>
</div>

สิ่งที่เกิดขึ้นเมื่อแสดงข้อผิดพลาดฟิลด์

  1. ค้นหาฟิลด์ - ค้นหาองค์ประกอบโดย ID หรือแอตทริบิวต์ name
  2. เก็บสถานะเดิม - บันทึกเนื้อหาข้อความเดิมหากมีอยู่
  3. เพิ่มคลาสข้อผิดพลาด - เพิ่ม errorClass ให้กับฟิลด์ พาเรนต์ .form-control และ container ฟิลด์
  4. แสดงข้อความ - แสดงข้อความข้อผิดพลาดใน container result_{fieldName}
  5. ตั้งค่าแอตทริบิวต์ ARIA - เพิ่ม aria-invalid="true" และ aria-errormessage
  6. การจัดการโฟกัส - โฟกัสฟิลด์ข้อผิดพลาดแรก (ถ้า autoFocus เปิดใช้งาน)
  7. เลื่อนไปที่ฟิลด์ - เลื่อนไปที่ฟิลด์ข้อผิดพลาด (ถ้า autoScroll เปิดใช้งาน)
  8. ปล่อยเหตุการณ์ - ยิงเหตุการณ์ form:error พร้อมรายละเอียดข้อผิดพลาด

ล้างข้อผิดพลาดของฟิลด์

ลบข้อผิดพลาดการตรวจสอบออกจากฟิลด์เฉพาะ:

// โดย ID หรือชื่อฟิลด์
FormError.clearFieldError('email');

// โดยองค์ประกอบฟิลด์
const emailField = document.getElementById('email');
FormError.clearFieldError(emailField);

สิ่งที่เกิดขึ้นเมื่อล้างข้อผิดพลาดฟิลด์

  1. ลบคลาสข้อผิดพลาด - ลบ errorClass ออกจากฟิลด์ พาเรนต์ และ container
  2. คืนค่าข้อความเดิม - คืนค่าเนื้อหาเดิมของ container result_{fieldName}
  3. ลบแอตทริบิวต์ ARIA - ลบ aria-invalid และ aria-errormessage
  4. ล้างสถานะ - ลบออกจากแผนที่ข้อผิดพลาดภายใน
  5. ปล่อยเหตุการณ์ - ยิงเหตุการณ์ form:clearError

ข้อความข้อผิดพลาดทั่วไป

แสดงข้อผิดพลาดทั่วไป

แสดงข้อความข้อผิดพลาดระดับฟอร์ม:

// ใช้กับองค์ประกอบฟอร์ม
const form = document.getElementById('loginForm');
FormError.showGeneralError('ข้อมูลเข้าสู่ระบบไม่ถูกต้อง', form);

// ใช้กับ ID ของ container
FormError.showGeneralError('เกิดข้อผิดพลาดของเซิร์ฟเวอร์', 'error-container');

// ไม่ระบุฟอร์ม (ใช้ container ค่าเริ่มต้น)
FormError.showGeneralError('เกิดข้อผิดพลาดที่ไม่คาดคิด');

// เปิดใช้งานการล้างอัตโนมัติ
FormError.showGeneralError('กรุณาแก้ไขข้อผิดพลาดด้านล่าง', form);
// จะล้างอัตโนมัติหลัง 5 วินาที (ถ้าเปิด autoClearErrors)

ลำดับความสำคัญการตรวจหา Container

FormError จะค้นหา container สำหรับข้อผิดพลาดตามลำดับต่อไปนี้:

  1. แอตทริบิวต์ data-error-container ของฟอร์ม

    <form data-error-container="my-errors">
  2. องค์ประกอบที่มี [data-error-container] อยู่ภายในฟอร์ม

    <div data-error-container class="error-box"></div>
  3. องค์ประกอบที่มีคลาส .form-message

    <div class="form-message"></div>
  4. องค์ประกอบที่มีคลาส .error-message

    <div class="error-message"></div>
  5. องค์ประกอบที่มีคลาส .login-message

    <div class="login-message"></div>
  6. container ข้อผิดพลาดเริ่มต้น (ตามการตั้งค่า)

    FormError.config.defaultErrorContainer // 'form-message'

การเก็บรักษาเนื้อหาเดิม

FormError จะเก็บรักษาเนื้อหาเดิมของ container ไว้:

<div id="form-message" class="form-message">
  <p>ยินดีต้อนรับ! กรุณาเข้าสู่ระบบเพื่อดำเนินการต่อ</p>
</div>

<script>
  // แสดงข้อผิดพลาด - บันทึกเนื้อหาเดิมไว้ก่อน
  FormError.showGeneralError('เข้าสู่ระบบล้มเหลว', document.getElementById('loginForm'));

  // ล้างข้อผิดพลาด - คืนค่าเนื้อหาเดิมกลับมา
  FormError.clearGeneralError('form-message');
  // จะกลับมาแสดงข้อความ "ยินดีต้อนรับ! กรุณาเข้าสู่ระบบเพื่อดำเนินการต่อ" อีกครั้ง
</script>

ล้างข้อผิดพลาดทั่วไป

Remove form-level error message:

// ระบุด้วย ID ของ container
FormError.clearGeneralError('form-message');

// ระบุด้วยองค์ประกอบฟอร์ม
const form = document.getElementById('loginForm');
FormError.clearGeneralError(form);

// ระบุด้วยองค์ประกอบ container โดยตรง
const container = document.getElementById('error-container');
FormError.clearGeneralError(container);

// ล้าง container ค่าเริ่มต้น
FormError.clearGeneralError();

ข้อความสำเร็จ

แสดงข้อความสำเร็จ

แสดงข้อความสำเร็จระดับฟอร์ม:

// ใช้กับองค์ประกอบฟอร์ม
const form = document.getElementById('loginForm');
FormError.showSuccess('เข้าสู่ระบบสำเร็จ!', form);

// ใช้กับ ID ของ container
FormError.showSuccess('อัปเดตโปรไฟล์เรียบร้อยแล้ว', 'success-container');

// ไม่ระบุฟอร์ม (ใช้ container ค่าเริ่มต้น)
FormError.showSuccess('บันทึกการเปลี่ยนแปลงแล้ว');

// เปิดใช้งานการล้างอัตโนมัติ
FormError.showSuccess('ลงทะเบียนเสร็จสมบูรณ์', form);
// จะล้างอัตโนมัติหลัง 5 วินาที (ถ้าเปิด autoClearErrors)

การตรวจหา Container สำเร็จ

มีหลักการเดียวกับ container ข้อผิดพลาด แต่จะค้นหาตามลำดับดังนี้:

  1. แอตทริบิวต์ data-success-container ของฟอร์ม
  2. องค์ประกอบที่มี [data-success-container] อยู่ภายในฟอร์ม
  3. องค์ประกอบที่มีคลาส .form-message
  4. องค์ประกอบที่มีคลาส .success-message
  5. องค์ประกอบที่มีคลาส .login-message
  6. container สำเร็จเริ่มต้น (อ้างอิงจากการตั้งค่า)

ล้างข้อความสำเร็จ

Remove success notification:

// ระบุด้วย ID ของ container
FormError.clearSuccess('success-container');

// ระบุด้วยองค์ประกอบฟอร์ม
const form = document.getElementById('loginForm');
FormError.clearSuccess(form);

// ระบุด้วยองค์ประกอบ container โดยตรง
const container = document.getElementById('success-container');
FormError.clearSuccess(container);

// ล้าง container ค่าเริ่มต้น
FormError.clearSuccess();

การดำเนินการเป็นกลุ่ม

แสดงข้อผิดพลาดหลายรายการ

แสดงข้อผิดพลาดหลายฟิลด์พร้อมกัน:

const errors = {
  email: 'ต้องระบุอีเมล',
  password: 'รหัสผ่านต้องมีอย่างน้อย 8 ตัวอักษร',
  username: 'ชื่อผู้ใช้นี้ถูกใช้แล้ว'
};

// แสดงข้อผิดพลาดทั้งหมด (ล้างข้อผิดพลาดเดิมก่อน)
FormError.showErrors(errors);

// ฟิลด์ข้อผิดพลาดอันแรกจะถูกโฟกัสและเลื่อนไปหา
// ข้อผิดพลาดอื่นจะแสดงแบบอินไลน์

// พร้อมตัวเลือกเพิ่มเติม
FormError.showErrors(errors, {
  focus: false,  // ไม่ต้องโฟกัสฟิลด์ใดเลย
  scroll: false  // ไม่ต้องเลื่อน
});

ล้างข้อความทั้งหมด

Remove all errors and success messages:

// ล้างข้อความข้อผิดพลาดและข้อความสำเร็จทั้งหมดในทั้งเอกสาร
FormError.clearAll();

// ล้างข้อความทั้งหมดเฉพาะในฟอร์มที่ระบุ
const form = document.getElementById('loginForm');
FormError.clearAll(form);

ล้างข้อความเฉพาะฟอร์ม

ล้างข้อความทั้งหมดของฟอร์มที่ระบุ:

// ระบุด้วยองค์ประกอบฟอร์ม
const form = document.getElementById('registrationForm');
FormError.clearFormMessages(form);

// ระบุด้วย ID ของฟอร์ม
FormError.clearFormMessages('registrationForm');

// ระบุด้วยค่า data-form ของฟอร์ม
FormError.clearFormMessages('myForm');

การจัดการโฟกัสและการเลื่อน

การโฟกัสอัตโนมัติ

ฟิลด์ที่พบข้อผิดพลาดรายการแรกจะถูกโฟกัสให้อัตโนมัติ:

FormError.showFieldError('email', 'ต้องระบุอีเมล');
FormError.showFieldError('password', 'ต้องระบุรหัสผ่าน');
// ฟิลด์อีเมลจะถูกโฟกัสก่อน (เป็นข้อผิดพลาดตัวแรก)

ปิดการโฟกัสอัตโนมัติ

// ปรับทั่วทั้งระบบ
FormError.configure({ autoFocus: false });

// ปรับเฉพาะครั้งที่เรียกใช้งาน
FormError.showFieldError('email', 'ข้อผิดพลาด', null, { focus: false });

การเลื่อนอัตโนมัติ

พฤติกรรมการเลื่อนแบบอัจฉริยะ:

// จะเลื่อนไปยังองค์ประกอบต่อเมื่อมองไม่เห็นในหน้าจอ
FormError.scrollToElement(element);

// องค์ประกอบจะถือว่าอยู่ในมุมมองหาก:
// - เห็นความสูงอย่างน้อย 30% ใน viewport
// - ส่วนบนหรือส่วนล่างอยู่ใน viewport

การตั้งค่าการเลื่อน

FormError.configure({
  autoScroll: true,      // เปิดการเลื่อนอัตโนมัติ
  scrollOffset: 100      // ระยะเยื้องจากด้านบนเป็นพิกเซล
});

การเลื่อนด้วยตนเอง

const element = document.getElementById('email');
FormError.scrollToElement(element);

คุณสมบัติด้านการเข้าถึง

แอตทริบิวต์ ARIA

FormError จะจัดการแอตทริบิวต์ ARIA ให้อัตโนมัติ:

<!-- Before error -->
<input type="email" id="email" name="email">

<!-- After showing error -->
<input
  type="email"
  id="email"
  name="email"
  aria-invalid="true"
  aria-errormessage="result_email">
<div id="result_email" role="alert">ต้องระบุอีเมล</div>

การรองรับ Screen Reader

// ข้อความข้อผิดพลาดจะถูกอ่านออกเสียงโดยโปรแกรมอ่านหน้าจอ
FormError.showFieldError('email', 'ต้องระบุอีเมล');

// ใช้ aria-live เพื่อติดตามข้อความแบบไดนามิก
// container ข้อผิดพลาดควรกำหนด role="alert" เพื่อประกาศทันที

การนำทางด้วยแป้นพิมพ์

// ฟิลด์ข้อผิดพลาดตัวแรกจะถูกโฟกัสสำหรับผู้ใช้คีย์บอร์ด
FormError.showErrors({
  email: 'ต้องระบุอีเมล',
  password: 'ต้องระบุรหัสผ่าน'
});
// ฟิลด์อีเมลจะถูกโฟกัส ผู้ใช้สามารถกด Tab ไปยังฟิลด์รหัสผ่านต่อได้

การจัดการสถานะ

ตรวจสอบข้อผิดพลาด

// ตรวจสอบว่ามีข้อผิดพลาดหรือไม่
if (FormError.hasErrors()) {
  console.log('ฟอร์มมีข้อผิดพลาด');
}

// ดูจำนวนข้อผิดพลาดทั้งหมด
const count = FormError.getErrorsCount();
console.log(`พบข้อผิดพลาดจำนวน ${count} รายการ`);

รับรายละเอียดข้อผิดพลาด

// รับข้อมูลข้อผิดพลาดทั้งหมดในปัจจุบัน
const errors = FormError.getErrors();
// ค่าที่ส่งกลับ: [
//   { field: 'email', element: <input>, messages: ['ต้องระบุอีเมล'] },
//   { field: 'password', element: <input>, messages: ['รหัสผ่านสั้นเกินไป'] }
// ]

// ประมวลผลรายการข้อผิดพลาด
errors.forEach(error => {
  console.log(`ฟิลด์: ${error.field}`);
  console.log(`ข้อความ: ${error.messages.join(', ')}`);
  console.log(`องค์ประกอบ:`, error.element);
});

รีเซ็ตสถานะ

ล้างข้อผิดพลาดทั้งหมด and reset internal state:

FormError.reset();
// จะล้างข้อมูลดังนี้:
// - ข้อผิดพลาดของฟิลด์ทั้งหมด
// - ข้อความระดับฟอร์มทั้งหมด
// - แคชข้อความเดิม
// - องค์ประกอบที่ถูกโฟกัสล่าสุด
// - แผนที่สถานะภายในทุกชุด

ระบบเหตุการณ์

FormError ปล่อยเหตุการณ์สำหรับการดำเนินการทั้งหมด:

เหตุการณ์ที่ใช้ได้

// เมื่อมีการแสดงข้อผิดพลาดของฟิลด์
Now.on('form:error', (data) => {
  console.log('ข้อผิดพลาดของฟิลด์:', data.field, data.messages);
  console.log('องค์ประกอบ:', data.element);
  console.log('ค่าการตั้งค่า:', data.config);
});

// เมื่อเคลียร์ข้อผิดพลาดของฟิลด์
Now.on('form:clearError', (data) => {
  console.log('ล้างข้อผิดพลาดแล้ว:', data.field);
  console.log('องค์ประกอบ:', data.element);
});

// เมื่อมีการแสดงข้อผิดพลาดหลายรายการพร้อมกัน
Now.on('form:errors', (data) => {
  console.log('รายการข้อผิดพลาด:', data.errors);
});

// เมื่อแสดงข้อผิดพลาดระดับฟอร์ม
Now.on('form:generalError', (data) => {
  console.log('ข้อผิดพลาดระดับฟอร์ม:', data.message);
  console.log('คอนเทนเนอร์:', data.containerId);
});

// เมื่อเคลียร์ข้อผิดพลาดระดับฟอร์ม
Now.on('form:clearGeneralError', (data) => {
  console.log('ล้างข้อผิดพลาดระดับฟอร์มแล้ว:', data.containerId);
});

// เมื่อแสดงข้อความสำเร็จ
Now.on('form:generalSuccess', (data) => {
  console.log('ข้อความสำเร็จ:', data.message);
  console.log('คอนเทนเนอร์:', data.containerId);
});

// เมื่อเคลียร์ข้อความสำเร็จ
Now.on('form:clearSuccess', (data) => {
  console.log('ล้างข้อความสำเร็จแล้ว:', data.containerId);
});

// เมื่อมีการล้างข้อผิดพลาดทั้งหมด
Now.on('form:clearAllErrors', (data) => {
  console.log('ล้างข้อผิดพลาดทั้งหมดแล้ว');
  if (data.form) {
    console.log('ฟอร์ม:', data.form);
  }
});

ตัวอย่างการใช้เหตุการณ์

// จัดเก็บข้อมูลข้อผิดพลาดเพื่อวิเคราะห์
Now.on('form:error', (data) => {
  analytics.track('Form Error', {
    field: data.field,
    message: data.messages[0],
    formId: data.element.form?.id
  });
});

// แสดงการแจ้งเตือนแบบกำหนดเอง
Now.on('form:generalError', (data) => {
  if (data.config.showErrorsInNotification) {
    NotificationSystem.error(data.message);
  }
});

// บันทึกเมื่อมีการล้างข้อผิดพลาดทั้งหมด
Now.on('form:clearAllErrors', () => {
  console.log('ผู้ใช้ล้างข้อผิดพลาดของฟอร์มทั้งหมดแล้ว');
});

การผสานรวมกับ FormManager

FormError สามารถทำงานร่วมกับ FormManager ได้อย่างราบรื่น:

การแสดงข้อผิดพลาดอัตโนมัติ

// FormManager จะเรียกใช้ FormError สำหรับข้อผิดพลาดการตรวจสอบโดยอัตโนมัติ
const form = document.getElementById('loginForm');
const formManager = FormManager.init(form);

// เมื่อการตรวจสอบล้มเหลว FormError จะแสดงข้อผิดพลาดให้อัตโนมัติ
formManager.validate(); // ข้อผิดพลาดจะแสดงผ่าน FormError

การตั้งค่า Inheritance

<form
  data-form="myForm"
  data-error-class="has-error"
  data-error-message-class="error-text"
  data-auto-focus="true"
  data-auto-scroll="false"
  data-error-container="form-errors">

  <!-- FormError จะใช้ค่าการตั้งหาเหล่านี้ -->
</form>

การจัดการข้อผิดพลาดด้วยตนเอง

// แสดงข้อผิดพลาดที่กำหนดเองหลังส่งฟอร์ม
Now.on('form:submit', async (data) => {
  try {
    const response = await fetch('/api/submit', {
      method: 'POST',
      body: JSON.stringify(data.formData)
    });

    const result = await response.json();

    if (!result.success && result.errors) {
      // แสดงข้อผิดพลาดจากการตรวจสอบที่ส่งมาจากเซิร์ฟเวอร์
      FormError.showErrors(result.errors);
    }
  } catch (error) {
    FormError.showGeneralError('เกิดข้อผิดพลาดของเซิร์ฟเวอร์', data.form);
  }
});

ตัวอย่างที่สมบูรณ์

ตัวอย่างที่ 1: ฟอร์มเข้าสู่ระบบพร้อมการจัดการข้อผิดพลาด

<!DOCTYPE html>
<html>
<head>
  <title>ฟอร์มเข้าสู่ระบบ</title>
  <script src="/Now/Now.js"></script>
  <style>
    .form-control { margin-bottom: 1rem; }
    .form-control.invalid input { border-color: #dc3545; }
    .comment { min-height: 1.5rem; font-size: 0.875rem; }
    .comment.error { color: #dc3545; }
    .form-message { padding: 1rem; margin-bottom: 1rem; border-radius: 4px; }
    .form-message.error { background: #f8d7da; color: #721c24; border: 1px solid #f5c6cb; }
    .form-message.success { background: #d4edda; color: #155724; border: 1px solid #c3e6cb; }
  </style>
</head>
<body>
  <form id="loginForm" data-form="loginForm">
    <div id="form-message" class="form-message"></div>

    <div class="form-control">
      <label for="email">อีเมล</label>
      <input type="email" id="email" name="email" required>
      <div id="result_email" class="comment"></div>
    </div>

    <div class="form-control">
      <label for="password">รหัสผ่าน</label>
      <input type="password" id="password" name="password" required>
      <div id="result_password" class="comment"></div>
    </div>

    <button type="submit">เข้าสู่ระบบ</button>
  </form>

  <script>
    // กำหนดค่า FormError
    FormError.configure({
      errorClass: 'invalid',
      autoFocus: true,
      autoScroll: true,
  autoClearErrors: false // ควรล้างข้อความเองสำหรับฟอร์มเข้าสู่ระบบ
    });

    // เริ่มต้นฟอร์ม
    const form = document.getElementById('loginForm');

    // จัดการเหตุการณ์เมื่อส่งฟอร์ม
    form.addEventListener('submit', async (e) => {
      e.preventDefault();

      // ล้างข้อผิดพลาดเดิมทั้งหมดก่อน
      FormError.clearAll(form);

      // อ่านข้อมูลจากฟอร์ม
      const formData = new FormData(form);
      const data = Object.fromEntries(formData);

      // ตรวจสอบข้อมูลบนฝั่งไคลเอนต์
      const errors = {};

      if (!data.email) {
        errors.email = 'ต้องระบุอีเมล';
      } else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(data.email)) {
        errors.email = 'กรุณากรอกอีเมลที่ถูกต้อง';
      }

      if (!data.password) {
        errors.password = 'ต้องระบุรหัสผ่าน';
      } else if (data.password.length < 8) {
        errors.password = 'รหัสผ่านต้องมีอย่างน้อย 8 ตัวอักษร';
      }

      // แสดงข้อผิดพลาดหากมี
      if (Object.keys(errors).length > 0) {
        FormError.showErrors(errors);
        FormError.showGeneralError('กรุณาแก้ไขข้อผิดพลาดด้านล่าง', form);
        return;
      }

      // ส่งข้อมูลไปยังเซิร์ฟเวอร์
      try {
        const response = await fetch('/api/login', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify(data)
        });

        const result = await response.json();

        if (result.success) {
          FormError.showSuccess('เข้าสู่ระบบสำเร็จ! กำลังเปลี่ยนเส้นทาง...', form);
          setTimeout(() => {
            window.location.href = result.redirect || '/dashboard';
          }, 1500);
        } else {
          // แสดงข้อผิดพลาดที่ส่งมาจากเซิร์ฟเวอร์
          if (result.errors) {
            FormError.showErrors(result.errors);
          }
          FormError.showGeneralError(result.message || 'เข้าสู่ระบบล้มเหลว', form);
        }
      } catch (error) {
        FormError.showGeneralError('เครือข่ายมีปัญหา กรุณาลองใหม่อีกครั้ง', form);
      }
    });

    // ล้างข้อผิดพลาดของฟิลด์เมื่อผู้ใช้พิมพ์ข้อมูลใหม่
    form.querySelectorAll('input').forEach(input => {
      input.addEventListener('input', () => {
        if (FormError.state.errors.has(input.name)) {
          FormError.clearFieldError(input.name);
        }
      });
    });
  </script>
</body>
</html>

ตัวอย่างที่ 2: ฟอร์มลงทะเบียนพร้อมการตรวจสอบหลายฟิลด์

<!DOCTYPE html>
<html>
<head>
  <title>ฟอร์มลงทะเบียน</title>
  <script src="/Now/Now.js"></script>
  <style>
    .form-control { margin-bottom: 1rem; }
    .form-control.invalid input { border-color: #dc3545; background-color: #fff5f5; }
    .comment { min-height: 1.5rem; font-size: 0.875rem; }
    .comment.error { color: #dc3545; }
    .form-message { padding: 1rem; margin-bottom: 1rem; border-radius: 4px; display: none; }
    .form-message.show { display: block; }
    .form-message.error { background: #f8d7da; color: #721c24; }
    .form-message.success { background: #d4edda; color: #155724; }
  </style>
</head>
<body>
  <form id="registrationForm" data-form="registrationForm">
    <div id="form-message" class="form-message"></div>

    <div class="form-control">
      <label for="username">ชื่อผู้ใช้</label>
      <input type="text" id="username" name="username" required>
      <div id="result_username" class="comment"></div>
    </div>

    <div class="form-control">
      <label for="email">อีเมล</label>
      <input type="email" id="email" name="email" required>
      <div id="result_email" class="comment"></div>
    </div>

    <div class="form-control">
      <label for="password">รหัสผ่าน</label>
      <input type="password" id="password" name="password" required>
      <div id="result_password" class="comment"></div>
    </div>

    <div class="form-control">
      <label for="confirmPassword">ยืนยันรหัสผ่าน</label>
      <input type="password" id="confirmPassword" name="confirmPassword" required>
      <div id="result_confirmPassword" class="comment"></div>
    </div>

    <div class="form-control">
      <label>
        <input type="checkbox" id="terms" name="terms" required>
        ฉันยอมรับข้อกำหนดและเงื่อนไข
      </label>
      <div id="result_terms" class="comment"></div>
    </div>

    <button type="submit">ลงทะเบียน</button>
  </form>

  <script>
    // กำหนดค่า FormError
    FormError.configure({
      errorClass: 'invalid',
      errorMessageClass: 'error',
      autoFocus: true,
      autoScroll: true,
      scrollOffset: 80,
      autoClearErrors: false
    });

    const form = document.getElementById('registrationForm');

    // กฎการตรวจสอบข้อมูล
    const validationRules = {
      username: {
        validate: (value) => {
          if (!value) return 'ต้องระบุชื่อผู้ใช้';
          if (value.length < 4) return 'ชื่อผู้ใช้ต้องมีอย่างน้อย 4 ตัวอักษร';
          if (!/^[a-zA-Z0-9_]+$/.test(value)) return 'ชื่อผู้ใช้ต้องเป็นตัวอักษร ตัวเลข หรือขีดล่างเท่านั้น';
          return null;
        }
      },
      email: {
        validate: (value) => {
          if (!value) return 'ต้องระบุอีเมล';
          if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) return 'กรุณากรอกอีเมลที่ถูกต้อง';
          return null;
        }
      },
      password: {
        validate: (value) => {
          if (!value) return 'ต้องระบุรหัสผ่าน';
          if (value.length < 8) return 'รหัสผ่านต้องมีอย่างน้อย 8 ตัวอักษร';
          if (!/[A-Z]/.test(value)) return 'รหัสผ่านต้องมีตัวพิมพ์ใหญ่อย่างน้อย 1 ตัว';
          if (!/[a-z]/.test(value)) return 'รหัสผ่านต้องมีตัวพิมพ์เล็กอย่างน้อย 1 ตัว';
          if (!/[0-9]/.test(value)) return 'รหัสผ่านต้องมีตัวเลขอย่างน้อย 1 ตัว';
          return null;
        }
      },
      confirmPassword: {
        validate: (value, formData) => {
          if (!value) return 'กรุณายืนยันรหัสผ่านของคุณ';
          if (value !== formData.password) return 'รหัสผ่านไม่ตรงกัน';
          return null;
        }
      },
      terms: {
        validate: (value) => {
          if (!value) return 'คุณต้องยอมรับข้อกำหนดและเงื่อนไขก่อน';
          return null;
        }
      }
    };

    // การตรวจสอบแบบเรียลไทม์เมื่อผู้ใช้เลื่อนออกจากฟิลด์
    form.querySelectorAll('input').forEach(input => {
      input.addEventListener('blur', () => {
        validateField(input.name);
      });

      // ล้างข้อความผิดพลาดเมื่อมีการกรอกข้อมูลใหม่
      input.addEventListener('input', () => {
        if (FormError.state.errors.has(input.name)) {
          FormError.clearFieldError(input.name);
        }
      });
    });

    // ตรวจสอบฟิลด์เดี่ยว
    function validateField(fieldName) {
      const rule = validationRules[fieldName];
      if (!rule) return true;

      const formData = new FormData(form);
      const data = Object.fromEntries(formData);
      const value = data[fieldName];

      const error = rule.validate(value, data);

      if (error) {
        FormError.showFieldError(fieldName, error, form, {
          focus: false,
          scroll: false
        });
        return false;
      } else {
        FormError.clearFieldError(fieldName);
        return true;
      }
    }

    // ตรวจสอบทุกฟิลด์ในคราวเดียว
    function validateForm() {
      const formData = new FormData(form);
      const data = Object.fromEntries(formData);
      const errors = {};

      Object.keys(validationRules).forEach(fieldName => {
        const rule = validationRules[fieldName];
        const value = data[fieldName];
        const error = rule.validate(value, data);

        if (error) {
          errors[fieldName] = error;
        }
      });

      return errors;
    }

    // จัดการเหตุการณ์เมื่อส่งฟอร์ม
    form.addEventListener('submit', async (e) => {
      e.preventDefault();

      // ล้างข้อผิดพลาดเดิมทั้งหมดก่อน
      FormError.clearAll(form);

      // ตรวจสอบข้อมูลทุกฟิลด์
      const errors = validateForm();

      if (Object.keys(errors).length > 0) {
        FormError.showErrors(errors);
        FormError.showGeneralError(`กรุณาแก้ไขข้อผิดพลาดจำนวน ${Object.keys(errors).length} รายการด้านล่าง`, form);
        return;
      }

      // ส่งข้อมูลไปยังเซิร์ฟเวอร์
      const formData = new FormData(form);
      const data = Object.fromEntries(formData);

      try {
        const response = await fetch('/api/register', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify(data)
        });

        const result = await response.json();

        if (result.success) {
          FormError.showSuccess('ลงทะเบียนสำเร็จ! กรุณาตรวจสอบอีเมลของคุณ', form);
          form.reset();

          setTimeout(() => {
            window.location.href = '/login';
          }, 2000);
        } else {
          // แสดงข้อผิดพลาดจากการตรวจสอบที่ส่งกลับจากเซิร์ฟเวอร์
          if (result.errors) {
            FormError.showErrors(result.errors);
          }
          FormError.showGeneralError(result.message || 'การลงทะเบียนไม่สำเร็จ', form);
        }
      } catch (error) {
        FormError.showGeneralError('เครือข่ายมีปัญหา กรุณาลองใหม่อีกครั้ง', form);
      }
    });
  </script>
</body>
</html>

ตัวอย่างที่ 3: ฟอร์มติดต่อพร้อมล้างข้อความอัตโนมัติ

<!DOCTYPE html>
<html>
<head>
  <title>ฟอร์มติดต่อ</title>
  <script src="/Now/Now.js"></script>
  <style>
    .form-control { margin-bottom: 1rem; }
    .form-control.invalid input,
    .form-control.invalid textarea { border-color: #dc3545; }
    .comment { min-height: 1.5rem; font-size: 0.875rem; }
    .comment.error { color: #dc3545; }
    #form-message {
      padding: 1rem;
      margin-bottom: 1rem;
      border-radius: 4px;
      display: none;
      animation: slideDown 0.3s ease-out;
    }
    #form-message.show { display: block; }
    #form-message.error { background: #f8d7da; color: #721c24; }
    #form-message.success { background: #d4edda; color: #155724; }

    @keyframes slideDown {
      from { opacity: 0; transform: translateY(-10px); }
      to { opacity: 1; transform: translateY(0); }
    }
  </style>
</head>
<body>
  <form id="contactForm" data-form="contactForm">
    <div id="form-message" class="form-message"></div>

    <div class="form-control">
      <label for="name">ชื่อ</label>
      <input type="text" id="name" name="name" required>
      <div id="result_name" class="comment"></div>
    </div>

    <div class="form-control">
      <label for="email">อีเมล</label>
      <input type="email" id="email" name="email" required>
      <div id="result_email" class="comment"></div>
    </div>

    <div class="form-control">
      <label for="subject">หัวข้อ</label>
      <input type="text" id="subject" name="subject" required>
      <div id="result_subject" class="comment"></div>
    </div>

    <div class="form-control">
      <label for="message">ข้อความ</label>
      <textarea id="message" name="message" rows="5" required></textarea>
      <div id="result_message" class="comment"></div>
    </div>

    <button type="submit">ส่งข้อความ</button>
  </form>

  <script>
    // กำหนดค่าพร้อมการล้างอัตโนมัติ
    FormError.configure({
      errorClass: 'invalid',
      errorMessageClass: 'error',
      autoFocus: true,
      autoScroll: true,
      autoClearErrors: true,
  autoClearErrorsDelay: 4000 // ล้างข้อความภายใน 4 วินาที
    });

    const form = document.getElementById('contactForm');

    // เพิ่มคลาส 'show' เมื่อมีการแสดงข้อความ
    Now.on('form:generalError', (data) => {
      document.getElementById(data.containerId).classList.add('show');
    });

    Now.on('form:generalSuccess', (data) => {
      document.getElementById(data.containerId).classList.add('show');
    });

    // จัดการการส่งฟอร์ม
    form.addEventListener('submit', async (e) => {
      e.preventDefault();

      // ล้างข้อความก่อนหน้า
      FormError.clearAll(form);

      // อ่านข้อมูลจากฟอร์ม
      const formData = new FormData(form);
      const data = Object.fromEntries(formData);

      // ตรวจสอบแบบง่าย
      const errors = {};
      if (!data.name) errors.name = 'ต้องระบุชื่อ';
      if (!data.email) errors.email = 'ต้องระบุอีเมล';
      if (!data.subject) errors.subject = 'ต้องระบุหัวข้อ';
      if (!data.message) errors.message = 'ต้องระบุข้อความ';

      if (Object.keys(errors).length > 0) {
        FormError.showErrors(errors);
        FormError.showGeneralError('กรุณากรอกข้อมูลที่จำเป็นให้ครบถ้วน', form);
        return;
      }

      // จำลองการเรียก API
      try {
  // แสดงสถานะกำลังประมวลผล
        const submitBtn = form.querySelector('button[type="submit"]');
        submitBtn.disabled = true;
        submitBtn.textContent = 'กำลังส่ง...';

        await new Promise(resolve => setTimeout(resolve, 1500));

  // จำลองสถานการณ์สำเร็จ
        FormError.showSuccess('ขอบคุณ! ส่งข้อความของคุณเรียบร้อยแล้ว', form);
        form.reset();

  // ข้อความจะถูกล้างอัตโนมัติภายใน 4 วินาที (autoClearErrorsDelay)

  // รีเซ็ตสถานะปุ่ม
        submitBtn.disabled = false;
        submitBtn.textContent = 'ส่งข้อความ';

      } catch (error) {
        FormError.showGeneralError('ส่งข้อความไม่สำเร็จ กรุณาลองอีกครั้ง', form);

  // รีเซ็ตสถานะปุ่ม
        const submitBtn = form.querySelector('button[type="submit"]');
        submitBtn.disabled = false;
        submitBtn.textContent = 'ส่งข้อความ';
      }
    });

    // ล้างข้อผิดพลาดของฟิลด์เมื่อมีการพิมพ์ข้อมูล
    form.querySelectorAll('input, textarea').forEach(field => {
      field.addEventListener('input', () => {
        if (FormError.state.errors.has(field.name)) {
          FormError.clearFieldError(field.name);
        }
      });
    });
  </script>
</body>
</html>

ตัวอย่างที่ 4: ฟอร์มหลายขั้นตอนพร้อมการเก็บข้อผิดพลาด

<!DOCTYPE html>
<html>
<head>
  <title>ฟอร์มหลายขั้นตอน</title>
  <script src="/Now/Now.js"></script>
  <style>
    .form-step { display: none; }
    .form-step.active { display: block; }
    .form-control { margin-bottom: 1rem; }
    .form-control.invalid input { border-color: #dc3545; }
    .comment { min-height: 1.5rem; font-size: 0.875rem; }
    .comment.error { color: #dc3545; }
    .form-message { padding: 1rem; margin-bottom: 1rem; border-radius: 4px; }
    .form-message.error { background: #f8d7da; color: #721c24; }
    .form-message.success { background: #d4edda; color: #155724; }
    .form-nav { margin-top: 1rem; display: flex; gap: 1rem; }
    .step-indicator { display: flex; gap: 1rem; margin-bottom: 1rem; }
    .step-indicator span {
      padding: 0.5rem 1rem;
      background: #e9ecef;
      border-radius: 4px;
    }
    .step-indicator span.active { background: #007bff; color: white; }
    .step-indicator span.completed { background: #28a745; color: white; }
  </style>
</head>
<body>
  <div class="step-indicator">
    <span class="active" data-step="1">ขั้นตอนที่ 1: ข้อมูลส่วนตัว</span>
    <span data-step="2">ขั้นตอนที่ 2: ที่อยู่</span>
    <span data-step="3">ขั้นตอนที่ 3: ยืนยัน</span>
  </div>

  <form id="multiStepForm" data-form="multiStepForm">
    <div id="form-message" class="form-message"></div>

    <!-- ขั้นตอนที่ 1: ข้อมูลส่วนตัว -->
    <div class="form-step active" data-step="1">
      <h3>ข้อมูลส่วนตัว</h3>

      <div class="form-control">
        <label for="firstName">ชื่อจริง</label>
        <input type="text" id="firstName" name="firstName" required>
        <div id="result_firstName" class="comment"></div>
      </div>

      <div class="form-control">
        <label for="lastName">นามสกุล</label>
        <input type="text" id="lastName" name="lastName" required>
        <div id="result_lastName" class="comment"></div>
      </div>

      <div class="form-control">
        <label for="email">อีเมล</label>
        <input type="email" id="email" name="email" required>
        <div id="result_email" class="comment"></div>
      </div>
    </div>

    <!-- ขั้นตอนที่ 2: ที่อยู่ -->
    <div class="form-step" data-step="2">
      <h3>ข้อมูลที่อยู่</h3>

      <div class="form-control">
        <label for="street">ที่อยู่</label>
        <input type="text" id="street" name="street" required>
        <div id="result_street" class="comment"></div>
      </div>

      <div class="form-control">
        <label for="city">เมือง</label>
        <input type="text" id="city" name="city" required>
        <div id="result_city" class="comment"></div>
      </div>

      <div class="form-control">
        <label for="zipcode">รหัสไปรษณีย์</label>
        <input type="text" id="zipcode" name="zipcode" required>
        <div id="result_zipcode" class="comment"></div>
      </div>
    </div>

    <!-- ขั้นตอนที่ 3: ยืนยัน -->
    <div class="form-step" data-step="3">
      <h3>การยืนยัน</h3>
      <div id="confirmationData"></div>
    </div>

    <div class="form-nav">
      <button type="button" id="prevBtn" style="display: none;">ย้อนกลับ</button>
      <button type="button" id="nextBtn">ถัดไป</button>
      <button type="submit" id="submitBtn" style="display: none;">ส่งข้อมูล</button>
    </div>
  </form>

  <script>
    FormError.configure({
      errorClass: 'invalid',
      autoFocus: true,
      autoScroll: false, // Disable for multi-step
      autoClearErrors: false
    });

    const form = document.getElementById('multiStepForm');
    let currentStep = 1;
    const totalSteps = 3;
    const stepErrors = {}; // เก็บข้อผิดพลาดของแต่ละขั้นตอน

    // กำหนดฟิลด์ที่ตรวจสอบในแต่ละขั้นตอน
    const stepFields = {
      1: ['firstName', 'lastName', 'email'],
      2: ['street', 'city', 'zipcode']
    };

    // อัปเดตส่วนแสดงผลของขั้นตอน
    function updateStepUI() {
      // ปรับการแสดงผลของแต่ละขั้นตอน
      document.querySelectorAll('.form-step').forEach(step => {
        step.classList.remove('active');
      });
      document.querySelector(`.form-step[data-step="${currentStep}"]`).classList.add('active');

      // ปรับตัวบ่งชี้ขั้นตอน
      document.querySelectorAll('.step-indicator span').forEach((span, index) => {
        const stepNum = index + 1;
        span.classList.remove('active', 'completed');
        if (stepNum < currentStep) {
          span.classList.add('completed');
        } else if (stepNum === currentStep) {
          span.classList.add('active');
        }
      });

      // ปรับสถานะปุ่มนำทาง
      document.getElementById('prevBtn').style.display = currentStep > 1 ? 'block' : 'none';
      document.getElementById('nextBtn').style.display = currentStep < totalSteps ? 'block' : 'none';
      document.getElementById('submitBtn').style.display = currentStep === totalSteps ? 'block' : 'none';

      // แสดงข้อมูลยืนยันในขั้นตอนสุดท้าย
      if (currentStep === 3) {
        showConfirmation();
      }

      // ล้างข้อความระดับฟอร์มเมื่อเปลี่ยนขั้นตอน
      FormError.clearGeneralError(form);
    }

    // ตรวจสอบข้อมูลของขั้นตอนปัจจุบัน
    function validateCurrentStep() {
      const fields = stepFields[currentStep];
      if (!fields) return true; // No validation for confirmation step

      const formData = new FormData(form);
      const data = Object.fromEntries(formData);
      const errors = {};

      fields.forEach(field => {
        const value = data[field];
        if (!value) {
          errors[field] = 'จำเป็นต้องกรอกข้อมูลในช่องนี้';
        }
      });

      // การตรวจสอบเพิ่มเติม
      if (currentStep === 1 && data.email && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(data.email)) {
        errors.email = 'กรุณากรอกอีเมลที่ถูกต้อง';
      }

      if (currentStep === 2 && data.zipcode && !/^\d{5}$/.test(data.zipcode)) {
        errors.zipcode = 'รหัสไปรษณีย์ต้องเป็นตัวเลข 5 หลัก';
      }

      // ล้างข้อผิดพลาดของขั้นตอนก่อนหน้า
      FormError.clearFormMessages(form);

      if (Object.keys(errors).length > 0) {
        stepErrors[currentStep] = errors;
        FormError.showErrors(errors);
        FormError.showGeneralError('กรุณาแก้ไขข้อผิดพลาดก่อนดำเนินการต่อ', form);
        return false;
      } else {
        delete stepErrors[currentStep];
        return true;
      }
    }

    // แสดงข้อมูลสรุปก่อนยืนยัน
    function showConfirmation() {
      const formData = new FormData(form);
      const data = Object.fromEntries(formData);

      const html = `
        <h4>ข้อมูลส่วนตัว</h4>
        <p><strong>ชื่อ:</strong> ${data.firstName} ${data.lastName}</p>
        <p><strong>อีเมล:</strong> ${data.email}</p>

        <h4>ที่อยู่</h4>
        <p><strong>ที่อยู่:</strong> ${data.street}</p>
        <p><strong>เมือง:</strong> ${data.city}</p>
        <p><strong>รหัสไปรษณีย์:</strong> ${data.zipcode}</p>
      `;

      document.getElementById('confirmationData').innerHTML = html;
    }

    // ปุ่มถัดไป
    document.getElementById('nextBtn').addEventListener('click', () => {
      if (validateCurrentStep()) {
        currentStep++;
        updateStepUI();
      }
    });

    // ปุ่มย้อนกลับ
    document.getElementById('prevBtn').addEventListener('click', () => {
      currentStep--;
      updateStepUI();

      // หากมีข้อผิดพลาดของขั้นตอนก่อนหน้าให้แสดงกลับมา
      if (stepErrors[currentStep]) {
        FormError.showErrors(stepErrors[currentStep]);
      }
    });

    // เมื่อส่งฟอร์ม
    form.addEventListener('submit', async (e) => {
      e.preventDefault();

      FormError.clearAll(form);

      const formData = new FormData(form);
      const data = Object.fromEntries(formData);

      try {
        const submitBtn = document.getElementById('submitBtn');
        submitBtn.disabled = true;
        submitBtn.textContent = 'กำลังส่ง...';

        // จำลองการเรียก API
        await new Promise(resolve => setTimeout(resolve, 1500));

        FormError.showSuccess('ลงทะเบียนเรียบร้อยแล้ว!', form);

        setTimeout(() => {
          window.location.href = '/dashboard';
        }, 2000);

      } catch (error) {
        FormError.showGeneralError('ส่งข้อมูลไม่สำเร็จ กรุณาลองใหม่อีกครั้ง', form);
        document.getElementById('submitBtn').disabled = false;
        document.getElementById('submitBtn').textContent = 'ส่งข้อมูล';
      }
    });

    // ล้างข้อผิดพลาดของฟิลด์เมื่อมีการพิมพ์ข้อมูลใหม่
    form.querySelectorAll('input').forEach(input => {
      input.addEventListener('input', () => {
        if (FormError.state.errors.has(input.name)) {
          FormError.clearFieldError(input.name);
        }
      });
    });

    // เริ่มต้นการทำงาน
    updateStepUI();
  </script>
</body>
</html>

เอกสารอ้างอิง API

การตั้งค่า

FormError.configure(options: {
  errorClass?: string;                  // คลาส CSS สำหรับฟิลด์ที่ไม่ผ่านการตรวจสอบ (ค่าเริ่มต้น: 'invalid')
  errorMessageClass?: string;           // คลาส CSS สำหรับข้อความข้อผิดพลาด (ค่าเริ่มต้น: 'error')
  autoFocus?: boolean;                  // โฟกัสฟิลด์ที่มีข้อผิดพลาดตัวแรกโดยอัตโนมัติ (ค่าเริ่มต้น: true)
  autoScroll?: boolean;                 // เลื่อนอัตโนมัติไปยังข้อผิดพลาดตัวแรก (ค่าเริ่มต้น: true)
  scrollOffset?: number;                // ระยะเลื่อนในหน่วยพิกเซล (ค่าเริ่มต้น: 100)
  debug?: boolean;                      // เปิดการบันทึกข้อมูลดีบัก (ค่าเริ่มต้น: false)
  showErrorsInline?: boolean;           // แสดงข้อความผิดพลาดถัดจากฟิลด์โดยตรง (ค่าเริ่มต้น: true)
  showErrorsInNotification?: boolean;   // แสดงผ่านระบบแจ้งเตือน (ค่าเริ่มต้น: false)
  autoClearErrors?: boolean;            // ล้างข้อความอัตโนมัติ (ค่าเริ่มต้น: true)
  autoClearErrorsDelay?: number;        // ระยะเวลาก่อนล้างอัตโนมัติ (มิลลิวินาที) (ค่าเริ่มต้น: 5000)
  defaultErrorContainer?: string;       // ID ของ container ข้อผิดพลาดเริ่มต้น (ค่าเริ่มต้น: 'form-message')
  defaultSuccessContainer?: string;     // ID ของ container ข้อความสำเร็จเริ่มต้น (ค่าเริ่มต้น: 'form-message')
}): void

ข้อผิดพลาดของฟิลด์

// แสดงข้อผิดพลาดฟิลด์
FormError.showFieldError(
  field: string,                        // ID หรือชื่อฟิลด์
  message: string | string[],           // ข้อความข้อผิดพลาด (หนึ่งหรือหลายข้อความ)
  form?: HTMLElement | null,            // องค์ประกอบฟอร์ม (ไม่จำเป็น)
  options?: {
    focus?: boolean;                    // กำหนดพฤติกรรมการโฟกัส
    scroll?: boolean;                   // กำหนดพฤติกรรมการเลื่อนหน้าจอ
  }
): void

// ล้างข้อผิดพลาดของฟิลด์
FormError.clearFieldError(
  field: string | HTMLElement           // ID, ชื่อ หรือองค์ประกอบของฟิลด์
): void

// แสดงข้อผิดพลาดหลายฟิลด์พร้อมกัน
FormError.showErrors(
  errors: { [field: string]: string | string[] },
  options?: {
    focus?: boolean;                    // ปรับการโฟกัส
    scroll?: boolean;                   // ปรับการเลื่อนหน้าจอ
  }
): void

ข้อความทั่วไป

// แสดงข้อผิดพลาดทั่วไป
FormError.showGeneralError(
  message: string,                      // ข้อความข้อผิดพลาดระดับฟอร์ม
  form?: HTMLElement | string | null,   // องค์ประกอบฟอร์มหรือ ID ของ container
  options?: {}                          // สำรองไว้สำหรับการใช้งานในอนาคต
): void

// แสดงข้อความสำเร็จ
FormError.showSuccess(
  message: string,                      // ข้อความสำเร็จ
  form?: HTMLElement | string | null,   // องค์ประกอบฟอร์มหรือ ID ของ container
  options?: {}                          // สำรองไว้สำหรับฟีเจอร์ในอนาคต
): void

// ล้างข้อความข้อผิดพลาดระดับฟอร์ม
FormError.clearGeneralError(
  containerOrForm?: string | HTMLElement | null // ID ของ container หรือองค์ประกอบฟอร์ม
): boolean

// ล้างข้อความสำเร็จ
FormError.clearSuccess(
  containerOrForm?: string | HTMLElement | null // ID ของ container หรือองค์ประกอบฟอร์ม
): boolean

การดำเนินการเป็นกลุ่ม

// ล้างข้อความทั้งหมด
FormError.clearAll(
  form?: HTMLElement | null             // ระบุฟอร์มที่ต้องการล้าง (ว่างได้)
): void

// ล้างข้อความเฉพาะฟอร์มที่ระบุ
FormError.clearFormMessages(
  form: HTMLElement | string            // องค์ประกอบฟอร์มหรือ ID ของฟอร์ม
): boolean

เครื่องมือช่วยเหลือ

// เลื่อนไปที่องค์ประกอบ
FormError.scrollToElement(
  element: HTMLElement                  // องค์ประกอบที่ต้องการเลื่อนไปหา
): void

// ตรวจสอบว่ามีข้อผิดพลาดอยู่หรือไม่
FormError.hasErrors(): boolean

// รับรายการข้อผิดพลาดทั้งหมด
FormError.getErrors(): Array<{
  field: string;
  element: HTMLElement;
  messages: string[];
}>

// ตรวจนับจำนวนข้อผิดพลาดทั้งหมด
FormError.getErrorsCount(): number

// รีเซ็ตสถานะทั้งหมดของ FormError
FormError.reset(): void

// รับการตั้งค่าเฉพาะของฟอร์มที่กำหนด
FormError.getFormConfig(
  form: HTMLElement                     // องค์ประกอบฟอร์มที่ต้องการตรวจสอบ
): ConfigObject

คุณสมบัติสถานะ

FormError.state = {
  errors: Map<string, {                 // รายการข้อผิดพลาดปัจจุบัน
    element: HTMLElement;
    messages: string[];
  }>,
  lastFocusedElement: HTMLElement | null, // องค์ประกอบที่ถูกโฟกัสล่าสุดโดยอัตโนมัติ
  originalMessages: Map<string, string>,  // ข้อความเดิมของฟิลด์แต่ละตัว
  originalGeneralMessages: Map<string, {  // เนื้อหาเดิมของ container ระดับฟอร์ม
    html: string;
    className: string;
  }>
}

FormError.config = {
  // ค่าการตั้งค่าปัจจุบัน (ดูรายละเอียดในหัวข้อการตั้งค่า)
}

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

1. ใช้โครงสร้าง HTML ที่เหมาะสม

<!-- ตัวอย่างที่ถูกต้อง: โครงสร้างเหมาะกับการแสดงข้อผิดพลาด -->
<div class="form-control">
  <label for="email">Email</label>
  <input type="email" id="email" name="email">
  <div id="result_email" class="comment"></div>
</div>

<!-- ตัวอย่างที่ไม่ถูกต้อง: ไม่มี container สำหรับข้อผิดพลาด -->
<div class="form-control">
  <label for="email">Email</label>
  <input type="email" id="email" name="email">
  <!-- ไม่มี container สำหรับข้อผิดพลาด -->
</div>

2. ล้างข้อผิดพลาดอย่างเหมาะสม

// ดี: ล้างข้อผิดพลาดเมื่อผู้ใช้เริ่มพิมพ์
input.addEventListener('input', () => {
  FormError.clearFieldError(input.name);
});

// ดี: ล้างข้อผิดพลาดทั้งหมดก่อนตรวจสอบรอบใหม่
FormError.clearAll(form);
const errors = validateForm();
if (errors) {
  FormError.showErrors(errors);
}

// ไม่ดี: ไม่ล้างข้อผิดพลาดเดิมก่อนแสดงข้อมูลใหม่
FormError.showFieldError('email', 'ข้อผิดพลาด 1');
FormError.showFieldError('email', 'ข้อผิดพลาด 2'); // จะถูกเขียนทับ แต่ควรล้างก่อน

3. ใช้เหตุการณ์สำหรับการผสานรวม

// ดี: รับฟังอีเวนต์ของ FormError เพื่อต่อยอดพฤติกรรมเอง
Now.on('form:error', (data) => {
  // บันทึกข้อผิดพลาดเพื่อใช้งานด้านการวิเคราะห์
  analytics.track('Validation Error', {
    field: data.field,
    message: data.messages[0]
  });
});

// ดี: ผสานเข้ากับระบบแจ้งเตือนที่มีอยู่
Now.on('form:generalError', (data) => {
  if (data.config.showErrorsInNotification) {
    showToast(data.message, 'error');
  }
});

4. ตั้งค่าตามกรณีการใช้งาน

// ฟอร์มเข้าสู่ระบบ: ไม่ต้องล้างข้อผิดพลาดอัตโนมัติ
FormError.configure({
  autoClearErrors: false
});

// ฟอร์มติดต่อ: ล้างอัตโนมัติหลังหน่วงเวลา
FormError.configure({
  autoClearErrors: true,
  autoClearErrorsDelay: 4000
});

// ฟอร์มลงทะเบียน: ปิดการเลื่อนอัตโนมัติสำหรับฟอร์มยาว
FormError.configure({
  autoScroll: false
});

5. รักษาประสบการณ์ผู้ใช้

// ดี: แสดงข้อผิดพลาดและโฟกัสที่ฟิลด์แรก
FormError.showErrors(errors); // ฟิลด์แรกจะถูกโฟกัสให้

// ดี: ไม่แย่งโฟกัสในระหว่างตรวจสอบเมื่อออกจากฟิลด์
FormError.showFieldError('email', 'Invalid', form, {
  focus: false,
  scroll: false
});

// ไม่ดี: แย่งโฟกัสระหว่างผู้ใช้กำลังพิมพ์
input.addEventListener('input', () => {
  FormError.showFieldError(input.name, 'Invalid', form); // Steals focus
});

6. จัดการข้อผิดพลาดของเซิร์ฟเวอร์อย่างเหมาะสม

// ดี: แม็ปข้อผิดพลาดจากเซิร์ฟเวอร์ไปยังฟิลด์ที่เกี่ยวข้อง
try {
  const response = await fetch('/api/submit', {
    method: 'POST',
    body: JSON.stringify(formData)
  });

  const result = await response.json();

  if (!result.success) {
    // แสดงข้อผิดพลาดในแต่ละฟิลด์
    if (result.errors) {
      FormError.showErrors(result.errors);
    }

    // แสดงข้อผิดพลาดระดับฟอร์ม
    FormError.showGeneralError(result.message || 'ส่งข้อมูลไม่สำเร็จ', form);
  }
} catch (error) {
  // Network error
  FormError.showGeneralError('เครือข่ายมีปัญหา กรุณาลองใหม่อีกครั้ง', form);
}

7. การปฏิบัติตามมาตรฐานการเข้าถึง

// ดี: FormError จัดการ ARIA attributes ให้อัตโนมัติ
FormError.showFieldError('email', 'อีเมลไม่ถูกต้อง');
// ผลลัพธ์: aria-invalid="true" และ aria-errormessage="result_email"

// ดี: ใช้ HTML ที่มีความหมายชัดเจน
<div id="result_email" role="alert">ต้องระบุอีเมล</div>

// ดี: ดูแลให้ผู้ใช้แป้นพิมพ์ใช้งานได้สะดวก
FormError.configure({
  autoFocus: true // First error field receives focus
});

8. การจัดการข้อผิดพลาดฟอร์มหลายขั้นตอน

// ดี: บันทึกข้อผิดพลาดของแต่ละขั้นตอน
const stepErrors = {};

function validateStep(stepNumber) {
  const errors = validateStepFields(stepNumber);

  if (Object.keys(errors).length > 0) {
    stepErrors[stepNumber] = errors;
    FormError.showErrors(errors);
    return false;
  } else {
    delete stepErrors[stepNumber];
    return true;
  }
}

// ดี: แสดงข้อผิดพลาดเดิมเมื่อย้อนกลับไป
function goToPreviousStep() {
  currentStep--;
  if (stepErrors[currentStep]) {
    FormError.showErrors(stepErrors[currentStep]);
  }
}

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

1. ขาด Container ข้อผิดพลาด

ปัญหา:

<!-- ไม่มี container result_ -->
<input type="email" id="email" name="email">

วิธีแก้:

<input type="email" id="email" name="email">
<div id="result_email" class="comment"></div>

2. ตัวระบุฟิลด์ไม่ถูกต้อง

ปัญหา:

// ฟิลด์มี id="userEmail" แต่พยายามแสดงข้อผิดพลาดให้กับ "email"
FormError.showFieldError('email', 'อีเมลไม่ถูกต้อง'); // ใช้งานไม่ได้

วิธีแก้:

// Use correct field ID or name
FormError.showFieldError('userEmail', 'อีเมลไม่ถูกต้อง');

3. ไม่ล้างข้อผิดพลาดก่อนตรวจสอบ

ปัญหา:

// Old errors still visible
FormError.showErrors(newErrors); // Mixed with old errors

วิธีแก้:

// ต้องล้างข้อมูลก่อนทุกครั้งก่อนแสดงข้อผิดพลาดใหม่
FormError.clearAll(form);
FormError.showErrors(newErrors);

4. การแย่งชิงโฟกัสระหว่างพิมพ์

ปัญหา:

// Steals focus while user is typing
input.addEventListener('input', () => {
  FormError.showFieldError(input.name, 'ข้อผิดพลาด', form); // autoFocus: true (ค่าเริ่มต้น)
});

วิธีแก้:

// Disable focus for real-time validation
input.addEventListener('input', () => {
  FormError.showFieldError(input.name, 'ข้อผิดพลาด', form, {
    focus: false,
    scroll: false
  });
});

5. ไม่จัดการเนื้อหาเดิม

ปัญหา:

// Manually clearing containers loses original content
container.textContent = '';

วิธีแก้:

// Use FormError methods to preserve/restore content
FormError.clearGeneralError(container);
// เนื้อหาเดิมจะถูกคืนค่าให้อัตโนมัติ

6. ลืมบริบทของฟอร์ม

ปัญหา:

// ไม่ได้ใช้งานการตั้งค่าเฉพาะฟอร์ม
FormError.showGeneralError('เกิดข้อผิดพลาด'); // ใช้ container ค่าเริ่มต้น

วิธีแก้:

// ส่งองค์ประกอบฟอร์มเข้าไปเพื่อใช้การตั้งค่าของฟอร์ม
const form = document.getElementById('myForm');
FormError.showGeneralError('เกิดข้อผิดพลาด', form); // ใช้ container ที่กำหนดในฟอร์ม

7. ไม่จัดการเวลาล้างอัตโนมัติ

ปัญหา:

// ฟอร์มถูกรีเซ็ตก่อนที่จะล้างอัตโนมัติ
FormError.showSuccess('Saved!', form); // ล้างอัตโนมัติหลังจาก 5 วินาที
setTimeout(() => {
  window.location.href = '/dashboard'; // นำทางหลังจาก 1 วินาที
}, 1000); // User never sees full 5 seconds

วิธีแก้:

// Coordinate timing or disable auto-clear
FormError.configure({ autoClearErrors: false });
FormError.showSuccess('Saved! Redirecting...', form);
setTimeout(() => {
  window.location.href = '/dashboard';
}, 2000); // Give user time to read message

8. รูปแบบข้อผิดพลาดจากเซิร์ฟเวอร์ไม่ถูกต้อง

ปัญหา:

// เซิร์ฟเวอร์ตอบกลับ: { error: "Login failed" }
// แต่ FormError คาดหวังรูปแบบ: { fieldName: "message" }

วิธีแก้:

const result = await response.json();

if (!result.success) {
  // Handle general error
  if (result.error) {
    FormError.showGeneralError(result.error, form);
  }

  // Handle field errors
  if (result.errors) {
    FormError.showErrors(result.errors);
  }
}

ความเข้ากันได้ของเบราว์เซอร์

  • Modern Browsers: รองรับอย่างเต็มรูปแบบ (Chrome, Firefox, Safari, Edge)
  • IE11 : ต้องใช้ polyfills สำหรับ Map, Object.entries
  • Mobile: รองรับ iOS Safari, Chrome Android อย่างเต็มรูปแบบ

เอกสารที่เกี่ยวข้อง

  • FormManager-ระบบการจัดการและการตรวจสอบฟอร์ม
  • FormValidation - กฎการตรวจสอบและตัวตรวจสอบที่กำหนดเอง
  • ResponseHandler - การจัดการการตอบสนองของ API
  • ElementManager - การจัดการสถานะขององค์ประกอบ UI

คู่มือการโยกย้าย

จากการจัดการข้อผิดพลาดด้วยตนเอง

Before:

// Manual error display
document.getElementById('email').classList.add('invalid');
document.getElementById('result_email').textContent = 'ต้องระบุอีเมล';
document.getElementById('email').focus();

After:

// Using FormError
FormError.showFieldError('email', 'ต้องระบุอีเมล');
// ระบบจะเพิ่มคลาส แสดงข้อความ โฟกัส และเลื่อนให้อัตโนมัติ

จากตัวจัดการข้อผิดพลาดที่กำหนดเอง

Before:

myErrorManager.showError({
  field: 'email',
  message: 'ไม่ถูกต้อง',
  focus: true
});

After:

FormError.showFieldError('email', 'Invalid');
// ลักษณะการทำงานเดียวกันกับ FormError API