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
// 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 ErrorBuilt-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 clearedState 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 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
License
MIT License - Now.js Framework
Last Updated
- Documentation Version: 1.0.0
- Last Updated: November 18, 2024
- Author: Now.js Team
- Github: https://github.com/goragodwiriya/nowjs