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:

  1. Searches for autohandler files from the root to the target component's directory
  2. Builds a chain of autohandlers (root to leaf)
  3. Executes them in order, each wrapping the next
  4. The innermost handler renders the actual component

Current Request Chain

This page is processed through the following chain:

  1. /data/embedded-apps/pages/autohandler
  2. /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:

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

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

Try It Yourself

The autohandler wrapping this page adds:

All of this happens automatically without any code in this component!