π Plugins
OnigiriJS features a powerful plugin system that allows you to extend the framework with additional functionality. Built-in plugins provide storage, routing, animations, security, and more.
Plugin System Overview
The OnigiriJS plugin system allows you to:
- Install pre-built plugins from the registry
- Create custom plugins to extend functionality
- Share and reuse code across projects
- Prevent duplicate plugin installations
- Access plugins through a centralized registry
Installing Plugins
// Install by name (from registry)
O.use('storage');
O.use('router');
O.use('animation');
// Install with options
O.use('storage', {
type: 'local',
prefix: 'myapp_'
});
// Install custom plugin
O.use(MyCustomPlugin);
// Chain installations
O.use('storage')
.use('router')
.use('animation');
Plugin Registry
// List available plugins
const plugins = O.pluginRegistry.list();
console.log(plugins); // ['storage', 'router', 'animation', ...]
// Check if plugin exists
if (O.pluginRegistry.has('storage')) {
console.log('Storage plugin available');
}
// Check if plugin is installed
if (O.pluginRegistry.isInstalled('storage')) {
console.log('Storage plugin is active');
}
// Get plugin definition
const storagePlugin = O.pluginRegistry.get('storage');
Built-in Plugins
ποΈ Storage
v1.0.0LocalStorage and SessionStorage wrapper with JSON serialization support.
π§ Router
v1.0.0Hash-based and History API routing with parameter extraction.
β¨ Animation
v1.0.0CSS animation helpers for fading, sliding, and custom animations.
π Security
v1.0.0CSRF token management for forms and AJAX requests.
πͺ Cookies
v1.0.0Cookie management with expiration and security options.
Storage Plugin
Provides a simple interface for working with browser storage (localStorage or sessionStorage) with automatic JSON serialization.
Installation
// Use localStorage (default)
O.use('storage');
// Use sessionStorage
O.use('storage', { type: 'session' });
// Custom prefix
O.use('storage', {
type: 'local',
prefix: 'myapp_',
serialize: true
});
Configuration Options
| Option | Type | Default | Description |
|---|---|---|---|
| type | string | 'local' | 'local' or 'session' |
| prefix | string | 'onigiri_' | Key prefix for all items |
| serialize | boolean | true | Auto JSON serialize/deserialize |
Methods
// Store data
O.storage.set('user', { name: 'John', id: 123 });
O.storage.set('theme', 'dark');
// Store arrays
O.storage.set('cart', [
{ id: 1, name: 'Salmon Onigiri' },
{ id: 2, name: 'Tuna Onigiri' }
]);
// Retrieve data
const user = O.storage.get('user');
console.log(user.name); // 'John'
// With default value
const theme = O.storage.get('theme', 'light');
// Non-existent key returns default
const missing = O.storage.get('nonexistent', null); // null
// Remove item
O.storage.remove('user');
// Chain operations
O.storage.remove('user').remove('theme');
// Remove all items with prefix
O.storage.clear();
// Check if key exists
if (O.storage.has('user')) {
console.log('User data found');
}
// Get all keys (without prefix)
const keys = O.storage.keys();
console.log(keys); // ['user', 'theme', 'cart']
Complete Example
// User preferences manager
O.use('storage', { prefix: 'app_' });
// Save preferences
function savePreferences(prefs) {
O.storage.set('preferences', prefs);
console.log('Preferences saved');
}
// Load preferences
function loadPreferences() {
return O.storage.get('preferences', {
theme: 'light',
language: 'en',
notifications: true
});
}
// Apply preferences
const prefs = loadPreferences();
document.body.className = prefs.theme;
// Update single preference
function updateTheme(theme) {
const prefs = loadPreferences();
prefs.theme = theme;
savePreferences(prefs);
}
Router Plugin
Provides client-side routing with support for hash-based and HTML5 History API modes, parameter extraction, and 404 handling.
Installation
// Hash-based routing (default)
O.use('router');
// HTML5 History API
O.use('router', {
mode: 'history',
root: '/app'
});
Configuration Options
| Option | Type | Default | Description |
|---|---|---|---|
| mode | string | 'hash' | 'hash' or 'history' |
| root | string | '/' | Base path for history mode |
Defining Routes
O.use('router');
// Simple route
O.router.add('/', (path, params) => {
console.log('Home page');
});
// Route with parameters
O.router.add('/user/:id', (path, params) => {
console.log('User ID:', params.id);
loadUser(params.id);
});
// Multiple parameters
O.router.add('/product/:category/:id', (path, params) => {
console.log('Category:', params.category);
console.log('Product ID:', params.id);
});
// 404 fallback
O.router.add('*', (path, params) => {
console.log('Page not found:', path);
show404Page();
});
// Start router
O.router.start();
Navigation
// Navigate programmatically
O.router.navigate('/user/123');
O.router.navigate('/product/onigiri/456');
// From links (hash mode)
<a href="#/user/123">View User</a>
// From links (history mode with data-route)
<a href="/user/123" data-route>View User</a>
Complete SPA Example
O.use('router');
// Define routes
O.router
.add('/', showHome)
.add('/about', showAbout)
.add('/products', showProducts)
.add('/product/:id', showProduct)
.add('*', show404);
// Route handlers
function showHome() {
O('#app').html(`
<h1>π Welcome to Onigiri Shop</h1>
<p>Browse our delicious selection!</p>
<a href="#/products">View Products</a>
`);
}
function showProducts() {
O('#app').html(`
<h1>Our Products</h1>
<div id="product-list"></div>
`);
// Load products
const products = [
{ id: 1, name: 'Salmon Onigiri' },
{ id: 2, name: 'Tuna Onigiri' },
{ id: 3, name: 'Umeboshi Onigiri' }
];
products.forEach(p => {
O('#product-list').append(`
<a href="#/product/${p.id}">${p.name}</a>
`);
});
}
function showProduct(path, params) {
const productId = params.id;
O('#app').html(`
<h1>Product #${productId}</h1>
<a href="#/products">Back to Products</a>
`);
}
function show404() {
O('#app').html(`
<h1>404 - Page Not Found</h1>
<a href="#/">Go Home</a>
`);
}
// Start
O.router.start();
Animation Plugin
Provides jQuery-like animation methods using CSS transitions for smooth, performant animations.
Installation
O.use('animation');
Fade Animations
// Fade in element
O('.modal').fadeIn(300);
// With callback
O('.notification').fadeIn(200, function() {
console.log('Notification shown');
});
// Fade out element
O('.modal').fadeOut(300);
// Remove after fade out
O('.alert').fadeOut(200, function() {
O(this).remove();
});
Slide Animations
// Slide down (reveal)
O('.dropdown-menu').slideDown(300);
// Toggle on click
O('.accordion-header').on('click', function() {
O(this).siblings('.accordion-content').slideDown(300);
});
// Slide up (hide)
O('.dropdown-menu').slideUp(300);
// Close accordion
O('.close-btn').on('click', function() {
O('.accordion-content').slideUp(300);
});
Custom Animations
// Custom animation
O('.box').animate({
width: '300px',
height: '200px',
opacity: '0.5'
}, 500);
// Complex animation
O('.card').animate({
transform: 'translateX(100px) rotate(10deg)',
backgroundColor: '#e74c3c',
borderRadius: '20px'
}, 800, function() {
console.log('Animation complete');
});
Animation Examples
// Modal with fade
O('#show-modal').on('click', () => {
O('.modal-overlay').fadeIn(200, () => {
O('.modal').fadeIn(300);
});
});
O('#close-modal').on('click', () => {
O('.modal').fadeOut(200, () => {
O('.modal-overlay').fadeOut(200);
});
});
// Accordion
O('.accordion-header').on('click', function() {
const content = O(this).siblings('.accordion-content');
const isOpen = O(this).hasClass('open');
// Close all
O('.accordion-content').slideUp(300);
O('.accordion-header').removeClass('open');
// Open clicked (if closed)
if (!isOpen) {
content.slideDown(300);
O(this).addClass('open');
}
});
// Notification system
function showNotification(message, type = 'info') {
const notification = document.createElement('div');
notification.className = `notification ${type}`;
notification.textContent = message;
document.body.appendChild(notification);
O(notification)
.fadeIn(200)
.animate({ transform: 'translateY(0)' }, 300);
setTimeout(() => {
O(notification).fadeOut(300, function() {
this.remove();
});
}, 3000);
}
Security Plugin
Manages CSRF tokens for forms and AJAX requests to protect against cross-site request forgery attacks.
Installation
O.use('security');
// Custom configuration
O.use('security', {
tokenName: 'csrf_token',
headerName: 'X-CSRF-Token',
metaName: 'csrf-token'
});
Setting CSRF Token
// Set token (usually from server)
O.security.setToken('abc123xyz');
// In your HTML (Laravel example)
<meta name="csrf-token" content="{{ csrf_token() }}">
// Token is automatically read from meta tag
Forms
// Auto-add to forms on submit
<form method="POST" action="/api/save">
<!-- CSRF token automatically added -->
<input name="email" type="email">
<button type="submit">Submit</button>
</form>
// Disable auto-add for specific form
<form data-no-csrf>
<!-- No CSRF token added -->
</form>
// Manually add to form
const form = document.querySelector('#myForm');
O.security.addCSRFToForm(form);
AJAX Requests
// Auto-add to AJAX requests (if ajax plugin loaded)
O.post('/api/user', { name: 'John' })
.then(data => console.log(data));
// Manually add to headers
const headers = O.security.addCSRFToHeaders({
'Content-Type': 'application/json'
});
fetch('/api/user', {
method: 'POST',
headers: headers,
body: JSON.stringify({ name: 'John' })
});
Complete Example
// Initialize with token from server
O.use('security');
// Token already in meta tag
<meta name="csrf-token" content="{{ csrf_token() }}">
// Forms automatically protected
O('#user-form').on('submit', function(e) {
e.preventDefault();
// CSRF token already added
const formData = new FormData(this);
fetch('/api/user', {
method: 'POST',
body: formData
}).then(response => response.json())
.then(data => console.log('Success:', data));
});
// AJAX with CSRF
O.ajax({
url: '/api/data',
method: 'POST',
data: { key: 'value' },
csrf: true // enabled by default
}).then(data => {
console.log('Data saved');
});
Cookies Plugin
Provides an easy-to-use interface for managing browser cookies with expiration, security, and SameSite options.
Installation
O.use('cookies');
Setting Cookies
// Simple cookie
O.cookies.set('username', 'john');
// With expiration (days)
O.cookies.set('session', 'abc123', { expires: 7 });
// Secure cookie
O.cookies.set('token', 'xyz789', {
expires: 30,
secure: true,
sameSite: 'Strict'
});
// Full options
O.cookies.set('preferences', JSON.stringify({ theme: 'dark' }), {
path: '/',
domain: '.example.com',
expires: 365,
secure: true,
sameSite: 'Lax'
});
Getting Cookies
// Get cookie value
const username = O.cookies.get('username');
console.log(username); // 'john'
// Non-existent cookie
const missing = O.cookies.get('nonexistent'); // null
// Parse JSON
const prefs = JSON.parse(O.cookies.get('preferences') || '{}');
Removing Cookies
// Remove cookie
O.cookies.remove('username');
// Match path and domain when removing
O.cookies.remove('session', {
path: '/',
domain: '.example.com'
});
Checking Cookies
// Check if cookie exists
if (O.cookies.has('session')) {
console.log('User is logged in');
} else {
redirectToLogin();
}
Cookie Options
| Option | Type | Default | Description |
|---|---|---|---|
| path | string | '/' | Cookie path |
| domain | string | current | Cookie domain |
| expires | number | null | Expiration in days |
| secure | boolean | false | HTTPS only |
| sameSite | string | 'Lax' | 'Strict', 'Lax', or 'None' |
Complete Example
O.use('cookies');
// User preferences manager
const UserPrefs = {
save(prefs) {
O.cookies.set('user_prefs', JSON.stringify(prefs), {
expires: 365,
secure: true
});
},
load() {
const data = O.cookies.get('user_prefs');
return data ? JSON.parse(data) : {
theme: 'light',
language: 'en'
};
},
update(key, value) {
const prefs = this.load();
prefs[key] = value;
this.save(prefs);
},
clear() {
O.cookies.remove('user_prefs');
}
};
// Usage
UserPrefs.update('theme', 'dark');
const prefs = UserPrefs.load();
console.log(prefs.theme); // 'dark'
// Session management
function createSession(token) {
O.cookies.set('session_token', token, {
expires: 1, // 1 day
secure: true,
sameSite: 'Strict'
});
}
function isLoggedIn() {
return O.cookies.has('session_token');
}
function logout() {
O.cookies.remove('session_token');
O.router.navigate('/login');
}
Creating Custom Plugins
You can create your own plugins to extend OnigiriJS functionality.
Plugin Structure
const MyPlugin = {
name: 'myplugin',
version: '1.0.0',
install: function(Onigiri, options) {
// Extend Onigiri prototype
Onigiri.prototype.myMethod = function() {
// Your code here
return this; // for chaining
};
// Add static methods
Onigiri.myStaticMethod = function() {
// Your code here
};
// Mark module as loaded
Onigiri.modules.myplugin = true;
}
};
// Register plugin
O.pluginRegistry.register('myplugin', MyPlugin);
// Use plugin
O.use('myplugin');
Example: Tooltip Plugin
const TooltipPlugin = {
name: 'tooltip',
version: '1.0.0',
install: function(Onigiri, options) {
const defaults = {
position: 'top',
delay: 200
};
const config = Onigiri.extend({}, defaults, options);
Onigiri.prototype.tooltip = function(text) {
this.each(el => {
el.setAttribute('data-tooltip', text);
el.addEventListener('mouseenter', (e) => {
setTimeout(() => {
const tooltip = document.createElement('div');
tooltip.className = 'onigiri-tooltip';
tooltip.textContent = text;
tooltip.style.position = 'absolute';
document.body.appendChild(tooltip);
// Position tooltip
const rect = el.getBoundingClientRect();
tooltip.style.left = rect.left + 'px';
tooltip.style.top = (rect.top - tooltip.offsetHeight - 5) + 'px';
el._tooltip = tooltip;
}, config.delay);
});
el.addEventListener('mouseleave', () => {
if (el._tooltip) {
el._tooltip.remove();
el._tooltip = null;
}
});
});
return this;
};
Onigiri.modules.tooltip = true;
}
};
// Register and use
O.pluginRegistry.register('tooltip', TooltipPlugin);
O.use('tooltip', { delay: 300 });
// Usage
O('.info-icon').tooltip('Click for more information');
O('.button').tooltip('Submit the form');
Example: Form Serializer Plugin
const FormSerializerPlugin = {
name: 'formserializer',
version: '1.0.0',
install: function(Onigiri) {
Onigiri.prototype.serialize = function() {
const form = this.elements[0];
if (!form || form.tagName !== 'FORM') {
console.error('serialize() requires a form element');
return {};
}
const data = {};
const formData = new FormData(form);
for (let [key, value] of formData.entries()) {
if (data[key]) {
// Handle multiple values (checkboxes, multi-select)
if (!Array.isArray(data[key])) {
data[key] = [data[key]];
}
data[key].push(value);
} else {
data[key] = value;
}
}
return data;
};
Onigiri.prototype.deserialize = function(data) {
const form = this.elements[0];
if (!form || form.tagName !== 'FORM') {
console.error('deserialize() requires a form element');
return this;
}
Object.keys(data).forEach(key => {
const input = form.querySelector(`[name="${key}"]`);
if (input) {
if (input.type === 'checkbox' || input.type === 'radio') {
input.checked = input.value === data[key] || data[key] === true;
} else {
input.value = data[key];
}
}
});
return this;
};
Onigiri.modules.formserializer = true;
}
};
// Register and use
O.pluginRegistry.register('formserializer', FormSerializerPlugin);
O.use('formserializer');
// Usage
const formData = O('#user-form').serialize();
console.log(formData); // { name: 'John', email: 'john@example.com' }
// Populate form
O('#user-form').deserialize({
name: 'John Doe',
email: 'john@example.com'
});
Example: Lazy Load Plugin
const LazyLoadPlugin = {
name: 'lazyload',
version: '1.0.0',
install: function(Onigiri, options) {
const defaults = {
threshold: 0.1,
rootMargin: '50px'
};
const config = Onigiri.extend({}, defaults, options);
Onigiri.prototype.lazyLoad = function() {
if (!('IntersectionObserver' in window)) {
// Fallback: load all images immediately
this.each(el => {
if (el.dataset.src) {
el.src = el.dataset.src;
}
});
return this;
}
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
if (img.dataset.src) {
img.src = img.dataset.src;
img.removeAttribute('data-src');
}
observer.unobserve(img);
}
});
}, {
threshold: config.threshold,
rootMargin: config.rootMargin
});
this.each(el => observer.observe(el));
return this;
};
Onigiri.modules.lazyload = true;
}
};
// Register and use
O.pluginRegistry.register('lazyload', LazyLoadPlugin);
O.use('lazyload');
// HTML
<img data-src="/images/photo1.jpg" alt="Photo 1">
<img data-src="/images/photo2.jpg" alt="Photo 2">
// Initialize lazy loading
O('img[data-src]').lazyLoad();
Plugin Best Practices
- Always include
nameandversionproperties - Return
thisfrom prototype methods for chaining - Use
Onigiri.extend()for option merging - Mark your module as loaded with
Onigiri.modules.yourplugin = true - Handle edge cases and provide fallbacks
- Document configuration options clearly
- Clean up event listeners and resources if needed
- Test plugin with multiple elements (use
each()) - Register plugins before using them
- Check for dependencies before installing
Multiple Plugin Usage
// Install multiple plugins at once
O.use('storage', { prefix: 'app_' })
.use('router')
.use('animation')
.use('security')
.use('cookies');
// Check what's loaded
console.log(O.modules);
// {
// core: true,
// storage: true,
// router: true,
// animation: true,
// security: true,
// cookies: true
// }
// Conditional plugin loading
if (needsRouting) {
O.use('router');
}
if (needsAnimations) {
O.use('animation');
}
Real-World Plugin Example
// Complete app using multiple plugins
O.use('storage', { prefix: 'shop_' })
.use('router')
.use('animation')
.use('security');
// Initialize app
const App = {
init() {
this.loadPreferences();
this.setupRoutes();
this.setupEvents();
},
loadPreferences() {
const prefs = O.storage.get('preferences', {
theme: 'light',
currency: 'USD'
});
document.body.className = prefs.theme;
},
setupRoutes() {
O.router
.add('/', this.showHome.bind(this))
.add('/products', this.showProducts.bind(this))
.add('/product/:id', this.showProduct.bind(this))
.add('/cart', this.showCart.bind(this))
.add('/checkout', this.showCheckout.bind(this))
.add('*', this.show404.bind(this))
.start();
},
setupEvents() {
// Add to cart
O(document).on('click', '.add-to-cart', (e) => {
const productId = O(e.target).data('productId');
this.addToCart(productId);
});
// Toggle theme
O('#theme-toggle').on('click', () => {
this.toggleTheme();
});
},
showHome() {
O('#app').html(`
<h1>π Onigiri Shop</h1>
<p>Welcome to our store!</p>
<a href="#/products">Browse Products</a>
`).fadeIn(300);
},
showProducts() {
const products = O.storage.get('products', []);
let html = '<h1>Products</h1><div class="product-grid">';
products.forEach(p => {
html += `
<div class="product-card">
<h3>${p.name}</h3>
<p>${p.price}</p>
<button class="add-to-cart" data-product-id="${p.id}">
Add to Cart
</button>
</div>
`;
});
html += '</div>';
O('#app').html(html).fadeIn(300);
},
showProduct(path, params) {
const product = this.getProduct(params.id);
O('#app').html(`
<h1>${product.name}</h1>
<p>${product.description}</p>
<p class="price">${product.price}</p>
<button class="add-to-cart" data-product-id="${product.id}">
Add to Cart
</button>
<a href="#/products">Back to Products</a>
`).fadeIn(300);
},
addToCart(productId) {
let cart = O.storage.get('cart', []);
const existing = cart.find(item => item.id === productId);
if (existing) {
existing.quantity++;
} else {
const product = this.getProduct(productId);
cart.push({
id: product.id,
name: product.name,
price: product.price,
quantity: 1
});
}
O.storage.set('cart', cart);
this.showNotification('Added to cart! π');
},
showNotification(message) {
const notif = document.createElement('div');
notif.className = 'notification';
notif.textContent = message;
document.body.appendChild(notif);
O(notif).fadeIn(200);
setTimeout(() => {
O(notif).fadeOut(300, function() {
this.remove();
});
}, 2000);
},
toggleTheme() {
const prefs = O.storage.get('preferences', {});
prefs.theme = prefs.theme === 'dark' ? 'light' : 'dark';
O.storage.set('preferences', prefs);
document.body.className = prefs.theme;
},
getProduct(id) {
const products = O.storage.get('products', []);
return products.find(p => p.id === parseInt(id));
},
show404() {
O('#app').html(`
<h1>404 - Page Not Found</h1>
<a href="#/">Go Home</a>
`).fadeIn(300);
}
};
// Start app
App.init();
Plugin Loading Order
<!-- Correct loading order -->
<script src="onigiri-core.js"></script>
<script src="onigiri-events.js"></script> <!-- Required by components -->
<script src="onigiri-ajax.js"></script> <!-- Can use security if loaded -->
<script src="onigiri-plugins.js"></script> <!-- Adds plugin system -->
<script src="onigiri-components.js"></script> <!-- Requires events -->
<script src="onigiri-validation.js"></script>
<script src="onigiri-translation.js"></script>
<!-- Initialize plugins -->
<script>
O.use('storage')
.use('router')
.use('animation')
.use('security')
.use('cookies');
</script>
Troubleshooting
Plugin Not Found
// Error: Plugin "storage" not found
// Solution: Make sure onigiri-plugins.js is loaded
<script src="onigiri-plugins.js"></script>
// Or register your custom plugin first
O.pluginRegistry.register('myplugin', MyPlugin);
O.use('myplugin');
Plugin Already Installed
// Warning: Plugin "storage" already installed
// This is safe - plugin won't be installed twice
O.use('storage'); // First time
O.use('storage'); // Ignored with warning
Missing Dependencies
// Check if required module is loaded
if (!O.modules.events) {
console.error('Events module required');
}
// Better: Check in plugin install
install: function(Onigiri) {
if (!Onigiri.modules.events) {
throw new Error('MyPlugin requires Events module');
}
// ... install code
}
Additional Resources
- Review the core documentation for base methods
- Check out example projects using plugins
- Explore community plugins (coming soon)
- Join the OnigiriJS community for support
- Contribute your own plugins to the ecosystem
β Development Roadmap
Track the progress of OnigiriJS modules. Tasks are marked complete by the development team.
OnigiriJS Module Roadmap
Implementation progress of planned modules