π Portal System
OnigiriJS Portal provides powerful DOM teleportation and overlay management for modals, dialogs, tooltips, and dynamic content mounting.
Quick Start
<script src="onigiri-core.js"></script>
<script src="onigiri-portal.js"></script>
<script>
// Create a simple modal
const modal = Onigiri.modal('<h2>Hello World!</h2><p>This is a modal.</p>');
modal.mount();
// Create a toast notification
Onigiri.toast('Profile updated successfully!', {
icon: 'β
',
duration: 3000
});
</script>
Interactive Demo
Modal Examples
Toast Notifications
Toast Positions
DOM Teleportation
Source Container
π I can teleport!
Target Container
Element will appear here...
Creating Portals
Basic Portal
// Create a portal with HTML content
const portal = Onigiri.portal.create('<div>Portal content</div>', {
overlay: true,
closeButton: true,
closeOnOverlayClick: true,
closeOnEscape: true
});
// Mount the portal
portal.mount();
// Unmount the portal
portal.unmount();
// Destroy the portal completely
portal.destroy();
Portal from Element
// Create portal from existing element
const content = document.querySelector('#my-content');
const portal = Onigiri.portal.create(content, options);
// Or use OnigiriJS selector
const portal = O('#my-content').portal();
portal.mount();
Portal Configuration
const portal = Onigiri.portal.create(content, {
overlay: true, // Show backdrop overlay
closeButton: true, // Show close button
closeOnOverlayClick: true, // Close when clicking overlay
closeOnEscape: true, // Close on ESC key
lockScroll: true, // Prevent body scrolling
animationDuration: 300, // Animation speed (ms)
zIndexBase: 9000 // Base z-index for stacking
});
Modal Helper
Simple Modal
// Quick modal creation
const modal = Onigiri.modal('<h2>Confirmation</h2><p>Are you sure?</p>');
modal.mount();
// Modal with custom options
const customModal = Onigiri.modal(content, {
closeOnOverlayClick: false, // Require explicit close
lockScroll: true
});
Form Modal Example
const formContent = `
<div class="modal-form">
<h2>Contact Us</h2>
<form id="contactForm">
<div class="form-group">
<label>Name</label>
<input type="text" name="name" required>
</div>
<div class="form-group">
<label>Email</label>
<input type="email" name="email" required>
</div>
<div class="form-group">
<label>Message</label>
<textarea name="message" required></textarea>
</div>
<button type="submit" class="btn btn-primary">Send</button>
</form>
</div>
`;
const modal = Onigiri.modal(formContent);
modal.mount();
// Handle form submission
O(document).on('submit', '#contactForm', async (e) => {
e.preventDefault();
// Process form...
modal.unmount();
Onigiri.toast('Message sent!', { icon: 'β
' });
});
Confirmation Modal
function confirmAction(message, onConfirm) {
const content = `
<div class="confirm-modal">
<h3>Confirm Action</h3>
<p>${message}</p>
<div class="button-group">
<button class="btn btn-secondary" data-action="cancel">Cancel</button>
<button class="btn btn-danger" data-action="confirm">Confirm</button>
</div>
</div>
`;
const modal = Onigiri.modal(content, {
closeButton: false,
closeOnOverlayClick: false,
closeOnEscape: false
});
modal.mount();
// Handle buttons
O(document).on('click', '[data-action="confirm"]', () => {
modal.unmount();
onConfirm();
});
O(document).on('click', '[data-action="cancel"]', () => {
modal.unmount();
});
}
// Usage
confirmAction('Delete this item?', () => {
console.log('Item deleted');
Onigiri.toast('Item deleted', { icon: 'ποΈ' });
});
Toast Notifications
Basic Toast
// Simple toast
Onigiri.toast('Hello World!');
// Toast with icon
Onigiri.toast('Success!', { icon: 'β
' });
// Toast with custom duration
Onigiri.toast('This stays longer', { duration: 5000 });
Toast Positions
// Position options
Onigiri.toast('Top Right', { position: 'top-right' });
Onigiri.toast('Top Left', { position: 'top-left' });
Onigiri.toast('Bottom Right', { position: 'bottom-right' });
Onigiri.toast('Bottom Left', { position: 'bottom-left' });
Onigiri.toast('Top Center', { position: 'top-center' });
Onigiri.toast('Bottom Center', { position: 'bottom-center' });
Toast Types
// Success
Onigiri.toast('Profile saved!', {
icon: 'β
',
position: 'top-right'
});
// Error
Onigiri.toast('Failed to save', {
icon: 'β',
position: 'top-right'
});
// Warning
Onigiri.toast('Your session will expire soon', {
icon: 'β οΈ',
duration: 5000
});
// Info
Onigiri.toast('New message received', {
icon: 'βΉοΈ'
});
Persistent Toast
// Toast that doesn't auto-dismiss
const toast = Onigiri.toast('Manual dismiss', {
duration: 0 // Never auto-dismiss
});
// Dismiss manually later
setTimeout(() => {
toast.unmount();
}, 10000);
DOM Teleportation
Teleport Element
// Teleport element to target
const element = document.querySelector('#myElement');
const teleport = Onigiri.portal.teleport(element, '#targetContainer');
// Return element to original position
teleport.return();
// Or use OnigiriJS selector
const teleport = O('#myElement').teleport('#targetContainer');
teleport.return();
Teleport with Animation
const teleport = Onigiri.portal.teleport('#element', '#target', {
animate: true,
returnOnUnmount: true
});
// Element smoothly animates to target
// Can be returned to original position later
teleport.return();
Multiple Element Teleportation
// Teleport multiple elements
O('.teleport-items').teleport('#target');
// Returns array of teleport objects
const teleports = O('.items').teleport('#target');
teleports.forEach(t => {
setTimeout(() => t.return(), 3000);
});
Advanced Portal Features
Portal Stacking
// Open multiple portals - they stack automatically
const modal1 = Onigiri.modal('First Modal');
modal1.mount();
const modal2 = Onigiri.modal('Second Modal');
modal2.mount();
// ESC closes top modal
// Each portal gets proper z-index
// Close top portal
Onigiri.portal.closeTop();
// Close all portals
Onigiri.portal.closeAll();
Get Active Portal
// Get currently active (top) portal
const active = Onigiri.portal.getActive();
if (active) {
console.log('Active portal:', active.element);
}
Update Portal Content
const portal = Onigiri.modal('Initial content');
portal.mount();
// Update content later
setTimeout(() => {
portal.update('<h2>Updated!</h2><p>Content changed</p>');
}, 2000);
Portal Lifecycle Events
// Listen for portal events
document.addEventListener('onigiri:portal:mounted', (e) => {
console.log('Portal mounted:', e.detail.portalId);
});
document.addEventListener('onigiri:portal:unmounted', (e) => {
console.log('Portal unmounted:', e.detail.portalId);
});
Custom Portal Styles
Custom Modal Styling
<style>
/* Override default portal styles */
.portal-content {
max-width: 600px;
border-radius: 16px;
padding: 2.5rem;
}
.onigiri-overlay {
background: rgba(0, 0, 0, 0.8);
backdrop-filter: blur(4px);
}
/* Dark mode support */
[data-theme="dark"] .portal-content {
background: #1f2937;
color: #f9fafb;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.6);
}
/* Custom toast styles */
.onigiri-toast {
border-left: 4px solid #10b981;
padding: 1.25rem 1.75rem;
}
.onigiri-toast .toast-icon {
font-size: 1.5rem;
}
</style>
Real-World Examples
Image Lightbox
function createLightbox(imageUrl, caption) {
const content = `
<div class="lightbox">
<img src="${imageUrl}" alt="${caption}">
${caption ? `<p class="caption">${caption}</p>` : ''}
</div>
`;
const lightbox = Onigiri.portal.create(content, {
overlay: true,
closeButton: true,
closeOnOverlayClick: true,
closeOnEscape: true
});
lightbox.mount();
return lightbox;
}
// Usage
O('.gallery img').on('click', function() {
const src = this.src;
const alt = this.alt;
createLightbox(src, alt);
});
Loading Spinner Portal
function showLoading(message = 'Loading...') {
const content = `
<div class="loading-portal">
<div class="spinner"></div>
<p>${message}</p>
</div>
`;
const loading = Onigiri.portal.create(content, {
overlay: true,
closeButton: false,
closeOnOverlayClick: false,
closeOnEscape: false,
lockScroll: true
});
loading.mount();
return loading;
}
// Usage
async function saveData() {
const loading = showLoading('Saving...');
try {
await Onigiri.post('/api/save', data);
loading.unmount();
Onigiri.toast('Saved successfully!', { icon: 'β
' });
} catch (error) {
loading.unmount();
Onigiri.toast('Save failed', { icon: 'β' });
}
}
Notification Center
function createNotificationCenter(notifications) {
const content = `
<div class="notification-center">
<h2>Notifications</h2>
<div class="notification-list">
${notifications.map(n => `
<div class="notification-item">
<span class="icon">${n.icon}</span>
<div>
<strong>${n.title}</strong>
<p>${n.message}</p>
<small>${n.time}</small>
</div>
</div>
`).join('')}
</div>
</div>
`;
return Onigiri.modal(content);
}
// Usage
const notifications = [
{ icon: 'π¬', title: 'New Message', message: 'John sent you a message', time: '2 min ago' },
{ icon: 'β€οΈ', title: 'New Like', message: 'Sarah liked your post', time: '1 hour ago' }
];
const center = createNotificationCenter(notifications);
center.mount();
π Portal Best Practices:
- Use modals for important user interactions that require attention
- Use toasts for brief, non-intrusive notifications
- Always provide a way to close modals (ESC, overlay, close button)
- Limit portal stacking to avoid overwhelming users
- Use animations sparingly for better performance
- Test keyboard navigation and screen reader compatibility
Configuration Reference
| Option | Type | Default | Description |
|---|---|---|---|
overlay |
boolean | true | Show backdrop overlay |
closeButton |
boolean | true | Show close button |
closeOnOverlayClick |
boolean | true | Close when clicking overlay |
closeOnEscape |
boolean | true | Close on ESC key press |
lockScroll |
boolean | true | Prevent body scrolling |
animationDuration |
number | 300 | Animation duration (ms) |
stackPortals |
boolean | true | Allow portal stacking |
zIndexBase |
number | 9000 | Base z-index value |
β 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.