Autohandlers - Hierarchical Layouts
Autohandlers are special components that automatically wrap other components in their directory and subdirectories. They're perfect for creating consistent layouts, adding headers/footers, and implementing nested template hierarchies.
What is an Autohandler?
An autohandler is a file named autohandler (with no extension) that wraps all components in its directory tree. Think of it as an automatic layout wrapper.
How Autohandlers Work
When a request is processed, StoneJS:
- Searches for
autohandlerfiles from the root to the target component's directory - Builds a chain of autohandlers (root to leaf)
- Executes them in order, each wrapping the next
- The innermost handler renders the actual component
Current Request Chain
This page is processed through the following chain:
/data/embedded-apps/pages/autohandler/data/embedded-apps/pages/demo/autohandlers.html(this page)
Creating an Autohandler
Create a file named autohandler in any directory. Use <%- await next() %> to insert child content:
<%
const sectionTitle = 'My Section';
%>
<!DOCTYPE html>
<html>
<head>
<title><%= sectionTitle %></title>
</head>
<body>
<header>
<h1><%= sectionTitle %></h1>
</header>
<main>
<%- await next() %>
</main>
<footer>
<p>Footer content</p>
</footer>
</body>
</html>
The next() Function
The next() function is special - it renders the next handler in the chain (or the final component). You must use <%- await next() %> (with unescaped output) to include the child content.
Important Notes:
- Use
<%-(not<%=) to avoid HTML escaping - Always use
awaitsince rendering is asynchronous - The
next()call determines where child content appears
Sharing Data Through Autohandlers
Autohandlers can set data in the global context for child components.
In autohandler:
<%
$context.set('sharedConfig', {
siteName: 'My Site',
theme: 'dark'
});
%>
In child component:
<%
const config = $context.get('sharedConfig');
%>
<h1><%= config.siteName %></h1>
Nested Autohandlers
You can have multiple autohandlers in different directories:
pages/
├── autohandler # Root layout (site-wide)
├── index.html
├── admin/
│ ├── autohandler # Admin section layout
│ ├── dashboard.html
│ └── settings/
│ ├── autohandler # Settings subsection layout
│ └── profile.html
When accessing /admin/settings/profile.html, all three autohandlers wrap the page!
Conditional Wrapping
Autohandlers can choose whether to wrap content based on the request:
<%
if ($req.headers['x-requested-with'] === 'XMLHttpRequest') {
%><%- await next() %><%
} else {
%>
<!DOCTYPE html>
<html>
<body>
<%- await next() %>
</body>
</html>
<%
}
%>
Common Use Cases
- Site-wide Layout: Add headers, footers, navigation to all pages
- Section Layouts: Different layouts for admin, public, API sections
- Authentication: Check user permissions before rendering pages
- Analytics: Add tracking code to all pages in a section
- Meta Tags: Set default meta tags that children can override
Example: Admin Section Autohandler
This example shows authentication, authorization, and shared data for an admin section:
<%
if (!$session.userId) {
$res.redirect('/login');
return;
}
if (!$session.isAdmin) {
$res.status(403).send('Forbidden');
return;
}
$context.set('adminName', $session.userName);
$context.set('adminRole', $session.userRole);
%>
<!DOCTYPE html>
<html>
<head>
<title>Admin Panel</title>
</head>
<body>
<nav>
<a href="/admin">Dashboard</a>
<a href="/admin/users">Users</a>
<a href="/admin/settings">Settings</a>
</nav>
<div class="admin-content">
<%- await next() %>
</div>
</body>
</html>
Best Practices
- Keep autohandlers focused on layout and shared functionality
- Don't put heavy business logic in autohandlers
- Use
$context.set()to share data with children - Remember that autohandlers execute for ALL files in their tree
- Test your autohandler chain to ensure proper nesting
Try It Yourself
The autohandler wrapping this page adds:
- The header with navigation
- The site-wide styling
- The footer with session information
All of this happens automatically without any code in this component!