Now.js Framework Documentation

Now.js Framework Documentation

ElementFactory - ระบบสร้างและจัดการ Element/Component

TH 18 Nov 2025 13:11

ElementFactory - ระบบสร้างและจัดการ Element/Component

เอกสารคู่มือการสร้าง Element และ Component โดยใช้ ElementFactory ของ Now.js Framework

📋 สารบัญ

  1. ภาพรวม
  2. สถาปัตยกรรม
  3. การเริ่มต้นใช้งาน
  4. การสร้าง Element Factory ใหม่
  5. การกำหนดค่า (Configuration)
  6. Property Handlers
  7. การตรวจสอบความถูกต้อง (Validation)
  8. Event Handling
  9. State Management
  10. ตัวอย่างการใช้งาน
  11. API Reference
  12. Best Practices
  13. 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
└── InputGroupElementFactory

Core Concepts

  1. Factory Methods:

    • create(def) - สร้าง element ใหม่จาก definition object
    • enhance(element, def) - ปรับปรุง HTML element ที่มีอยู่แล้ว
  2. Instance Structure:

    • element - DOM element ต้นฉบับ
    • config - Configuration object
    • state - State management (private)
    • wrapper, label, container - Wrapper elements
  3. 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

  1. สืบทอดจาก ElementFactory หรือ subclass
  2. กำหนด static config
  3. Override extractCustomConfig() (ถ้าต้องการ)
  4. Override setupElement() (สำหรับ logic เฉพาะ)
  5. กำหนด propertyHandlers (ถ้าต้องการ)
  6. 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 มีลำดับความสำคัญดังนี้ (จากมากไปน้อย):

  1. Runtime config (ส่งเข้า create() หรือ enhance())
  2. Data attributes (HTML data-*)
  3. Element attributes (HTML attributes ปกติ)
  4. 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 Error

Built-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">  // NumberElementFactory

Custom 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 message

Reset 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 ที่ต้องการ enhance
  • def (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 config
  • config (Object) - Base configuration
  • def (Object) - Default values

Return: (Object) - Merged configuration

extractCustomConfig(element, def, dataset)

Override method นี้เพื่อ extract custom configuration (ใน subclass)

Parameters:

  • element (HTMLElement) - Element ที่กำลังประมวลผล
  • def (Object) - Definition object
  • dataset (DOMStringMap) - Element's dataset

Return: (Object) - Custom configuration

parseNumeric(key, element, def, dataset)

Parse numeric value จาก attributes

Parameters:

  • key (String) - Attribute name
  • element (HTMLElement) - Element
  • def (Object) - Default values
  • dataset (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 กับ UI

6. 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 message

Common 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 reflow

Security 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>

สัญญาอนุญาต

MIT License - Now.js Framework

ข้อมูลอัปเดตล่าสุด

  • เอกสารเวอร์ชัน: 1.0.0
  • อัปเดตล่าสุด: 18 พฤศจิกายน 2568
  • ผู้เขียน: Now.js Team
  • Github: https://github.com/goragodwiriya/nowjs