Now.js Framework Documentation
สร้าง Custom Elements
สร้าง Custom Elements
คู่มือสำหรับการสร้าง Element Factory classes กำหนดเองเพื่อขยายความสามารถของ Now.js form handling
📋 สารบัญ
- ภาพรวม
- เมื่อไหร่ควรสร้าง Custom Elements
- คู่มือแบบทีละขั้นตอน
- Configuration
- Property Handlers
- Validation
- Registration
- ตัวอย่างเต็ม
- แนวทางปฏิบัติที่ดี
ภาพรวม
Form elements ทั้งหมดใน Now.js ขยายจาก ElementFactory base class การสร้าง custom element ประกอบด้วย:
- ขยาย
ElementFactory(หรือ factory เฉพาะทาง เช่นTextElementFactory) - กำหนด configuration defaults
- Implement
setupElement()สำหรับ custom behavior - เพิ่ม property handlers และ validators (optional)
- ลงทะเบียนกับ
ElementManager
Class Hierarchy
ElementFactory (base)
├── TextElementFactory # ขยายสำหรับ text-based inputs
├── NumberElementFactory # ขยายสำหรับ numeric inputs
├── SelectElementFactory # ขยายสำหรับ dropdown inputs
└── YourCustomFactory # Custom element ของคุณเมื่อไหร่ควรสร้าง Custom Elements
✅ สร้าง Custom Element เมื่อ:
- ต้องการ reusable specialized input behavior
- Standard elements ไม่รองรับ data format ของคุณ
- ต้องการ custom validation logic
- ต้องการ UI/UX ที่สม่ำเสมอสำหรับ input types เฉพาะ
❌ อย่าสร้าง Custom Element เมื่อ:
- Standard validation rules เพียงพอ (ใช้
data-validate) - ต้องการ customization แบบครั้งเดียว (ใช้ JavaScript)
- Behavior สามารถทำได้ด้วย configuration
คู่มือแบบทีละขั้นตอน
ขั้นตอน 1: สร้าง Factory Class
class RatingElementFactory extends ElementFactory {
// Configuration จะใส่ที่นี่
}ขั้นตอน 2: กำหนด Configuration
class RatingElementFactory extends ElementFactory {
static config = {
...ElementFactory.config, // สืบทอด base config
type: 'rating',
min: 1,
max: 5,
step: 1,
iconFilled: '★',
iconEmpty: '☆',
validationMessages: {
required: 'กรุณาเลือกคะแนน',
min: 'คะแนนต้องอย่างน้อย {min}',
max: 'คะแนนต้องไม่เกิน {max}'
}
};
}ขั้นตอน 3: Implement setupElement
class RatingElementFactory extends ElementFactory {
// ... code ก่อนหน้า
static setupElement(instance) {
const { element, config } = instance;
// ซ่อน input ต้นฉบับ
element.type = 'hidden';
// สร้าง rating UI
const wrapper = document.createElement('div');
wrapper.className = 'rating-wrapper';
element.parentNode.insertBefore(wrapper, element);
// สร้างดาว
for (let i = config.min; i <= config.max; i += config.step) {
const star = document.createElement('span');
star.className = 'rating-star';
star.dataset.value = i;
star.textContent = config.iconEmpty;
star.addEventListener('click', () => {
this.setValue(instance, i);
});
wrapper.appendChild(star);
}
instance.wrapper = wrapper;
instance.stars = wrapper.querySelectorAll('.rating-star');
// Initialize display
this.updateDisplay(instance);
return instance;
}
static updateDisplay(instance) {
const { element, config, stars } = instance;
const value = parseFloat(element.value) || 0;
stars.forEach(star => {
const starValue = parseFloat(star.dataset.value);
star.textContent = starValue <= value
? config.iconFilled
: config.iconEmpty;
});
}
static setValue(instance, value) {
instance.element.value = value;
this.updateDisplay(instance);
instance.element.dispatchEvent(new Event('change', { bubbles: true }));
}
}ขั้นตอน 4: ลงทะเบียนกับ ElementManager
// ลงทะเบียน element type
ElementManager.registerElement('rating', RatingElementFactory);
// Expose globally (optional)
window.RatingElementFactory = RatingElementFactory;การตั้งค่า
Static Config Object
static config = {
// สืบทอดจาก parent
...ElementFactory.config,
// Element type
type: 'your-type',
// Custom properties
myOption: true,
anotherOption: 'default',
// Validation messages (i18n keys)
validationMessages: {
required: 'This field is required',
custom: 'Custom validation failed'
}
};อ่าน Configuration จาก HTML
ใช้ extractCustomConfig() เพื่ออ่าน data attributes:
static extractCustomConfig(element, def, dataset) {
return {
// Boolean: เปรียบเทียบกับ 'true'
myBoolean: dataset.myBoolean === 'true',
// Number: ใช้ parseNumeric helper
myNumber: this.parseNumeric('myNumber', element, def, dataset),
// String: ใช้ dataset หรือ default
myString: dataset.myString || def.myString
};
}ลำดับความสำคัญของ Configuration
- JavaScript options (สูงสุด)
- *data- attributes**
- HTML attributes (min, max, required)
- Static config (ต่ำสุด)
Property Handlers
Property handlers ให้ getter/setter สำหรับ element properties:
static propertyHandlers = {
propertyName: {
get(element) {
// คืนค่าปัจจุบัน
return element.value;
},
set(instance, newValue) {
// ใช้ค่าใหม่
instance.element.value = newValue;
// อัพเดท UI ถ้าต้องการ
MyFactory.updateDisplay(instance);
}
}
};Validation
validateSpecific Method
เพิ่ม custom validation ใน setupElement:
instance.validateSpecific = function(value) {
// ตรวจสอบกฎกำหนดเอง
if (!this.isValidFormat(value)) {
return 'รูปแบบไม่ถูกต้อง';
}
// คืนค่า null ถ้าถูกต้อง
return null;
};Registration
กับ ElementManager
// ลงทะเบียน element type
ElementManager.registerElement('my-element', MyElementFactory);Auto-Enhancement
เมื่อลงทะเบียนแล้ว elements จะถูก auto-enhanced:
<input data-element="my-element" name="field">ตัวอย่างเต็ม
PhoneElementFactory
/**
* PhoneElementFactory - Thai phone number input พร้อม formatting
*/
class PhoneElementFactory extends TextElementFactory {
static config = {
...TextElementFactory.config,
type: 'tel',
pattern: '###-###-####',
placeholder: '081-234-5678',
validationMessages: {
required: 'กรุณากรอกหมายเลขโทรศัพท์',
pattern: 'กรุณากรอกหมายเลขโทรศัพท์ที่ถูกต้อง'
}
};
static setupElement(instance) {
super.setupElement?.(instance);
const { element, config } = instance;
// ตั้งค่า input attributes
element.type = 'tel';
element.inputMode = 'numeric';
element.placeholder = config.placeholder;
// เพิ่ม formatting on input
instance.formatValue = (value) => {
const digits = value.replace(/\D/g, '').substring(0, 10);
if (digits.length <= 3) return digits;
if (digits.length <= 6) return `${digits.slice(0,3)}-${digits.slice(3)}`;
return `${digits.slice(0,3)}-${digits.slice(3,6)}-${digits.slice(6)}`;
};
// Custom validation
instance.validateSpecific = function(value) {
if (!value && this.element.required) {
return Now.translate(config.validationMessages.required);
}
if (value && !/^\d{3}-\d{3}-\d{4}$/.test(value)) {
return Now.translate(config.validationMessages.pattern);
}
return null;
};
// เพิ่ม input handler
EventSystemManager.addHandler(element, 'input', (e) => {
const formatted = instance.formatValue(e.target.value);
if (formatted !== e.target.value) {
e.target.value = formatted;
}
});
return instance;
}
}
// ลงทะเบียน
ElementManager.registerElement('phone', PhoneElementFactory);
window.PhoneElementFactory = PhoneElementFactory;การใช้งาน
<input data-element="phone"
name="phone"
required>
<!-- Auto-formats เป็น: 081-234-5678 -->แนวทางปฏิบัติที่ดี
1. ✅ ขยาย Base Class ที่เหมาะสม
// สำหรับ text-based inputs
class MyElement extends TextElementFactory { }
// สำหรับ numeric inputs
class MyElement extends NumberElementFactory { }
// สำหรับ basic functionality
class MyElement extends ElementFactory { }2. ✅ เรียก Super Methods เสมอ
static setupElement(instance) {
super.setupElement?.(instance); // เรียก parent ก่อน
// แล้วเพิ่ม custom behavior
}3. ✅ ใช้ EventSystemManager
// ✅ ดี: Events ที่ติดตามได้
EventSystemManager.addHandler(element, 'click', handler);
// ❌ ไม่ดี: Native events ไม่ถูกติดตาม
element.addEventListener('click', handler);4. ✅ Implement Cleanup
static cleanup(instance) {
// ลบ elements ที่สร้างขึ้น
if (instance.wrapper?.parentNode) {
instance.wrapper.parentNode.removeChild(instance.wrapper);
}
// เรียก parent cleanup
super.cleanup?.(instance);
}5. ✅ รองรับ i18n
// ใช้ Now.translate สำหรับข้อความที่ผู้ใช้เห็น
const message = Now.translate(config.validationMessages.required);เอกสารที่เกี่ยวข้อง
- Form Elements ภาพรวม
- ElementFactory - Base class reference
- FormManager - Form integration