Layout
A layout defines the base HTML structure of your application. It acts as the root container where all slots, includes, and bindings are applied. The layout is bound once and everything else flows from it.
---1. Basic Usage
Layouts are loaded through CandidTemplateAdaptor using
bind(). The adaptor resolves the file path and loads
the HTML into the root CandidTemplate instance.
// Via adaptor singleton — recommended for web applications
$view = CandidTemplateAdaptor::getInstance();
$view->bind('/path/to/themes/default/index.html');
// Access root template for direct bindings
$tpl = $view->getTpl();
---
2. Example Layout File
themes/default/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>My Application</title>
<link href="/css/style.css" rel="stylesheet">
</head>
<body>
<div data-slot="topbar"></div>
<div data-slot="navbar"></div>
<div data-slot="content"></div>
<div data-slot="footer"></div>
<script src="/js/main.js"></script>
</body>
</html>
data-slot wrapper elements are completely removed after
rendering — only the slot template content remains in the final output.
No empty wrapper divs are left behind.
3. How Layout Works
bind('index.html') — load layout into root template
↓
slot() / slotIf() — register slot templates
↓
include() — register includes
↓
pick() — queue data bindings
↓
build() — retrieve root template
↓
render() — apply all bindings, render final HTML
---
4. Route-Based Layout Binding
Different layouts can be bound based on the current route.
The singleton pattern ensures the same instance is used
across include.php and page controllers.
// include.php — bind layout based on route
$view = CandidTemplateAdaptor::getInstance();
if (str_contains(getCurrentURL(), '/news/')) {
$view->bind('/themes/default/index.html');
} else {
$view->bind('/themes/inapp/index.html');
}
$tpl = $view->getTpl();
updateLink($tpl);
updateScriptSrc($tpl);
---
5. Using Layout with Slots
Register slot templates against data-slot placeholders
in the layout. Sub-slots must always be registered on their
parent slot — not on the root.
// Top-level slots registered on root via adaptor
$view->slot('topbar', 'topbar');
$navbar = $view->slot('navbar', 'navbar');
$navbar->pick('nav_home')->activeIf($page === 'home');
$navbar->pick('nav_category')->activeIf($page === 'category');
// Sub-slots registered on parent — not on $view
$home = $view->slot('content', 'home');
$home->slot('slider', 'slider');
$home->slot('breaking', 'breaking');
$home->slot('featured', 'featured');
$home->slot('sidebar', 'sidebar');
$view->slot('footer', 'footer');
$tpl = $view->build();
echo $tpl->render();
---
6. Conditional Slots — slotIf()
Skip loading a slot entirely when not needed. The placeholder is removed from the DOM — no file I/O occurs.
// Admin panel only loads if user is admin
$admin = $view->slotIf($user->isAdmin(), 'admin', 'admin-panel');
$admin?->pick('username')->content($user->name);
// Breaking news only shows when available
$home->slotIf($news->hasBreaking(), 'breaking', 'breaking');
// Sidebar conditional on feature flag
$home->slotIf($features->sidebarEnabled(), 'sidebar', 'sidebar');
---
7. Using Layout with Includes
Includes inject a template into an existing element — the element itself is replaced by the include content.
// Layout HTML
<div id="breadcrumb"></div>
// PHP
$breadcrumb = $view->include('#breadcrumb', 'breadcrumb');
$breadcrumb->pick('current')->content('Home');
---
8. Multiple Layouts — Different Themes
// Frontend theme
if (isFrontend()) {
$view->bind('/themes/default/index.html');
$tpl = $view->getTpl();
updateLink($tpl);
updateScriptSrc($tpl);
}
// Admin theme
if (isAdmin()) {
$view->bind('/themes/inapp/index.html');
$tpl = $view->getTpl();
updateLink($tpl);
updateScriptSrc($tpl);
}
---
9. Layout Render Flow
root template (index.html)
├── slot: topbar → topbar.html
├── slot: navbar → navbar.html
├── slot: content → home.html
│ ├── slot: slider → slider.html
│ ├── slot: breaking → breaking.html (slotIf)
│ ├── slot: featured → featured.html
│ └── slot: sidebar → sidebar.html (slotIf)
└── slot: footer → footer.html
Render order:
- Root layout HTML is loaded
- Includes are resolved and injected
- Slots are resolved depth-first and injected
- Includes introduced by slots are resolved
- Loops are applied
- All
pick()assignments are applied - Final HTML string is returned
10. Rules
- Layout must be a valid HTML file
- Slot names must exactly match
data-slotattribute values - The
data-slotwrapper is removed — only content remains - Sub-slots must be registered on parent slot, not root
- Always call
build()beforerender() slotIf()returns?CandidTemplate— use null-safe operator?->- Use absolute file paths in
bind()to avoid path resolution issues
11. Common Mistakes
❌ Missing
data-slot in layout — slot silently skipped❌ Wrong slot name — does not match
data-slot value❌ Duplicate slot names in same layout
❌ Using
showIf() for large sections —
use slotIf() instead❌ Calling
render() before build()❌ Forgetting null-safe operator on
slotIf() return value
12. Best Practices
- Bind layout in
include.php— keep page controllers clean - Register sub-slots on parent template, not root
- Use
slotIf()overshowIf()for entire sections - Keep layout HTML minimal — only structure and
data-slotplaceholders - Use semantic HTML —
header,main,footer,nav - Use absolute paths for asset URLs to avoid sub-page path issues
- Avoid business logic in layout — layout is structure only
13. Performance Notes
- Layout is parsed once per request via
DOMDocument - Use
slotIf(false)to skip file I/O entirely for conditional sections - Keep DOM depth reasonable — deeply nested slots increase parse time
- Static sections (header, footer) are good candidates for output caching when available
include.php,
register slots on their correct parent, and let render()
assemble the final HTML cleanly with no leftover placeholder elements.