π§ Router System
OnigiriJS Router provides lightweight, PJAX-first routing for smoother page transitions without full reloads. Perfect for traditional server-rendered applications that want SPA-like navigation.
Quick Start
<script src="onigiri-core.js"></script>
<script src="onigiri-security.js"></script>
<script src="onigiri-router.js"></script>
<script>
// Initialize router
Onigiri.router.init({
container: '#main-content',
pjax: true,
scrollToTop: true
});
// Define routes (optional - PJAX works without route definitions)
Onigiri.router.route('/about', (ctx) => {
console.log('About page loaded');
});
// That's it! Links with data-route will now use PJAX
</script>
<!-- Links automatically use PJAX -->
<a href="/about" data-route>About</a>
Interactive Demo
PJAX Navigation
Click these links to see PJAX in action (content loads without full page reload):
π Welcome to OnigiriJS Router Demo
Click the buttons above to see smooth page transitions.
Current route: /demo
Route Parameters
Routes can extract dynamic parameters from URLs:
Select a user to view their profile
Browser Navigation
Router Modes
History Mode (Default)
// Uses HTML5 History API for clean URLs
Onigiri.router.init({
mode: 'history', // Clean URLs: /about, /users/123
root: '/'
});
// URLs look like:
// https://example.com/about
// https://example.com/users/123
// https://example.com/blog/my-post
Hash Mode
// Uses URL hash for routing (no server configuration needed)
Onigiri.router.init({
mode: 'hash' // Hash URLs: #/about, #/users/123
});
// URLs look like:
// https://example.com/#/about
// https://example.com/#/users/123
// https://example.com/#/blog/my-post
Configuration Options
Onigiri.router.init({
mode: 'history', // 'history' or 'hash'
root: '/', // Base path for router
container: '#main', // Container for PJAX content
linkSelector: 'a[data-route]', // Links to intercept
formSelector: 'form[data-route]', // Forms to intercept
pjax: true, // Enable PJAX
pjaxTimeout: 5000, // PJAX request timeout (ms)
scrollToTop: true, // Scroll to top on navigation
scrollBehavior: 'smooth', // 'smooth' or 'auto'
updateTitle: true, // Update document title from response
csrf: true, // Include CSRF token
cachePages: true, // Cache visited pages
maxCache: 20, // Maximum cached pages
prefetch: false, // Prefetch links on hover
prefetchDelay: 100, // Prefetch delay (ms)
loadingClass: 'route-loading', // Class added during load
transitionDuration: 300 // Transition duration (ms)
});
Defining Routes
Basic Route
// Define a route handler
Onigiri.router.route('/about', (context) => {
console.log('About page loaded');
console.log('Path:', context.path);
});
Route with Parameters
// Extract parameters from URL
Onigiri.router.route('/users/:id', (context) => {
const userId = context.params.id;
console.log('User ID:', userId);
// Load user data
loadUserProfile(userId);
});
// Matches: /users/123, /users/456, etc.
// context.params.id = "123", "456", etc.
Multiple Parameters
// Multiple dynamic segments
Onigiri.router.route('/blog/:category/:slug', (context) => {
console.log('Category:', context.params.category);
console.log('Slug:', context.params.slug);
});
// Matches: /blog/tech/my-post
// context.params = { category: 'tech', slug: 'my-post' }
Wildcard Routes
// Catch-all route
Onigiri.router.route('/admin/*', (context) => {
console.log('Admin section:', context.path);
});
// Matches: /admin/users, /admin/settings, /admin/anything
Named Routes
// Define named route
Onigiri.router.route('/profile/:username', profileHandler, {
name: 'user.profile'
});
// Generate URL from name
const url = Onigiri.router.url('user.profile', {
username: 'john'
});
console.log(url); // "/profile/john"
// Navigate to named route
Onigiri.navigate(url);
Bulk Route Registration
// Register multiple routes at once
Onigiri.router.route({
'/': homeHandler,
'/about': aboutHandler,
'/contact': contactHandler,
'/users/:id': userHandler,
'/blog/:slug': blogHandler
});
Navigation
Programmatic Navigation
// Navigate to a path
Onigiri.router.navigate('/about');
// Navigate with options
Onigiri.router.navigate('/users/123', {
trigger: true, // Trigger route handler
replace: false, // Use pushState (not replaceState)
data: { id: 123 }, // State data
scroll: true // Scroll to top
});
// Shorthand
Onigiri.navigate('/about');
Browser Navigation
// Go back
Onigiri.router.back();
// Go forward
Onigiri.router.forward();
// Reload current route
Onigiri.router.reload();
// Reload and bypass cache
Onigiri.router.reload(true);
Link Navigation
<!-- Links with data-route use PJAX -->
<a href="/about" data-route>About</a>
<a href="/users/123" data-route>User Profile</a>
<!-- Regular links (full page reload) -->
<a href="/logout">Logout</a>
Form Navigation
<!-- Forms with data-route submit via PJAX -->
<form action="/search" method="GET" data-route>
<input type="text" name="q" placeholder="Search...">
<button type="submit">Search</button>
</form>
Lifecycle Hooks
Before Navigation
// Run before every navigation
Onigiri.router.before((context) => {
console.log('Navigating to:', context.path);
console.log('From:', context.from);
// Cancel navigation
if (needsAuth && !isAuthenticated) {
context.cancel = true;
Onigiri.navigate('/login');
}
});
After Navigation
// Run after every navigation
Onigiri.router.after((context) => {
console.log('Navigation complete:', context.path);
// Analytics
trackPageView(context.path);
// Update UI
updateActiveNavLinks(context.path);
});
Error Handling
// Handle navigation errors
Onigiri.router.onError((context) => {
console.error('Navigation error:', context.error);
// Show error to user
Onigiri.toast('Failed to load page', { icon: 'β' });
context.handled = true; // Prevent default error handling
});
Middleware
Route Middleware
// Define middleware functions
const authMiddleware = (context, next) => {
if (isAuthenticated()) {
next(); // Continue to route handler
} else {
Onigiri.navigate('/login');
}
};
const loggerMiddleware = (context, next) => {
console.log('Route accessed:', context.path);
next();
};
// Apply to specific route
Onigiri.router.route('/admin', adminHandler, {
middleware: [authMiddleware, loggerMiddleware]
});
Chaining Middleware
const checkRole = (role) => {
return (context, next) => {
if (userHasRole(role)) {
next();
} else {
Onigiri.navigate('/unauthorized');
}
};
};
Onigiri.router.route('/admin/users', usersHandler, {
middleware: [
authMiddleware,
checkRole('admin'),
loggerMiddleware
]
});
Advanced Features
Page Caching
// Enable caching (default: true)
Onigiri.router.init({
cachePages: true,
maxCache: 20 // Cache up to 20 pages
});
// Clear specific page cache
Onigiri.router.clearCache('/about');
// Clear all cache
Onigiri.router.clearCache();
// Disable cache for specific route
Onigiri.router.route('/fresh-data', handler, {
cache: false
});
Link Prefetching
// Enable prefetching on hover
Onigiri.router.init({
prefetch: true,
prefetchDelay: 100 // Wait 100ms before prefetching
});
// Manual prefetch
Onigiri.router.prefetch('/about');
Query Parameters
Onigiri.router.route('/search', (context) => {
// Access query parameters
console.log(context.query);
// URL: /search?q=onigiri&page=2
// context.query = { q: 'onigiri', page: '2' }
const searchTerm = context.query.q;
const page = context.query.page || 1;
});
State Management
// Navigate with state
Onigiri.router.navigate('/users/123', {
data: {
referrer: 'search',
filters: { role: 'admin' }
}
});
// Access state in route handler
Onigiri.router.route('/users/:id', (context) => {
console.log(context.state);
// { referrer: 'search', filters: { role: 'admin' } }
});
Get Current Route
// Get current path
const path = Onigiri.router.getCurrentPath();
// Get current route object
const route = Onigiri.router.getCurrentRoute();
console.log(route.path);
console.log(route.route);
Server-Side Configuration
PHP (Apache with .htaccess)
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
# Redirect all requests to index.php
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php?url=$1 [QSA,L]
</IfModule>
PHP (index.php)
<?php
// Get the requested path
$path = $_GET['url'] ?? '/';
// Your routing logic here
// Include appropriate template based on path
?>
<!DOCTYPE html>
<html>
<head>
<title>My App</title>
</head>
<body>
<nav>
<a href="/" data-route>Home</a>
<a href="/about" data-route>About</a>
<a href="/contact" data-route>Contact</a>
</nav>
<main id="content">
<?php include "pages/{$path}.php"; ?>
</main>
<script src="onigiri-core.js"></script>
<script src="onigiri-router.js"></script>
<script>
Onigiri.router.init({
container: '#content',
pjax: true
});
</script>
</body>
</html>
Node.js/Express
const express = require('express');
const app = express();
// Serve static files
app.use(express.static('public'));
// Catch-all route
app.get('*', (req, res) => {
res.sendFile(__dirname + '/public/index.html');
});
Real-World Examples
Forum Application
// Initialize router
Onigiri.router.init({
container: '.main',
pjax: true,
scrollToTop: true,
cachePages: true
});
// Define routes
Onigiri.router.route({
'/': () => {
console.log('Home page');
},
'/category/:slug': (ctx) => {
loadCategory(ctx.params.slug);
},
'/topic/:slug': (ctx) => {
loadTopic(ctx.params.slug);
trackTopicView(ctx.params.slug);
},
'/profile/:username': (ctx) => {
loadProfile(ctx.params.username);
}
});
// Authentication middleware
const requireAuth = (ctx, next) => {
if (currentUser) {
next();
} else {
Onigiri.toast('Please login first', { icon: 'π' });
Onigiri.navigate('/login');
}
};
// Protected routes
Onigiri.router.route('/admin', adminHandler, {
middleware: [requireAuth]
});
// Track page views
Onigiri.router.after((ctx) => {
analytics.track('pageview', { path: ctx.path });
});
E-commerce Site
// Product pages
Onigiri.router.route('/products/:id', async (context) => {
const productId = context.params.id;
try {
const product = await loadProduct(productId);
renderProduct(product);
} catch (error) {
Onigiri.toast('Product not found', { icon: 'β' });
Onigiri.navigate('/products');
}
});
// Shopping cart
Onigiri.router.route('/cart', (context) => {
if (cart.isEmpty()) {
Onigiri.toast('Your cart is empty', { icon: 'π' });
Onigiri.navigate('/products');
return;
}
renderCart();
});
// Checkout with auth
Onigiri.router.route('/checkout', checkoutHandler, {
middleware: [requireAuth, validateCart]
});
Dashboard with Tabs
// Tab navigation
Onigiri.router.route('/dashboard/:tab?', (context) => {
const tab = context.params.tab || 'overview';
// Update active tab
O('.tab').removeClass('active');
O(\`[data-tab="\${tab}"]\`).addClass('active');
// Load tab content
loadDashboardTab(tab);
});
// Links update URL without reload
O('.tab-link').on('click', function(e) {
e.preventDefault();
const tab = this.dataset.tab;
Onigiri.navigate(\`/dashboard/\${tab}\`);
});
Router Events
// Listen for router events
document.addEventListener('onigiri:router:ready', () => {
console.log('Router initialized');
});
document.addEventListener('onigiri:router:beforeLoad', (e) => {
console.log('Loading:', e.detail.path);
showLoadingBar();
});
document.addEventListener('onigiri:router:complete', (e) => {
console.log('Loaded:', e.detail.path);
hideLoadingBar();
});
π Router Best Practices:
- Use PJAX for content-heavy sites to improve perceived performance
- Enable caching for frequently visited pages
- Add loading indicators during route transitions
- Use middleware for authentication and authorization
- Handle errors gracefully with error hooks
- Configure server to handle HTML5 History API
- Test with JavaScript disabled (graceful degradation)
Configuration Reference
| Option | Type | Default | Description |
|---|---|---|---|
mode |
string | 'history' | Routing mode: 'history' or 'hash' |
root |
string | '/' | Base path for router |
container |
string | '#main' | Container selector for PJAX |
pjax |
boolean | true | Enable PJAX navigation |
scrollToTop |
boolean | true | Scroll to top on navigation |
cachePages |
boolean | true | Cache visited pages |
maxCache |
number | 20 | Maximum cached pages |
prefetch |
boolean | false | Prefetch links on hover |
updateTitle |
boolean | true | Update document title |
β 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.