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

462 lines
11 KiB
Markdown
Raw Normal View History

# Working with Templates
Templates control how your content is presented. FolderWeb uses a simple PHP-based template system—no complex templating languages, just HTML with a sprinkle of PHP.
## Template Types
FolderWeb has three template levels:
### 1. Base Template (`base.php`)
The HTML scaffold wrapping every page:
```
<!DOCTYPE html>
<html lang="en">
<head>
<title>Page Title</title>
<link rel="stylesheet" href="...">
</head>
<body>
<header><!-- Navigation --></header>
<main><!-- Page content here --></main>
<footer><!-- Footer --></footer>
</body>
</html>
```
**You typically customize this once** to set up your site structure.
### 2. Page Template (`page.php`)
Wraps single-page content:
```php
<article>
<?= $content ?>
</article>
```
**Customize this** to control how individual pages look.
### 3. List Template (`list.php`, `list-grid.php`, `list-compact.php`)
Displays multiple items from subdirectories:
```php
<?= $pageContent ?>
<div class="item-list">
<?php foreach ($items as $item): ?>
<article>
<h2><a href="<?= $item['url'] ?>"><?= $item['title'] ?></a></h2>
<p><?= $item['summary'] ?></p>
</article>
<?php endforeach; ?>
</div>
```
**Customize this** to control how lists of content (blogs, portfolios, etc.) appear.
## Template Location
Templates live in your `custom/` directory:
```
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/` if a custom template doesn't exist.
## Customizing the Base Template
Let's modify `base.php` to add your site name and custom navigation:
**custom/templates/base.php:**
```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="/" class="logo">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>
<p>
<small>Generated in <?= number_format($pageLoadTime, 4) ?>s</small>
</p>
</footer>
</body>
</html>
```
**Key points:**
- Always escape user content: `htmlspecialchars($var)`
- Use short echo tags: `<?= $var ?>`
- Check if variables exist: `isset($var)`
- The `$content` variable contains the rendered page/list content
## Customizing Page Templates
The page template wraps your single-page content. Let's add a reading time estimate:
**custom/templates/page.php:**
```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; ?>
<?php
// Estimate reading time (avg 200 words/min)
$wordCount = str_word_count(strip_tags($content));
$readingTime = max(1, round($wordCount / 200));
?>
<p class="reading-time"><?= $readingTime ?> min read</p>
</header>
<?php endif; ?>
<div class="content">
<?= $content ?>
</div>
</article>
```
## Customizing List Templates
List templates display collections of content. Let's create a custom blog list:
**custom/templates/list.php:**
```php
<?php if ($pageContent): ?>
<div class="page-intro">
<?= $pageContent ?>
</div>
<?php endif; ?>
<div class="blog-list">
<?php foreach ($items as $item): ?>
<article class="blog-item">
<?php if (isset($item['cover_image'])): ?>
<a href="<?= $item['url'] ?>">
<img
src="<?= $item['cover_image'] ?>"
alt="<?= htmlspecialchars($item['title']) ?>"
loading="lazy"
>
</a>
<?php endif; ?>
<header>
<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; ?>
</header>
<?php if (isset($item['summary'])): ?>
<p><?= htmlspecialchars($item['summary']) ?></p>
<?php endif; ?>
<a href="<?= $item['url'] ?>">Read more →</a>
</article>
<?php endforeach; ?>
</div>
```
## Choosing List Templates
You can create multiple list templates and select them per directory:
**Available by default:**
- `list.php` — Simple vertical list
- `list-grid.php` — Card grid layout
- `list-compact.php` — Minimal compact list
**Select in metadata.ini:**
```ini
title = "Projects"
[settings]
page_template = "list-grid"
```
Now the `projects/` directory uses the grid layout.
## Creating Custom List Templates
Let's create a timeline template for a blog:
**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;
// Print year marker if it changed
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>
<div class="timeline-content">
<h4><a href="<?= $item['url'] ?>"><?= htmlspecialchars($item['title']) ?></a></h4>
<?php if (isset($item['summary'])): ?>
<p><?= htmlspecialchars($item['summary']) ?></p>
<?php endif; ?>
</div>
</article>
<?php endforeach; ?>
</div>
```
**Use in metadata.ini:**
```ini
[settings]
page_template = "list-timeline"
```
## Available Template Variables
Templates have access to these variables (see [Reference: Template Variables](#) for complete list):
**Base template:**
```php
$content // Rendered page/list HTML
$pageTitle // Page title for <title> tag
$metaDescription // SEO description
$navigation // Array of menu items
$homeLabel // "Home" link text (translated)
$currentLang // Current language code
$languageUrls // Links to other language versions
$translations // Translated UI strings
$cssHash // Cache-busting hash for CSS
$pageCssUrl // Page-specific CSS URL (if exists)
$pageLoadTime // Page generation time
```
**Page template:**
```php
$content // Rendered HTML
$metadata // Metadata array (title, date, etc.)
```
**List template:**
```php
$items // Array of items to display
$pageContent // Optional intro content from page
$metadata // Directory metadata
// Each $item has:
$item['url'] // Full URL to item
$item['title'] // Item title
$item['summary'] // Short description
$item['date'] // ISO date (YYYY-MM-DD)
$item['formatted_date'] // Localized date string
$item['cover_image'] // Cover image URL (if exists)
```
## Best Practices
### 1. Always Escape Output
```php
<!-- Bad -->
<h1><?= $title ?></h1>
<!-- Good -->
<h1><?= htmlspecialchars($title) ?></h1>
```
**Exception:** Already-sanitized HTML like `$content` (rendered from Markdown).
### 2. Check Variables Exist
```php
<!-- Bad -->
<p><?= $metadata['summary'] ?></p>
<!-- Good -->
<?php if (isset($metadata['summary'])): ?>
<p><?= htmlspecialchars($metadata['summary']) ?></p>
<?php endif; ?>
```
### 3. Use Short Echo Tags
```php
<!-- Verbose -->
<?php echo htmlspecialchars($title); ?>
<!-- Concise -->
<?= htmlspecialchars($title) ?>
```
### 4. Keep Logic Minimal
Templates should display data, not process it. Complex logic belongs in plugins.
```php
<!-- Bad: complex logic in template -->
<?php
$posts = array_filter($items, function($item) {
return strtotime($item['date']) > strtotime('-30 days');
});
usort($posts, function($a, $b) {
return strcmp($b['date'], $a['date']);
});
?>
<!-- Good: prepare data in a plugin, display in template -->
<?php foreach ($recentPosts as $post): ?>
...
<?php endforeach; ?>
```
### 5. Use Semantic HTML
```php
<!-- Bad -->
<div class="title">Title</div>
<div class="content">Content</div>
<!-- Good -->
<article>
<h1>Title</h1>
<div class="content">Content</div>
</article>
```
## Practical Examples
### Simple Portfolio Page Template
```php
<article class="portfolio-item">
<header>
<?php if (isset($metadata['cover_image'])): ?>
<img src="<?= $metadata['cover_image'] ?>" alt="">
<?php endif; ?>
<h1><?= htmlspecialchars($metadata['title'] ?? 'Untitled') ?></h1>
</header>
<div class="content">
<?= $content ?>
</div>
<?php if (isset($metadata['project_url'])): ?>
<footer>
<a href="<?= htmlspecialchars($metadata['project_url']) ?>"
class="button">View Project →</a>
</footer>
<?php endif; ?>
</article>
```
### Card Grid List Template
```php
<?= $pageContent ?>
<div class="card-grid">
<?php foreach ($items as $item): ?>
<article class="card">
<?php if (isset($item['cover_image'])): ?>
<img src="<?= $item['cover_image'] ?>" alt="" loading="lazy">
<?php endif; ?>
<div class="card-content">
<h3>
<a href="<?= $item['url'] ?>">
<?= htmlspecialchars($item['title']) ?>
</a>
</h3>
<?php if (isset($item['summary'])): ?>
<p><?= htmlspecialchars($item['summary']) ?></p>
<?php endif; ?>
</div>
</article>
<?php endforeach; ?>
</div>
```
## What's Next?
You now know how to customize templates. Next, learn about:
- **[Template Variables Reference](#)** — Complete list of available variables
- **[Creating Plugins](#)** — Extend functionality and add custom data to templates
- **[Internationalization](#)** — Build multilingual sites
Or explore the examples in `app/default/content/examples/templates-demo/` to see templates in action.