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

16 KiB

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

// 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

// 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

// 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

// 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

// 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

// 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

// 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

// 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

// 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

// 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

// 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
For component integration, see UI Components