๐ฆ Components
Components are the building blocks of OnigiriJS applications. They encapsulate data, logic, and templates into reusable, reactive pieces.
Creating a Component
const onigiriShop = new Onigiri.prototype.Component({
// Reactive data
data: {
inventory: 10,
price: 3.50,
customerName: '',
orders: []
},
// Computed properties (auto-update when dependencies change)
computed: {
totalValue() {
return this.inventory * this.price;
},
inStock() {
return this.inventory > 0;
},
formattedPrice() {
return \`$\${this.price.toFixed(2)}\`;
}
},
// Methods
methods: {
sell(quantity) {
if (this.inventory >= quantity) {
this.inventory -= quantity;
this.orders.push({
quantity: quantity,
time: new Date(),
customer: this.customerName
});
return true;
}
return false;
},
restock(quantity) {
this.inventory += quantity;
}
},
// Data watchers
watchers: {
inventory(newVal, oldVal) {
console.log(\`Inventory changed: \${oldVal} โ \${newVal}\`);
if (newVal === 0) {
alert('๐ Out of onigiri! Time to restock!');
} else if (newVal < 5) {
console.warn('๐ Low stock warning!');
}
},
orders(newOrders) {
// Save orders to storage
Onigiri.storage.set('orders', newOrders);
}
},
// Template (can be function or string)
template: function() {
return \`
<div class="onigiri-shop">
<h2>๐ Onigiri Shop</h2>
<p>In Stock: \${this.inventory}</p>
<p>Price: \${this.formattedPrice}</p>
<p>Total Value: $\${this.totalValue.toFixed(2)}</p>
<p>Status: \${this.inStock ? 'Available' : 'Sold Out'}</p>
</div>
\`;
}
});
๐ Lifecycle Hooks
Components go through a lifecycle, and you can hook into different stages:
Before component initialization
Component created, data available
Before mounting to DOM
Mounted to DOM, ready to use
Before data changes
After data changes
Before component destruction
Component destroyed, cleaned up
Using Lifecycle Hooks
const component = new Onigiri.prototype.Component({
data: {
message: 'Hello',
apiData: null
},
beforeCreate() {
console.log('โณ Component is being created...');
// No access to data yet
},
created() {
console.log('โ
Component created!');
// Data is now available
// Good place to load data from storage
this.message = Onigiri.storage.get('message', 'Hello');
},
beforeMount() {
console.log('๐ฏ About to mount to DOM...');
// Component not yet in DOM
},
mounted() {
console.log('๐ Component is now in the DOM!');
// Perfect place to:
// - Make API calls
// - Initialize third-party libraries
// - Set up event listeners
this.loadData();
},
beforeUpdate() {
console.log('๐ Data is about to change...');
},
updated() {
console.log('โจ Data has been updated!');
// DOM has been re-rendered
// Save state to storage
Onigiri.storage.set('message', this.message);
},
beforeDestroy() {
console.log('๐ Cleaning up...');
// Remove event listeners
// Cancel pending requests
// Clear timers
},
destroyed() {
console.log('๐ค Component destroyed');
// Component is gone
},
methods: {
async loadData() {
this.apiData = await Onigiri.get('/api/data');
}
}
});
โก Reactive Data
All properties in the data object are reactive. When they change, watchers are triggered and events are emitted automatically:
const cart = new Onigiri.prototype.Component({
data: {
items: [],
total: 0
},
methods: {
addItem(item) {
this.items.push(item);
this.total += item.price;
// Component automatically updates!
// Watchers fire!
// Events emit!
}
}
});
// Listen to data changes
cart.on('change:total', (newVal, oldVal) => {
console.log(\`Total: $\${oldVal} โ $\${newVal}\`);
});
// Listen to any update
cart.on('update', (property, newVal, oldVal) => {
console.log(\`\${property} changed!\`);
});
๐งฎ Computed Properties
Computed properties automatically recalculate when their dependencies change:
const kitchen = new Onigiri.prototype.Component({
data: {
rice: 100, // grams
nori: 50, // sheets
filling: 30 // portions
},
computed: {
// Automatically updates when rice, nori, or filling changes
maxOnigiri() {
return Math.min(
Math.floor(this.rice / 10),
this.nori,
this.filling
);
},
canMake() {
return this.maxOnigiri > 0;
},
status() {
if (this.maxOnigiri === 0) return 'Cannot make onigiri';
if (this.maxOnigiri < 5) return 'Low ingredients';
return 'Ready to cook!';
}
}
});
console.log(kitchen.maxOnigiri); // 10
kitchen.rice = 50;
console.log(kitchen.maxOnigiri); // 5 (automatically updated!)
๐ Watchers
React to specific data changes with watchers:
const inventory = new Onigiri.prototype.Component({
data: {
onigiriCount: 10,
alertThreshold: 5,
lastRestocked: null
},
watchers: {
onigiriCount(newCount, oldCount) {
console.log(\`๐ Inventory: \${oldCount} โ \${newCount}\`);
// Low stock alert
if (newCount < this.alertThreshold && oldCount >= this.alertThreshold) {
alert(\`๐ Low stock! Only \${newCount} left!\`);
this.notifyManager();
}
// Out of stock
if (newCount === 0) {
console.error('โ Out of stock!');
this.disableOrdering();
}
// Save to storage
Onigiri.storage.set('inventory', newCount);
},
alertThreshold(newThreshold) {
console.log('Alert threshold updated to:', newThreshold);
Onigiri.storage.set('alertThreshold', newThreshold);
}
},
methods: {
notifyManager() {
Onigiri.post('/api/notify', {
type: 'low_stock',
count: this.onigiriCount
});
},
disableOrdering() {
O('.order-button').attr('disabled', true);
}
}
});
๐ฏ Mounting Components
// Mount to selector
const component = new Onigiri.prototype.Component({ /* config */ });
component.mount('#app');
// Mount to element
const el = document.querySelector('.widget');
component.mount(el);
// Access mounted element
console.log(component.$el); // The mounted DOM element
๐งน Destroying Components
const component = new Onigiri.prototype.Component({
data: { timer: null },
mounted() {
// Start a timer
this.timer = setInterval(() => {
console.log('Tick');
}, 1000);
},
beforeDestroy() {
// Clean up timer
if (this.timer) {
clearInterval(this.timer);
this.timer = null;
}
}
});
// Later, destroy the component
component.destroy();
๐ Component Communication
// Parent component
const parent = new Onigiri.prototype.Component({
data: {
sharedData: 'Hello from parent'
},
created() {
// Listen to child events
this.on('child:update', (data) => {
console.log('Child updated:', data);
this.sharedData = data;
});
}
});
// Child component
const child = new Onigiri.prototype.Component({
data: {
localData: 'Child data'
},
methods: {
notifyParent() {
// Emit event to parent
parent.emit('child:update', this.localData);
}
}
});
๐ก Complete Component Example
const todoApp = new Onigiri.prototype.Component({
data: {
todos: [],
newTodo: '',
filter: 'all' // all, active, completed
},
computed: {
filteredTodos() {
if (this.filter === 'active') {
return this.todos.filter(t => !t.completed);
}
if (this.filter === 'completed') {
return this.todos.filter(t => t.completed);
}
return this.todos;
},
activeCount() {
return this.todos.filter(t => !t.completed).length;
}
},
methods: {
addTodo() {
if (this.newTodo.trim()) {
this.todos.push({
id: Date.now(),
text: this.newTodo,
completed: false
});
this.newTodo = '';
}
},
toggleTodo(id) {
const todo = this.todos.find(t => t.id === id);
if (todo) {
todo.completed = !todo.completed;
// Trigger reactivity
this.todos = [...this.todos];
}
},
removeTodo(id) {
this.todos = this.todos.filter(t => t.id !== id);
}
},
watchers: {
todos(newTodos) {
// Auto-save to storage
Onigiri.storage.set('todos', newTodos);
}
},
created() {
// Load saved todos
this.todos = Onigiri.storage.get('todos', []);
},
mounted() {
console.log('๐ Todo app ready!');
},
template: function() {
return \`
<div class="todo-app">
<h1>๐ Onigiri Todo</h1>
<input
value="\${this.newTodo}"
placeholder="What needs to be done?"
>
<button onclick="todoApp.addTodo()">Add</button>
<p>\${this.activeCount} items left</p>
</div>
\`;
}
});
todoApp.mount('#app');
Next Steps
โ Development Roadmap
Track the progress of OnigiriJS modules. Tasks are marked complete by the development team.
OnigiriJS Module Roadmap
Implementation progress of planned modules