Files
spore-ui/docs/FRAMEWORK_README.md
2025-08-28 10:21:14 +02:00

6.0 KiB

SPORE UI Framework

A clean, component-based frontend framework with pub/sub communication and view models.

Architecture Overview

The framework follows a clean architecture pattern with the following layers:

  1. Components - Handle UI rendering and user interactions
  2. View Models - Hold data and business logic
  3. API Client - Communicate with the backend
  4. Event Bus - Pub/sub communication between components
  5. Framework Core - Base classes and application management

Key Features

  • Component-based architecture - Reusable, self-contained UI components
  • View Models - Data flows from backend → view model → UI rendering
  • Pub/Sub system - Components communicate through events
  • Automatic cleanup - Event listeners and subscriptions are automatically cleaned up
  • Type-safe property access - View models provide get/set methods with change notifications
  • Routing - Built-in navigation between views

File Structure

public/
├── framework.js          # Core framework classes
├── api-client.js         # Backend API communication
├── view-models.js        # View models for each component
├── components.js         # UI components
├── app.js               # Main application setup
├── index.html           # HTML template
└── styles.css           # Styling

Usage

1. Creating a View Model

class MyViewModel extends ViewModel {
    constructor() {
        super();
        this.setMultiple({
            data: [],
            isLoading: false,
            error: null
        });
    }

    async loadData() {
        try {
            this.set('isLoading', true);
            const data = await window.apiClient.getData();
            this.set('data', data);
        } catch (error) {
            this.set('error', error.message);
        } finally {
            this.set('isLoading', false);
        }
    }
}

2. Creating a Component

class MyComponent extends Component {
    constructor(container, viewModel, eventBus) {
        super(container, viewModel, eventBus);
    }

    setupEventListeners() {
        const button = this.findElement('.my-button');
        if (button) {
            this.addEventListener(button, 'click', this.handleClick.bind(this));
        }
    }

    setupViewModelListeners() {
        this.subscribeToProperty('data', this.render.bind(this));
        this.subscribeToProperty('isLoading', this.render.bind(this));
        this.subscribeToProperty('error', this.render.bind(this));
    }

    render() {
        const data = this.viewModel.get('data');
        const isLoading = this.viewModel.get('isLoading');
        const error = this.viewModel.get('error');

        if (isLoading) {
            this.setHTML('', '<div>Loading...</div>');
            return;
        }

        if (error) {
            this.setHTML('', `<div class="error">${error}</div>`);
            return;
        }

        this.setHTML('', `<div>${this.renderData(data)}</div>`);
    }

    renderData(data) {
        return data.map(item => `<div>${item.name}</div>`).join('');
    }

    handleClick() {
        // Handle user interaction
        this.viewModel.loadData();
    }
}

3. Using the Event Bus

// Subscribe to events
this.subscribeToEvent('user-logged-in', (userData) => {
    console.log('User logged in:', userData);
});

// Publish events
this.viewModel.publish('data-updated', { timestamp: Date.now() });

4. Component Helper Methods

The framework provides several helper methods for common DOM operations:

// Find elements
const element = this.findElement('.my-class');
const elements = this.findAllElements('.my-class');

// Update content
this.setHTML('.my-container', '<div>New content</div>');
this.setText('.my-text', 'New text');

// Manage classes
this.setClass('.my-element', 'active', true);
this.setClass('.my-element', 'hidden', false);

// Show/hide elements
this.setVisible('.my-element', false);
this.setEnabled('.my-button', false);

// Manage styles
this.setStyle('.my-element', 'color', 'red');

5. Registering Routes

// In app.js
window.app.registerRoute('my-view', MyComponent, 'my-view-container');

6. Navigation

// Navigate to a route
window.app.navigateTo('my-view');

Data Flow

  1. User Interaction → Component handles event
  2. Component → Calls view model method
  3. View Model → Makes API call or processes data
  4. View Model → Updates properties (triggers change notifications)
  5. Component → Receives change notification and re-renders
  6. UI → Updates to reflect new data

Best Practices

  1. Keep components focused - Each component should have a single responsibility
  2. Use view models for business logic - Don't put API calls or data processing in components
  3. Subscribe to specific properties - Only listen to properties your component needs
  4. Use the event bus sparingly - Prefer direct view model communication for related components
  5. Clean up resources - The framework handles most cleanup automatically, but be mindful of custom event listeners
  6. Error handling - Always handle errors in async operations and update the view model accordingly

Migration from Old Code

The old monolithic script.js has been broken down into:

  • API callsapi-client.js
  • Data managementview-models.js
  • UI logiccomponents.js
  • Application setupapp.js

Each piece is now more focused, testable, and maintainable.

Benefits

  • Maintainability - Smaller, focused files are easier to understand and modify
  • Testability - View models can be tested independently of UI
  • Reusability - Components can be reused across different views
  • Scalability - Easy to add new features without affecting existing code
  • Debugging - Clear separation of concerns makes issues easier to track down
  • Performance - Components only re-render when their specific data changes