v1.0.0

๐Ÿ“ฆ 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:

1
beforeCreate

Before component initialization

โ†“
2
created

Component created, data available

โ†“
3
beforeMount

Before mounting to DOM

โ†“
4
mounted

Mounted to DOM, ready to use

โ†“
5
beforeUpdate

Before data changes

โ†“
6
updated

After data changes

โ†“
7
beforeDestroy

Before component destruction

โ†“
8
destroyed

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);
        }
    }
});
๐Ÿ™ Remember: Changes to reactive data automatically trigger watchers, emit change events, and update computed properties!

๐Ÿ’ก 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

6 / 21 completed (29%)
onigiri-state
Shared global & scoped state management
onigiri-directives
Declarative DOM bindings (o-show, o-model, etc.)
onigiri-resource
REST-style data models over AJAX
onigiri-observe
Intersection & Mutation observer helpers
onigiri-humhub-ui
Standard HumHub UI abstractions (modal, notify, confirm)
onigiri-lifecycle
Component lifecycle hooks
onigiri-guard
Debounce, throttle, single-run guards
onigiri-scroll
Scroll save/restore & helpers (PJAX-friendly)
onigiri-permission
Client-side permission awareness
onigiri-portal
DOM teleport / overlay mounting
onigiri-router
Micro router (non-SPA, PJAX-first)
onigiri-sanitize
HTML & input sanitization
onigiri-shortcut
Keyboard shortcut manager
onigiri-queue
Sequential async task runner
onigiri-gesture
Touch & swipe helpers
onigiri-devtools
Debugging & inspection helpers
onigiri-plugin
Plugin registration system
onigiri-time
Relative time & timezone utilities
onigiri-emojis
Emoji Picker and Manager
onigiri-tasks
Task Management
onigiri-polls
Polls creation and management
Note: Task completion is managed by the OnigiriJS development team.