Now.js Framework Documentation

Now.js Framework Documentation

ElementFactory - Element/Component Creation System

EN 18 Nov 2025 13:11

ElementFactory - Element/Component Creation System

Complete guide for creating Elements and Components using ElementFactory in the Now.js Framework

📋 Table of Contents

  1. Overview
  2. Architecture
  3. Getting Started
  4. Creating New Element Factories
  5. Configuration
  6. Property Handlers
  7. Validation
  8. Event Handling
  9. State Management
  10. Usage Examples
  11. API Reference
  12. Best Practices
  13. Common Pitfalls

Overview

ElementFactory is the base class for creating and managing form elements and UI components in the Now.js Framework. It provides a comprehensive system for handling validation, formatting, event management, and state tracking.

Key Features

  • Factory Pattern: Create elements with configuration-based approach
  • Enhance Existing Elements: Upgrade existing HTML elements with additional capabilities
  • Validation System: Flexible and extensible validation framework
  • Property Handlers: Reactive property management
  • State Management: Secure element state tracking
  • Event System: Efficient event handling
  • Wrapper Support: Automatic HTML structure creation around elements
  • Inheritance: Easy extension and customization

When to Use

Recommended when:

  • Creating custom input components with validation
  • Need formatting and data transformation
  • Require structured state and event management
  • Building reusable and maintainable components

Consider alternatives when:

  • Simple elements without validation or special logic
  • Component complexity exceeds typical form element scope (use UI Component instead)

Architecture

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) - Create new element from definition object
    • enhance(element, def) - Enhance existing HTML element
  2. Instance Structure:

    • element - Original 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

Getting Started

Loading Scripts

<!-- Load ElementFactory (usually auto-loaded via Now.js) -->
<script src="/Now/js/ElementFactory.js"></script>

<!-- Load Specific Factories (if needed) -->
<script src="/Now/js/TextElementFactory.js"></script>
<script src="/Now/js/NumberElementFactory.js"></script>

Creating Simple Elements

// Create new text input
const textInput = TextElementFactory.create({
  type: 'text',
  id: 'username',
  name: 'username',
  placeholder: 'Enter username',
  required: true
});

// Render to DOM
document.getElementById('form-container').appendChild(textInput.wrapper);

Enhancing Existing Elements

<!-- 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 (not filled yet)

Creating New Element Factories

Steps to Create Custom Factory

  1. Extend ElementFactory or subclass
  2. Define static config
  3. Override extractCustomConfig() (if needed)
  4. Override setupElement() (for specific logic)
  5. Define propertyHandlers (if needed)
  6. Register validators (if needed)

Example: Creating PhoneElementFactory

/**
 * PhoneElementFactory - Manages phone number inputs
 */
class PhoneElementFactory extends TextElementFactory {
  // 1. Define default configuration
  static config = {
    type: 'tel',
    inputMode: 'numeric',
    pattern: '^[0-9]{10}$',
    placeholder: '0xxxxxxxxx',
    maxLength: 10,
    validationMessages: {
      phone: 'Please enter a 10-digit phone number',
      required: 'Phone number is required'
    },
    // Specific configuration
    autoFormat: true,
    allowInternational: false
  };

  // 2. Define custom validators
  static validators = {
    phone: (value) => {
      // Check Thai phone number (0x-xxx-xxxx)
      return /^0[0-9]{9}$/.test(value.replace(/-/g, ''));
    },
    international: (value) => {
      // International format +66xxxxxxxxx
      return /^\+[0-9]{11,15}$/.test(value);
    }
  };

  // 3. Extract custom configuration from 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;

    // Add phone number formatter
    if (config.autoFormat) {
      instance.formatter = (value) => {
        // Remove non-numeric characters
        value = value.replace(/\D/g, '');

        // Limit length
        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;
      };

      // Apply formatter on 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. Define property handlers (if needed)
  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 to window object
window.PhoneElementFactory = PhoneElementFactory;

Using Custom Factory

// Create new element
const phoneInput = PhoneElementFactory.create({
  id: 'mobile',
  name: 'mobile',
  required: true,
  autoFormat: true
});

document.getElementById('form').appendChild(phoneInput.wrapper);

// Or 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,              // Show comment element

    // Messages
    validationMessages: {
      required: 'Please fill in',
      pattern: 'Invalid format'
    },

    // Custom properties
    formatter: null,
    validation: null
  };
}

Configuration Priority

Configuration priority order (highest to lowest):

  1. Runtime config (passed to create() or enhance())
  2. Data attributes (HTML data-*)
  3. Element attributes (standard HTML attributes)
  4. Static config (default values in class)
// Static config
static config = { required: false, maxLength: 50 };

// HTML
<input type="text" maxlength="100" data-required="true">

// Runtime
TextElementFactory.enhance(element, { maxLength: 200 });

// Result:
// required: true (from data-attribute)
// maxLength: 200 (from runtime config)

extractConfig()

This method consolidates configuration from various sources:

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 in subclass)
  if (typeof this.extractCustomConfig === 'function') {
    Object.assign(result, this.extractCustomConfig(element, def, dataset));
  }

  return result;
}

extractCustomConfig()

Override this method to extract factory-specific configuration:

static extractCustomConfig(element, def, dataset) {
  return {
    // Example: autocomplete configuration
    autocomplete: {
      enabled: dataset.autocomplete === 'true' || def.autocomplete?.enabled,
      source: dataset.source || def.autocomplete?.source,
      minLength: this.parseNumeric('minLength', element, def.autocomplete, dataset)
    },

    // Example: formatter function
    formatter: dataset.formatter ? window[dataset.formatter] : def.formatter
  };
}

Property Handlers

Property handlers allow you to define reactive behavior for properties:

Defining Property Handlers

class MyElementFactory extends ElementFactory {
  static propertyHandlers = {
    // Property: value
    value: {
      get(element) {
        // Read value from element
        return element.value;
      },
      set(instance, newValue) {
        // Set new value
        instance.element.value = newValue;

        // Trigger validation
        instance.validate(newValue, true);
      }
    },

    // Property: min (for 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 necessary
          if (instance.state.modified) {
            instance.validate(instance.element.value, false);
          }
        }
      }
    }
  };
}

Built-in Property Handlers

ElementFactory includes these basic handlers:

// value - manages element value
instance.value = 'new value';
console.log(instance.value); // 'new value'

// required - manages required attribute
instance.required = true;
console.log(instance.required); // true

// placeholder - manages placeholder text
instance.placeholder = 'Enter name';
console.log(instance.placeholder); // 'Enter name'

Using Property Handlers

const instance = TextElementFactory.create({
  id: 'username',
  name: 'username'
});

// Set properties (calls setter)
instance.value = 'john_doe';
instance.required = true;
instance.placeholder = 'Username';

// Get properties (calls 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 (from subclass)
<input type="email">  // EmailElementFactory
<input type="url">    // UrlElementFactory
<input type="number" min="0" max="100">  // NumberElementFactory

Custom Validation - validateSpecific()

Override validateSpecific() in setupElement():

static setupElement(instance) {
  const {element, config} = instance;

  // Define specific validation logic
  instance.validateSpecific = function(value) {
    // Example: 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)

<!-- Define validation rules via data-validate -->
<input type="text"
       data-validate="required,email,minLength:5"
       data-error-required="Email is required"
       data-error-email="Invalid email format"
       data-error-minLength="Email must be at least 5 characters">
// 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() - validates and updates state
const validatedValue = instance.validate(value, valueChange);

// validateValue() - validates without updating state
const {validatedValue, error} = instance.validateValue(value, valueChange);

// isValid() - check validation status
if (instance.isValid()) {
  console.log('Input is valid');
}

// getError() - retrieve error message
const errorMsg = instance.getError();
if (errorMsg) {
  console.log('Error:', errorMsg);
}

Event Handling

Built-in Events

ElementFactory sets up these basic event listeners:

static setupEventListeners(instance) {
  const {element} = instance;

  // input event - debounced validation
  EventSystemManager.addHandler(element, 'input', () => {
    instance.state.modified = true;
    debouncedValidate();
  });

  // change event - immediate validation
  EventSystemManager.addHandler(element, 'change', () => {
    const validatedValue = instance.validate(element.value, true);
    if (validatedValue !== element.value) {
      element.value = validatedValue;
    }
  });

  // blur event - validate when leaving field
  EventSystemManager.addHandler(element, 'blur', () => {
    const validatedValue = instance.validate(element.value, true);
    if (validatedValue !== element.value) {
      element.value = validatedValue;
    }
  });

  // focus event - clear error
  EventSystemManager.addHandler(element, 'focus', () => {
    FormError.clearFieldError(element.id);
  });
}

Adding Custom Events

static setupElement(instance) {
  const {element, config} = instance;

  // Add custom event handlers
  EventSystemManager.addHandler(element, 'keydown', (e) => {
    // Example: prevent Enter key
    if (e.key === 'Enter') {
      e.preventDefault();
      // Trigger validation
      instance.validate(element.value, true);
    }
  });

  EventSystemManager.addHandler(element, 'paste', (e) => {
    // Example: process 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 when no longer needed
instance.cleanup();

// Or remove element and auto-cleanup
if (instance.element.parentNode) {
  instance.element.parentNode.removeChild(instance.element);
  instance.cleanup();
}

State Management

Private State

ElementFactory uses WeakMap to store private state:

static _privateState = new WeakMap();

// State structure
{
  validating: false,      // Currently validating?
  valid: true,           // Latest validation result
  modified: false,       // Has been modified?
  error: null,          // Latest error message
  originalValue: '',     // Initial value
  originalConfig: {},    // Initial config
  formatting: false,     // Currently formatting?
  recursionGuard: {}    // Prevent infinite loops
}

Accessing State

// Via 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 or error message

// Set state
instance.state.modified = true;
instance.state.valid = false;

// Via helper methods (read-only)
instance.isValid();      // true/false
instance.isModified();   // true/false
instance.getError();     // null or error message

Reset State

// Reset element to initial state
instance.reset();

// After reset:
// - value returns to initial value
// - state.valid = true
// - state.modified = false
// - state.error = null
// - error display cleared

State Events

// Listen to 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);
});

Usage Examples

Example 1: Create Basic Text Input

// Create 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
});

// Render to DOM
document.getElementById('form-container').appendChild(usernameInput.wrapper);

// Access element
console.log(usernameInput.element);  // <input> element
console.log(usernameInput.value);    // Current value
console.log(usernameInput.isValid()); // Validation status

Example 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 is required',
    email: 'Invalid email format'
  }
});

// Validate on 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());
  }
});

Example 3: Number Input with Formatting

// Create 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);

// Test usage
priceInput.value = 12345.67;
console.log(priceInput.element.value); // "12,345.67" (formatted)

Example 4: Autocomplete Input

// Create 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);

// When selected
countryInput.element.addEventListener('change', () => {
  console.log('Selected value:', countryInput.hiddenInput.value); // 'TH'
  console.log('Display text:', countryInput.value); // 'Thailand'
});

Example 5: Custom Validator

// Create password input with custom validation
const passwordInput = TextElementFactory.create({
  id: 'password',
  name: 'password',
  type: 'password',
  placeholder: 'Enter password',
  required: true,
  minLength: 8
});

// Add custom validation
const instance = TextElementFactory.enhance(passwordInput.element);
instance.validateSpecific = function(value) {
  // Must contain uppercase
  if (!/[A-Z]/.test(value)) {
    return 'Password must contain at least one uppercase letter';
  }

  // Must contain number
  if (!/[0-9]/.test(value)) {
    return 'Password must contain at least one number';
  }

  // Must contain special character
  if (!/[!@#$%^&*]/.test(value)) {
    return 'Password must contain at least one special character (!@#$%^&*)';
  }

  return null; // Valid
};

document.getElementById('form-container').appendChild(passwordInput.wrapper);

Example 6: Dynamic Configuration

// Create input with changeable config
const dynamicInput = TextElementFactory.create({
  id: 'dynamic',
  name: 'dynamic',
  required: false,
  maxLength: 50
});

document.getElementById('form-container').appendChild(dynamicInput.wrapper);

// Change configuration dynamically
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);
});

Example 7: Multiple Elements Management

// Create and manage multiple 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
  })
};

// Render all
const container = document.getElementById('form-container');
Object.values(formElements).forEach(instance => {
  container.appendChild(instance.wrapper);
});

// Validate all
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;
}

// Usage
document.getElementById('submitBtn').addEventListener('click', () => {
  if (validateAll()) {
    console.log('All fields are valid!');
    // Submit form
  }
});

API Reference

Static Methods

create(def)

Create new element from definition object

Parameters:

  • def (Object) - Definition object with configuration

Return: (Object) - Instance object with element and methods

Example:

const instance = TextElementFactory.create({
  id: 'myInput',
  name: 'myInput',
  type: 'text',
  required: true
});

enhance(element, def)

Enhance existing HTML element with additional capabilities

Parameters:

  • element (HTMLElement) - Element to enhance
  • def (Object, optional) - Additional configuration

Return: (Object) - Instance object

Example:

const element = document.getElementById('email');
const instance = TextElementFactory.enhance(element, {
  validationMessages: {
    email: 'Invalid email format'
  }
});

extractConfig(element, config, def)

Extract configuration from element attributes and data attributes

Parameters:

  • element (HTMLElement) - Element to extract config from
  • config (Object) - Base configuration
  • def (Object) - Default values

Return: (Object) - Merged configuration

extractCustomConfig(element, def, dataset)

Override this method to extract custom configuration (in subclass)

Parameters:

  • element (HTMLElement) - Element being processed
  • def (Object) - Definition object
  • dataset (DOMStringMap) - Element's dataset

Return: (Object) - Custom configuration

parseNumeric(key, element, def, dataset)

Parse numeric value from 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 (if any)
instance.container        // HTMLElement - Container element (if any)
instance.comment          // HTMLElement - Comment/error element (if any)
instance.state            // Object - State management (reactive)

Instance Methods

validate(value, valueChange)

Validate value and update state

Parameters:

  • value (any, optional) - Value to validate (uses current if omitted)
  • valueChange (Boolean) - true if value changed

Return: (any) - Validated value

validateValue(value, valueChange)

Validate value without updating state

Return: (Object) - {validatedValue, error}

isValid()

Check if element is valid

Return: (Boolean)

isModified()

Check if element has been modified

Return: (Boolean)

getError()

Get latest error message

Return: (String|null)

reset()

Reset element to initial state

Return: (Object) - this (for chaining)

cleanup()

Remove event handlers and clear 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. Inheritance and Extension

Do:

// Extend from appropriate factory
class UsernameFactory extends TextElementFactory {
  // Add specific functionality
}

// Call super if overriding
static setupElement(instance) {
  super.setupElement && super.setupElement(instance);
  // Custom logic
}

Don't:

// Extend from inappropriate factory
class ButtonFactory extends NumberElementFactory { // ❌
  // Should extend from ElementFactory instead
}

2. Configuration Management

Do:

// Use static config for defaults
static config = {
  type: 'text',
  minLength: 3,
  maxLength: 50
};

// Use extractCustomConfig for custom attributes
static extractCustomConfig(element, def, dataset) {
  return {
    customProp: dataset.customProp || def.customProp
  };
}

Don't:

// Define config in constructor (no constructor exists)
// Hardcode config in setupElement
static setupElement(instance) {
  instance.config.minLength = 3; // ❌ Should define in static config
}

3. Validation

Do:

// Separate validation logic clearly
instance.validateSpecific = function(value) {
  // Business logic validation
  if (!isValidFormat(value)) {
    return 'Invalid format';
  }
  return null;
};

// Use async validation when necessary
instance.validateSpecific = async function(value) {
  const isAvailable = await checkAvailability(value);
  return isAvailable ? null : 'Not available';
};

Don't:

// Validate directly in 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(); // ❌ Use async instead
  xhr.open('GET', '/api/check', false);
  xhr.send();
  return xhr.responseText;
};

4. Event Handling

Do:

// Use EventSystemManager
EventSystemManager.addHandler(element, 'input', handler);

// Cleanup events
instance.cleanup();

// Use debounce for frequent events
const debouncedHandler = Utils.function.debounce(handler, 300);
EventSystemManager.addHandler(element, 'input', debouncedHandler);

Don't:

// Use addEventListener directly
element.addEventListener('input', handler); // ❌ Hard to cleanup

// Don't cleanup events
// Memory leak if listeners not removed

// Don't debounce expensive operations
EventSystemManager.addHandler(element, 'input', () => {
  expensiveOperation(); // ❌ Will be called too frequently
});

5. State Management

Do:

// Access state via instance.state
if (instance.state.modified && !instance.state.valid) {
  // Handle invalid modified state
}

// Use helper methods
if (instance.isValid()) {
  // Process valid input
}

// Reset state when necessary
instance.reset();

Don't:

// Access _privateState directly
const state = ElementFactory._privateState.get(element); // ❌

// Modify state without proper methods
element._valid = true; // ❌

// Forget to reset state
// State will be out of sync with UI

6. Memory Management

Do:

// Cleanup when removing element
if (instance.element.parentNode) {
  instance.element.parentNode.removeChild(instance.element);
  instance.cleanup(); // ⭐ Important
}

// Use WeakMap for private data
static _privateData = new WeakMap();

Don't:

// Forget cleanup
element.remove(); // ❌ Event listeners still attached

// Store unnecessary references
window.allElements = []; // ❌ Memory leak
window.allElements.push(instance);

7. Error Handling

Do:

// Handle errors gracefully
try {
  const result = instance.validate(value, true);
} catch (error) {
  console.error('Validation error:', error);
  FormError.showFieldError(element.id, 'Validation failed');
}

// Provide clear error messages
return 'Password must contain at least 8 characters';

Don't:

// Silent failures
instance.validate(value, true); // ❌ Don't check error

// Unclear error messages
return 'Invalid'; // ❌ Invalid what?

// Unnecessary exceptions
if (!value) throw new Error('Empty'); // ❌ Should return error message

Common Pitfalls

1. ❌ Forgetting to Call Super Methods

// ❌ Wrong
class MyFactory extends TextElementFactory {
  static setupElement(instance) {
    // Custom logic
    // Forgot to call parent's setupElement
  }
}

// ✅ Correct
class MyFactory extends TextElementFactory {
  static setupElement(instance) {
    // Call parent's method first
    super.setupElement && super.setupElement(instance);
    // Custom logic
  }
}

Impact: Functionality from parent class won't work

2. ❌ Modifying element.value Directly in setupElement

// ❌ Wrong
static setupElement(instance) {
  instance.element.value = 'default'; // Triggers infinite loop
}

// ✅ Correct
static setupElement(instance) {
  // Use state.formatting flag
  instance.state.formatting = true;
  instance.element.value = 'default';
  instance.state.formatting = false;

  // Or use initValue
  instance.initValue = (value) => {
    return value || 'default';
  };
}

Impact: Infinite loop from value property setter

3. ❌ Using innerHTML with User Input

// ❌ Wrong - XSS vulnerability
element.innerHTML = userInput;

// ✅ Correct
element.textContent = userInput;

// Or create DOM nodes
const span = document.createElement('span');
span.textContent = userInput;
element.appendChild(span);

Impact: Security vulnerability (XSS attacks)

4. ❌ Validation that Blocks UI Thread

// ❌ Wrong
instance.validateSpecific = function(value) {
  // Synchronous API call
  const xhr = new XMLHttpRequest();
  xhr.open('GET', '/api/validate', false); // false = synchronous
  xhr.send();
  return xhr.responseText;
};

// ✅ Correct
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';
  }
};

Impact: UI freeze, poor user experience

5. ❌ Not Handling Edge Cases

// ❌ Wrong
instance.formatter = (value) => {
  return value.toUpperCase(); // Will error if value is null/undefined
};

// ✅ Correct
instance.formatter = (value) => {
  if (!value || typeof value !== 'string') return value;
  return value.toUpperCase();
};

Impact: Runtime errors, application crash

6. ❌ Forgetting to Translate Messages

// ❌ Wrong
return 'This field is required'; // Hardcoded English

// ✅ Correct
return Now.translate('Please fill in');

// Or use config messages
return config.validationMessages.required;

Impact: No multilingual support

7. ❌ Mutating Config Object Directly

// ❌ Wrong
instance.config.minLength = 10; // Modify config directly

// ✅ Correct
instance.minLength = 10; // Use property setter (if exists)

// Or use setAttribute
instance.element.setAttribute('minlength', 10);
instance.config.minLength = 10;

Impact: State out of sync, validation not updated

8. ❌ Using Global Variables

// ❌ Wrong
let currentValue; // Global variable

static setupElement(instance) {
  EventSystemManager.addHandler(instance.element, 'input', () => {
    currentValue = instance.element.value; // Conflict with multiple instances
  });
}

// ✅ Correct
static setupElement(instance) {
  EventSystemManager.addHandler(instance.element, 'input', () => {
    instance.currentValue = instance.element.value; // Instance property
  });
}

Impact: Conflicts between instances, hard-to-find bugs

Performance Considerations

1. Debouncing

Use debouncing for expensive validation:

const debouncedValidate = Utils.function.debounce(() => {
  instance.validate(undefined, false);
}, 300);

EventSystemManager.addHandler(element, 'input', debouncedValidate);

2. Lazy Initialization

Create elements only when necessary:

// Create when 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 when not in use
instances.forEach(instance => {
  instance.cleanup();
  if (instance.element.parentNode) {
    instance.element.parentNode.removeChild(instance.element);
  }
});
instances = [];

4. Batch Operations

// Create multiple elements together
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

// ✅ Use textContent instead of innerHTML
element.textContent = userInput;

// ✅ Create DOM nodes instead of using strings
const span = document.createElement('span');
span.textContent = userInput;
element.appendChild(span);

// ❌ Don't use innerHTML with 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 should work with FormManager which has CSRF protection:

// FormManager adds CSRF token automatically
// ElementFactory doesn't need to handle this directly

Browser Compatibility

Supported Browsers

  • ✅ Chrome 80+
  • ✅ Firefox 75+
  • ✅ Safari 13+
  • ✅ Edge 80+

Required Features

  • ES6+ (class, arrow functions, destructuring)
  • WeakMap
  • Proxy (for some features)
  • Custom Events

Polyfills

For older browsers:

<!-- Polyfills for IE11 -->
<script src="https://cdn.polyfill.io/v3/polyfill.min.js?features=WeakMap,Promise"></script>

License

MIT License - Now.js Framework

Last Updated