# Template System Examples
This directory contains comprehensive examples demonstrating all features of the template system.
## Overview
The template system is a compile-time HTML template engine that generates Rust code from template files. It supports:
- Conditional rendering (`tpl-if`)
- Dynamic text content (`tpl-text`)
- Raw HTML content (`tpl-html`, `tpl-outer-html`)
- Dynamic attributes (`tpl-attr:*`)
- Optional attributes (`tpl-optional-attr:*`)
- Repeating elements (`tpl-repeat`)
- Nested templates with argument passing (`tpl-template`, `tpl-include`)
- Slot-based content passing (`tpl-slot:*`, `tpl-slot-items:*`)
- Complex Rust expressions
- Proper handling of void/self-closing elements
## Files
- **`templates/`** - Template source files (*.html)
- `showcase.html` - Main template demonstrating all features
- `card.html` - Reusable card component template
- `header.html` - Header component with slot-items for top items and menu
- `list.html` - List component with slot-items for children
- `page.html` - Page layout composing header and list components
- `simple.html` - Simple static HTML example
- **`showcase_example/`** - Multi-file example directory
- `main.rs` - Example entry point
- `types.rs` - Data structures (Item, User) used by templates
- `generated.rs` - Auto-generated template rendering code
- **`formefile.ts`** - Config file for running the template compiler
- **`README.md`** - This file
## Quick Start
### Step 1: Generate Template Code
Run the template compiler to generate Rust code from the templates:
```bash
# Using the config file
cargo run -- --config examples/formefile.ts
# Or using explicit flags
cargo run -- --source examples/templates --output examples/showcase_example/generated.rs
```
This will:
- Read all `.html` files in the `examples/templates` directory
- Generate Rust functions for each template
- Save the code to `examples/showcase_example/generated.rs`
### Step 2: Run the Example
The example code shows how to use the generated templates:
```bash
cargo run --example showcase_example
```
### Step 3: View the Output
Open the generated HTML files in your browser:
```bash
# Linux
xdg-open examples/showcase_output.html
# macOS
open examples/showcase_output.html
# Or just open the files directly in your browser
```
## Template Features
All directive names start with `tpl-` prefix.
### 0. Template Arguments (`<tpl-arg>`)
Declare the arguments your template expects:
```html
<tpl-arg name="title" type="&str"/>
<tpl-arg name="show_header" type="bool"/>
<tpl-arg name="items" type="&[super::types::Item]"/>
<tpl-arg name="user" type="Option<&super::types::User>"/>
<!DOCTYPE html>
<html>
</html>
```
These declarations generate the function signature with the specified parameters.
You can provide default values using the `default` attribute:
```html
<tpl-arg name="content" type="&str" default='""'/>
<tpl-arg name="content_html" type="&str" default='""'/>
```
When a default is provided, the argument becomes optional when calling the template via `tpl-template` or `tpl-include` -- if no value is passed, the default is used.
**Note:** When referencing custom types from your codebase, use proper module paths relative to the generated module. For example, if your types are in a `types` module at the same level, use `super::types::TypeName`.
### 1. Conditional Rendering (`tpl-if`)
Show or hide elements based on a boolean expression:
```html
<header tpl-if="show_header">
<h1>This only renders if show_header is true</h1>
</header>
<div tpl-if="user.is_some()">
User is logged in
</div>
<p tpl-if="items.len() == 0">No items available</p>
```
### 2. Text Content (`tpl-text`)
Insert dynamic text content from Rust expressions. The content is HTML-escaped:
```html
<p tpl-text="user_name">Fallback name</p>
<span tpl-text='format!("Hello {}!", name)'>Hello!</span>
<h1 tpl-text="title">Default Title</h1>
```
The text inside the element acts as a fallback and will be replaced by the expression result.
**Note:** Use single quotes for the attribute when the expression contains double quotes.
### 3. Raw HTML Content (`tpl-html` and `tpl-outer-html`)
Insert raw (unescaped) HTML content:
**`tpl-html`** replaces the element's children with raw HTML:
```html
<div tpl-html="content_html">Fallback content</div>
```
This renders `<div>` with `content_html` as its inner HTML (not escaped).
**`tpl-outer-html`** replaces the entire element (including its tags) with raw HTML:
```html
<div tpl-outer-html="content_html">Fallback content</div>
```
This renders only `content_html` -- the `<div>` wrapper is removed entirely.
### 4. Dynamic Attributes (`tpl-attr:*`)
Generate attribute values dynamically:
```html
<div tpl-attr:class='format!("user-{}", user_id)'
tpl-attr:id="element_id">
Content
</div>
<img tpl-attr:src="image_url"
tpl-attr:alt="image_description">
<a tpl-attr:href='format!("/user/{}", user.id)'>Profile</a>
```
Each `tpl-attr:name` creates a dynamic `name` attribute.
### 5. Optional Attributes (`tpl-optional-attr:*`)
Add attributes conditionally based on boolean expressions:
```html
<button tpl-optional-attr:disabled="!user.is_active">
Click me
</button>
<input type="text"
tpl-optional-attr:readonly="is_locked"
tpl-optional-attr:required="is_mandatory">
<input type="checkbox"
tpl-optional-attr:checked="user.is_admin">
```
The attribute is only rendered if the expression evaluates to `true`.
### 6. Repeating Elements (`tpl-repeat`)
Loop over collections using the syntax `tpl-repeat="variable in iterator"`:
```html
<ul>
<li tpl-repeat="item in items.iter()">
<span tpl-text="item.name">Item</span>
</li>
</ul>
```
With enumeration:
```html
<li tpl-repeat="(idx, item) in items.iter().enumerate()">
<span tpl-text='format!("{}. {}", idx + 1, item.name)'>Item</span>
</li>
```
With filtering:
```html
</div>
```
### 7. Nested Templates (`tpl-template` and `tpl-include`)
Use other templates as components:
```html
<div tpl-template="card.html" tpl-arg:title='&"Card Title"' tpl-arg:content='&"This is the content"'></div>
<div tpl-include="button.html" tpl-arg:text="&user_name"></div>
```
- Include the `.html` extension in the template name
- Pass arguments using `tpl-arg:name` attributes where `name` matches the template's argument
- Use `&` prefix for string literals or owned values to pass as references
- `tpl-template` replaces the element's children with the rendered template
- `tpl-include` replaces the entire element with the rendered template
### 8. Slot Content Passing (`tpl-slot:*` and `tpl-slot-items:*`)
For more complex argument passing to nested templates, you can use slots. Slots let you pass rendered HTML content as template arguments instead of simple expressions.
**`tpl-slot:name`** renders all children into a single `&str` argument:
```html
<div tpl-template="card.html" tpl-arg:title="&u.name">
<template tpl-slot:content_html>
<p><strong>Username:</strong> <span tpl-text="u.name">Unknown</span></p>
<p><strong>Email:</strong> <span tpl-text="u.email">no-email</span></p>
</template>
</div>
```
The children of the `<template tpl-slot:content_html>` element are rendered to a single HTML string and passed as the `content_html: &str` argument to `card.html`.
**`tpl-slot-items:name`** renders each direct child element as a separate string in a `Vec<String>`:
```html
<template tpl-include="header.html">
<template tpl-slot-items:top_items>
<a href="/index.html">Home</a>
<a href="/account.html">Account</a>
</template>
<template tpl-slot-items:menu_items>
<a href="/index.html">Home</a>
<a href="/account.html">Account</a>
</template>
</template>
```
Each direct child element inside the slot is rendered to its own `String` and collected into a `Vec<String>`, which is passed as the argument. This is useful for list-type parameters.
Children inside `tpl-slot-items:` can use directives like `tpl-repeat`, `tpl-if`, `tpl-text`, etc.
### 9. Complex Expressions
Use any valid Rust expression in directives:
```html
<p tpl-text='format!("Total: {}", items.len())'>0 items</p>
<div tpl-attr:class='format!("status-{}", if active { "on" } else { "off" })'>
Status indicator
</div>
<span tpl-text='user.as_ref().map(|u| u.name.as_str()).unwrap_or("Guest")'>
Guest
</span>
```
**Important:** Use single quotes for attributes containing double quotes inside expressions.
### 10. Combining Features
You can combine multiple directives on the same element:
```html
<div tpl-if="user.is_some()">
<div tpl-repeat="u in user.iter()">
<p tpl-text='format!("Welcome {}!", u.name)'>Guest</p>
<div tpl-if="u.is_admin">
<span class="badge">Admin</span>
</div>
</div>
</div>
```
With attributes:
```html
<button tpl-optional-attr:disabled="!is_enabled"
tpl-attr:class='format!("btn-{}", button_style)'
tpl-text="button_label">
Click
</button>
```
## Template Structure
Templates are standard HTML files with directives:
```html
<tpl-arg name="title" type="&str"/>
<tpl-arg name="items" type="&[Item]"/>
<tpl-arg name="user" type="Option<&User>"/>
<!DOCTYPE html>
<html>
<head>
<title tpl-text="title">Default Title</title>
</head>
<body>
<h1 tpl-text="title">Fallback Title</h1>
<div tpl-if="items.len() > 0">
<ul>
<li tpl-repeat="item in items.iter()">
<span tpl-text="item.name">Item</span>
</li>
</ul>
</div>
</body>
</html>
```
## Generated Code
The template compiler generates Rust functions with parameters based on `<tpl-arg>` declarations:
```rust
pub fn render_card(
out: &mut impl std::fmt::Write,
title: &str,
content: &str,
content_html: &str,
) -> std::fmt::Result {
// Generated rendering code
}
```
Function naming:
- `showcase.html` -> `render_showcase(out, ...args...)`
- `card.html` -> `render_card(out, ...args...)`
- `simple.html` -> `render_simple(out)` (if no tpl-arg)
The generated file includes `#![cfg_attr(rustfmt, rustfmt::skip)]` at the top so `cargo fmt` skips it.
## Project Structure
The example uses a clean directory-based structure:
```
examples/
├── formefile.ts # Config file for the template compiler
├── templates/ # Template source files
│ ├── showcase.html # Main showcase template
│ ├── card.html # Reusable card component
│ ├── header.html # Header with slot-items
│ ├── list.html # List with slot-items
│ ├── page.html # Page layout composing header and list
│ └── simple.html # Simple static template
└── showcase_example/ # Multi-file example
├── main.rs # Example entry point
├── types.rs # Data structures (Item, User)
└── generated.rs # Auto-generated template code
```
### Data Structures Module
Data structures are defined in `types.rs`:
```rust
// examples/showcase_example/types.rs
#[derive(Debug)]
pub struct Item {
pub id: u32,
pub name: String,
pub price: f64,
pub in_stock: bool,
pub is_featured: bool,
}
#[derive(Debug)]
pub struct User {
pub id: u32,
pub name: String,
pub email: String,
pub is_admin: bool,
}
```
Templates reference these types using proper module paths:
```html
<tpl-arg name="items" type="&[super::types::Item]"/>
<tpl-arg name="user" type="Option<&super::types::User>"/>
```
The `super::types::` path is relative to the generated module, allowing the generated code to properly reference your custom types.
## Usage in Your Code
After generating the templates, use them as standalone modules:
```rust
// examples/showcase_example/main.rs
// Import types from types module
mod types;
// Import generated template code as a standalone module
// The generated code references types using super::types::Item paths
mod generated;
// Re-export the render functions for convenience
use generated::{render_card, render_page, render_showcase, render_simple};
// Re-export types for convenience
use types::{Item, User};
fn main() {
// Create a String to collect output
let mut html = String::new();
// Call the generated render function
// Note: the function writes to the output, doesn't return HTML
render_simple(&mut html).expect("Failed to render");
// Use the HTML
std::fs::write("output.html", html).unwrap();
}
```
With data:
```rust
// Pass the template arguments explicitly
let title = "My Page";
let show_header = true;
let items = vec![
Item {
id: 1,
name: "Widget".into(),
price: 9.99,
in_stock: true,
is_featured: false,
},
];
let user = Some(User {
id: 1,
name: "Alice".into(),
email: "alice@example.com".into(),
is_admin: true,
});
let mut html = String::new();
render_showcase(
&mut html,
title,
show_header,
&items,
user.as_ref(),
// ... other arguments
).expect("Failed to render");
```
### Why This Structure?
1. **Directory-Based Organization**: Example is a proper multi-file project
2. **Separation of Concerns**: Types are in their own module (`types.rs`)
3. **Proper Module Paths**: Templates use explicit type paths like `super::types::Item`
4. **Clean Interface**: Re-export functions and types for easier use
5. **No Manual Edits**: `generated.rs` stays untouched and can be regenerated
6. **Type Safety**: Full type checking with proper module resolution
7. **Pure Module Declaration**: Uses standard `mod generated;` without any `include!()` macro
8. **Scalable**: Easy to add more modules or examples following the same pattern
**Note:** The `generated.rs` file is auto-generated and should not be manually edited. Templates reference custom types using proper module paths (e.g., `super::types::TypeName`) relative to the generated module. Since all files are in the `showcase_example` directory, Rust's module system automatically finds them when you declare `mod types;` and `mod generated;`.
**Important:** All variables used in template expressions must be passed as arguments or be in scope.
## Directive Reference
| `<tpl-arg>` | `<tpl-arg name="var" type="Type"/>` | Declare template arguments |
| `<tpl-arg>` | `<tpl-arg name="var" type="Type" default='expr'/>` | Declare with default value |
| `tpl-if` | `tpl-if="condition"` | Conditional rendering |
| `tpl-repeat` | `tpl-repeat="var in iterator"` | Loop over collections |
| `tpl-text` | `tpl-text="expression"` | Dynamic text content (HTML-escaped) |
| `tpl-html` | `tpl-html="expression"` | Raw HTML content replacing children |
| `tpl-outer-html` | `tpl-outer-html="expression"` | Raw HTML replacing entire element |
| `tpl-attr:name` | `tpl-attr:class="expr"` | Dynamic attribute value |
| `tpl-optional-attr:name` | `tpl-optional-attr:disabled="bool_expr"` | Conditional attribute |
| `tpl-template` | `tpl-template="name.html"` | Include nested template (wrapping) |
| `tpl-include` | `tpl-include="name.html"` | Include nested template (inline) |
| `tpl-arg:name` | `tpl-arg:title="expr"` | Pass argument to nested template |
| `tpl-slot:name` | `tpl-slot:content_html` (on `<template>` child) | Pass slot content as `&str` argument |
| `tpl-slot-items:name` | `tpl-slot-items:children` (on `<template>` child) | Pass slot items as `Vec<String>` argument |
## Tips
1. **Quote Handling**: Use single quotes for attributes containing double quotes
```html
<p tpl-text='format!("Hello {}", name)'>Hi</p>
```
2. **Type Safety**: The Rust compiler validates your template expressions
3. **Fallback Content**: Text inside elements with `tpl-text` serves as fallback
4. **Template Arguments**: Declare all arguments with `<tpl-arg>` at the top of the template
5. **Default Values**: Use the `default` attribute on `<tpl-arg>` for optional arguments
6. **Reusable Components**: Create small templates and compose with `tpl-template`
7. **Reference Passing**: Use `&` when passing string literals or owned values to nested templates
```html
<div tpl-template="card.html" tpl-arg:title='&"Title"' tpl-arg:content='&format!("Content: {}", val)'></div>
```
8. **Slot Content**: Use `tpl-slot:` for passing rendered HTML as `&str`, and `tpl-slot-items:` for passing a list of rendered items as `Vec<String>`
9. **Type Paths**: Reference custom types in templates using proper module paths relative to the generated module:
```html
<tpl-arg name="items" type="&[super::types::Item]"/>
```
10. **Debug**: Check `generated.rs` to see the actual Rust code produced
## Troubleshooting
### Template not found
**Problem:** `Found 0 template file(s)`
**Solution:** Ensure files have `.html` or `.htm` extension (not `.tpl`)
### Type errors in generated code
**Problem:** Compilation errors about undefined variables
**Solution:**
- Pass all required arguments to the render function
- Check that argument types match the `<tpl-arg>` declarations
- Ensure types match what expressions expect
- Variables used in expressions must be passed as arguments or available in scope
- Verify type paths are correct (e.g., `super::types::Item` not `super::showcase_types::Item`)
### Attributes not rendering
**Problem:** Attributes don't appear in output
**Verification:**
- Correct syntax: `tpl-attr:name="value"` (not `attr-name`)
- For optional attrs: `tpl-optional-attr:name="bool_expr"`
- Check `generated.rs` to see what code was produced
### Quote/escaping issues
**Problem:** Invalid Rust syntax in generated code
**Solution:**
- Use single quotes for the attribute when expression contains double quotes
- Avoid inline `<style>` tags with CSS (use external stylesheets instead)
- Braces `{}` in CSS conflict with Rust format strings
## Learn More
- See `showcase.html` for a complete example demonstrating all features
- See `card.html` for a simple reusable component with slot support
- See `page.html` for a layout template composing header and list components
- See `simple.html` for a basic static template
- Run `cargo run --example showcase_example` to see it in action