6.0 KiB
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:
- Components - Handle UI rendering and user interactions
- View Models - Hold data and business logic
- API Client - Communicate with the backend
- Event Bus - Pub/sub communication between components
- 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
- User Interaction → Component handles event
- Component → Calls view model method
- View Model → Makes API call or processes data
- View Model → Updates properties (triggers change notifications)
- Component → Receives change notification and re-renders
- UI → Updates to reflect new data
Best Practices
- Keep components focused - Each component should have a single responsibility
- Use view models for business logic - Don't put API calls or data processing in components
- Subscribe to specific properties - Only listen to properties your component needs
- Use the event bus sparingly - Prefer direct view model communication for related components
- Clean up resources - The framework handles most cleanup automatically, but be mindful of custom event listeners
- 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 calls →
api-client.js - Data management →
view-models.js - UI logic →
components.js - Application setup →
app.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