Files
michaelschiemer/backups/docs-backup-20250731125004/design-system/javascript.md
Michael Schiemer 55a330b223 Enable Discovery debug logging for production troubleshooting
- Add DISCOVERY_LOG_LEVEL=debug
- Add DISCOVERY_SHOW_PROGRESS=true
- Temporary changes for debugging InitializerProcessor fixes on production
2025-08-11 20:13:26 +02:00

642 lines
16 KiB
Markdown

# JavaScript Module System
> Modern ES6+ modular JavaScript architecture with performance monitoring, state management, and component-based UI interactions.
## 📁 Module Structure
```
resources/js/
├── main.js # Entry point
├── core/ # Core framework modules
│ ├── index.js # Core exports
│ ├── init.js # Initialization
│ ├── router.js # SPA routing
│ ├── state.js # State management
│ ├── EventManager.js # Event system
│ ├── PerformanceMonitor.js # Performance tracking
│ └── logger.js # Logging utilities
├── modules/ # Feature modules
│ ├── index.js # Module registry
│ ├── ui/ # UI components
│ │ ├── UIManager.js # UI coordinator
│ │ └── components/ # Individual components
│ ├── scroll-fx/ # Scroll animations
│ ├── lightbox/ # Image lightbox
│ └── parallax/ # Parallax effects
├── utils/ # Utility functions
└── docs/ # Module documentation
```
## 🚀 Core System
### Application Initialization
```javascript
// main.js - Application entry point
import { init } from './core/init.js';
import { ModuleRegistry } from './modules/index.js';
// Initialize core systems
await init({
performance: true,
router: true,
state: true,
logging: 'development'
});
// Register and load modules
ModuleRegistry.register('ui', () => import('./modules/ui/index.js'));
ModuleRegistry.register('scrollfx', () => import('./modules/scrollfx/index.js'));
// Auto-load modules based on DOM attributes
ModuleRegistry.autoLoad();
```
### Router System
```javascript
// SPA routing with layout animations
import { Router } from './core/router.js';
const router = new Router({
mode: 'history',
base: '/',
transitions: true,
prefetch: true
});
// Route definitions
router.addRoute('/admin/:page?', async (ctx) => {
const { page = 'dashboard' } = ctx.params;
// Layout animation
if (ctx.isLayoutChange) {
await animateLayoutSwitch('admin');
}
// Load page content
return await loadAdminPage(page);
});
// Meta data extraction from HTML
router.onNavigate((ctx) => {
// Extract and apply meta data
const metaTitle = ctx.dom.querySelector('[data-meta-title]');
if (metaTitle) {
document.title = metaTitle.dataset.metaTitle;
}
const metaTheme = ctx.dom.querySelector('[data-meta-theme]');
if (metaTheme) {
document.documentElement.style.setProperty('--theme-color', metaTheme.dataset.metaTheme);
}
});
```
### State Management
```javascript
// Reactive state system
import { State } from './core/state.js';
// Global state
const appState = new State({
user: null,
theme: 'light',
admin: {
currentPage: 'dashboard',
notifications: []
}
});
// Reactive updates
appState.watch('theme', (newTheme, oldTheme) => {
document.documentElement.dataset.theme = newTheme;
localStorage.setItem('preferred-theme', newTheme);
});
// Component state binding
appState.bind('[data-user-name]', 'user.name');
appState.bind('[data-notification-count]', 'admin.notifications.length');
```
### Event System
```javascript
// Centralized event management
import { EventManager } from './core/EventManager.js';
const events = new EventManager();
// Global event delegation
events.delegate('click', '[data-action]', (event, element) => {
const action = element.dataset.action;
const target = element.dataset.target;
switch (action) {
case 'toggle-theme':
appState.set('theme', appState.get('theme') === 'light' ? 'dark' : 'light');
break;
case 'show-modal':
UIManager.showModal(target);
break;
}
});
// Custom events
events.on('admin:page-change', (data) => {
appState.set('admin.currentPage', data.page);
PerformanceMonitor.mark(`admin-${data.page}-loaded`);
});
```
## 🧩 UI Component System
### Component Architecture
```javascript
// Base component class
class BaseComponent {
constructor(element, options = {}) {
this.element = element;
this.options = { ...this.defaults, ...options };
this.state = new State(this.initialState);
this.init();
this.bindEvents();
}
init() {
// Override in subclasses
}
bindEvents() {
// Override in subclasses
}
destroy() {
this.state.destroy();
this.element.removeEventListener();
}
}
```
### Modal Component
```javascript
// UI Modal component
class Modal extends BaseComponent {
defaults = {
closeOnOverlay: true,
closeOnEscape: true,
animation: 'fade'
};
initialState = {
isOpen: false,
content: null
};
init() {
this.overlay = this.element.querySelector('.modal-overlay');
this.content = this.element.querySelector('.modal-content');
this.closeBtn = this.element.querySelector('[data-modal-close]');
// State reactivity
this.state.watch('isOpen', (isOpen) => {
this.element.classList.toggle('is-open', isOpen);
this.element.setAttribute('aria-hidden', !isOpen);
if (isOpen) {
this.trapFocus();
} else {
this.releaseFocus();
}
});
}
bindEvents() {
if (this.closeBtn) {
this.closeBtn.addEventListener('click', () => this.close());
}
if (this.options.closeOnOverlay) {
this.overlay.addEventListener('click', () => this.close());
}
if (this.options.closeOnEscape) {
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && this.state.get('isOpen')) {
this.close();
}
});
}
}
open(content = null) {
if (content) {
this.setContent(content);
}
this.state.set('isOpen', true);
events.emit('modal:opened', { modal: this });
}
close() {
this.state.set('isOpen', false);
events.emit('modal:closed', { modal: this });
}
setContent(content) {
if (typeof content === 'string') {
this.content.innerHTML = content;
} else if (content instanceof HTMLElement) {
this.content.innerHTML = '';
this.content.appendChild(content);
}
this.state.set('content', content);
}
}
// Auto-initialization
document.querySelectorAll('[data-modal]').forEach(element => {
new Modal(element);
});
```
### Admin-specific Components
```javascript
// Admin Stats Card
class AdminStatsCard extends BaseComponent {
defaults = {
updateInterval: 30000,
animateChanges: true
};
init() {
this.valueElement = this.element.querySelector('.stat-value');
this.labelElement = this.element.querySelector('.stat-label');
this.trendElement = this.element.querySelector('.stat-trend');
if (this.options.updateInterval) {
this.startPolling();
}
}
startPolling() {
this.pollInterval = setInterval(() => {
this.updateValue();
}, this.options.updateInterval);
}
async updateValue() {
const endpoint = this.element.dataset.endpoint;
if (!endpoint) return;
try {
const response = await fetch(endpoint);
const data = await response.json();
if (this.options.animateChanges) {
this.animateValueChange(data.value);
} else {
this.setValue(data.value);
}
if (data.trend) {
this.setTrend(data.trend);
}
} catch (error) {
console.error('Failed to update stat:', error);
}
}
animateValueChange(newValue) {
const currentValue = parseInt(this.valueElement.textContent) || 0;
const duration = 1000;
const steps = 60;
const increment = (newValue - currentValue) / steps;
let step = 0;
const timer = setInterval(() => {
step++;
const value = Math.round(currentValue + (increment * step));
this.valueElement.textContent = value.toLocaleString();
if (step >= steps) {
clearInterval(timer);
this.valueElement.textContent = newValue.toLocaleString();
}
}, duration / steps);
}
}
```
## 📊 Performance Monitoring
```javascript
// Performance tracking
import { PerformanceMonitor } from './core/PerformanceMonitor.js';
// Page load metrics
PerformanceMonitor.mark('page-start');
PerformanceMonitor.measure('page-load', 'page-start', 'page-end');
// Component performance
class ComponentWithMetrics extends BaseComponent {
init() {
PerformanceMonitor.mark(`${this.constructor.name}-init-start`);
// Component initialization
super.init();
PerformanceMonitor.mark(`${this.constructor.name}-init-end`);
PerformanceMonitor.measure(
`${this.constructor.name}-init`,
`${this.constructor.name}-init-start`,
`${this.constructor.name}-init-end`
);
}
}
// Performance reporting
PerformanceMonitor.report((metrics) => {
// Send to analytics
if (window.gtag) {
gtag('event', 'performance_metric', {
custom_map: { metric_name: 'custom_metric_name' },
metric_name: metrics.name,
value: metrics.duration
});
}
});
```
## 🎨 Admin Interface Integration
### Theme System
```javascript
// Admin theme management
class AdminThemeManager {
constructor() {
this.themes = ['light', 'dark', 'auto'];
this.currentTheme = localStorage.getItem('admin-theme') || 'auto';
this.mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
this.init();
}
init() {
this.applyTheme(this.currentTheme);
this.bindEvents();
}
bindEvents() {
// Theme toggle button
document.addEventListener('click', (e) => {
if (e.target.matches('[data-theme-toggle]')) {
this.toggleTheme();
}
});
// System theme changes
this.mediaQuery.addEventListener('change', () => {
if (this.currentTheme === 'auto') {
this.applyTheme('auto');
}
});
}
applyTheme(theme) {
let resolvedTheme = theme;
if (theme === 'auto') {
resolvedTheme = this.mediaQuery.matches ? 'dark' : 'light';
}
document.documentElement.dataset.theme = resolvedTheme;
document.documentElement.style.setProperty('--theme-preference', theme);
// Update theme color meta tag
const metaThemeColor = document.querySelector('meta[name="theme-color"]');
if (metaThemeColor) {
const color = resolvedTheme === 'dark' ? '#1e293b' : '#ffffff';
metaThemeColor.setAttribute('content', color);
}
}
toggleTheme() {
const currentIndex = this.themes.indexOf(this.currentTheme);
const nextIndex = (currentIndex + 1) % this.themes.length;
const nextTheme = this.themes[nextIndex];
this.setTheme(nextTheme);
}
setTheme(theme) {
this.currentTheme = theme;
localStorage.setItem('admin-theme', theme);
this.applyTheme(theme);
events.emit('theme:changed', { theme, resolvedTheme: this.getResolvedTheme() });
}
getResolvedTheme() {
if (this.currentTheme === 'auto') {
return this.mediaQuery.matches ? 'dark' : 'light';
}
return this.currentTheme;
}
}
// Initialize admin theme
new AdminThemeManager();
```
### Data Tables
```javascript
// Admin data table component
class AdminDataTable extends BaseComponent {
defaults = {
sortable: true,
filterable: true,
paginated: true,
pageSize: 25
};
init() {
this.table = this.element.querySelector('table');
this.tbody = this.table.querySelector('tbody');
this.headers = [...this.table.querySelectorAll('th[data-sort]')];
this.filterInput = this.element.querySelector('[data-table-filter]');
this.data = this.extractData();
this.filteredData = [...this.data];
this.currentSort = { column: null, direction: 'asc' };
this.currentPage = 1;
this.bindEvents();
this.render();
}
bindEvents() {
// Column sorting
this.headers.forEach(header => {
header.addEventListener('click', () => {
const column = header.dataset.sort;
this.sort(column);
});
});
// Filtering
if (this.filterInput) {
this.filterInput.addEventListener('input', debounce(() => {
this.filter(this.filterInput.value);
}, 300));
}
}
sort(column) {
if (this.currentSort.column === column) {
this.currentSort.direction = this.currentSort.direction === 'asc' ? 'desc' : 'asc';
} else {
this.currentSort.column = column;
this.currentSort.direction = 'asc';
}
this.filteredData.sort((a, b) => {
const aVal = a[column];
const bVal = b[column];
const modifier = this.currentSort.direction === 'asc' ? 1 : -1;
if (aVal < bVal) return -1 * modifier;
if (aVal > bVal) return 1 * modifier;
return 0;
});
this.currentPage = 1;
this.render();
}
filter(query) {
if (!query) {
this.filteredData = [...this.data];
} else {
const searchTerm = query.toLowerCase();
this.filteredData = this.data.filter(row => {
return Object.values(row).some(value =>
String(value).toLowerCase().includes(searchTerm)
);
});
}
this.currentPage = 1;
this.render();
}
render() {
// Update table body
this.renderTableBody();
// Update sort indicators
this.updateSortIndicators();
// Update pagination
if (this.options.paginated) {
this.renderPagination();
}
}
}
```
## 🔧 Utility Functions
```javascript
// Common utility functions
export const utils = {
// Debounce function calls
debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
},
// Throttle function calls
throttle(func, limit) {
let inThrottle;
return function(...args) {
if (!inThrottle) {
func.apply(this, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
},
// DOM manipulation helpers
dom: {
ready(fn) {
if (document.readyState !== 'loading') {
fn();
} else {
document.addEventListener('DOMContentLoaded', fn);
}
},
create(tag, attributes = {}, children = []) {
const element = document.createElement(tag);
Object.entries(attributes).forEach(([key, value]) => {
if (key === 'className') {
element.className = value;
} else if (key.startsWith('data-')) {
element.dataset[key.slice(5)] = value;
} else {
element.setAttribute(key, value);
}
});
children.forEach(child => {
if (typeof child === 'string') {
element.appendChild(document.createTextNode(child));
} else {
element.appendChild(child);
}
});
return element;
}
},
// Format utilities
format: {
bytes(bytes, decimals = 2) {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(decimals)) + ' ' + sizes[i];
},
duration(ms) {
if (ms < 1000) return `${ms}ms`;
if (ms < 60000) return `${(ms / 1000).toFixed(1)}s`;
return `${(ms / 60000).toFixed(1)}m`;
}
}
};
```
---
*For specific module documentation, see individual files in `/resources/js/docs/`*
*For performance optimization, see [Performance Guidelines](../development/performance.md)*
*For component integration, see [UI Components](components.md)*