Now.js Framework Documentation
ElementFactory - Element/Component Creation System
ElementFactory - Element/Component Creation System
Complete guide for creating Elements and Components using ElementFactory in the Now.js Framework
📋 Table of Contents
- Overview
- Architecture
- Getting Started
- Creating New Element Factories
- Configuration
- Property Handlers
- Validation
- Event Handling
- State Management
- Usage Examples
- API Reference
- Best Practices
- 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
└── InputGroupElementFactoryCore Concepts
-
Factory Methods:
create(def)- Create new element from definition objectenhance(element, def)- Enhance existing HTML element
-
Instance Structure:
element- Original DOM elementconfig- Configuration objectstate- State management (private)wrapper,label,container- Wrapper elements
-
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
- Extend ElementFactory or subclass
- Define static config
- Override extractCustomConfig() (if needed)
- Override setupElement() (for specific logic)
- Define propertyHandlers (if needed)
- 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
unit: '', // Unit after the input ('days' · '%') — a translation key → <span class="unit"> in the form-control box
// Messages
validationMessages: {
required: 'Please fill in',
pattern: 'Invalid format'
},
// Custom properties
formatter: null,
validation: null
};
}Configuration Priority
Configuration priority order (highest to lowest):
- Runtime config (passed to
create()orenhance()) - Data attributes (HTML
data-*) - Element attributes (standard HTML attributes)
- 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 ErrorValues set from code are checked quietly. Assigning element.value (and
FormManager.setFormData(), including the change event it dispatches) still
formats and checks the value and updates isValid(), but does not show a new
error — otherwise a new-record form would open with every empty required field
outlined red before the user typed anything. An error that is already on screen
is re-checked, so a valid value from code clears it. The user's own
input/change/blur and the form submit validate and show errors as usual.
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"> // NumberElementFactoryCustom 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 messageReset 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 clearedReading State
ElementFactory emits no state event. State lives in the ElementFactory._privateState
WeakMap and is read through the instance methods:
const instance = ElementManager.getInstance(element);
console.log('Element:', instance.element.id);
console.log('Valid:', instance.isValid());
console.log('Modified:', instance.isModified());
console.log('Error:', instance.getError());To react to a change, listen to the element's native change or input event
and read the state inside the handler.
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 statusExample 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 enhancedef(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 fromconfig(Object) - Base configurationdef(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 processeddef(Object) - Definition objectdataset(DOMStringMap) - Element's dataset
Return: (Object) - Custom configuration
parseNumeric(key, element, def, dataset)
Parse numeric value from 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 (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 UI6. 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 messageCommon 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 reflowSecurity 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 directlyBrowser 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>Related Documentation
- FormManager - Form management system
- TextElementFactory - Text input elements
- NumberElementFactory - Number input elements
- SelectElementFactory - Select/dropdown elements
- FileElementFactory - File upload elements