Episode 21: Advanced JavaScript Development
Mastering Modern JavaScript Patterns for Enterprise Power Pages
Introduction
Professional Power Pages development requires sophisticated JavaScript implementations that go far beyond basic scripting. This comprehensive episode covers modern JavaScript patterns, ES6+ features, advanced async programming, modular architecture, and enterprise-grade development practices. You’ll learn to build robust, maintainable, and scalable JavaScript solutions that integrate seamlessly with Power Pages while following industry best practices.
Key Concepts
- Modern JavaScript Patterns: ES6+ features, modules, and advanced programming paradigms
- Async Programming: Promises, async/await, and reactive programming patterns
- Modular Architecture: Component-based development and dependency management
- Enterprise Development: Testing, debugging, and production-ready JavaScript practices
- Performance Optimization: Memory management, code splitting, and execution optimization
Learning Objectives
By the end of this episode, you will be able to:
- Implement advanced JavaScript patterns using ES6+ features and modern syntax
- Design and build modular, component-based JavaScript architectures
- Master asynchronous programming with promises, async/await, and reactive patterns
- Develop robust error handling and debugging strategies for complex applications
- Optimize JavaScript performance for enterprise-scale portal implementations
Why Advanced JavaScript Development Matters
Modern Power Pages require sophisticated client-side functionality that demands professional JavaScript development skills. Advanced JavaScript enables rich user experiences, complex business logic implementation, efficient data handling, and seamless integration with external services while maintaining code quality, performance, and maintainability.
Step-by-Step Guide
1. Modern JavaScript Patterns and ES6+ Features
Advanced Class-Based Architecture:
// Modern JavaScript Class with Advanced Features
class AdvancedPortalManager {
// Private fields (ES2022)
#apiClient;
#eventBus;
#configuration;
#performanceMonitor;
// Static properties
static VERSION = '2.1.0';
static SUPPORTED_FEATURES = new Set(['async', 'modules', 'workers', 'cache']);
constructor(config = {}) {
// Destructuring with defaults
const {
apiEndpoint = '/api/v2',
enableCache = true,
performanceTracking = true,
eventBusEnabled = true,
...additionalConfig
} = config;
this.#configuration = {
apiEndpoint,
enableCache,
performanceTracking,
eventBusEnabled,
...additionalConfig
};
// Initialize private components
this.#apiClient = new APIClient(this.#configuration);
this.#eventBus = eventBusEnabled ? new EventBus() : null;
this.#performanceMonitor = performanceTracking ? new PerformanceMonitor() : null;
// Bind methods to maintain context
this.handleUserAction = this.handleUserAction.bind(this);
this.processData = this.processData.bind(this);
this.initialize();
}
// Async initialization with error handling
async initialize() {
try {
await this.#loadConfiguration();
await this.#setupEventListeners();
await this.#initializeComponents();
this.#eventBus?.emit('portal:initialized', {
version: AdvancedPortalManager.VERSION,
timestamp: new Date().toISOString()
});
} catch (error) {
console.error('Portal initialization failed:', error);
await this.#handleInitializationError(error);
}
}
// Advanced method with multiple parameter patterns
async fetchData(resource, options = {}) {
// Parameter destructuring with validation
const {
filters = {},
pagination = { page: 1, limit: 10 },
include = [],
cache = this.#configuration.enableCache,
timeout = 30000,
retries = 3
} = options;
// Input validation using modern JavaScript
if (!resource || typeof resource !== 'string') {
throw new TypeError('Resource parameter must be a non-empty string');
}
// Performance tracking
const performanceId = this.#performanceMonitor?.startOperation('fetchData', { resource });
try {
// Build query parameters using URLSearchParams
const queryParams = new URLSearchParams();
// Add filters
Object.entries(filters).forEach(([key, value]) => {
if (Array.isArray(value)) {
value.forEach(v => queryParams.append(`${key}[]`, v));
} else {
queryParams.set(key, value);
}
});
// Add pagination
queryParams.set('page', pagination.page.toString());
queryParams.set('limit', pagination.limit.toString());
// Add includes
if (include.length > 0) {
queryParams.set('include', include.join(','));
}
// Execute request with retry logic
const result = await this.#executeWithRetry(async () => {
return await this.#apiClient.get(`/${resource}?${queryParams.toString()}`, {
timeout,
cache
});
}, retries);
// Transform and validate response
const processedData = await this.#processApiResponse(result, resource);
// Emit success event
this.#eventBus?.emit('data:fetched', {
resource,
count: processedData.length,
timestamp: new Date().toISOString()
});
return processedData;
} catch (error) {
// Enhanced error handling
const enhancedError = this.#enhanceError(error, 'fetchData', { resource, options });
this.#eventBus?.emit('data:fetchError', enhancedError);
throw enhancedError;
} finally {
this.#performanceMonitor?.endOperation(performanceId);
}
}
// Advanced data processing with functional programming
async processData(data, processors = []) {
// Default processors using arrow functions and advanced patterns
const defaultProcessors = [
// Data validation processor
(items) => items.filter(item => this.#validateDataItem(item)),
// Data enrichment processor
async (items) => await Promise.all(
items.map(async item => ({
...item,
processedAt: new Date().toISOString(),
enrichmentData: await this.#enrichDataItem(item)
}))
),
// Data transformation processor
(items) => items.map(item => this.#transformDataItem(item)),
// Data sorting processor
(items) => items.sort((a, b) =>
new Date(b.createdAt) - new Date(a.createdAt)
)
];
const allProcessors = [...defaultProcessors, ...processors];
// Process data through pipeline using reduce
return await allProcessors.reduce(async (accPromise, processor) => {
const acc = await accPromise;
return await processor(acc);
}, Promise.resolve(data));
}
// Advanced event handling with delegation and modern patterns
#setupEventListeners() {
// Use modern event delegation
document.addEventListener('click', this.#handleGlobalClick.bind(this));
document.addEventListener('submit', this.#handleGlobalSubmit.bind(this));
// Custom event listeners with cleanup
this.#eventBus?.on('user:action', this.handleUserAction);
this.#eventBus?.on('data:changed', this.#handleDataChange.bind(this));
// Setup cleanup on page unload
window.addEventListener('beforeunload', () => {
this.#cleanup();
});
}
#handleGlobalClick(event) {
// Modern event delegation using closest()
const actionElement = event.target.closest('[data-action]');
if (!actionElement) return;
const action = actionElement.dataset.action;
const config = this.#parseActionConfig(actionElement);
// Prevent default if needed
if (config.preventDefault) {
event.preventDefault();
}
// Execute action with error boundary
this.#executeAction(action, config, event).catch(error => {
console.error(`Action ${action} failed:`, error);
this.#showUserError(`Failed to execute ${action}`);
});
}
// Advanced async action execution
async #executeAction(actionName, config, event) {
const actionMap = new Map([
['fetchRecords', () => this.#handleFetchRecords(config)],
['saveRecord', () => this.#handleSaveRecord(config)],
['deleteRecord', () => this.#handleDeleteRecord(config)],
['exportData', () => this.#handleExportData(config)],
['importData', () => this.#handleImportData(config)],
['validateForm', () => this.#handleValidateForm(config)]
]);
const actionHandler = actionMap.get(actionName);
if (!actionHandler) {
throw new Error(`Unknown action: ${actionName}`);
}
// Execute with performance tracking
const startTime = performance.now();
try {
const result = await actionHandler();
const duration = performance.now() - startTime;
this.#performanceMonitor?.recordAction(actionName, duration, true);
return result;
} catch (error) {
const duration = performance.now() - startTime;
this.#performanceMonitor?.recordAction(actionName, duration, false);
throw error;
}
}
// Modern async error handling and retry logic
async #executeWithRetry(operation, retries = 3, delay = 1000) {
let lastError;
for (let attempt = 1; attempt <= retries; attempt++) {
try {
return await operation();
} catch (error) {
lastError = error;
// Don't retry on certain error types
if (this.#isNonRetryableError(error) || attempt === retries) {
throw error;
}
// Exponential backoff delay
const backoffDelay = delay * Math.pow(2, attempt - 1);
await this.#sleep(backoffDelay);
console.warn(`Operation failed, retrying (${attempt}/${retries}):`, error.message);
}
}
throw lastError;
}
// Utility method using Promise-based sleep
#sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// Advanced error enhancement with context
#enhanceError(originalError, operation, context = {}) {
const enhancedError = new Error(
`${operation} failed: ${originalError.message}`
);
// Preserve original error properties
enhancedError.name = `Enhanced${originalError.name}`;
enhancedError.originalError = originalError;
enhancedError.operation = operation;
enhancedError.context = context;
enhancedError.timestamp = new Date().toISOString();
enhancedError.stack = originalError.stack;
// Add debugging information
enhancedError.debugInfo = {
userAgent: navigator.userAgent,
url: window.location.href,
timestamp: Date.now(),
sessionId: this.#getSessionId(),
...context
};
return enhancedError;
}
// Memory management and cleanup
#cleanup() {
// Remove event listeners
this.#eventBus?.removeAllListeners();
// Clear caches
this.#apiClient?.clearCache();
// Cancel pending operations
this.#performanceMonitor?.cleanup();
// Clear references
this.#apiClient = null;
this.#eventBus = null;
this.#performanceMonitor = null;
}
// Public method for manual cleanup
destroy() {
this.#cleanup();
}
// Getter methods for controlled access to private data
get configuration() {
// Return a deep copy to prevent external modification
return JSON.parse(JSON.stringify(this.#configuration));
}
get isInitialized() {
return !!(this.#apiClient && this.#eventBus);
}
// Static factory method
static create(config) {
return new AdvancedPortalManager(config);
}
// Static utility methods
static isSupported() {
return AdvancedPortalManager.SUPPORTED_FEATURES.size > 0 &&
'Promise' in window &&
'fetch' in window;
}
}
Advanced Module System and Dependency Management:
// ES6 Module with Advanced Exports
// utils/dataProcessor.js
export class DataProcessor {
static #transforms = new Map();
// Register custom transform functions
static registerTransform(name, transformFn) {
if (typeof transformFn !== 'function') {
throw new TypeError('Transform must be a function');
}
this.#transforms.set(name, transformFn);
}
// Apply transforms in sequence
static async applyTransforms(data, transformNames = []) {
return await transformNames.reduce(async (accPromise, transformName) => {
const acc = await accPromise;
const transform = this.#transforms.get(transformName);
if (!transform) {
throw new Error(`Transform '${transformName}' not found`);
}
return await transform(acc);
}, Promise.resolve(data));
}
}
// Default transforms
DataProcessor.registerTransform('normalize', (data) => {
return Array.isArray(data) ? data : [data];
});
DataProcessor.registerTransform('validate', (data) => {
return data.filter(item => item && typeof item === 'object');
});
DataProcessor.registerTransform('enrich', async (data) => {
return Promise.all(data.map(async item => ({
...item,
id: item.id || crypto.randomUUID(),
timestamp: new Date().toISOString()
})));
});
export default DataProcessor;
// Advanced API Client Module
// services/apiClient.js
export class APIClient {
#baseURL;
#defaultHeaders;
#cache;
#requestInterceptors;
#responseInterceptors;
constructor(config = {}) {
this.#baseURL = config.baseURL || '';
this.#defaultHeaders = {
'Content-Type': 'application/json',
...config.headers
};
this.#cache = new Map();
this.#requestInterceptors = [];
this.#responseInterceptors = [];
}
// Interceptor methods for middleware pattern
addRequestInterceptor(interceptor) {
this.#requestInterceptors.push(interceptor);
return () => {
const index = this.#requestInterceptors.indexOf(interceptor);
if (index > -1) {
this.#requestInterceptors.splice(index, 1);
}
};
}
addResponseInterceptor(interceptor) {
this.#responseInterceptors.push(interceptor);
return () => {
const index = this.#responseInterceptors.indexOf(interceptor);
if (index > -1) {
this.#responseInterceptors.splice(index, 1);
}
};
}
// Advanced request method with full HTTP support
async request(method, endpoint, options = {}) {
const {
data,
headers = {},
cache = false,
timeout = 30000,
signal,
...fetchOptions
} = options;
// Build full URL
const url = new URL(endpoint, this.#baseURL).toString();
// Check cache first
if (cache && method === 'GET') {
const cached = this.#cache.get(url);
if (cached && Date.now() < cached.expires) {
return cached.data;
}
}
// Prepare request config
let requestConfig = {
method,
headers: { ...this.#defaultHeaders, ...headers },
signal: signal || AbortSignal.timeout(timeout),
...fetchOptions
};
// Add body for non-GET requests
if (data && !['GET', 'HEAD'].includes(method)) {
requestConfig.body = JSON.stringify(data);
}
// Apply request interceptors
for (const interceptor of this.#requestInterceptors) {
requestConfig = await interceptor(requestConfig) || requestConfig;
}
try {
// Execute request
const response = await fetch(url, requestConfig);
// Apply response interceptors
let processedResponse = response;
for (const interceptor of this.#responseInterceptors) {
processedResponse = await interceptor(processedResponse) || processedResponse;
}
// Handle response
if (!processedResponse.ok) {
throw new HTTPError(
`HTTP ${processedResponse.status}: ${processedResponse.statusText}`,
processedResponse.status,
processedResponse
);
}
// Parse response
const responseData = await this.#parseResponse(processedResponse);
// Cache successful GET requests
if (cache && method === 'GET') {
this.#cache.set(url, {
data: responseData,
expires: Date.now() + (cache === true ? 300000 : cache) // 5 min default
});
}
return responseData;
} catch (error) {
if (error.name === 'AbortError') {
throw new TimeoutError(`Request timeout after ${timeout}ms`);
}
throw error;
}
}
// Convenience methods
get(endpoint, options = {}) {
return this.request('GET', endpoint, options);
}
post(endpoint, data, options = {}) {
return this.request('POST', endpoint, { ...options, data });
}
put(endpoint, data, options = {}) {
return this.request('PUT', endpoint, { ...options, data });
}
patch(endpoint, data, options = {}) {
return this.request('PATCH', endpoint, { ...options, data });
}
delete(endpoint, options = {}) {
return this.request('DELETE', endpoint, options);
}
// Advanced batch request method
async batch(requests) {
const results = await Promise.allSettled(
requests.map(({ method, endpoint, ...options }) =>
this.request(method, endpoint, options)
)
);
return results.map((result, index) => ({
request: requests[index],
success: result.status === 'fulfilled',
data: result.status === 'fulfilled' ? result.value : null,
error: result.status === 'rejected' ? result.reason : null
}));
}
// Response parsing with content type detection
async #parseResponse(response) {
const contentType = response.headers.get('content-type') || '';
if (contentType.includes('application/json')) {
return await response.json();
} else if (contentType.includes('text/')) {
return await response.text();
} else if (contentType.includes('application/octet-stream')) {
return await response.arrayBuffer();
} else {
return await response.blob();
}
}
clearCache() {
this.#cache.clear();
}
}
// Custom error classes
export class HTTPError extends Error {
constructor(message, status, response) {
super(message);
this.name = 'HTTPError';
this.status = status;
this.response = response;
}
}
export class TimeoutError extends Error {
constructor(message) {
super(message);
this.name = 'TimeoutError';
}
}
2. Advanced Asynchronous Programming Patterns
Reactive Programming with Observables:
// Advanced Observable Pattern Implementation
class Observable {
constructor(subscribeFn) {
this._subscribeFn = subscribeFn;
}
subscribe(observer) {
const subscription = this._subscribeFn(observer);
return {
unsubscribe: subscription?.unsubscribe || (() => {})
};
}
// Transformation operators
map(transformFn) {
return new Observable(observer => {
return this.subscribe({
next: value => observer.next(transformFn(value)),
error: error => observer.error(error),
complete: () => observer.complete()
});
});
}
filter(predicateFn) {
return new Observable(observer => {
return this.subscribe({
next: value => {
if (predicateFn(value)) {
observer.next(value);
}
},
error: error => observer.error(error),
complete: () => observer.complete()
});
});
}
debounce(delay) {
return new Observable(observer => {
let timeoutId;
return this.subscribe({
next: value => {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => observer.next(value), delay);
},
error: error => observer.error(error),
complete: () => observer.complete()
});
});
}
throttle(delay) {
return new Observable(observer => {
let lastEmit = 0;
return this.subscribe({
next: value => {
const now = Date.now();
if (now - lastEmit >= delay) {
lastEmit = now;
observer.next(value);
}
},
error: error => observer.error(error),
complete: () => observer.complete()
});
});
}
// Combination operators
static merge(...observables) {
return new Observable(observer => {
const subscriptions = observables.map(obs =>
obs.subscribe(observer)
);
return {
unsubscribe: () => subscriptions.forEach(sub => sub.unsubscribe())
};
});
}
static combineLatest(...observables) {
return new Observable(observer => {
const values = new Array(observables.length);
const hasValue = new Array(observables.length).fill(false);
let completedCount = 0;
const subscriptions = observables.map((obs, index) =>
obs.subscribe({
next: value => {
values[index] = value;
hasValue[index] = true;
if (hasValue.every(Boolean)) {
observer.next([...values]);
}
},
error: error => observer.error(error),
complete: () => {
completedCount++;
if (completedCount === observables.length) {
observer.complete();
}
}
})
);
return {
unsubscribe: () => subscriptions.forEach(sub => sub.unsubscribe())
};
});
}
// Factory methods
static fromEvent(element, eventName) {
return new Observable(observer => {
const handler = event => observer.next(event);
element.addEventListener(eventName, handler);
return {
unsubscribe: () => element.removeEventListener(eventName, handler)
};
});
}
static fromPromise(promise) {
return new Observable(observer => {
promise
.then(value => {
observer.next(value);
observer.complete();
})
.catch(error => observer.error(error));
});
}
static interval(delay) {
return new Observable(observer => {
let count = 0;
const intervalId = setInterval(() => {
observer.next(count++);
}, delay);
return {
unsubscribe: () => clearInterval(intervalId)
};
});
}
}
// Advanced Async Controller for Power Pages
class AsyncController {
constructor() {
this.activeOperations = new Map();
this.operationQueue = [];
this.maxConcurrent = 5;
this.defaultTimeout = 30000;
}
// Execute async operation with advanced control
async execute(operationId, asyncFn, options = {}) {
const {
timeout = this.defaultTimeout,
retries = 3,
priority = 'normal',
cancelPrevious = false,
dependencies = []
} = options;
// Cancel previous operation if requested
if (cancelPrevious && this.activeOperations.has(operationId)) {
this.cancel(operationId);
}
// Wait for dependencies
if (dependencies.length > 0) {
await this.waitForOperations(dependencies);
}
// Queue operation if at max capacity
if (this.activeOperations.size >= this.maxConcurrent) {
await this.queueOperation(operationId, asyncFn, options);
return;
}
return this.executeImmediate(operationId, asyncFn, { timeout, retries, priority });
}
async executeImmediate(operationId, asyncFn, options) {
const { timeout, retries, priority } = options;
// Create abort controller for cancellation
const abortController = new AbortController();
const timeoutId = setTimeout(() => abortController.abort(), timeout);
const operation = {
id: operationId,
abortController,
timeoutId,
priority,
startTime: Date.now()
};
this.activeOperations.set(operationId, operation);
try {
// Execute with retry logic
const result = await this.executeWithRetry(
() => asyncFn(abortController.signal),
retries,
abortController.signal
);
return result;
} catch (error) {
if (error.name === 'AbortError') {
throw new Error(`Operation ${operationId} was cancelled`);
}
throw error;
} finally {
// Cleanup
clearTimeout(timeoutId);
this.activeOperations.delete(operationId);
// Process queued operations
this.processQueue();
}
}
async executeWithRetry(operation, retries, signal) {
let lastError;
for (let attempt = 1; attempt <= retries; attempt++) {
if (signal.aborted) {
throw new DOMException('Operation aborted', 'AbortError');
}
try {
return await operation();
} catch (error) {
lastError = error;
if (attempt === retries || signal.aborted) {
throw error;
}
// Exponential backoff
const delay = Math.min(1000 * Math.pow(2, attempt - 1), 10000);
await this.sleep(delay, signal);
}
}
throw lastError;
}
async queueOperation(operationId, asyncFn, options) {
return new Promise((resolve, reject) => {
this.operationQueue.push({
id: operationId,
asyncFn,
options,
resolve,
reject
});
// Sort queue by priority
this.operationQueue.sort((a, b) => {
const priorities = { high: 3, normal: 2, low: 1 };
return priorities[b.options.priority || 'normal'] -
priorities[a.options.priority || 'normal'];
});
});
}
async processQueue() {
while (this.operationQueue.length > 0 &&
this.activeOperations.size < this.maxConcurrent) {
const queuedOperation = this.operationQueue.shift();
const { id, asyncFn, options, resolve, reject } = queuedOperation;
try {
const result = await this.executeImmediate(id, asyncFn, options);
resolve(result);
} catch (error) {
reject(error);
}
}
}
cancel(operationId) {
const operation = this.activeOperations.get(operationId);
if (operation) {
operation.abortController.abort();
return true;
}
// Remove from queue if not yet started
const queueIndex = this.operationQueue.findIndex(op => op.id === operationId);
if (queueIndex !== -1) {
const queuedOperation = this.operationQueue.splice(queueIndex, 1)[0];
queuedOperation.reject(new Error(`Operation ${operationId} was cancelled`));
return true;
}
return false;
}
cancelAll() {
// Cancel active operations
for (const [id, operation] of this.activeOperations) {
operation.abortController.abort();
}
// Cancel queued operations
while (this.operationQueue.length > 0) {
const queuedOperation = this.operationQueue.shift();
queuedOperation.reject(new Error('All operations cancelled'));
}
}
async waitForOperations(operationIds) {
const checkInterval = 100;
while (operationIds.some(id => this.activeOperations.has(id))) {
await this.sleep(checkInterval);
}
}
sleep(ms, signal) {
return new Promise((resolve, reject) => {
const timeoutId = setTimeout(resolve, ms);
if (signal) {
signal.addEventListener('abort', () => {
clearTimeout(timeoutId);
reject(new DOMException('Sleep aborted', 'AbortError'));
});
}
});
}
getStatus() {
return {
activeOperations: this.activeOperations.size,
queuedOperations: this.operationQueue.length,
operations: Array.from(this.activeOperations.entries()).map(([id, op]) => ({
id,
priority: op.priority,
duration: Date.now() - op.startTime
}))
};
}
}
3. Component-Based Architecture
Advanced Component System:
// Base Component Class with Advanced Features
class Component {
static #registry = new Map();
static #globalMixins = [];
#element;
#props;
#state;
#subscriptions;
#childComponents;
#isDestroyed;
constructor(element, props = {}) {
this.#element = element;
this.#props = { ...props };
this.#state = {};
this.#subscriptions = [];
this.#childComponents = new Set();
this.#isDestroyed = false;
// Apply global mixins
Component.#globalMixins.forEach(mixin => {
Object.assign(this, mixin);
});
// Initialize component
this.created();
this.render();
this.mounted();
}
// Lifecycle methods (to be overridden)
created() {}
mounted() {}
updated() {}
destroyed() {}
// State management
setState(newState, callback) {
if (this.#isDestroyed) return;
const prevState = { ...this.#state };
this.#state = { ...this.#state, ...newState };
// Trigger update
this.updated(prevState, this.#state);
this.render();
// Execute callback after update
if (callback) {
requestAnimationFrame(callback);
}
}
getState() {
return { ...this.#state };
}
// Props management
setProps(newProps) {
if (this.#isDestroyed) return;
const prevProps = { ...this.#props };
this.#props = { ...this.#props, ...newProps };
this.propsChanged(prevProps, this.#props);
this.render();
}
getProps() {
return { ...this.#props };
}
propsChanged(prevProps, newProps) {}
// Event system
emit(eventName, detail = {}) {
const event = new CustomEvent(eventName, {
detail: { component: this, ...detail },
bubbles: true,
cancelable: true
});
this.#element.dispatchEvent(event);
return event;
}
on(eventName, handler, options = {}) {
this.#element.addEventListener(eventName, handler, options);
// Track subscription for cleanup
const cleanup = () => this.#element.removeEventListener(eventName, handler);
this.#subscriptions.push(cleanup);
return cleanup;
}
// Child component management
createChildComponent(ComponentClass, element, props) {
const child = new ComponentClass(element, props);
this.#childComponents.add(child);
return child;
}
destroyChildComponent(child) {
if (this.#childComponents.has(child)) {
child.destroy();
this.#childComponents.delete(child);
}
}
// DOM utilities
$(selector) {
return this.#element.querySelector(selector);
}
$$(selector) {
return Array.from(this.#element.querySelectorAll(selector));
}
// Template rendering (to be overridden)
render() {
// Default implementation - override in subclasses
}
// Cleanup
destroy() {
if (this.#isDestroyed) return;
this.#isDestroyed = true;
// Destroy child components
this.#childComponents.forEach(child => child.destroy());
this.#childComponents.clear();
// Clean up subscriptions
this.#subscriptions.forEach(cleanup => cleanup());
this.#subscriptions = [];
// Call lifecycle method
this.destroyed();
// Remove from parent if exists
if (this.parent) {
this.parent.destroyChildComponent(this);
}
// Clear references
this.#element = null;
this.#props = null;
this.#state = null;
}
// Static registry methods
static register(name, ComponentClass) {
this.#registry.set(name, ComponentClass);
}
static create(name, element, props) {
const ComponentClass = this.#registry.get(name);
if (!ComponentClass) {
throw new Error(`Component '${name}' not found in registry`);
}
return new ComponentClass(element, props);
}
static addGlobalMixin(mixin) {
this.#globalMixins.push(mixin);
}
// Auto-initialization from DOM
static initializeFromDOM() {
document.querySelectorAll('[data-component]').forEach(element => {
const componentName = element.dataset.component;
const props = element.dataset.props ? JSON.parse(element.dataset.props) : {};
try {
this.create(componentName, element, props);
} catch (error) {
console.error(`Failed to initialize component '${componentName}':`, error);
}
});
}
}
// Advanced Form Component Example
class AdvancedFormComponent extends Component {
created() {
this.validators = new Map();
this.errors = new Map();
this.validationRules = this.getProps().validationRules || {};
this.setupValidation();
}
mounted() {
// Set up form event listeners
this.on('submit', this.handleSubmit.bind(this));
this.on('input', this.handleInput.bind(this));
this.on('blur', this.handleBlur.bind(this), true); // Use capture
// Initialize field validation
this.initializeFields();
}
setupValidation() {
// Register built-in validators
this.registerValidator('required', (value) => {
return value != null && value !== '';
});
this.registerValidator('email', (value) => {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return !value || emailRegex.test(value);
});
this.registerValidator('minLength', (value, min) => {
return !value || value.length >= min;
});
this.registerValidator('maxLength', (value, max) => {
return !value || value.length <= max;
});
this.registerValidator('pattern', (value, pattern) => {
const regex = new RegExp(pattern);
return !value || regex.test(value);
});
}
registerValidator(name, validatorFn) {
this.validators.set(name, validatorFn);
}
async handleSubmit(event) {
event.preventDefault();
const isValid = await this.validateAll();
if (isValid) {
const formData = this.getFormData();
try {
await this.submitForm(formData);
this.emit('form:success', { data: formData });
} catch (error) {
this.emit('form:error', { error });
this.showError('Submission failed. Please try again.');
}
} else {
this.emit('form:invalid', { errors: this.errors });
this.focusFirstError();
}
}
async validateAll() {
const fields = this.$$('[data-validate]');
const validationPromises = fields.map(field => this.validateField(field));
await Promise.all(validationPromises);
return this.errors.size === 0;
}
async validateField(field) {
const value = field.value;
const rules = field.dataset.validate.split('|');
const fieldName = field.name || field.id;
// Clear previous errors
this.clearFieldError(fieldName);
for (const rule of rules) {
const [ruleName, ...params] = rule.split(':');
const validator = this.validators.get(ruleName);
if (!validator) {
console.warn(`Validator '${ruleName}' not found`);
continue;
}
const isValid = await validator(value, ...params);
if (!isValid) {
const errorMessage = this.getErrorMessage(fieldName, ruleName, params);
this.setFieldError(fieldName, errorMessage);
this.showFieldError(field, errorMessage);
break;
}
}
}
getFormData() {
const formData = new FormData(this.#element);
const data = {};
for (const [key, value] of formData.entries()) {
// Handle multiple values (checkboxes, multi-select)
if (data[key]) {
if (Array.isArray(data[key])) {
data[key].push(value);
} else {
data[key] = [data[key], value];
}
} else {
data[key] = value;
}
}
return data;
}
async submitForm(data) {
const submitUrl = this.#element.action || this.getProps().submitUrl;
const method = this.#element.method || 'POST';
const response = await fetch(submitUrl, {
method,
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return await response.json();
}
setFieldError(fieldName, message) {
this.errors.set(fieldName, message);
}
clearFieldError(fieldName) {
this.errors.delete(fieldName);
}
showFieldError(field, message) {
// Remove existing error display
const existingError = field.parentNode.querySelector('.field-error');
if (existingError) {
existingError.remove();
}
// Create error element
const errorElement = document.createElement('div');
errorElement.className = 'field-error';
errorElement.textContent = message;
// Insert after field
field.parentNode.insertBefore(errorElement, field.nextSibling);
// Add error class to field
field.classList.add('error');
}
getErrorMessage(fieldName, ruleName, params) {
const messages = {
required: `${fieldName} is required`,
email: `Please enter a valid email address`,
minLength: `${fieldName} must be at least ${params[0]} characters`,
maxLength: `${fieldName} must not exceed ${params[0]} characters`,
pattern: `${fieldName} format is invalid`
};
return messages[ruleName] || `${fieldName} is invalid`;
}
}
// Register components
Component.register('advanced-form', AdvancedFormComponent);
Practical Example: Complete Modern JavaScript Application
Let’s build a comprehensive data management interface using all the advanced patterns:
// Modern Power Pages Data Manager
class PowerPagesDataManager {
constructor(config = {}) {
this.config = {
apiEndpoint: '/api/data',
enableRealTime: true,
cacheStrategy: 'aggressive',
batchSize: 100,
...config
};
// Initialize core services
this.apiClient = new APIClient({ baseURL: this.config.apiEndpoint });
this.asyncController = new AsyncController();
this.eventBus = new EventBus();
this.componentManager = new ComponentManager();
// Set up real-time updates
if (this.config.enableRealTime) {
this.setupRealTimeUpdates();
}
this.initialize();
}
async initialize() {
try {
// Load initial configuration
await this.loadConfiguration();
// Initialize components
await this.initializeComponents();
// Set up event listeners
this.setupEventListeners();
// Load initial data
await this.loadInitialData();
console.log('PowerPagesDataManager initialized successfully');
} catch (error) {
console.error('Initialization failed:', error);
this.handleInitializationError(error);
}
}
async loadConfiguration() {
const config = await this.apiClient.get('/config');
this.config = { ...this.config, ...config };
}
async initializeComponents() {
// Initialize data table component
const tableElement = document.querySelector('#data-table');
if (tableElement) {
this.dataTable = Component.create('data-table', tableElement, {
columns: this.config.tableColumns,
pageSize: this.config.pageSize,
sortable: true,
filterable: true
});
}
// Initialize form components
document.querySelectorAll('.advanced-form').forEach(formElement => {
Component.create('advanced-form', formElement, {
validationRules: this.config.validationRules,
submitUrl: this.config.submitUrl
});
});
}
setupEventListeners() {
// Listen for data changes
this.eventBus.on('data:changed', this.handleDataChange.bind(this));
this.eventBus.on('data:error', this.handleDataError.bind(this));
// Listen for user actions
document.addEventListener('click', this.handleGlobalClick.bind(this));
}
async loadInitialData() {
const data = await this.asyncController.execute(
'loadInitialData',
() => this.apiClient.get('/records'),
{ priority: 'high' }
);
this.processData(data);
}
setupRealTimeUpdates() {
// Set up Server-Sent Events for real-time updates
const eventSource = new EventSource(`${this.config.apiEndpoint}/events`);
eventSource.onmessage = (event) => {
const data = JSON.parse(event.data);
this.handleRealTimeUpdate(data);
};
eventSource.onerror = (error) => {
console.error('Real-time connection error:', error);
this.scheduleReconnection();
};
}
handleRealTimeUpdate(data) {
switch (data.type) {
case 'record_created':
this.handleRecordCreated(data.record);
break;
case 'record_updated':
this.handleRecordUpdated(data.record);
break;
case 'record_deleted':
this.handleRecordDeleted(data.recordId);
break;
}
}
async createRecord(recordData) {
try {
const newRecord = await this.asyncController.execute(
`createRecord_${Date.now()}`,
() => this.apiClient.post('/records', recordData),
{ priority: 'high' }
);
this.eventBus.emit('data:created', { record: newRecord });
return newRecord;
} catch (error) {
this.eventBus.emit('data:error', { operation: 'create', error });
throw error;
}
}
async updateRecord(recordId, updates) {
try {
const updatedRecord = await this.asyncController.execute(
`updateRecord_${recordId}`,
() => this.apiClient.patch(`/records/${recordId}`, updates),
{
priority: 'high',
cancelPrevious: true // Cancel any pending updates for this record
}
);
this.eventBus.emit('data:updated', { record: updatedRecord });
return updatedRecord;
} catch (error) {
this.eventBus.emit('data:error', { operation: 'update', error });
throw error;
}
}
async deleteRecord(recordId) {
try {
await this.asyncController.execute(
`deleteRecord_${recordId}`,
() => this.apiClient.delete(`/records/${recordId}`),
{ priority: 'high' }
);
this.eventBus.emit('data:deleted', { recordId });
} catch (error) {
this.eventBus.emit('data:error', { operation: 'delete', error });
throw error;
}
}
async batchOperation(operations) {
try {
const results = await this.asyncController.execute(
'batchOperation',
() => this.apiClient.post('/batch', { operations }),
{ priority: 'normal', timeout: 60000 }
);
this.eventBus.emit('data:batchCompleted', { results });
return results;
} catch (error) {
this.eventBus.emit('data:error', { operation: 'batch', error });
throw error;
}
}
}
// Initialize when DOM is ready
document.addEventListener('DOMContentLoaded', () => {
window.dataManager = new PowerPagesDataManager({
apiEndpoint: '/api/v2',
enableRealTime: true,
pageSize: 25,
tableColumns: [
{ field: 'name', title: 'Name', sortable: true },
{ field: 'email', title: 'Email', sortable: true },
{ field: 'status', title: 'Status', filterable: true },
{ field: 'createdAt', title: 'Created', sortable: true }
]
});
});
Troubleshooting Common Issues
Advanced JavaScript Debugging:
- Memory Leaks: Use proper cleanup patterns and WeakMap/WeakSet for object references
- Async Race Conditions: Implement proper cancellation and operation queuing
- Event Listener Cleanup: Always remove event listeners in component destruction
- Module Dependency Issues: Use proper import/export patterns and avoid circular dependencies
- Performance Bottlenecks: Profile code execution and optimize critical paths
JavaScript Best Practices
Enterprise-Grade Development Standards:
- Type Safety: Use JSDoc comments or TypeScript for better type checking
- Error Boundaries: Implement comprehensive error handling at component and application levels
- Testing Strategy: Write unit tests for utility functions and integration tests for components
- Code Organization: Use consistent file structure and naming conventions
- Documentation: Maintain clear documentation for all public APIs and complex logic
Key Takeaways
Advanced JavaScript development enables sophisticated Power Pages functionality through modern patterns, asynchronous programming, and component-based architecture. Focus on maintainable code organization, robust error handling, and performance optimization to build enterprise-grade portal solutions that scale effectively and provide exceptional user experiences.
In the next episode, you’ll learn about Custom API Development to build powerful backend integrations that complement your advanced JavaScript implementations.
3. Quality Assurance
Advanced solutions require rigorous testing:
- Functional Testing: Verify all features work as specified
- Performance Testing: Ensure acceptable performance under load
- Security Testing: Validate security controls and access restrictions
- User Acceptance Testing: Confirm the solution meets business requirements
Practical Implementation
When implementing advanced Power Pages features:
- Start with Clear Requirements: Document exactly what functionality is needed
- Design for Scale: Consider how the solution will perform as usage grows
- Implement Security by Design: Build security considerations into every component
- Plan for Maintenance: Create solutions that can be updated and maintained over time
Enterprise Architecture Patterns
| Pattern | Use Case | Benefits | Complexity |
|---|---|---|---|
| API Gateway | External integrations | Centralized security, rate limiting | High |
| Event-Driven | Real-time updates | Scalability, loose coupling | Medium |
| Microservices | Complex business logic | Independent deployment, technology diversity | High |
| CQRS | Read/write optimization | Performance, scalability | High |
Advanced Troubleshooting
Advanced implementations require sophisticated troubleshooting approaches:
- Logging Strategy: Implement structured logging with correlation IDs
- Performance Monitoring: Use APM tools to identify bottlenecks
- Error Handling: Implement graceful degradation and circuit breaker patterns
- Security Monitoring: Monitor for security threats and anomalies
Production Considerations
Deployment Best Practices
- Use proper environment promotion strategies
- Implement automated testing in deployment pipelines
- Maintain rollback procedures for failed deployments
- Document all customizations and their dependencies
Monitoring and Maintenance
- Implement comprehensive application monitoring
- Set up alerting for critical system events
- Establish maintenance schedules and procedures
- Plan for platform updates and compatibility testing
Summary
Advanced Power Pages development enables enterprise-scale solutions that integrate seamlessly with complex business requirements. Focus on architecture, follow enterprise development practices, and always plan for long-term maintenance and scalability. These advanced techniques transform Power Pages from a simple portal platform into a robust enterprise application platform.
In the next episode, you’ll continue building on these advanced concepts to create even more sophisticated portal solutions.