Component System - cynchro/OldSchoolFrontFrame GitHub Wiki
Components are reusable UI pieces with their own state, methods, and template. They share the same API as modules but are not tied to a route.
- The same UI appears in more than one module (e.g. a modal, a data table, a notification banner)
- A piece of UI is complex enough to benefit from its own state and lifecycle
Don't create a component for a UI element used only once — just put the HTML inline in the module.
// components/alert/alert.js
import { Component } from '../../framework/component/component.js';
export const Alert = Component({
state: () => ({
message: '',
type: 'info', // 'info' | 'success' | 'error'
visible: false,
}),
template: `
<div class="alert" data-bind="type">
<span data-bind="message"></span>
<button data-click="close">✕</button>
</div>
`,
methods: {
show(ctx, message, type = 'info') {
ctx.state.message = message;
ctx.state.type = type;
ctx.state.visible = true;
},
close(ctx) {
ctx.state.visible = false;
},
},
mounted(ctx) {
// Optional setup
},
destroyed(ctx) {
// Optional cleanup
},
});// Inside a module's mounted() hook
import { Alert } from '../../components/alert/alert.js';
defineModule({
mounted(ctx) {
const alertRoot = ctx.root.querySelector('#alert-container');
const alert = Alert.mount(alertRoot);
// Keep a reference to call methods later
ctx._alert = alert;
},
methods: {
async save(ctx) {
try {
await saveData();
ctx._alert.methods.show(ctx._alert, 'Saved successfully!', 'success');
} catch (e) {
ctx._alert.methods.show(ctx._alert, e.message, 'error');
}
},
},
destroyed(ctx) {
ctx._alert.destroy();
},
});const instance = Alert.mount(root, {
initialMessage: 'Welcome!',
type: 'info',
});Props are available in mounted(ctx) via ctx.props:
mounted(ctx) {
if (ctx.props.initialMessage) {
ctx.state.message = ctx.props.initialMessage;
ctx.state.visible = true;
}
},Component(definition).mount(root, props) returns an instance:
| Property | Description |
|---|---|
instance.state |
Reactive state |
instance.methods |
Bound methods |
instance.root |
DOM root element |
instance.destroy() |
Unmount and clean up |
For components used throughout the app, register them globally:
// app.js
import { defineComponent, mountComponent } from './framework/component/component.js';
import { Alert } from './components/alert/alert.js';
defineComponent('alert', Alert);Then mount by name from any module:
import { mountComponent } from '../../framework/component/component.js';
defineModule({
mounted(ctx) {
ctx._alert = mountComponent('alert', ctx.root.querySelector('#alert'));
},
});components/
└── alert/
├── alert.js # Component definition
├── alert.css # Styles (optional)
└── alert.html # External template (optional, use template: `` otherwise)