432 lines
11 KiB
Markdown
432 lines
11 KiB
Markdown
# Template Structure and Conventions - Public Web Frontend
|
|
|
|
**Last Updated:** November 18, 2025
|
|
|
|
---
|
|
|
|
## Overview
|
|
|
|
This document outlines the template structure, naming conventions, and best practices for Jinja2 templates in the Code of Conquest web frontend.
|
|
|
|
**Template Philosophy:**
|
|
- Clean, semantic HTML
|
|
- Separation of concerns (templates, styles, scripts)
|
|
- Reusable components via includes and macros
|
|
- Responsive design patterns
|
|
- Accessibility-first
|
|
|
|
---
|
|
|
|
## Directory Structure
|
|
|
|
```
|
|
templates/
|
|
├── base.html # Base template (all pages extend this)
|
|
├── errors/ # Error pages
|
|
│ ├── 404.html
|
|
│ ├── 500.html
|
|
│ └── 403.html
|
|
├── auth/ # Authentication pages
|
|
│ ├── login.html
|
|
│ ├── register.html
|
|
│ └── forgot_password.html
|
|
├── dashboard/ # User dashboard
|
|
│ └── index.html
|
|
├── characters/ # Character management
|
|
│ ├── list.html
|
|
│ ├── create.html
|
|
│ ├── view.html
|
|
│ └── edit.html
|
|
├── sessions/ # Game sessions
|
|
│ ├── create.html
|
|
│ ├── active.html
|
|
│ ├── history.html
|
|
│ └── view.html
|
|
├── multiplayer/ # Multiplayer sessions
|
|
│ ├── create.html
|
|
│ ├── lobby.html
|
|
│ ├── session.html
|
|
│ └── complete.html
|
|
├── partials/ # Reusable partial templates
|
|
│ ├── navigation.html
|
|
│ ├── footer.html
|
|
│ ├── character_card.html
|
|
│ ├── combat_ui.html
|
|
│ └── session_summary.html
|
|
├── components/ # Reusable UI components (macros)
|
|
│ ├── forms.html
|
|
│ ├── buttons.html
|
|
│ ├── alerts.html
|
|
│ └── modals.html
|
|
└── macros/ # Jinja2 macros
|
|
├── form_fields.html
|
|
└── ui_elements.html
|
|
```
|
|
|
|
---
|
|
|
|
## Base Template
|
|
|
|
**File:** `templates/base.html`
|
|
|
|
```html
|
|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>{% block title %}Code of Conquest{% endblock %}</title>
|
|
|
|
<!-- CSS -->
|
|
<link rel="stylesheet" href="{{ url_for('static', filename='css/main.css') }}">
|
|
{% block extra_css %}{% endblock %}
|
|
|
|
<!-- HTMX -->
|
|
<script src="https://unpkg.com/htmx.org@1.9.10"></script>
|
|
|
|
<!-- Appwrite SDK (for realtime) -->
|
|
<script src="https://cdn.jsdelivr.net/npm/appwrite@latest"></script>
|
|
|
|
{% block extra_head %}{% endblock %}
|
|
</head>
|
|
<body>
|
|
{% include 'partials/navigation.html' %}
|
|
|
|
<main class="container">
|
|
{% block flash_messages %}
|
|
{% with messages = get_flashed_messages(with_categories=true) %}
|
|
{% if messages %}
|
|
<div class="alerts">
|
|
{% for category, message in messages %}
|
|
<div class="alert alert-{{ category }}">
|
|
{{ message }}
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
{% endif %}
|
|
{% endwith %}
|
|
{% endblock %}
|
|
|
|
{% block content %}
|
|
<!-- Page content goes here -->
|
|
{% endblock %}
|
|
</main>
|
|
|
|
{% include 'partials/footer.html' %}
|
|
|
|
<!-- JavaScript -->
|
|
<script src="{{ url_for('static', filename='js/main.js') }}"></script>
|
|
{% block extra_js %}{% endblock %}
|
|
</body>
|
|
</html>
|
|
```
|
|
|
|
---
|
|
|
|
## Template Naming Conventions
|
|
|
|
### File Names
|
|
- Use lowercase with underscores: `character_list.html`, `session_create.html`
|
|
- Partial templates prefix with underscore: `_card.html`, `_form.html` (optional)
|
|
- Component files describe what they contain: `forms.html`, `buttons.html`
|
|
|
|
### Template Variables
|
|
- Use snake_case: `character`, `session_data`, `user_info`
|
|
- Prefix collections with descriptive names: `characters_list`, `sessions_active`
|
|
- Boolean flags use `is_` or `has_` prefix: `is_authenticated`, `has_premium`
|
|
|
|
### Block Names
|
|
- Use descriptive names: `{% block sidebar %}`, `{% block page_header %}`
|
|
- Common blocks:
|
|
- `title` - Page title
|
|
- `content` - Main content area
|
|
- `extra_css` - Additional CSS files
|
|
- `extra_js` - Additional JavaScript files
|
|
- `extra_head` - Additional head elements
|
|
|
|
---
|
|
|
|
## Template Patterns
|
|
|
|
### Extending Base Template
|
|
|
|
```html
|
|
{% extends "base.html" %}
|
|
|
|
{% block title %}Character List - Code of Conquest{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="character-list">
|
|
<h1>Your Characters</h1>
|
|
<!-- Content -->
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block extra_js %}
|
|
<script src="{{ url_for('static', filename='js/characters.js') }}"></script>
|
|
{% endblock %}
|
|
```
|
|
|
|
### Including Partial Templates
|
|
|
|
```html
|
|
{% include 'partials/character_card.html' with character=char %}
|
|
```
|
|
|
|
**Or without context:**
|
|
```html
|
|
{% include 'partials/navigation.html' %}
|
|
```
|
|
|
|
### Using Macros
|
|
|
|
**Define macro** in `templates/macros/form_fields.html`:
|
|
|
|
```html
|
|
{% macro text_input(name, label, value="", required=False, placeholder="") %}
|
|
<div class="form-group">
|
|
<label for="{{ name }}">{{ label }}{% if required %} <span class="required">*</span>{% endif %}</label>
|
|
<input type="text"
|
|
id="{{ name }}"
|
|
name="{{ name }}"
|
|
value="{{ value }}"
|
|
placeholder="{{ placeholder }}"
|
|
{% if required %}required{% endif %}>
|
|
</div>
|
|
{% endmacro %}
|
|
```
|
|
|
|
**Use macro:**
|
|
```html
|
|
{% from 'macros/form_fields.html' import text_input %}
|
|
|
|
<form>
|
|
{{ text_input('character_name', 'Character Name', required=True, placeholder='Enter name') }}
|
|
</form>
|
|
```
|
|
|
|
### Conditional Rendering
|
|
|
|
```html
|
|
{% if user.is_authenticated %}
|
|
<p>Welcome, {{ user.username }}!</p>
|
|
{% else %}
|
|
<a href="{{ url_for('auth.login') }}">Login</a>
|
|
{% endif %}
|
|
```
|
|
|
|
### Loops
|
|
|
|
```html
|
|
<div class="character-grid">
|
|
{% for character in characters %}
|
|
<div class="character-card">
|
|
<h3>{{ character.name }}</h3>
|
|
<p>Level {{ character.level }} {{ character.player_class.name }}</p>
|
|
</div>
|
|
{% else %}
|
|
<p>No characters found. <a href="{{ url_for('characters.create') }}">Create one</a>?</p>
|
|
{% endfor %}
|
|
</div>
|
|
```
|
|
|
|
---
|
|
|
|
## HTMX Integration in Templates
|
|
|
|
### Basic HTMX Attributes
|
|
|
|
```html
|
|
<!-- Form submission via HTMX -->
|
|
<form hx-post="{{ url_for('characters.create') }}"
|
|
hx-target="#character-list"
|
|
hx-swap="beforeend">
|
|
<!-- form fields -->
|
|
<button type="submit">Create Character</button>
|
|
</form>
|
|
```
|
|
|
|
### HTMX with Confirmation
|
|
|
|
```html
|
|
<button hx-delete="{{ url_for('characters.delete', character_id=char.character_id) }}"
|
|
hx-confirm="Are you sure you want to delete this character?"
|
|
hx-target="closest .character-card"
|
|
hx-swap="outerHTML">
|
|
Delete
|
|
</button>
|
|
```
|
|
|
|
### HTMX Polling
|
|
|
|
```html
|
|
<div hx-get="{{ url_for('sessions.status', session_id=session.session_id) }}"
|
|
hx-trigger="every 5s"
|
|
hx-target="#session-status">
|
|
Loading...
|
|
</div>
|
|
```
|
|
|
|
---
|
|
|
|
## Component Patterns
|
|
|
|
### Character Card Component
|
|
|
|
**File:** `templates/partials/character_card.html`
|
|
|
|
```html
|
|
<div class="character-card" data-character-id="{{ character.character_id }}">
|
|
<div class="card-header">
|
|
<h3>{{ character.name }}</h3>
|
|
<span class="level-badge">Lvl {{ character.level }}</span>
|
|
</div>
|
|
<div class="card-body">
|
|
<p class="class">{{ character.player_class.name }}</p>
|
|
<p class="stats">
|
|
HP: {{ character.current_hp }}/{{ character.max_hp }} |
|
|
Gold: {{ character.gold }}
|
|
</p>
|
|
</div>
|
|
<div class="card-actions">
|
|
<a href="{{ url_for('characters.view', character_id=character.character_id) }}" class="btn btn-primary">View</a>
|
|
<a href="{{ url_for('sessions.create', character_id=character.character_id) }}" class="btn btn-secondary">Play</a>
|
|
</div>
|
|
</div>
|
|
```
|
|
|
|
**Usage:**
|
|
```html
|
|
{% for character in characters %}
|
|
{% include 'partials/character_card.html' with character=character %}
|
|
{% endfor %}
|
|
```
|
|
|
|
### Alert Component Macro
|
|
|
|
**File:** `templates/components/alerts.html`
|
|
|
|
```html
|
|
{% macro alert(message, category='info', dismissible=True) %}
|
|
<div class="alert alert-{{ category }}{% if dismissible %} alert-dismissible{% endif %}" role="alert">
|
|
{{ message }}
|
|
{% if dismissible %}
|
|
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
|
|
<span aria-hidden="true">×</span>
|
|
</button>
|
|
{% endif %}
|
|
</div>
|
|
{% endmacro %}
|
|
```
|
|
|
|
**Usage:**
|
|
```html
|
|
{% from 'components/alerts.html' import alert %}
|
|
|
|
{{ alert('Character created successfully!', 'success') }}
|
|
{{ alert('Invalid character name', 'error') }}
|
|
```
|
|
|
|
---
|
|
|
|
## Accessibility Guidelines
|
|
|
|
### Semantic HTML
|
|
- Use proper heading hierarchy (`<h1>`, `<h2>`, etc.)
|
|
- Use `<nav>`, `<main>`, `<article>`, `<section>` elements
|
|
- Use `<button>` for actions, `<a>` for navigation
|
|
|
|
### ARIA Labels
|
|
```html
|
|
<button aria-label="Delete character">
|
|
<span class="icon icon-trash"></span>
|
|
</button>
|
|
|
|
<nav aria-label="Main navigation">
|
|
<!-- navigation links -->
|
|
</nav>
|
|
```
|
|
|
|
### Form Labels
|
|
```html
|
|
<label for="character-name">Character Name</label>
|
|
<input type="text" id="character-name" name="character_name">
|
|
```
|
|
|
|
### Focus Management
|
|
```html
|
|
<button class="btn" autofocus>Primary Action</button>
|
|
```
|
|
|
|
---
|
|
|
|
## Best Practices
|
|
|
|
### 1. Keep Templates DRY (Don't Repeat Yourself)
|
|
- Use includes for repeated sections
|
|
- Create macros for reusable components
|
|
- Extend base template consistently
|
|
|
|
### 2. Separate Logic from Presentation
|
|
- Avoid complex Python logic in templates
|
|
- Use template filters instead of inline calculations
|
|
- Pass fully-formed data from views
|
|
|
|
### 3. Use Template Comments
|
|
```html
|
|
{# This is a Jinja2 comment, not rendered in HTML #}
|
|
|
|
<!-- This is an HTML comment, visible in source -->
|
|
```
|
|
|
|
### 4. Whitespace Control
|
|
```html
|
|
{% for item in items -%}
|
|
<li>{{ item }}</li>
|
|
{%- endfor %}
|
|
```
|
|
|
|
### 5. Template Inheritance Hierarchy
|
|
```
|
|
base.html
|
|
├── dashboard/base.html (extends base.html)
|
|
│ ├── dashboard/index.html
|
|
│ └── dashboard/profile.html
|
|
└── sessions/base.html (extends base.html)
|
|
├── sessions/create.html
|
|
└── sessions/active.html
|
|
```
|
|
|
|
---
|
|
|
|
## Testing Templates
|
|
|
|
### Manual Checklist
|
|
- [ ] Templates extend base.html correctly
|
|
- [ ] All blocks are properly closed
|
|
- [ ] Variables are defined before use
|
|
- [ ] Forms have CSRF protection
|
|
- [ ] Links use `url_for()` instead of hardcoded paths
|
|
- [ ] Images have alt text
|
|
- [ ] Buttons have descriptive text or aria-labels
|
|
- [ ] Mobile responsive (test at 320px, 768px, 1024px)
|
|
|
|
### Template Linting
|
|
- Use `djLint` for Jinja2 template linting
|
|
- Ensure consistent indentation (2 or 4 spaces)
|
|
- Validate HTML with W3C validator
|
|
|
|
---
|
|
|
|
## Related Documentation
|
|
|
|
- **[HTMX_PATTERNS.md](HTMX_PATTERNS.md)** - HTMX integration patterns
|
|
- **[TESTING.md](TESTING.md)** - Manual testing guide
|
|
- **[/api/docs/API_REFERENCE.md](../../api/docs/API_REFERENCE.md)** - API endpoints for HTMX calls
|
|
|
|
---
|
|
|
|
**Document Version:** 1.0
|
|
**Created:** November 18, 2025
|
|
**Last Updated:** November 18, 2025
|