gitea_ui/docs/views.md
Mahmoud Emad 4cc61a955d feat: Add .gitignore and initial project structure
- Add a comprehensive .gitignore to manage project files.
- Create the basic project structure including Cargo.toml,
  LICENSE, and README.md.
- Add basic project documentation.
2025-05-11 09:09:45 +03:00

6.1 KiB

Views Guide

This guide provides detailed instructions on how to create and customize views in the Hostbasket application.

Table of Contents

  1. Introduction to Tera Templates
  2. Template Structure
  3. Creating a New View
  4. Template Inheritance
  5. Using Variables
  6. Control Structures
  7. Filters
  8. Macros
  9. Custom Functions
  10. Best Practices

Introduction to Tera Templates

Hostbasket uses Tera as its template engine. Tera is inspired by Jinja2 and Django templates and provides a powerful way to create dynamic HTML pages.

Template Structure

Templates are stored in the src/views directory. The directory structure typically follows the application's features:

src/views/
├── auth/
│   ├── login.html
│   └── register.html
├── home/
│   ├── index.html
│   ├── about.html
│   └── contact.html
├── tickets/
│   ├── index.html
│   ├── new.html
│   └── show.html
├── assets/
│   ├── index.html
│   ├── create.html
│   └── detail.html
└── base.html

Creating a New View

To create a new view:

  1. Create a new HTML file in the appropriate directory under src/views
  2. Use template inheritance to extend the base template
  3. Add your content within the appropriate blocks

Example:

{% extends "base.html" %}

{% block title %}My New Page{% endblock %}

{% block content %}
<div class="container">
    <h1>Welcome to My New Page</h1>
    <p>This is a custom page.</p>
</div>
{% endblock %}

Template Inheritance

Tera supports template inheritance, which allows you to define a base template with common elements and extend it in child templates.

Base Template

The base template (base.html) typically contains the HTML structure, header, footer, and navigation menu:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{% block title %}Hostbasket{% endblock %}</title>
    <link rel="stylesheet" href="/static/css/bootstrap.min.css">
    <link rel="stylesheet" href="/static/css/styles.css">
    {% block head %}{% endblock %}
</head>
<body>
    <nav class="navbar navbar-expand-lg navbar-dark bg-dark">
        <!-- Navigation content -->
    </nav>

    <main>
        {% block content %}{% endblock %}
    </main>

    <footer class="footer mt-auto py-3 bg-light">
        <!-- Footer content -->
    </footer>

    <script src="/static/js/bootstrap.bundle.min.js"></script>
    {% block scripts %}{% endblock %}
</body>
</html>

Child Template

Child templates extend the base template and override specific blocks:

{% extends "base.html" %}

{% block title %}Home{% endblock %}

{% block content %}
<div class="container">
    <h1>Welcome to Hostbasket</h1>
    <p>This is the home page.</p>
</div>
{% endblock %}

{% block scripts %}
<script src="/static/js/home.js"></script>
{% endblock %}

Using Variables

You can pass variables from your controller to the template and use them in your HTML:

In the Controller

pub async fn index(tmpl: web::Data<Tera>) -> Result<impl Responder> {
    let mut ctx = tera::Context::new();
    ctx.insert("title", "Welcome to Hostbasket");
    ctx.insert("user_name", "John Doe");
    
    render_template(&tmpl, "home/index.html", &ctx)
}

In the Template

<h1>{{ title }}</h1>
<p>Hello, {{ user_name }}!</p>

Control Structures

Tera provides various control structures for conditional rendering and iteration.

Conditionals

{% if user %}
    <p>Welcome, {{ user.name }}!</p>
{% else %}
    <p>Please log in.</p>
{% endif %}

Loops

<ul>
    {% for item in items %}
        <li>{{ item.name }}</li>
    {% endfor %}
</ul>

{% if items is empty %}
    <p>No items found.</p>
{% endif %}

Filters

Filters transform the values of variables:

<p>{{ user.name | upper }}</p>
<p>{{ user.bio | truncate(length=100) }}</p>
<p>{{ user.created_at | date(format="%Y-%m-%d") }}</p>

Macros

Macros are reusable template fragments:

{% macro input(name, value='', type='text', label='') %}
    <div class="mb-3">
        {% if label %}
            <label for="{{ name }}" class="form-label">{{ label }}</label>
        {% endif %}
        <input type="{{ type }}" name="{{ name }}" id="{{ name }}" value="{{ value }}" class="form-control">
    </div>
{% endmacro %}

<!-- Usage -->
{{ input(name="email", type="email", label="Email Address") }}

Custom Functions

You can register custom functions in Rust and use them in your templates:

In Rust

fn register_tera_functions(tera: &mut Tera) {
    tera.register_function("format_date", format_date);
}

fn format_date(args: &HashMap<String, Value>) -> Result<Value, tera::Error> {
    // Implementation
}

In the Template

<p>{{ format_date(date=user.created_at, format="%B %d, %Y") }}</p>

Best Practices

  1. Use Template Inheritance: Extend the base template to maintain consistency across pages.
  2. Organize Templates by Feature: Group related templates in subdirectories.
  3. Keep Templates Simple: Move complex logic to the controller or custom functions.
  4. Use Meaningful Variable Names: Choose descriptive names for variables and blocks.
  5. Comment Your Templates: Add comments to explain complex sections.
  6. Validate User Input: Always validate and sanitize user input in the controller before passing it to the template.
  7. Use Partials for Reusable Components: Extract common components into separate files and include them where needed.
  8. Optimize for Performance: Minimize the use of expensive operations in templates.
  9. Test Your Templates: Ensure that your templates render correctly with different data.
  10. Follow Accessibility Guidelines: Make your templates accessible to all users.