203 lines
6.0 KiB
Markdown
203 lines
6.0 KiB
Markdown
# 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
|
|
|
|
```javascript
|
|
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
|
|
|
|
```javascript
|
|
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
|
|
|
|
```javascript
|
|
// 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:
|
|
|
|
```javascript
|
|
// 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
|
|
|
|
```javascript
|
|
// In app.js
|
|
window.app.registerRoute('my-view', MyComponent, 'my-view-container');
|
|
```
|
|
|
|
### 6. Navigation
|
|
|
|
```javascript
|
|
// 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 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 |