HTML Templates #
The std/html/templates module provides a powerful HTML templating engine powered by Tera, which uses a Jinja2-like syntax. Use it to generate dynamic HTML content for web applications, emails, reports, and more.
Quick Start #
use "std/html/templates" as templates
# Create a template engine
let tmpl = templates.create()
# Add a template
tmpl.add_template("greeting", "Hello, {{ name }}!")
# Render with data
let html = tmpl.render("greeting", {"name": "Alice"})
puts(html) # "Hello, Alice!"
Creating Template Engines #
create() #
Creates a new empty template engine instance.
let tmpl = templates.create()
from_dir(pattern) #
Creates a template engine and loads all templates matching a glob pattern.
# Load all HTML files from templates directory
let tmpl = templates.from_dir("templates/**/*.html")
# Load specific patterns
let tmpl = templates.from_dir("views/*.html")
Supported glob patterns:
*.html- All HTML files in current directorytemplates/**/*.html- All HTML files recursively{emails,pages}/*.html- Files in multiple directories
Template Methods #
render(template_name, context) #
Renders a named template with the given context data.
tmpl.add_template("user", "Name: {{ user.name }}")
let html = tmpl.render("user", {"user": {"name": "Bob"}})
Parameters:
template_name(String) - Name of the template to rendercontext(Dict) - Data to pass to the template
Returns: String containing rendered HTML
render_str(template_string, context) #
Renders a template string directly without registering it.
let template = "Count: {{ count }}"
let html = tmpl.render_str(template, {"count": 42})
Use cases:
- One-off templates
- Dynamic template generation
- Testing template syntax
add_template(name, content) #
Registers a template from a string.
tmpl.add_template("header", "<h1>{{ title }}</h1>")
add_template_file(name, path) #
Registers a template from a file.
tmpl.add_template_file("layout", "templates/base.html")
get_template_names() #
Returns an array of all registered template names.
let names = tmpl.get_template_names()
puts(names) # ["header", "footer", "layout"]
Template Syntax #
Variables #
Use {{ variable }} to output values:
let tmpl = templates.create()
let html = tmpl.render_str("Hello, {{ name }}!", {"name": "World"})
# Output: "Hello, World!"
Nested properties:
let context = {
"user": {
"profile": {
"name": "Alice"
}
}
}
let html = tmpl.render_str("{{ user.profile.name }}", context)
# Output: "Alice"
Array indexing:
let context = {"items": [1, 2, 3]}
let html = tmpl.render_str("{{ items.0 }}", context)
# Output: "1"
Conditionals #
Use {% if %} for conditional rendering:
let template = """
{% if user.is_admin %}
<span class="badge">Admin</span>
{% else %}
<span class="badge">User</span>
{% endif %}
"""
tmpl.render_str(template, {"user": {"is_admin": true}})
Conditional operators:
==- Equals!=- Not equals>,<,>=,<=- Comparisonsand,or,not- Logical operators
{% if count > 10 and status == "active" %}
High activity
{% elif count > 0 %}
Some activity
{% else %}
No activity
{% endif %}
Loops #
Use {% for %} to iterate over arrays:
let template = """
<ul>
{% for item in items %}
<li>{{ item }}</li>
{% endfor %}
</ul>
"""
tmpl.render_str(template, {"items": [1, 2, 3]})
Loop over objects:
{% for user in users %}
<div class="user">
<h3>{{ user.name }}</h3>
<p>{{ user.email }}</p>
</div>
{% endfor %}
Loop variables:
loop.index- Current iteration (1-indexed)loop.index0- Current iteration (0-indexed)loop.first- True on first iterationloop.last- True on last iteration
{% for item in items %}
<li class="{% if loop.first %}first{% endif %}">
{{ loop.index }}. {{ item }}
</li>
{% endfor %}
Filters #
Filters transform values using the pipe | operator:
{{ name | upper }} # ALICE
{{ name | lower }} # alice
{{ items | length }} # 5
{{ price | round }} # 10
{{ text | truncate(length=20) }} # Truncate to 20 chars
{{ content | safe }} # Don't escape HTML
Common filters:
upper/lower- Change caselength- Get length of array/stringround(precision=N)- Round numberstruncate(length=N)- Truncate stringsdefault(value="X")- Provide default valuesafe- Disable HTML escapingescape- HTML escape (default for all variables)
Chaining filters:
{{ name | lower | truncate(length=10) | upper }}
Template Inheritance #
Create reusable base templates with {% extends %} and {% block %}:
base.html:
<!DOCTYPE html>
<html>
<head>
<title>{% block title %}Default Title{% endblock %}</title>
</head>
<body>
<header>
{% block header %}Default Header{% endblock %}
</header>
<main>
{% block content %}{% endblock %}
</main>
<footer>
{% block footer %}© 2024{% endblock %}
</footer>
</body>
</html>
page.html:
{% extends "base.html" %}
{% block title %}My Page{% endblock %}
{% block content %}
<h1>Welcome!</h1>
<p>This is my page content.</p>
{% endblock %}
Quest code:
let tmpl = templates.from_dir("templates/**/*.html")
let html = tmpl.render("page.html", {})
# Outputs full HTML with base layout
Comments #
Use {# #} for template comments (not included in output):
{# This is a comment and won't appear in rendered HTML #}
<p>This will appear</p>
Whitespace Control #
Control whitespace with - in tags:
{%- if true -%}
No whitespace before or after
{%- endif -%}
Common Use Cases #
Email Templates #
use "std/html/templates" as templates
let tmpl = templates.create()
tmpl.add_template_file("welcome", "templates/emails/welcome.html")
let html = tmpl.render("welcome", {
"user": {"name": "Alice"},
"action_url": "https://example.com/verify",
"sender": {"name": "Support Team"}
})
# Send email with html content
Web Pages #
# Load all templates
let tmpl = templates.from_dir("views/**/*.html")
# Render page
let html = tmpl.render("home.html", {
"user": current_user,
"posts": recent_posts,
"stats": dashboard_stats
})
Reports #
let tmpl = templates.create()
tmpl.add_template_file("report", "reports/sales.html")
let html = tmpl.render("report", {
"period": "Q4 2024",
"revenue": 125000.50,
"orders": order_list,
"growth": 15.5
})
# Export to PDF or save as HTML
Forms #
let form_html = tmpl.render("form.html", {
"form": {
"title": "Contact Form",
"action": "/submit",
"fields": [
{"name": "email", "type": "email", "required": true},
{"name": "message", "type": "textarea", "rows": 5}
]
},
"errors": validation_errors
})
Best Practices #
1. Organize Templates by Purpose #
templates/
├── layouts/
│ └── base.html
├── emails/
│ ├── welcome.html
│ └── notification.html
└── pages/
├── home.html
└── dashboard.html
2. Use Template Inheritance #
Create a base layout and extend it:
# Load once
let tmpl = templates.from_dir("templates/**/*.html")
# All pages inherit from base.html
tmpl.render("pages/home.html", data)
tmpl.render("pages/about.html", data)
3. Validate Context Data #
Ensure all required data is present:
fun render_user_page(user)
if user == nil
raise "User required for user page template"
end
tmpl.render("user.html", {
"user": user,
"timestamp": time.now()
})
end
4. Use Filters for Safety #
Always escape user-generated content (automatic by default):
{# Automatically escaped #}
<p>{{ user_comment }}</p>
{# Only use 'safe' for trusted HTML #}
<div>{{ trusted_html | safe }}</div>
5. Cache Template Instances #
Create the template engine once and reuse:
# At startup
let app_templates = templates.from_dir("templates/**/*.html")
# In handlers
fun handle_request(request)
let html = app_templates.render("page.html", request.data)
response.send(html)
end
Error Handling #
Template errors include helpful messages:
try
let html = tmpl.render("missing.html", {})
catch e
puts(e.message()) # "Template error: Template 'missing.html' not found"
end
try
tmpl.render_str("{% if %}", {})
catch e
puts(e.message()) # "Template error: Failed to parse..."
end
Performance Tips #
- Load templates once: Create template engine at startup
- Use
from_dir(): Load all templates in one call - Avoid
render_str()in loops: Register templates instead - Keep templates simple: Complex logic belongs in Quest code
Template Engine Details #
Quest's HTML templates module uses Tera, a mature Rust templating engine that provides:
- Jinja2-compatible syntax: Familiar to Python/Flask developers
- Fast rendering: Compiled templates for performance
- Safe by default: Automatic HTML escaping
- Rich filter library: Built-in text transformations
- Template inheritance: Reusable layouts and components
Type Reference #
HtmlTemplate Type #
Methods:
render(name, context)- Render named templaterender_str(template, context)- Render string templateadd_template(name, content)- Add template from stringadd_template_file(name, path)- Add template from fileget_template_names()- List registered templatescls()- Returns"HtmlTemplate"
Examples #
See comprehensive examples:
examples/html_templates_demo.q- Basic inline templatesexamples/html_file_templates_demo.q- File-based templates with inheritancetest/html/templates/- Real-world template examples
See Also #
- JSON Module - For data serialization
- IO Module - For file operations
- String Methods - For string manipulation
- Tera Documentation - Full Tera syntax reference