v1.0.0

πŸ’Ύ Local Storage

OnigiriJS makes data persistence simple with an easy-to-use storage API, automatic expiration, and support for both local and session storage.

Basic Operations

Set Data

// Store simple values
Onigiri.storage.set('username', 'chef');
Onigiri.storage.set('theme', 'dark');

// Store objects (automatically serialized)
Onigiri.storage.set('user', {
    name: 'Chef',
    email: 'chef@onigiri.com',
    favoriteOnigiri: 'salmon',
    preferences: {
        notifications: true,
        newsletter: false
    }
});

// Store arrays
Onigiri.storage.set('cart', [
    { id: 1, name: 'Salmon Onigiri', price: 3.50 },
    { id: 2, name: 'Tuna Onigiri', price: 3.50 }
]);

Get Data

// Get simple value
const username = Onigiri.storage.get('username');
console.log(username); // 'chef'

// Get object
const user = Onigiri.storage.get('user');
console.log(user.name); // 'Chef'

// Get with default value
const theme = Onigiri.storage.get('theme', 'light');
const lang = Onigiri.storage.get('language', 'en');

Remove Data

// Remove specific item
Onigiri.storage.remove('theme');

// Remove multiple items
['temp1', 'temp2', 'cache'].forEach(key => {
    Onigiri.storage.remove(key);
});

Clear All Data

// Clear all storage (respects prefix)
Onigiri.storage.clear();

// Caution: This removes all data with your app's prefix

⏰ Expiration

Set with Expiration

// Expire after 1 hour (3,600,000 milliseconds)
Onigiri.storage.set('session', userData, {
    expires: 3600000
});

// Expire after 24 hours
Onigiri.storage.set('dailySpecial', {
    onigiri: 'Spicy Tuna',
    price: 3.99,
    date: new Date()
}, {
    expires: 24 * 60 * 60 * 1000
});

// Expire after 30 minutes
Onigiri.storage.set('tempData', data, {
    expires: 30 * 60 * 1000
});

// Expire after 7 days
Onigiri.storage.set('userPreferences', prefs, {
    expires: 7 * 24 * 60 * 60 * 1000
});

Auto-Expiration

// Data automatically expires
Onigiri.storage.set('code', 'ABC123', { expires: 300000 }); // 5 min

// After 5 minutes...
const code = Onigiri.storage.get('code');
console.log(code); // null (expired and removed)

// Get with default for expired data
const session = Onigiri.storage.get('session', { guest: true });
// Returns default if expired

πŸ” Utility Methods

Check if Key Exists

// Check existence
if (Onigiri.storage.has('user')) {
    console.log('User data found!');
    const user = Onigiri.storage.get('user');
}

// Conditional loading
if (!Onigiri.storage.has('appData')) {
    loadDataFromServer();
}

Get All Keys

// Get all storage keys
const keys = Onigiri.storage.keys();
console.log('Stored keys:', keys);

// List all data
keys.forEach(key => {
    const value = Onigiri.storage.get(key);
    console.log(\`\${key}:\`, value);
});

Get All Items with Prefix

// Get all user preferences
const userPrefs = Onigiri.storage.getAll('user_');
// Returns: { 'user_theme': 'dark', 'user_lang': 'en', ... }

// Get all onigiri data
const onigiriData = Onigiri.storage.getAll('onigiri_');

// Get all temporary data
const tempData = Onigiri.storage.getAll('temp_');

Get Storage Size

// Get number of items
const itemCount = Onigiri.storage.size();
console.log(\`Storage contains \${itemCount} items\`);

🎯 Storage Prefix

Set Prefix

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

// Now all keys are prefixed
Onigiri.storage.set('user', data);
// Stored as: myapp_user

// Different apps can use different prefixes
Onigiri.storage.setPrefix('blog_');
Onigiri.storage.set('posts', posts);
// Stored as: blog_posts

Why Use Prefixes?

// Prevents conflicts between apps
// App 1
Onigiri.storage.setPrefix('shop_');
Onigiri.storage.set('cart', shopCart);

// App 2
Onigiri.storage.setPrefix('blog_');
Onigiri.storage.set('cart', blogCart);

// No conflict! Different storage keys:
// shop_cart vs blog_cart

🎫 Session Storage

Session Storage Basics

// Session storage (cleared on tab/window close)
Onigiri.storage.session.set('tempCart', {
    items: ['Salmon Onigiri', 'Tuna Onigiri'],
    total: 7.00
});

// Retrieve session data
const cart = Onigiri.storage.session.get('tempCart');

// Get with default
const wizardStep = Onigiri.storage.session.get('wizardStep', 1);

// Remove session data
Onigiri.storage.session.remove('tempCart');

// Clear all session data
Onigiri.storage.session.clear();

Session vs Local Storage

// Local Storage: Persists indefinitely
Onigiri.storage.set('userPreferences', prefs);
// Available across browser sessions

// Session Storage: Cleared on tab close
Onigiri.storage.session.set('tempData', data);
// Lost when tab/window closes

// Use cases:
// Local: User settings, saved data, cache
// Session: Wizard state, temp forms, one-time data

πŸ’‘ Practical Examples

User Preferences

const preferences = {
    save() {
        Onigiri.storage.set('preferences', {
            theme: this.theme,
            language: this.language,
            notifications: this.notifications
        });
    },
    
    load() {
        const saved = Onigiri.storage.get('preferences', {
            theme: 'light',
            language: 'en',
            notifications: true
        });
        
        Object.assign(this, saved);
        this.apply();
    },
    
    apply() {
        document.body.className = \`theme-\${this.theme}\`;
        document.documentElement.lang = this.language;
    },
    
    reset() {
        Onigiri.storage.remove('preferences');
        this.load(); // Revert to defaults
    }
};

preferences.load();

Shopping Cart Persistence

const cart = new Onigiri.prototype.Component({
    data: {
        items: [],
        total: 0
    },
    
    computed: {
        itemCount() {
            return this.items.reduce((sum, item) => sum + item.quantity, 0);
        }
    },
    
    methods: {
        addItem(item) {
            const existing = this.items.find(i => i.id === item.id);
            
            if (existing) {
                existing.quantity++;
            } else {
                this.items.push({ ...item, quantity: 1 });
            }
            
            this.updateTotal();
            this.save();
        },
        
        removeItem(itemId) {
            this.items = this.items.filter(i => i.id !== itemId);
            this.updateTotal();
            this.save();
        },
        
        updateTotal() {
            this.total = this.items.reduce((sum, item) => {
                return sum + (item.price * item.quantity);
            }, 0);
        },
        
        save() {
            Onigiri.storage.set('cart', {
                items: this.items,
                total: this.total,
                updated: new Date()
            });
        },
        
        clear() {
            this.items = [];
            this.total = 0;
            Onigiri.storage.remove('cart');
        }
    },
    
    created() {
        // Load saved cart
        const saved = Onigiri.storage.get('cart');
        
        if (saved) {
            this.items = saved.items;
            this.total = saved.total;
            console.log('πŸ™ Cart restored from storage');
        }
    }
});

Form Draft Auto-Save

const formDraft = {
    saveInterval: null,
    
    init() {
        const form = O('#article-form');
        
        // Auto-save every 30 seconds
        this.saveInterval = setInterval(() => {
            this.saveDraft();
        }, 30000);
        
        // Save on input (debounced)
        const debouncedSave = Onigiri.debounce(() => {
            this.saveDraft();
        }, 2000);
        
        form.on('input', 'input, textarea', debouncedSave);
        
        // Load draft on init
        this.loadDraft();
    },
    
    saveDraft() {
        const draft = {
            title: O('[name="title"]').val(),
            content: O('[name="content"]').val(),
            tags: O('[name="tags"]').val(),
            timestamp: new Date()
        };
        
        Onigiri.storage.set('article_draft', draft, {
            expires: 7 * 24 * 60 * 60 * 1000 // 7 days
        });
        
        O('#save-indicator').text('πŸ™ Draft saved').fadeIn(200);
        setTimeout(() => {
            O('#save-indicator').fadeOut(200);
        }, 2000);
    },
    
    loadDraft() {
        const draft = Onigiri.storage.get('article_draft');
        
        if (draft) {
            O('[name="title"]').val(draft.title);
            O('[name="content"]').val(draft.content);
            O('[name="tags"]').val(draft.tags);
            
            const time = new Date(draft.timestamp);
            alert(\`πŸ™ Draft restored from \${time.toLocaleString()}\`);
        }
    },
    
    clearDraft() {
        Onigiri.storage.remove('article_draft');
        clearInterval(this.saveInterval);
    }
};

formDraft.init();

Recent Searches

const searchHistory = {
    maxItems: 10,
    
    add(query) {
        if (!query.trim()) return;
        
        let history = Onigiri.storage.get('search_history', []);
        
        // Remove if already exists
        history = history.filter(q => q !== query);
        
        // Add to beginning
        history.unshift(query);
        
        // Limit to maxItems
        history = history.slice(0, this.maxItems);
        
        // Save
        Onigiri.storage.set('search_history', history);
    },
    
    get() {
        return Onigiri.storage.get('search_history', []);
    },
    
    clear() {
        Onigiri.storage.remove('search_history');
    },
    
    render() {
        const history = this.get();
        const container = O('#search-history');
        
        container.empty();
        
        if (history.length === 0) {
            container.html('<p>No recent searches</p>');
            return;
        }
        
        history.forEach(query => {
            container.append(\`
                <div class="history-item">
                    <a href="#" data-query="\${query}">
                        πŸ™ \${query}
                    </a>
                </div>
            \`);
        });
    }
};

// Add to history on search
O('#search-form').on('submit', function(e) {
    const query = O('#search-input').val();
    searchHistory.add(query);
});

// Show history
searchHistory.render();

Theme Switcher

const themeManager = {
    themes: ['light', 'dark', 'auto'],
    current: 'light',
    
    init() {
        // Load saved theme
        this.current = Onigiri.storage.get('theme', 'light');
        this.apply();
        
        // Theme switcher buttons
        O('.theme-btn').on('click', function() {
            const theme = O(this).data('theme');
            themeManager.setTheme(theme);
        });
    },
    
    setTheme(theme) {
        if (!this.themes.includes(theme)) return;
        
        this.current = theme;
        Onigiri.storage.set('theme', theme);
        this.apply();
    },
    
    apply() {
        // Remove all theme classes
        this.themes.forEach(t => {
            document.body.classList.remove(\`theme-\${t}\`);
        });
        
        // Apply current theme
        document.body.classList.add(\`theme-\${this.current}\`);
        
        // Update active button
        O('.theme-btn').removeClass('active');
        O(\`.theme-btn[data-theme="\${this.current}"]\`).addClass('active');
        
        console.log(\`πŸ™ Theme applied: \${this.current}\`);
    }
};

themeManager.init();

Component State Persistence

const todoApp = new Onigiri.prototype.Component({
    data: {
        todos: [],
        filter: 'all',
        lastSync: null
    },
    
    methods: {
        addTodo(text) {
            this.todos.push({
                id: Date.now(),
                text: text,
                completed: false,
                createdAt: new Date()
            });
        },
        
        toggleTodo(id) {
            const todo = this.todos.find(t => t.id === id);
            if (todo) {
                todo.completed = !todo.completed;
            }
        }
    },
    
    watchers: {
        todos() {
            // Auto-save on any change
            this.saveState();
        },
        
        filter() {
            this.saveState();
        }
    },
    
    created() {
        // Load saved state
        this.loadState();
    },
    
    saveState() {
        Onigiri.storage.set('todoApp', {
            todos: this.todos,
            filter: this.filter,
            lastSync: new Date()
        });
    },
    
    loadState() {
        const saved = Onigiri.storage.get('todoApp');
        
        if (saved) {
            this.todos = saved.todos;
            this.filter = saved.filter;
            this.lastSync = saved.lastSync;
            
            console.log('πŸ™ State restored');
        }
    }
});
πŸ™ Best Practice: Always provide default values when getting data to handle missing or expired items gracefully!

Storage Best Practices

  • Use meaningful, consistent key names
  • Always provide default values with get()
  • Set prefixes to avoid naming conflicts
  • Use expiration for temporary data
  • Don't store sensitive data (passwords, tokens)
  • Keep stored data size reasonable (<5MB recommended)
  • Use session storage for temporary UI state
  • Clear old/unused data periodically
  • Handle storage quota errors gracefully
  • Serialize complex objects before storing

Storage Limits

// Check available space (approximate)
function checkStorageSpace() {
    let total = 0;
    
    for (let key in localStorage) {
        if (localStorage.hasOwnProperty(key)) {
            total += localStorage[key].length + key.length;
        }
    }
    
    const used = (total / 1024).toFixed(2);
    console.log(\`Storage used: \${used} KB\`);
    
    // Most browsers allow ~5-10MB
}

βœ… 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.