folderweb/docs/02-tutorial/03-templates.md

290 lines
7.9 KiB
Markdown
Raw Normal View History

# Templates
Templates control how content is presented. FolderWeb uses plain PHP templates — HTML with embedded PHP for dynamic output.
## Template Types
FolderWeb has three template levels:
### Base Template (`base.php`)
The HTML scaffold wrapping every page. Contains `<head>`, navigation, footer, and a `$content` placeholder for the inner template.
### Page Template (`page.php`)
Wraps single-page content. Receives the rendered content and metadata.
### List Templates (`list.php`, `list-grid.php`, `list-compact.php`)
Displays collections of items from subdirectories. Receives an `$items` array and optional intro content.
## Template Location
Templates live in `custom/templates/`:
```
custom/
└── templates/
├── base.php # HTML scaffold
├── page.php # Single page wrapper
├── list.php # Default list layout
├── list-grid.php # Grid card layout
└── list-compact.php # Compact list layout
```
FolderWeb falls back to `app/default/templates/` for any template not present in `custom/`.
## Base Template
The base template wraps every page. Here is a minimal example:
```php
<!DOCTYPE html>
<html lang="<?= $currentLang ?? 'en' ?>">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?= htmlspecialchars($pageTitle) ?></title>
<?php if (isset($metaDescription)): ?>
<meta name="description" content="<?= htmlspecialchars($metaDescription) ?>">
<?php endif; ?>
<?php if (isset($socialImageUrl)): ?>
<meta property="og:image" content="<?= $socialImageUrl ?>">
<?php endif; ?>
<link rel="stylesheet" href="/custom/styles/base.css?v=<?= $cssHash ?? '' ?>">
<?php if (isset($pageCssUrl)): ?>
<link rel="stylesheet" href="<?= $pageCssUrl ?>?v=<?= $pageCssHash ?? '' ?>">
<?php endif; ?>
</head>
<body>
<header>
<nav>
<a href="<?= htmlspecialchars($langPrefix ?? '') ?>/">My Site</a>
<ul>
<?php foreach ($navigation as $item): ?>
<li>
<a href="<?= htmlspecialchars($item['url']) ?>">
<?= htmlspecialchars($item['title']) ?>
</a>
</li>
<?php endforeach; ?>
</ul>
</nav>
</header>
<main>
<?= $content ?>
</main>
<footer>
<p>&copy; <?= date('Y') ?> My Site</p>
</footer>
<?php if (!empty($pageJsUrl)): ?>
<script defer src="<?= htmlspecialchars($pageJsUrl) ?>?v=<?= $pageJsHash ?? '' ?>"></script>
<?php endif; ?>
</body>
</html>
```
Key points:
- `$content` contains the rendered output from the page or list template
- Always escape user-facing values with `htmlspecialchars()`
- Check optional variables with `isset()` before using them
## Page Template
The page template wraps individual page content:
```php
<article>
<?php if (isset($metadata['title'])): ?>
<header>
<h1><?= htmlspecialchars($metadata['title']) ?></h1>
<?php if (isset($metadata['date']) && ($metadata['show_date'] ?? true)): ?>
<time datetime="<?= $metadata['date'] ?>">
<?= $metadata['formatted_date'] ?>
</time>
<?php endif; ?>
</header>
<?php endif; ?>
<div class="content">
<?= $content ?>
</div>
</article>
```
## List Template
List templates display items from subdirectories:
```php
<?php if ($pageContent): ?>
<div class="page-intro">
<?= $pageContent ?>
</div>
<?php endif; ?>
<div class="blog-list">
<?php foreach ($items as $item): ?>
<article>
<?php if (isset($item['cover_image'])): ?>
<a href="<?= $item['url'] ?>">
<img src="<?= $item['cover_image'] ?>"
alt="<?= htmlspecialchars($item['title']) ?>"
loading="lazy">
</a>
<?php endif; ?>
<h2>
<a href="<?= $item['url'] ?>">
<?= htmlspecialchars($item['title']) ?>
</a>
</h2>
<?php if (isset($item['date'])): ?>
<time datetime="<?= $item['date'] ?>">
<?= $item['formatted_date'] ?? $item['date'] ?>
</time>
<?php endif; ?>
<?php if (isset($item['summary'])): ?>
<p><?= htmlspecialchars($item['summary']) ?></p>
<?php endif; ?>
</article>
<?php endforeach; ?>
</div>
```
## Choosing a List Template
Select which list template to use per directory in `metadata.ini`:
```ini
title = "Projects"
[settings]
page_template = "list-grid"
```
Built-in options:
- `list` — vertical list (default)
- `list-grid` — card grid
- `list-compact` — minimal compact list
## Creating Custom List Templates
Create a new file in `custom/templates/`. For example, a timeline layout:
**custom/templates/list-timeline.php:**
```php
<?= $pageContent ?>
<div class="timeline">
<?php
$currentYear = null;
foreach ($items as $item):
$year = isset($item['date']) ? date('Y', strtotime($item['date'])) : null;
if ($year && $year !== $currentYear):
$currentYear = $year;
?>
<div class="year-marker">
<h3><?= $year ?></h3>
</div>
<?php endif; ?>
<article class="timeline-item">
<time><?= $item['formatted_date'] ?? '' ?></time>
<h4><a href="<?= $item['url'] ?>"><?= htmlspecialchars($item['title']) ?></a></h4>
<?php if (isset($item['summary'])): ?>
<p><?= htmlspecialchars($item['summary']) ?></p>
<?php endif; ?>
</article>
<?php endforeach; ?>
</div>
```
Use it with:
```ini
[settings]
page_template = "list-timeline"
```
## Template Variables
### Base template
| Variable | Type | Description |
|---|---|---|
| `$content` | string | Rendered page/list HTML |
| `$pageTitle` | string | Page title for `<title>` |
| `$metaDescription` | string | SEO description |
| `$navigation` | array | Menu items (`title`, `url`, `order`) |
| `$homeLabel` | string | Translated "Home" text |
| `$currentLang` | string | Current language code |
| `$langPrefix` | string | URL language prefix (`""` or `"/no"`) |
| `$languageUrls` | array | Links to other language versions |
| `$translations` | array | Translated UI strings |
| `$cssHash` | string | Cache-busting hash for base CSS |
| `$pageCssUrl` | string | Page-specific CSS URL (if exists) |
| `$pageJsUrl` | string | Page-specific JS URL (if exists) |
| `$pageLoadTime` | float | Page generation time |
### Page template
| Variable | Type | Description |
|---|---|---|
| `$content` | string | Rendered HTML from content files |
| `$metadata` | array | Page metadata (title, date, etc.) |
### List template
| Variable | Type | Description |
|---|---|---|
| `$items` | array | Items to display |
| `$pageContent` | string | Rendered intro content |
| `$metadata` | array | Directory metadata |
Each item in `$items`:
| Key | Type | Description |
|---|---|---|
| `url` | string | Full URL to item |
| `title` | string | Item title |
| `summary` | string | Short description |
| `date` | string | ISO date (YYYY-MM-DD) |
| `formatted_date` | string | Localized date string |
| `cover_image` | string | Cover image URL |
See the [Template Variables Reference](../03-reference/03-template-variables.md) for the complete list.
## Best Practices
**Escape output.** Always use `htmlspecialchars()` for user-facing values. The `$content` variable is already rendered HTML and does not need escaping.
**Check variables.** Use `isset()` before accessing optional values:
```php
<?php if (isset($metadata['summary'])): ?>
<p><?= htmlspecialchars($metadata['summary']) ?></p>
<?php endif; ?>
```
**Keep logic minimal.** Templates should display data, not process it. Complex logic belongs in plugins.
**Use semantic HTML.** Prefer `<article>`, `<header>`, `<nav>`, `<time>` over generic `<div>` elements.
## Next Steps
- [Template Variables Reference](../03-reference/03-template-variables.md) — Complete variable list
- [Internationalization](../03-reference/04-internationalization.md) — Multi-language support
- [Configuration Reference](../03-reference/01-configuration.md) — All configuration options