v1.0.0

🚀 Quick Start

Your First Secure Component

Let's build a multilingual counter with CSRF protection and local storage:

<!-- HTML -->
<meta name="csrf-token" content="<?= csrf_token() ?>">
<div id="app"></div>

<!-- Load OnigiriJS -->
<script src="onigiri-core.js"></script>
<script src="onigiri-events.js"></script>
<script src="onigiri-components.js"></script>
<script src="onigiri-security.js"></script>
<script src="onigiri-ajax.js"></script>
<script src="onigiri-storage.js"></script>
<script src="onigiri-translation.js"></script>
// Initialize security and translations
Onigiri.security.init({ autoInjectCSRF: true });
Onigiri.i18n.init({ locale: 'en' });

// Add translations
Onigiri.i18n.addMessages({
    en: {
        counter: {
            title: 'Rice Ball Counter: {count}',
            totalClicks: 'Total clicks: {total}',
            lastSaved: 'Last saved: {time}',
            addButton: 'Add Rice Ball',
            resetButton: 'Reset',
            never: 'Never'
        }
    },
    es: {
        counter: {
            title: 'Contador de Onigiri: {count}',
            totalClicks: 'Clics totales: {total}',
            lastSaved: 'Último guardado: {time}',
            addButton: 'Añadir Onigiri',
            resetButton: 'Reiniciar',
            never: 'Nunca'
        }
    }
});

// Create a reactive counter component
const counter = new Onigiri.prototype.Component({
    data: {
        count: 0,
        clicks: [],
        lastSaved: null
    },
    
    computed: {
        totalClicks() {
            return this.clicks.length;
        }
    },
    
    methods: {
        async increment() {
            this.count++;
            this.clicks.push({
                action: 'increment',
                count: this.count,
                time: new Date().toLocaleTimeString()
            });
            
            // Save to server with auto-CSRF
            try {
                await Onigiri.post('/api/counter', {
                    count: this.count
                });
                this.lastSaved = new Date().toLocaleTimeString();
            } catch (error) {
                console.error('Failed to save:', error);
            }
        },
        
        reset() {
            this.count = 0;
            this.clicks = [];
            this.lastSaved = null;
            Onigiri.storage.remove('counter');
        }
    },
    
    watchers: {
        count(newVal) {
            // Auto-save to local storage
            Onigiri.storage.set('counter', newVal);
            console.log('🍙 Count saved:', newVal);
        }
    },
    
    template: function() {
        const title = Onigiri.t('counter.title', { count: this.count });
        const totalText = Onigiri.t('counter.totalClicks', { total: this.totalClicks });
        const savedTime = this.lastSaved || Onigiri.t('counter.never');
        const savedText = Onigiri.t('counter.lastSaved', { time: savedTime });
        const addBtn = Onigiri.t('counter.addButton');
        const resetBtn = Onigiri.t('counter.resetButton');
        
        return \`
            <div class="counter-widget">
                <h2>🍙 \${title}</h2>
                <p>\${totalText}</p>
                <p>\${savedText}</p>
                <button onclick="counter.increment()">\${addBtn}</button>
                <button onclick="counter.reset()">\${resetBtn}</button>
            </div>
        \`;
    },
    
    created() {
        // Load saved count
        this.count = Onigiri.storage.get('counter', 0);
        console.log('🍙 Component created!');
    },
    
    mounted() {
        console.log('🍙 Counter mounted to DOM!');
    }
});

// Mount the component
counter.mount('#app');

🎨 DOM Manipulation

OnigiriJS provides jQuery-like syntax for DOM operations:

// Select and manipulate elements
O('.button').on('click', function() {
    O(this).addClass('active');
    O('#message').text('🍙 Button clicked!');
});

// Chain methods
O('.items')
    .addClass('loaded')
    .attr('data-status', 'ready')
    .css({ color: 'green', fontSize: '16px' })
    .fadeIn(300);

// Traverse DOM
O('.item').parent().addClass('parent-active');
O('.container').children().addClass('child');
O('.item').siblings().addClass('sibling');

🌐 Multilingual Applications

Build fully internationalized apps with automatic HTML translation:

<!-- HTML with translation attributes -->
<div id="language-app">
    <h1 data-i18n="app.welcome"></h1>
    <p data-i18n="app.description"></p>
    
    <form>
        <input type="text" 
               data-i18n-placeholder="form.username" 
               name="username">
        <input type="email" 
               data-i18n-placeholder="form.email" 
               name="email">
        <button type="submit" data-i18n="form.submit"></button>
    </form>
    
    <p id="item-count"></p>
    <p id="last-login"></p>
    
    <select id="language-selector">
        <option value="en">English</option>
        <option value="es">Español</option>
        <option value="fr">Français</option>
        <option value="ja">日本語</option>
    </select>
</div>

<script src="onigiri.translation.js"></script>
<script>
// Initialize translations
Onigiri.i18n.init({ 
    locale: 'en',
    autoDetect: true  // Auto-detect from browser
});

// Add translations for multiple languages
Onigiri.i18n.addMessages({
    en: {
        app: {
            welcome: 'Welcome to OnigiriJS!',
            description: 'Build amazing multilingual applications'
        },
        form: {
            username: 'Enter your username',
            email: 'Enter your email',
            submit: 'Submit Form'
        },
        items: {
            zero: 'No items',
            one: 'One item',
            other: '{count} items'
        },
        lastLogin: 'Last login: {date}'
    },
    es: {
        app: {
            welcome: '¡Bienvenido a OnigiriJS!',
            description: 'Crea aplicaciones multilingües increíbles'
        },
        form: {
            username: 'Ingresa tu nombre de usuario',
            email: 'Ingresa tu correo',
            submit: 'Enviar Formulario'
        },
        items: {
            zero: 'Sin artículos',
            one: 'Un artículo',
            other: '{count} artículos'
        },
        lastLogin: 'Último acceso: {date}'
    },
    fr: {
        app: {
            welcome: 'Bienvenue sur OnigiriJS!',
            description: 'Créez des applications multilingues incroyables'
        },
        form: {
            username: 'Entrez votre nom d\'utilisateur',
            email: 'Entrez votre email',
            submit: 'Soumettre le Formulaire'
        },
        items: {
            zero: 'Aucun élément',
            one: 'Un élément',
            other: '{count} éléments'
        },
        lastLogin: 'Dernière connexion: {date}'
    },
    ja: {
        app: {
            welcome: 'OnigiriJSへようこそ!',
            description: '素晴らしい多言語アプリケーションを構築'
        },
        form: {
            username: 'ユーザー名を入力',
            email: 'メールアドレスを入力',
            submit: 'フォームを送信'
        },
        items: {
            zero: 'アイテムなし',
            one: '1つのアイテム',
            other: '{count}個のアイテム'
        },
        lastLogin: '最終ログイン: {date}'
    }
});

// Translate the page
Onigiri.i18n.translatePage();

// Use plural forms
const itemCount = 5;
O('#item-count').text(Onigiri.tc('items', itemCount, { count: itemCount }));

// Format dates by locale
const loginDate = new Date();
const formattedDate = Onigiri.i18n.formatDate(loginDate, 'datetime');
O('#last-login').text(Onigiri.t('lastLogin', { date: formattedDate }));

// Language switcher
O('#language-selector').on('change', function(e) {
    Onigiri.i18n.setLocale(e.target.value);
    // Page automatically re-translates!
});

// Listen for locale changes
document.addEventListener('onigiri:locale:changed', function(e) {
    console.log('🌐 Locale changed to:', e.detail.locale);
    
    // Update dynamic content
    const count = 5;
    O('#item-count').text(Onigiri.tc('items', count, { count }));
    
    const date = Onigiri.i18n.formatDate(new Date(), 'datetime');
    O('#last-login').text(Onigiri.t('lastLogin', { date }));
});
</script>
🍙 Pro Tip: Translations are automatically saved to localStorage and persist across sessions!

✅ Form Validation Example

<form id="registration-form">
    <input type="text" name="username" placeholder="Username">
    <input type="email" name="email" placeholder="Email">
    <input type="password" name="password" placeholder="Password">
    <input type="number" name="age" placeholder="Age">
    <button type="submit">Register</button>
</form>

<script>
const form = O('#registration-form');

form.on('submit', async function(e) {
    e.preventDefault();
    
    // Validate form
    const result = O(this).validate({
        username: {
            required: true,
            minLength: 3,
            maxLength: 20,
            alphanumeric: true
        },
        email: {
            required: true,
            email: true
        },
        password: {
            required: true,
            minLength: 8,
            pattern: '^(?=.*[A-Z])(?=.*[0-9])'
        },
        age: {
            required: true,
            numeric: true,
            min: 18,
            max: 120
        }
    });
    
    if (result.isValid) {
        // Submit with CSRF protection
        const formData = {
            username: O('[name="username"]').val(),
            email: O('[name="email"]').val(),
            password: O('[name="password"]').val(),
            age: O('[name="age"]').val()
        };
        
        await Onigiri.post('/api/register', formData);
        alert('🍙 Registration successful!');
    } else {
        // Show validation errors
        Object.keys(result.errors).forEach(field => {
            console.log(\`\${field}: \${result.errors[field].join(', ')}\`);
        });
    }
});
</script>

🎨 Animation Example

// Smooth animations built-in
O('.message').fadeIn(300, function() {
    console.log('Fade in complete!');
});

O('.alert').fadeOut(500);

O('.menu').slideDown(400);

O('.dropdown').slideUp(300);

// Toggle with animation
const toggleMenu = () => {
    const menu = O('.mobile-menu');
    if (menu.hasClass('open')) {
        menu.removeClass('open').slideUp(300);
    } else {
        menu.addClass('open').slideDown(300);
    }
};

💾 Storage with Expiration

// Set storage prefix for your app
Onigiri.storage.setPrefix('myapp_');

// Save data
Onigiri.storage.set('user', {
    name: 'Chef',
    favoriteOnigiri: 'salmon'
});

// Save with 1 hour expiration
Onigiri.storage.set('session', userData, {
    expires: 3600000 // milliseconds
});

// Get with default value
const user = Onigiri.storage.get('user', { name: 'Guest' });

// Session storage (cleared on tab close)
Onigiri.storage.session.set('tempCart', cartItems);

// Get all keys with prefix
const allUserData = Onigiri.storage.getAll('user_');

📡 AJAX with Auto-CSRF

// GET request
const users = await Onigiri.get('/api/users');

// POST request with CSRF token (automatic)
await Onigiri.post('/api/users', {
    name: 'John Doe',
    email: 'john@example.com'
});

// PUT request
await Onigiri.put('/api/users/1', {
    name: 'Jane Doe'
});

// DELETE request
await Onigiri.delete('/api/users/1');

// Custom AJAX with options
Onigiri.ajax({
    url: '/api/endpoint',
    method: 'POST',
    data: { key: 'value' },
    csrf: true, // default
    timeout: 5000
}).then(response => {
    console.log('Success:', response);
}).catch(error => {
    console.error('Error:', error);
});

🔄 PJAX Navigation

// Initialize PJAX
Onigiri.pjax.init({
    timeout: 5000,
    scrollTo: 0
});

// HTML markup
<div id="pjax-container">
    <!-- Content loaded here -->
</div>

<a href="/page" data-pjax="#pjax-container">
    Load Page via PJAX
</a>

// Listen to PJAX events
document.addEventListener('onigiri:pjax:complete', (e) => {
    console.log('🍙 Page loaded:', e.detail.url);
    // Re-initialize components and translations
    Onigiri.i18n.translatePage();
});
🍙 Pro Tip: All AJAX requests and form submissions automatically include CSRF tokens when the security module is initialized!

🔌 HumHub Integration with i18n

// Create a multilingual HumHub module component
Onigiri.humhub('onigiriWidget', {
    selector: '.onigiri-widget',
    pjax: true, // Auto-remount on PJAX
    
    data: {
        items: [],
        loading: false
    },
    
    methods: {
        async loadItems() {
            this.loading = true;
            try {
                const data = await Onigiri.get('/onigiri/api/items');
                this.items = data;
            } catch (error) {
                console.error('Failed to load items:', error);
            } finally {
                this.loading = false;
            }
        }
    },
    
    created() {
        // Auto-detect HumHub language
        if (typeof humhub !== 'undefined') {
            const locale = humhub.config.get('language');
            Onigiri.i18n.setLocale(locale);
        }
        console.log('🍙 HumHub widget created!');
    },
    
    mounted() {
        // Translate page elements
        Onigiri.i18n.translatePage();
        this.loadItems();
    }
});

🎯 Complete Multilingual Example

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <meta name="csrf-token" content="<?= csrf_token() ?>">
    <title>OnigiriJS Demo</title>
</head>
<body>
    <div id="app">
        <h1 data-i18n="app.title"></h1>
        <select id="lang">
            <option value="en">English</option>
            <option value="es">Español</option>
            <option value="ja">日本語</option>
        </select>
    </div>
    
    <!-- Load OnigiriJS modules -->
    <script src="onigiri-core.js"></script>
    <script src="onigiri-events.js"></script>
    <script src="onigiri-components.js"></script>
    <script src="onigiri-security.js"></script>
    <script src="onigiri-ajax.js"></script>
    <script src="onigiri-storage.js"></script>
    <script src="onigiri-animate.js"></script>
    <script src="onigiri-translation.js"></script>
    
    <script>
        // Initialize security and translations
        Onigiri.security.init({ autoInjectCSRF: true });
        Onigiri.i18n.init({ locale: 'en', autoDetect: true });
        
        // Add translations
        Onigiri.i18n.addMessages({
            en: { app: { title: 'Hello OnigiriJS! 🍙' } },
            es: { app: { title: '¡Hola OnigiriJS! 🍙' } },
            ja: { app: { title: 'こんにちは OnigiriJS! 🍙' } }
        });
        
        // Your application code
        const app = new Onigiri.prototype.Component({
            data: {
                message: 'Hello OnigiriJS! 🍙'
            },
            template: function() {
                return \`<h1>\${this.message}</h1>\`;
            },
            mounted() {
                // Translate page
                Onigiri.i18n.translatePage();
                
                // Language switcher
                O('#lang').on('change', (e) => {
                    Onigiri.i18n.setLocale(e.target.value);
                });
                
                console.log('🍙 App mounted successfully!');
            }
        });
        
        app.mount('#app');
    </script>
</body>
</html>

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.