Now.js Framework Documentation
ElementFactory - ระบบสร้างและจัดการ Element/Component
ElementFactory - ระบบสร้างและจัดการ Element/Component
เอกสารคู่มือการสร้าง Element และ Component โดยใช้ ElementFactory ของ Now.js Framework
📋 สารบัญ
- ภาพรวม
- สถาปัตยกรรม
- การเริ่มต้นใช้งาน
- การสร้าง Element Factory ใหม่
- การกำหนดค่า (Configuration)
- Property Handlers
- การตรวจสอบความถูกต้อง (Validation)
- Event Handling
- State Management
- ตัวอย่างการใช้งาน
- API Reference
- Best Practices
- Common Pitfalls
ภาพรวม
ElementFactory เป็นคลาสพื้นฐานสำหรับสร้างและจัดการ form elements และ UI components ใน Now.js Framework โดยให้ระบบที่ครบถ้วนในการจัดการ validation, formatting, event handling และ state management
ฟีเจอร์หลัก
- ✅ Factory Pattern: สร้าง elements ด้วย configuration-based approach
- ✅ Enhance Existing Elements: ปรับปรุง HTML elements ที่มีอยู่แล้วให้มีความสามารถเพิ่มเติม
- ✅ Validation System: ระบบ validation ที่ยืดหยุ่นและขยายได้
- ✅ Property Handlers: จัดการ properties แบบ reactive
- ✅ State Management: ติดตามสถานะของ element อย่างปลอดภัย
- ✅ Event System: จัดการ events อย่างมีประสิทธิภาพ
- ✅ Wrapper Support: สร้าง HTML structure รอบๆ element อัตโนมัติ
- ✅ Inheritance: สามารถสืบทอดและขยายความสามารถได้ง่าย
เมื่อควรใช้
✅ แนะนำให้ใช้เมื่อ:
- ต้องการสร้าง custom input component ที่มี validation
- ต้องการ formatting และ transformation ข้อมูล
- ต้องการจัดการ state และ events แบบมีโครงสร้าง
- ต้องการสร้าง component ที่ reusable และ maintainable
❌ พิจารณาทางเลือกอื่นเมื่อ:
- ต้องการ element ธรรมดาที่ไม่มี validation หรือ logic พิเศษ
- Component มีความซับซ้อนมากเกินกว่า form element ปกติ (ใช้ UI Component แทน)
สถาปัตยกรรม
Hierarchy
ElementFactory (Base Class)
├── TextElementFactory
│ ├── EmailElementFactory
│ ├── UrlElementFactory
│ ├── UsernameElementFactory
│ ├── SearchElementFactory
│ └── PasswordElementFactory
├── NumberElementFactory
│ └── CurrencyElementFactory
├── MaskElementFactory
│ └── TelElementFactory
├── SelectElementFactory
├── MultiSelectElementFactory
├── TextareaElementFactory
├── FileElementFactory
├── DateElementFactory
├── ColorElementFactory
├── RangeElementFactory
└── InputGroupElementFactoryCore Concepts
-
Factory Methods:
create(def)- สร้าง element ใหม่จาก definition objectenhance(element, def)- ปรับปรุง HTML element ที่มีอยู่แล้ว
-
Instance Structure:
element- DOM element ต้นฉบับconfig- Configuration objectstate- State management (private)wrapper,label,container- Wrapper elements
-
Lifecycle:
Definition Object → extractConfig() → create/enhance() → createInstance() → setupProperties() → setupElement() → setupEventListeners() → Instance Ready
การเริ่มต้นใช้งาน
การโหลด Script
<!-- โหลด ElementFactory (มักจะโหลดผ่าน Now.js อัตโนมัติ) -->
<script src="/Now/js/ElementFactory.js"></script>
<!-- โหลด Specific Factory (ถ้าต้องการ) -->
<script src="/Now/js/TextElementFactory.js"></script>
<script src="/Now/js/NumberElementFactory.js"></script>การสร้าง Element แบบง่าย
// สร้าง text input ใหม่
const textInput = TextElementFactory.create({
type: 'text',
id: 'username',
name: 'username',
placeholder: 'Enter username',
required: true
});
// แสดงผลใน DOM
document.getElementById('form-container').appendChild(textInput.wrapper);การ Enhance Element ที่มีอยู่แล้ว
<!-- HTML Element -->
<input type="text" id="email" name="email"
data-validate="email" required>// Enhance existing element
const emailInput = document.getElementById('email');
const instance = TextElementFactory.enhance(emailInput);
console.log(instance.isValid()); // false (ยังไม่ได้กรอก)การสร้าง Element Factory ใหม่
ขั้นตอนการสร้าง Custom Factory
- สืบทอดจาก ElementFactory หรือ subclass
- กำหนด static config
- Override extractCustomConfig() (ถ้าต้องการ)
- Override setupElement() (สำหรับ logic เฉพาะ)
- กำหนด propertyHandlers (ถ้าต้องการ)
- Register validators (ถ้าต้องการ)
ตัวอย่าง: สร้าง PhoneElementFactory
/**
* PhoneElementFactory - จัดการ input สำหรับเบอร์โทรศัพท์
*/
class PhoneElementFactory extends TextElementFactory {
// 1. กำหนด default configuration
static config = {
type: 'tel',
inputMode: 'numeric',
pattern: '^[0-9]{10}$',
placeholder: '0xxxxxxxxx',
maxLength: 10,
validationMessages: {
phone: 'กรุณากรอกเบอร์โทรศัพท์ 10 หลัก',
required: 'กรุณากรอกเบอร์โทรศัพท์'
},
// Configuration เฉพาะ
autoFormat: true,
allowInternational: false
};
// 2. กำหนด custom validators
static validators = {
phone: (value) => {
// ตรวจสอบเบอร์โทรศัพท์ไทย (0x-xxx-xxxx)
return /^0[0-9]{9}$/.test(value.replace(/-/g, ''));
},
international: (value) => {
// รูปแบบสากล +66xxxxxxxxx
return /^\+[0-9]{11,15}$/.test(value);
}
};
// 3. Extract custom configuration จาก data attributes
static extractCustomConfig(element, def, dataset) {
return {
autoFormat: dataset.autoFormat === 'true' || def.autoFormat,
allowInternational: dataset.allowInternational === 'true' || def.allowInternational
};
}
// 4. Setup element-specific behavior
static setupElement(instance) {
const {element, config} = instance;
// เพิ่ม formatter สำหรับเบอร์โทร
if (config.autoFormat) {
instance.formatter = (value) => {
// ลบตัวอักษรที่ไม่ใช่ตัวเลข
value = value.replace(/\D/g, '');
// จำกัดความยาว
if (value.length > 10) value = value.substring(0, 10);
// Format: 0xx-xxx-xxxx
if (value.length > 6) {
return `${value.substring(0, 3)}-${value.substring(3, 6)}-${value.substring(6)}`;
} else if (value.length > 3) {
return `${value.substring(0, 3)}-${value.substring(3)}`;
}
return value;
};
// ใช้ formatter ตอน input
EventSystemManager.addHandler(element, 'input', () => {
const cursorPos = element.selectionStart;
const oldValue = element.value;
const newValue = instance.formatter(oldValue);
if (oldValue !== newValue) {
element.value = newValue;
// Adjust cursor position
const diff = newValue.length - oldValue.length;
element.setSelectionRange(cursorPos + diff, cursorPos + diff);
}
});
}
// Custom validation
instance.validateSpecific = function(value) {
const cleanValue = value.replace(/\D/g, '');
if (config.allowInternational && value.startsWith('+')) {
if (!PhoneElementFactory.validators.international(value)) {
return config.validationMessages.phone;
}
} else {
if (!PhoneElementFactory.validators.phone(cleanValue)) {
return config.validationMessages.phone;
}
}
return null; // Valid
};
}
// 5. กำหนด property handlers (ถ้าต้องการ)
static propertyHandlers = {
autoFormat: {
get(element) {
return element.dataset.autoFormat === 'true';
},
set(instance, newValue) {
instance.config.autoFormat = newValue;
if (newValue) {
element.value = instance.formatter(element.value);
}
}
}
};
}
// 6. Register กับ window object
window.PhoneElementFactory = PhoneElementFactory;การใช้งาน Custom Factory
// สร้าง element ใหม่
const phoneInput = PhoneElementFactory.create({
id: 'mobile',
name: 'mobile',
required: true,
autoFormat: true
});
document.getElementById('form').appendChild(phoneInput.wrapper);
// หรือ enhance existing element
const existingPhone = document.getElementById('phone');
const instance = PhoneElementFactory.enhance(existingPhone, {
autoFormat: true
});การกำหนดค่า (Configuration)
Static Config Object
class MyElementFactory extends ElementFactory {
static config = {
// Basic properties
type: 'text',
tagName: 'input',
// Validation
required: false,
pattern: null,
minLength: null,
maxLength: null,
// Wrapper configuration
wrapper: 'label', // 'label', 'div', false
wrapperClass: 'form-control',
comment: true, // แสดง comment element
// Messages
validationMessages: {
required: 'Please fill in',
pattern: 'Invalid format'
},
// Custom properties
formatter: null,
validation: null
};
}Configuration Priority
Configuration มีลำดับความสำคัญดังนี้ (จากมากไปน้อย):
- Runtime config (ส่งเข้า
create()หรือenhance()) - Data attributes (HTML
data-*) - Element attributes (HTML attributes ปกติ)
- Static config (ค่าเริ่มต้นใน class)
// Static config
static config = { required: false, maxLength: 50 };
// HTML
<input type="text" maxlength="100" data-required="true">
// Runtime
TextElementFactory.enhance(element, { maxLength: 200 });
// ผลลัพธ์:
// required: true (จาก data-attribute)
// maxLength: 200 (จาก runtime config)extractConfig()
Method นี้รวบรวม configuration จากแหล่งต่างๆ:
static extractConfig(element, config = {}, def) {
const dataset = element.dataset;
const result = {};
// Parse boolean attributes
result.required = def.required !== undefined ? def.required :
dataset.required !== undefined ? dataset.required === 'true' :
element.hasAttribute('required');
// Parse string attributes
result.placeholder = def.placeholder || dataset.placeholder || element.placeholder;
// Parse numeric attributes
result.minLength = this.parseNumeric('minLength', element, def, dataset);
// Custom configuration (override ใน subclass)
if (typeof this.extractCustomConfig === 'function') {
Object.assign(result, this.extractCustomConfig(element, def, dataset));
}
return result;
}extractCustomConfig()
Override method นี้เพื่อ extract configuration เฉพาะของ Factory:
static extractCustomConfig(element, def, dataset) {
return {
// ตัวอย่าง: autocomplete configuration
autocomplete: {
enabled: dataset.autocomplete === 'true' || def.autocomplete?.enabled,
source: dataset.source || def.autocomplete?.source,
minLength: this.parseNumeric('minLength', element, def.autocomplete, dataset)
},
// ตัวอย่าง: formatter function
formatter: dataset.formatter ? window[dataset.formatter] : def.formatter
};
}Property Handlers
Property handlers ให้คุณกำหนดพฤติกรรมแบบ reactive สำหรับ properties:
การกำหนด Property Handlers
class MyElementFactory extends ElementFactory {
static propertyHandlers = {
// Property: value
value: {
get(element) {
// อ่านค่าจาก element
return element.value;
},
set(instance, newValue) {
// ตั้งค่าใหม่
instance.element.value = newValue;
// Trigger validation
instance.validate(newValue, true);
}
},
// Property: min (สำหรับ number input)
min: {
get(element) {
const min = element.getAttribute('min');
return min !== null ? parseFloat(min) : null;
},
set(instance, newValue) {
if (newValue === null) {
instance.element.removeAttribute('min');
} else {
instance.element.setAttribute('min', newValue);
// Re-validate ถ้าจำเป็น
if (instance.state.modified) {
instance.validate(instance.element.value, false);
}
}
}
}
};
}Built-in Property Handlers
ElementFactory มี handlers พื้นฐานดังนี้:
// value - จัดการค่าของ element
instance.value = 'new value';
console.log(instance.value); // 'new value'
// required - จัดการ required attribute
instance.required = true;
console.log(instance.required); // true
// placeholder - จัดการ placeholder text
instance.placeholder = 'Enter name';
console.log(instance.placeholder); // 'Enter name'การใช้งาน Property Handlers
const instance = TextElementFactory.create({
id: 'username',
name: 'username'
});
// Set properties (จะเรียก setter)
instance.value = 'john_doe';
instance.required = true;
instance.placeholder = 'Username';
// Get properties (จะเรียก getter)
console.log(instance.value); // 'john_doe'
console.log(instance.required); // true
console.log(instance.placeholder); // 'Username'การตรวจสอบความถูกต้อง (Validation)
Validation Flow
User Input → input/change event → validate() → validateValue()
→ validateSpecific() → customValidate() → Update State → Show/Clear ErrorBuilt-in Validation
// Required validation
<input required>
// Length validation
<input minlength="3" maxlength="20">
// Pattern validation
<input pattern="[A-Za-z]+">
// Type-specific validation (จาก subclass)
<input type="email"> // EmailElementFactory
<input type="url"> // UrlElementFactory
<input type="number" min="0" max="100"> // NumberElementFactoryCustom Validation - validateSpecific()
Override validateSpecific() ใน setupElement():
static setupElement(instance) {
const {element, config} = instance;
// กำหนด validation logic เฉพาะ
instance.validateSpecific = function(value) {
// ตัวอย่าง: username validation
if (!/^[a-zA-Z0-9_]+$/.test(value)) {
return 'Username can only contain letters, numbers, and underscores';
}
if (value.length < 3) {
return 'Username must be at least 3 characters';
}
if (/^[0-9]/.test(value)) {
return 'Username cannot start with a number';
}
// Valid
return null;
};
}Validation Rules (Data Attributes)
<!-- กำหนด validation rules ผ่าน data-validate -->
<input type="text"
data-validate="required,email,minLength:5"
data-error-required="กรุณากรอกอีเมล"
data-error-email="รูปแบบอีเมลไม่ถูกต้อง"
data-error-minLength="อีเมลต้องมีอย่างน้อย 5 ตัวอักษร">// Parse validation rules
static parseValidationRules(rules) {
if (!rules) return {rules: []};
const ruleList = Array.isArray(rules) ? rules : rules.split(',').map(r => r.trim());
return {
rules: ruleList.map(rule => {
if (typeof rule === 'string' && rule.includes(':')) {
const [name, param] = rule.split(':');
return {name: name.trim(), param: param.trim()};
}
return {name: rule};
}),
validating: false
};
}Async Validation
instance.validateSpecific = async function(value) {
// Check format first (sync)
if (!/^[a-zA-Z0-9_]+$/.test(value)) {
return 'Invalid username format';
}
// Check availability (async)
try {
const response = await fetch(`/api/check-username?username=${value}`);
const data = await response.json();
if (!data.available) {
return 'This username is already taken';
}
return null; // Valid
} catch (error) {
console.error('Validation error:', error);
return 'Unable to validate username';
}
};Validation Methods
// validate() - ตรวจสอบและอัปเดต state
const validatedValue = instance.validate(value, valueChange);
// validateValue() - ตรวจสอบโดยไม่อัปเดต state
const {validatedValue, error} = instance.validateValue(value, valueChange);
// isValid() - เช็คสถานะ validation
if (instance.isValid()) {
console.log('Input is valid');
}
// getError() - ดึง error message
const errorMsg = instance.getError();
if (errorMsg) {
console.log('Error:', errorMsg);
}Event Handling
Built-in Events
ElementFactory ตั้งค่า event listeners พื้นฐานเหล่านี้:
static setupEventListeners(instance) {
const {element} = instance;
// input event - validate แบบ debounce
EventSystemManager.addHandler(element, 'input', () => {
instance.state.modified = true;
debouncedValidate();
});
// change event - validate ทันที
EventSystemManager.addHandler(element, 'change', () => {
const validatedValue = instance.validate(element.value, true);
if (validatedValue !== element.value) {
element.value = validatedValue;
}
});
// blur event - validate เมื่อออกจาก field
EventSystemManager.addHandler(element, 'blur', () => {
const validatedValue = instance.validate(element.value, true);
if (validatedValue !== element.value) {
element.value = validatedValue;
}
});
// focus event - ล้าง error
EventSystemManager.addHandler(element, 'focus', () => {
FormError.clearFieldError(element.id);
});
}เพิ่ม Custom Events
static setupElement(instance) {
const {element, config} = instance;
// เพิ่ม custom event handlers
EventSystemManager.addHandler(element, 'keydown', (e) => {
// ตัวอย่าง: ป้องกันการกด Enter
if (e.key === 'Enter') {
e.preventDefault();
// Trigger validation
instance.validate(element.value, true);
}
});
EventSystemManager.addHandler(element, 'paste', (e) => {
// ตัวอย่าง: ประมวลผล paste event
e.preventDefault();
const pastedText = (e.clipboardData || window.clipboardData).getData('text');
const cleanedText = pastedText.replace(/[^a-zA-Z0-9]/g, '');
document.execCommand('insertText', false, cleanedText);
});
}Event Cleanup
// Cleanup events เมื่อไม่ใช้แล้ว
instance.cleanup();
// หรือ remove element และ cleanup อัตโนมัติ
if (instance.element.parentNode) {
instance.element.parentNode.removeChild(instance.element);
instance.cleanup();
}State Management
Private State
ElementFactory ใช้ WeakMap เก็บ private state:
static _privateState = new WeakMap();
// State structure
{
validating: false, // กำลัง validate อยู่หรือไม่
valid: true, // ผลการ validate ล่าสุด
modified: false, // มีการแก้ไขค่าหรือไม่
error: null, // Error message ล่าสุด
originalValue: '', // ค่าเริ่มต้น
originalConfig: {}, // Config เริ่มต้น
formatting: false, // กำลัง format อยู่หรือไม่
recursionGuard: {} // ป้องกัน infinite loop
}การเข้าถึง State
// ผ่าน instance.state property (read/write)
console.log(instance.state.valid); // true/false
console.log(instance.state.modified); // true/false
console.log(instance.state.error); // null หรือ error message
// Set state
instance.state.modified = true;
instance.state.valid = false;
// ผ่าน helper methods (read-only)
instance.isValid(); // true/false
instance.isModified(); // true/false
instance.getError(); // null หรือ error messageReset State
// Reset element กลับไปสู่สถานะเริ่มต้น
instance.reset();
// หลัง reset:
// - value กลับไปเป็นค่าเริ่มต้น
// - state.valid = true
// - state.modified = false
// - state.error = null
// - error display ถูกล้างState Events
// ฟัง state changes
document.addEventListener('element:state:change', (e) => {
const {element, state} = e.detail;
console.log('Element:', element.id);
console.log('Valid:', state.valid);
console.log('Modified:', state.modified);
console.log('Error:', state.error);
});ตัวอย่างการใช้งาน
ตัวอย่างที่ 1: สร้าง Text Input พื้นฐาน
// สร้าง username input
const usernameInput = TextElementFactory.create({
id: 'username',
name: 'username',
type: 'text',
placeholder: 'Enter username',
required: true,
minLength: 3,
maxLength: 20,
pattern: '^[a-zA-Z0-9_]+$',
wrapper: 'label',
wrapperClass: 'form-control',
comment: true
});
// แสดงผลใน DOM
document.getElementById('form-container').appendChild(usernameInput.wrapper);
// เข้าถึง element
console.log(usernameInput.element); // <input> element
console.log(usernameInput.value); // ค่าปัจจุบัน
console.log(usernameInput.isValid()); // สถานะ validationตัวอย่างที่ 2: Enhance Existing Element
<!-- HTML -->
<form id="myForm">
<input type="email"
id="email"
name="email"
placeholder="your@email.com"
required
data-validate="email">
<span id="result_email" class="error-message"></span>
</form>// Enhance existing input
const emailElement = document.getElementById('email');
const emailInstance = TextElementFactory.enhance(emailElement, {
validationMessages: {
required: 'กรุณากรอกอีเมล',
email: 'รูปแบบอีเมลไม่ถูกต้อง'
}
});
// ตรวจสอบเมื่อ submit
document.getElementById('myForm').addEventListener('submit', (e) => {
e.preventDefault();
// Validate
emailInstance.validate(emailInstance.value, true);
if (emailInstance.isValid()) {
console.log('Email is valid:', emailInstance.value);
// Submit form
} else {
console.log('Error:', emailInstance.getError());
}
});ตัวอย่างที่ 3: Number Input พร้อม Formatting
// สร้าง currency input
const priceInput = NumberElementFactory.create({
id: 'price',
name: 'price',
type: 'number',
placeholder: '0.00',
min: 0,
max: 1000000,
precision: 2,
useGrouping: true,
groupingSeparator: ',',
decimalSeparator: '.',
required: true
});
document.getElementById('form-container').appendChild(priceInput.wrapper);
// ทดสอบการใช้งาน
priceInput.value = 12345.67;
console.log(priceInput.element.value); // "12,345.67" (formatted)ตัวอย่างที่ 4: Autocomplete Input
// สร้าง autocomplete input
const countryInput = TextElementFactory.create({
id: 'country',
name: 'country',
placeholder: 'Select country',
autocomplete: {
enabled: true,
minLength: 2,
maxResults: 10,
source: [
{value: 'TH', text: 'Thailand'},
{value: 'US', text: 'United States'},
{value: 'JP', text: 'Japan'},
{value: 'GB', text: 'United Kingdom'}
]
}
});
document.getElementById('form-container').appendChild(countryInput.wrapper);
// เมื่อมีการเลือก
countryInput.element.addEventListener('change', () => {
console.log('Selected value:', countryInput.hiddenInput.value); // 'TH'
console.log('Display text:', countryInput.value); // 'Thailand'
});ตัวอย่างที่ 5: Custom Validator
// สร้าง password input พร้อม custom validation
const passwordInput = TextElementFactory.create({
id: 'password',
name: 'password',
type: 'password',
placeholder: 'Enter password',
required: true,
minLength: 8
});
// เพิ่ม custom validation
const instance = TextElementFactory.enhance(passwordInput.element);
instance.validateSpecific = function(value) {
// ต้องมีตัวพิมพ์ใหญ่
if (!/[A-Z]/.test(value)) {
return 'Password must contain at least one uppercase letter';
}
// ต้องมีตัวเลข
if (!/[0-9]/.test(value)) {
return 'Password must contain at least one number';
}
// ต้องมีอักขระพิเศษ
if (!/[!@#$%^&*]/.test(value)) {
return 'Password must contain at least one special character (!@#$%^&*)';
}
return null; // Valid
};
document.getElementById('form-container').appendChild(passwordInput.wrapper);ตัวอย่างที่ 6: Dynamic Configuration
// สร้าง input ที่สามารถเปลี่ยน config ได้
const dynamicInput = TextElementFactory.create({
id: 'dynamic',
name: 'dynamic',
required: false,
maxLength: 50
});
document.getElementById('form-container').appendChild(dynamicInput.wrapper);
// เปลี่ยน configuration แบบ dynamic
document.getElementById('toggleRequired').addEventListener('click', () => {
dynamicInput.required = !dynamicInput.required;
console.log('Required:', dynamicInput.required);
});
document.getElementById('changeMaxLength').addEventListener('click', () => {
dynamicInput.maxLength = 100;
console.log('New max length:', dynamicInput.maxLength);
});ตัวอย่างที่ 7: Multiple Elements Management
// สร้างและจัดการหลาย elements
const formElements = {
username: TextElementFactory.create({
id: 'username',
name: 'username',
required: true
}),
email: TextElementFactory.create({
id: 'email',
name: 'email',
type: 'email',
required: true
}),
age: NumberElementFactory.create({
id: 'age',
name: 'age',
min: 18,
max: 100,
required: true
})
};
// แสดงผลทั้งหมด
const container = document.getElementById('form-container');
Object.values(formElements).forEach(instance => {
container.appendChild(instance.wrapper);
});
// Validate ทั้งหมด
function validateAll() {
let allValid = true;
Object.entries(formElements).forEach(([key, instance]) => {
instance.validate(instance.value, true);
if (!instance.isValid()) {
allValid = false;
console.log(`${key} is invalid:`, instance.getError());
}
});
return allValid;
}
// ใช้งาน
document.getElementById('submitBtn').addEventListener('click', () => {
if (validateAll()) {
console.log('All fields are valid!');
// Submit form
}
});API Reference
Static Methods
create(def)
สร้าง element ใหม่จาก definition object
Parameters:
def(Object) - Definition object พร้อม configuration
Return: (Object) - Instance object พร้อม element และ methods
ตัวอย่าง:
const instance = TextElementFactory.create({
id: 'myInput',
name: 'myInput',
type: 'text',
required: true
});enhance(element, def)
Enhance HTML element ที่มีอยู่แล้วให้มีความสามารถเพิ่มเติม
Parameters:
element(HTMLElement) - Element ที่ต้องการ enhancedef(Object, optional) - Configuration เพิ่มเติม
Return: (Object) - Instance object
ตัวอย่าง:
const element = document.getElementById('email');
const instance = TextElementFactory.enhance(element, {
validationMessages: {
email: 'Invalid email format'
}
});extractConfig(element, config, def)
Extract configuration จาก element attributes และ data attributes
Parameters:
element(HTMLElement) - Element ที่ต้องการ extract configconfig(Object) - Base configurationdef(Object) - Default values
Return: (Object) - Merged configuration
extractCustomConfig(element, def, dataset)
Override method นี้เพื่อ extract custom configuration (ใน subclass)
Parameters:
element(HTMLElement) - Element ที่กำลังประมวลผลdef(Object) - Definition objectdataset(DOMStringMap) - Element's dataset
Return: (Object) - Custom configuration
parseNumeric(key, element, def, dataset)
Parse numeric value จาก attributes
Parameters:
key(String) - Attribute nameelement(HTMLElement) - Elementdef(Object) - Default valuesdataset(DOMStringMap) - Element's dataset
Return: (Number|undefined) - Parsed numeric value
Instance Properties
instance.element // HTMLElement - DOM element
instance.config // Object - Configuration object
instance.wrapper // HTMLElement - Wrapper element
instance.label // HTMLElement - Label element (ถ้ามี)
instance.container // HTMLElement - Container element (ถ้ามี)
instance.comment // HTMLElement - Comment/error element (ถ้ามี)
instance.state // Object - State management (reactive)Instance Methods
validate(value, valueChange)
ตรวจสอบความถูกต้องและอัปเดต state
Parameters:
value(any, optional) - ค่าที่ต้องการตรวจสอบ (ถ้าไม่ส่งจะใช้ค่าปัจจุบัน)valueChange(Boolean) - true ถ้ามีการเปลี่ยนค่า
Return: (any) - Validated value
validateValue(value, valueChange)
ตรวจสอบค่าโดยไม่อัปเดต state
Return: (Object) - {validatedValue, error}
isValid()
เช็คว่า element valid หรือไม่
Return: (Boolean)
isModified()
เช็คว่า element ถูกแก้ไขแล้วหรือไม่
Return: (Boolean)
getError()
ดึง error message ล่าสุด
Return: (String|null)
reset()
Reset element กลับสู่สถานะเริ่มต้น
Return: (Object) - this (for chaining)
cleanup()
ลบ event handlers และล้าง error displays
Return: (Object) - this (for chaining)
focus()
Focus element
Return: (Object) - this (for chaining)
blur()
Blur element
Return: (Object) - this (for chaining)
Best Practices
1. การสืบทอดและขยายความสามารถ
✅ ควรทำ:
// สืบทอดจาก factory ที่เหมาะสม
class UsernameFactory extends TextElementFactory {
// เพิ่ม functionality เฉพาะ
}
// ใช้ super ถ้าต้อง override
static setupElement(instance) {
super.setupElement && super.setupElement(instance);
// Custom logic
}✗ ไม่ควรทำ:
// สืบทอดจาก factory ที่ไม่เหมาะสม
class ButtonFactory extends NumberElementFactory { // ❌
// ควรสืบทอดจาก ElementFactory แทน
}2. Configuration Management
✅ ควรทำ:
// ใช้ static config สำหรับค่าเริ่มต้น
static config = {
type: 'text',
minLength: 3,
maxLength: 50
};
// ใช้ extractCustomConfig สำหรับ custom attributes
static extractCustomConfig(element, def, dataset) {
return {
customProp: dataset.customProp || def.customProp
};
}✗ ไม่ควรทำ:
// กำหนด config ใน constructor (ไม่มี constructor)
// Hardcode config ใน setupElement
static setupElement(instance) {
instance.config.minLength = 3; // ❌ ควรกำหนดใน static config
}3. Validation
✅ ควรทำ:
// แยก validation logic ชัดเจน
instance.validateSpecific = function(value) {
// Business logic validation
if (!isValidFormat(value)) {
return 'Invalid format';
}
return null;
};
// ใช้ async validation เมื่อจำเป็น
instance.validateSpecific = async function(value) {
const isAvailable = await checkAvailability(value);
return isAvailable ? null : 'Not available';
};✗ ไม่ควรทำ:
// Validate ใน event handler โดยตรง
EventSystemManager.addHandler(element, 'input', () => {
if (element.value.length < 3) { // ❌
alert('Too short');
}
});
// Blocking synchronous API call
instance.validateSpecific = function(value) {
const xhr = new XMLHttpRequest(); // ❌ ใช้ async แทน
xhr.open('GET', '/api/check', false);
xhr.send();
return xhr.responseText;
};4. Event Handling
✅ ควรทำ:
// ใช้ EventSystemManager
EventSystemManager.addHandler(element, 'input', handler);
// Cleanup events
instance.cleanup();
// ใช้ debounce สำหรับ frequent events
const debouncedHandler = Utils.function.debounce(handler, 300);
EventSystemManager.addHandler(element, 'input', debouncedHandler);✗ ไม่ควรทำ:
// ใช้ addEventListener โดยตรง
element.addEventListener('input', handler); // ❌ ยาก cleanup
// ไม่ cleanup events
// Memory leak ถ้าไม่ remove listeners
// ไม่ใช้ debounce กับ expensive operations
EventSystemManager.addHandler(element, 'input', () => {
expensiveOperation(); // ❌ จะถูกเรียกบ่อยเกินไป
});5. State Management
✅ ควรทำ:
// เข้าถึง state ผ่าน instance.state
if (instance.state.modified && !instance.state.valid) {
// Handle invalid modified state
}
// ใช้ helper methods
if (instance.isValid()) {
// Process valid input
}
// Reset state เมื่อจำเป็น
instance.reset();✗ ไม่ควรทำ:
// เข้าถึง _privateState โดยตรง
const state = ElementFactory._privateState.get(element); // ❌
// แก้ไข state โดยไม่ผ่าน proper methods
element._valid = true; // ❌
// ลืม reset state
// State จะไม่ sync กับ UI6. Memory Management
✅ ควรทำ:
// Cleanup เมื่อลบ element
if (instance.element.parentNode) {
instance.element.parentNode.removeChild(instance.element);
instance.cleanup(); // ⭐ สำคัญ
}
// ใช้ WeakMap สำหรับ private data
static _privateData = new WeakMap();✗ ไม่ควรทำ:
// ลืม cleanup
element.remove(); // ❌ event listeners ยังค้างอยู่
// เก็บ reference ที่ไม่จำเป็น
window.allElements = []; // ❌ Memory leak
window.allElements.push(instance);7. Error Handling
✅ ควรทำ:
// Handle errors gracefully
try {
const result = instance.validate(value, true);
} catch (error) {
console.error('Validation error:', error);
FormError.showFieldError(element.id, 'Validation failed');
}
// ให้ error messages ที่ชัดเจน
return 'Password must contain at least 8 characters';✗ ไม่ควรทำ:
// Silent failures
instance.validate(value, true); // ❌ ไม่เช็ค error
// Error messages ที่ไม่ชัดเจน
return 'Invalid'; // ❌ Invalid อะไร?
// Throw errors ที่ไม่จำเป็น
if (!value) throw new Error('Empty'); // ❌ ควร return error messageCommon Pitfalls
1. ❌ ลืมเรียก super methods
// ❌ ผิด
class MyFactory extends TextElementFactory {
static setupElement(instance) {
// Custom logic
// ลืมเรียก parent's setupElement
}
}
// ✅ ถูกต้อง
class MyFactory extends TextElementFactory {
static setupElement(instance) {
// เรียก parent's method ก่อน
super.setupElement && super.setupElement(instance);
// Custom logic
}
}ผลกระทบ: Functionality จาก parent class จะไม่ทำงาน
2. ❌ แก้ไข element.value โดยตรงใน setupElement
// ❌ ผิด
static setupElement(instance) {
instance.element.value = 'default'; // จะ trigger infinite loop
}
// ✅ ถูกต้อง
static setupElement(instance) {
// ใช้ state.formatting flag
instance.state.formatting = true;
instance.element.value = 'default';
instance.state.formatting = false;
// หรือใช้ initValue
instance.initValue = (value) => {
return value || 'default';
};
}ผลกระทบ: Infinite loop จาก value property setter
3. ❌ ใช้ innerHTML กับ user input
// ❌ ผิด - XSS vulnerability
element.innerHTML = userInput;
// ✅ ถูกต้อง
element.textContent = userInput;
// หรือสร้าง DOM nodes
const span = document.createElement('span');
span.textContent = userInput;
element.appendChild(span);ผลกระทบ: ช่องโหว่ด้านความปลอดภัย (XSS attacks)
4. ❌ Validation ที่ block UI thread
// ❌ ผิด
instance.validateSpecific = function(value) {
// Synchronous API call
const xhr = new XMLHttpRequest();
xhr.open('GET', '/api/validate', false); // false = synchronous
xhr.send();
return xhr.responseText;
};
// ✅ ถูกต้อง
instance.validateSpecific = async function(value) {
try {
const response = await fetch('/api/validate');
const data = await response.json();
return data.valid ? null : data.error;
} catch (error) {
console.error('Validation error:', error);
return 'Unable to validate';
}
};ผลกระทบ: UI freeze, ประสบการณ์ผู้ใช้แย่
5. ❌ ไม่ handle edge cases
// ❌ ผิด
instance.formatter = (value) => {
return value.toUpperCase(); // จะ error ถ้า value เป็น null/undefined
};
// ✅ ถูกต้อง
instance.formatter = (value) => {
if (!value || typeof value !== 'string') return value;
return value.toUpperCase();
};ผลกระทบ: Runtime errors, application crash
6. ❌ ลืม translate messages
// ❌ ผิด
return 'This field is required'; // Hardcoded English
// ✅ ถูกต้อง
return Now.translate('Please fill in');
// หรือใช้ config messages
return config.validationMessages.required;ผลกระทบ: ไม่รองรับหลายภาษา
7. ❌ Mutate config object โดยตรง
// ❌ ผิด
instance.config.minLength = 10; // แก้ไข config โดยตรง
// ✅ ถูกต้อง
instance.minLength = 10; // ใช้ property setter (ถ้ามี)
// หรือใช้ setAttribute
instance.element.setAttribute('minlength', 10);
instance.config.minLength = 10;ผลกระทบ: State ไม่ sync, validation ไม่อัปเดต
8. ❌ ใช้ global variables
// ❌ ผิด
let currentValue; // Global variable
static setupElement(instance) {
EventSystemManager.addHandler(instance.element, 'input', () => {
currentValue = instance.element.value; // Conflict ถ้ามีหลาย instances
});
}
// ✅ ถูกต้อง
static setupElement(instance) {
EventSystemManager.addHandler(instance.element, 'input', () => {
instance.currentValue = instance.element.value; // Instance property
});
}ผลกระทบ: Conflicts ระหว่าง instances, bugs ที่หาได้ยาก
Performance Considerations
1. Debouncing
ใช้ debouncing สำหรับ validation ที่มีค่าใช้จ่ายสูง:
const debouncedValidate = Utils.function.debounce(() => {
instance.validate(undefined, false);
}, 300);
EventSystemManager.addHandler(element, 'input', debouncedValidate);2. Lazy Initialization
สร้าง elements เมื่อจำเป็นเท่านั้น:
// สร้างเมื่อ visible
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const instance = TextElementFactory.create(config);
entry.target.appendChild(instance.wrapper);
observer.unobserve(entry.target);
}
});
});3. Memory Management
// Cleanup เมื่อไม่ใช้
instances.forEach(instance => {
instance.cleanup();
if (instance.element.parentNode) {
instance.element.parentNode.removeChild(instance.element);
}
});
instances = [];4. Batch Operations
// สร้างหลาย elements พร้อมกัน
const fragment = document.createDocumentFragment();
configs.forEach(config => {
const instance = TextElementFactory.create(config);
fragment.appendChild(instance.wrapper);
});
container.appendChild(fragment); // Single reflowSecurity Considerations
1. XSS Prevention
// ✅ ใช้ textContent แทน innerHTML
element.textContent = userInput;
// ✅ สร้าง DOM nodes แทนการใช้ string
const span = document.createElement('span');
span.textContent = userInput;
element.appendChild(span);
// ❌ อย่าใช้ innerHTML กับ user input
element.innerHTML = userInput; // XSS vulnerability!2. Input Sanitization
instance.validateSpecific = function(value) {
// Sanitize input
value = value.trim();
value = value.replace(/[<>]/g, ''); // Remove < >
// Validate sanitized value
return /^[a-zA-Z0-9_]+$/.test(value) ? null : 'Invalid format';
};3. CSRF Protection
Form elements ควรทำงานร่วมกับ FormManager ที่มี CSRF protection:
// FormManager จะเพิ่ม CSRF token อัตโนมัติ
// ElementFactory ไม่ต้องจัดการเรื่องนี้โดยตรงBrowser Compatibility
รองรับ Browsers
- ✅ Chrome 80+
- ✅ Firefox 75+
- ✅ Safari 13+
- ✅ Edge 80+
Required Features
- ES6+ (class, arrow functions, destructuring)
- WeakMap
- Proxy (สำหรับบาง features)
- Custom Events
Polyfills
ถ้าต้องรองรับ browsers เก่า:
<!-- Polyfills สำหรับ IE11 -->
<script src="https://cdn.polyfill.io/v3/polyfill.min.js?features=WeakMap,Promise"></script>Related Documentation
- FormManager - ระบบจัดการฟอร์ม
- TextElementFactory - Text input elements
- NumberElementFactory - Number input elements
- SelectElementFactory - Select/dropdown elements
- FileElementFactory - File upload elements
สัญญาอนุญาต
MIT License - Now.js Framework
ข้อมูลอัปเดตล่าสุด
- เอกสารเวอร์ชัน: 1.0.0
- อัปเดตล่าสุด: 18 พฤศจิกายน 2568
- ผู้เขียน: Now.js Team
- Github: https://github.com/goragodwiriya/nowjs