Introduction: Your First Dynamic Web App with HTMX!

Welcome to Chapter 15! This is where all the foundational HTMX knowledge we’ve been carefully building comes together in a truly exciting way. In this chapter, we’re going to embark on our very first full-fledged project: a Dynamic Todo List Application.

Why a todo list? Because it’s the perfect blend of simplicity and complexity to showcase HTMX’s power. We’ll be creating, listing, updating (marking complete), and deleting items, all without writing a single line of client-side JavaScript for our dynamic interactions! You’ll see firsthand how HTMX allows you to build modern, interactive user interfaces using just HTML, backed by a lightweight server. This project will solidify your understanding of hx-get, hx-post, hx-put, hx-delete, hx-target, hx-swap, and more, giving you the confidence to tackle more complex applications.

Before we dive in, make sure you’re comfortable with the core HTMX attributes we’ve covered in previous chapters, and have a basic understanding of server-side web frameworks (we’ll use Python with Flask for our backend, but the HTMX concepts apply universally). Ready to see your HTML come alive? Let’s get building!

Core Concepts: The Anatomy of a Dynamic Todo App

A dynamic todo list isn’t just a static HTML page; it needs to interact with a server to store and retrieve data. Here’s what we’ll be building and the core concepts involved:

1. The Backend: Our Data Store and HTML Provider

Every dynamic web application needs a server. For our todo list, the backend will be responsible for a few key things:

  • Storing Todos: We’ll keep our todo items in a simple in-memory list for this tutorial. In a real application, this would be a database.
  • Serving the Initial Page: When a user first navigates to our app, the backend will send them the base HTML page.
  • Handling HTMX Requests: When HTMX sends a request (like adding a todo or marking one complete), the backend will process it, update its data, and then send back just the HTML fragment that needs to be updated on the client. This is the magic of HTMX!

We’ll use Flask, a lightweight Python web framework, for our backend. It’s perfect for quickly setting up endpoints that return HTML.

2. The Frontend: HTML with HTMX Superpowers

Our frontend will be pure HTML. We’ll leverage HTMX attributes to:

  • Add New Todos: A form with hx-post will send new todo data to the server.
  • Display Todo List: An area on the page will dynamically load and update the list of todos using hx-get.
  • Toggle Todo Completion: Each todo item will have an interactive element (like a checkbox) that uses hx-put to tell the server to change its status.
  • Delete Todos: A button on each todo item will use hx-delete to remove it from the list.

The key idea here is that the server doesn’t send back entire pages. Instead, it sends small snippets of HTML (partials) that HTMX then seamlessly injects into the right place on your existing page. This makes the user experience feel fast and responsive, much like a Single Page Application (SPA), but with the simplicity of traditional server-rendered HTML.

3. Understanding the Flow: Request, Process, Swap

Let’s visualize the typical HTMX interaction for our todo app:

  1. User Action: You click the “Add Todo” button or mark a checkbox.
  2. HTMX Request: The HTMX attribute (hx-post, hx-put, hx-delete) on that element triggers an AJAX request to a specific URL on your backend.
  3. Server Processing: Your Flask backend receives the request, processes the data (e.g., adds a todo to the list, updates its status), and generates a new HTML fragment.
  4. Server Response: The backend sends this HTML fragment back as the response.
  5. HTMX Swapping: HTMX receives the HTML fragment and, based on the hx-target and hx-swap attributes, intelligently updates only the specified part of your page.

This cycle repeats for every dynamic interaction, giving you a smooth, interactive experience without complex JavaScript frameworks.

Step-by-Step Implementation: Building Our Todo List

Let’s get our hands dirty! We’ll start by setting up our Flask backend and then incrementally build our HTML frontend with HTMX.

Step 1: Setting Up Your Project Environment

First, let’s create a project directory and set up a virtual environment.

  1. Create Project Folder:

    mkdir htmx-todo-app
    cd htmx-todo-app
    
  2. Create and Activate Virtual Environment:

    python -m venv venv
    # On macOS/Linux:
    source venv/bin/activate
    # On Windows:
    .\venv\Scripts\activate
    
  3. Install Flask:

    pip install Flask==3.0.3
    

    Why Flask==3.0.3? As of late 2024/early 2025, Flask 3.0.3 is the latest stable release. It’s always good practice to pin your dependencies for reproducibility!

  4. Create app.py: This will be our main backend file.

    touch app.py
    
  5. Create templates Directory: Flask expects HTML templates to be in a templates folder.

    mkdir templates
    

Step 2: The Barebones Flask Backend

Let’s start app.py with a basic Flask application that serves a simple HTML page.

File: app.py

from flask import Flask, render_template, request, redirect, url_for
import uuid # For generating unique IDs for our todos

app = Flask(__name__)

# --- In-memory "Database" ---
# In a real application, this would be a database like PostgreSQL, SQLite, etc.
# For simplicity, we'll store todos in a list in memory.
todos = [] # Each todo will be a dictionary: {'id': '...', 'task': '...', 'completed': False}

# --- Routes ---

@app.route("/")
def index():
    """Serves the main HTML page."""
    return render_template("index.html")

# --- Run the app ---
if __name__ == "__main__":
    app.run(debug=True)
  • from flask import Flask, render_template, request, redirect, url_for: We import the necessary components from Flask. Flask for our app, render_template to serve HTML files, request to handle incoming data, redirect and url_for (we’ll use these later).
  • import uuid: We’ll use this to give each todo item a unique identifier, which is crucial for updating and deleting specific items.
  • app = Flask(__name__): This line creates our Flask application instance.
  • todos = []: Our super-simple “database.” It’s just a Python list that will hold dictionaries representing our todo items.
  • @app.route("/"): This decorator tells Flask that the index() function should be called when someone visits the root URL (/) of our application.
  • return render_template("index.html"): This tells Flask to find and render a file named index.html from our templates directory.
  • if __name__ == "__main__": app.run(debug=True): This standard Python construct ensures our Flask app runs when the script is executed directly. debug=True is great for development as it provides helpful error messages and automatically reloads the server on code changes.

Step 3: The Initial HTML Page

Now, let’s create our index.html file inside the templates directory. This will be the foundation of our frontend.

File: templates/index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>HTMX Todo App</title>
    <!-- Basic Tailwind CSS for quick styling (optional but nice!) -->
    <script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="bg-gray-100 min-h-screen flex items-center justify-center">
    <div class="bg-white p-8 rounded-lg shadow-md w-full max-w-md">
        <h1 class="text-3xl font-bold mb-6 text-center text-gray-800">My HTMX Todo List</h1>

        <!-- This is where our todo form will go -->
        <div id="todo-form-container">
            <!-- We'll add the form here shortly -->
        </div>

        <hr class="my-6 border-gray-300">

        <!-- This is where our todo list will be displayed -->
        <div id="todo-list-container">
            <p class="text-center text-gray-500">Loading todos...</p>
        </div>
    </div>

    <!-- HTMX Script - CRITICAL! -->
    <script src="https://unpkg.com/[email protected]/dist/htmx.min.js"
            integrity="sha384-yqH74S9VqL9K/wQ545Q4v0+uF+tE7+L1+B8jP/N8j+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+Q+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+G+2.  The `index.html` file will be replaced by the content fetched from the server.
    3.  **The new list is inserted into the `todo-list-container`**.

This is why we want `hx-swap="innerHTML"` on the parent container, not on the form. If we put it on the form, the form would be replaced by the list, which isn't what we want!

### Step 7: Toggling Todo Completion

Our todo list is great, but what if we want to mark an item as done? Let's add that functionality. This will involve:

1.  Adding a checkbox (or similar element) to each todo item.
2.  Using `hx-put` on the checkbox to update the server.
3.  Having the server toggle the `completed` status for that todo.
4.  Having the server return the *updated HTML for that single todo item*, which HTMX will then swap in place.

First, let's update our `_todo_item.html` partial to include a checkbox and use a bit of conditional styling.

**File: `templates/_todo_item.html` (Update)**
```html
<li id="todo-{{ todo.id }}" class="flex items-center justify-between p-3 border-b last:border-b-0
    {% if todo.completed %} bg-green-50 text-gray-500 line-through {% endif %}">
    <div class="flex items-center">
        <input type="checkbox"
               class="form-checkbox h-5 w-5 text-blue-600 rounded"
               {% if todo.completed %} checked {% endif %}
               hx-put="/todos/{{ todo.id }}/toggle"
               hx-target="#todo-{{ todo.id }}"
               hx-swap="outerHTML">
        <span class="ml-3 text-lg">{{ todo.task }}</span>
    </div>
    <button class="text-red-500 hover:text-red-700 font-bold"
            hx-delete="/todos/{{ todo.id }}"
            hx-target="#todo-{{ todo.id }}"
            hx-swap="outerHTML">
        &times;
    </button>
</li>
  • <li id="todo-{{ todo.id }}">: We give each li a unique ID. This is crucial for hx-target to work correctly when we want to update or delete a specific item.
  • {% if todo.completed %} bg-green-50 text-gray-500 line-through {% endif %}: This Jinja2 conditional statement applies Tailwind CSS classes if the todo.completed flag is True, visually indicating it’s done.
  • <input type="checkbox" ... hx-put="/todos/{{ todo.id }}/toggle" hx-target="#todo-{{ todo.id }}" hx-swap="outerHTML">:
    • hx-put="/todos/{{ todo.id }}/toggle": When this checkbox is clicked, an HTTP PUT request is sent to /todos/<todo_id>/toggle. PUT is conventionally used for updating an existing resource.
    • hx-target="#todo-{{ todo.id }}": Tells HTMX to target the specific li element with the matching ID.
    • hx-swap="outerHTML": This is key! Instead of just swapping the inner content, outerHTML replaces the entire targeted element (the li itself) with the new HTML received from the server. This is perfect for updating the whole item, including its id and conditional styling.

Now, let’s add the corresponding backend route in app.py.

File: app.py (Add new route)

# ... (existing imports, app, todos list) ...

@app.route("/")
def index():
    """Serves the main HTML page."""
    return render_template("index.html")

@app.route("/todos", methods=["GET", "POST"])
def todo_list():
    """Handles GET to list todos and POST to add a new todo."""
    if request.method == "POST":
        task = request.form.get("task")
        if task:
            new_todo = {"id": str(uuid.uuid4()), "task": task, "completed": False}
            todos.append(new_todo)
        # After adding, render the updated list
        return render_template("_todo_list.html", todos=todos)
    else: # GET request
        return render_template("_todo_list.html", todos=todos)

@app.route("/todos/<todo_id>/toggle", methods=["PUT"])
def toggle_todo(todo_id):
    """Toggles the completion status of a specific todo item."""
    for todo in todos:
        if todo["id"] == todo_id:
            todo["completed"] = not todo["completed"] # Flip the status
            # Render and return just the updated todo item HTML
            return render_template("_todo_item.html", todo=todo)
    # If todo not found, return an empty response or error (for simplicity, we'll assume it's found)
    return "", 404 # Not Found, or an appropriate error response
  • @app.route("/todos/<todo_id>/toggle", methods=["PUT"]): This new route handles PUT requests to a URL like /todos/123/toggle. The <todo_id> part is a variable that Flask captures from the URL.
  • for todo in todos:: We loop through our in-memory todos list.
  • if todo["id"] == todo_id:: We find the todo item that matches the ID from the URL.
  • todo["completed"] = not todo["completed"]: We simply flip the boolean value of the completed flag.
  • return render_template("_todo_item.html", todo=todo): This is the crucial part! Instead of returning the entire list, we only render and return the HTML for the single updated todo item. HTMX, with hx-target="#todo-{{ todo.id }}" and hx-swap="outerHTML", will then replace the old li element with this new one, preserving the rest of the page.

Test it out! Run your Flask app (python app.py), refresh your browser, and try clicking the checkboxes. You should see the styling change instantly without a full page reload!

Step 8: Deleting Todo Items

Our todo list is almost complete, but we need a way to remove items. This will be very similar to toggling completion:

  1. Add a delete button to each todo item in _todo_item.html.
  2. Use hx-delete on the button.
  3. Have the server remove the todo from its list.
  4. Have the server return an empty response, allowing HTMX to remove the element.

We already added the delete button in _todo_item.html in the previous step, so let’s review its attributes:

File: templates/_todo_item.html (Review Delete Button)

<li id="todo-{{ todo.id }}" class="flex items-center justify-between p-3 border-b last:border-b-0
    {% if todo.completed %} bg-green-50 text-gray-500 line-through {% endif %}">
    <div class="flex items-center">
        <input type="checkbox"
               class="form-checkbox h-5 w-5 text-blue-600 rounded"
               {% if todo.completed %} checked {% endif %}
               hx-put="/todos/{{ todo.id }}/toggle"
               hx-target="#todo-{{ todo.id }}"
               hx-swap="outerHTML">
        <span class="ml-3 text-lg">{{ todo.task }}</span>
    </div>
    <button class="text-red-500 hover:text-red-700 font-bold"
            hx-delete="/todos/{{ todo.id }}"
            hx-target="#todo-{{ todo.id }}"
            hx-swap="outerHTML">
        &times;
    </button>
</li>
  • <button ... hx-delete="/todos/{{ todo.id }}" hx-target="#todo-{{ todo.id }}" hx-swap="outerHTML">:
    • hx-delete="/todos/{{ todo.id }": When this button is clicked, an HTTP DELETE request is sent to /todos/<todo_id>. DELETE is conventionally used for removing a resource.
    • hx-target="#todo-{{ todo.id }}": Targets the specific li element.
    • hx-swap="outerHTML": When the server responds, HTMX will replace the targeted li element. If the server sends an empty response (as we will), outerHTML effectively removes the element from the DOM!

Now, let’s add the corresponding backend route in app.py.

File: app.py (Add new route)

# ... (existing imports, app, todos list, other routes) ...

@app.route("/todos/<todo_id>", methods=["DELETE"])
def delete_todo(todo_id):
    """Deletes a specific todo item."""
    global todos # Declare that we intend to modify the global 'todos' list

    # Filter out the todo with the matching ID
    # This creates a new list without the deleted item
    todos = [todo for todo in todos if todo["id"] != todo_id]

    # HTMX expects *something* in response. An empty string with a 200 OK status
    # is perfectly valid and tells HTMX to proceed with the swap (removing the element).
    return "", 200
  • @app.route("/todos/<todo_id>", methods=["DELETE"]): This route handles DELETE requests to a URL like /todos/123.
  • global todos: Since we’re reassigning the todos list within the function (not just modifying an item inside it), we need to declare todos as global. This tells Python to modify the list defined outside the function’s scope.
  • todos = [todo for todo in todos if todo["id"] != todo_id]: This is a Python list comprehension that creates a new list containing all todos except the one whose ID matches todo_id. We then reassign our todos variable to this new list.
  • return "", 200: We return an empty string with an HTTP status code of 200 OK. When HTMX receives an empty response and hx-swap="outerHTML" is specified, it effectively removes the targeted element from the DOM. Clean and simple!

Test it out! Run your Flask app, refresh, add some todos, mark some complete, and try deleting them. You now have a fully functional, dynamic todo list application built with HTMX and a minimal Flask backend! How cool is that?


Mini-Challenge: Add a “Clear Completed” Button

You’ve done an amazing job building the core functionality. Now, for a little challenge to test your understanding!

Challenge: Add a “Clear Completed” button somewhere on your index.html page (perhaps below the todo form). When clicked, this button should send a request to the server, which then deletes all completed todo items. After the server processes this, the todo list should automatically refresh, showing only the remaining (incomplete) items.

Hint:

  1. You’ll need a new backend route (e.g., DELETE /todos/completed).
  2. The backend route will filter the todos list to keep only the incomplete items.
  3. Instead of returning an empty response, think about what HTML fragment the server should send back to refresh the entire list container. Remember how we load the initial list?

What to Observe/Learn: This challenge will help you think about:

  • How to trigger actions that affect multiple elements.
  • When to refresh a whole section of the page versus just a single element.
  • Designing backend endpoints for batch operations.

Take your time, experiment, and don’t be afraid to try different HTMX attributes and backend responses!


Common Pitfalls & Troubleshooting

Even with baby steps, developing dynamic applications can sometimes lead to unexpected behavior. Here are a few common pitfalls you might encounter and how to troubleshoot them:

  1. HTMX Not Loading/Working:

    • Symptom: Your HTMX attributes (hx-post, etc.) don’t seem to do anything. The page might refresh entirely, or nothing happens.
    • Fix: Double-check that you’ve included the HTMX script in your index.html file, usually just before the closing </body> tag. Ensure the URL for the CDN is correct.
      <script src="https://unpkg.com/[email protected]/dist/htmx.min.js" ...></script>
      
    • Check: Open your browser’s developer tools (F12), go to the “Network” tab, and observe if any requests are being sent when you interact with your HTMX-enabled elements. Also, check the “Console” for any JavaScript errors related to HTMX.
  2. Incorrect hx-target or hx-swap:

    • Symptom: An action occurs, but the wrong part of the page updates, or no update happens, or the entire page seems to disappear.
    • Fix: Carefully review your hx-target and hx-swap attributes.
      • hx-target must point to an existing element’s ID or CSS selector. If it’s #my-element, make sure there’s an <div id="my-element"> on the page.
      • hx-swap determines how the content is replaced.
        • innerHTML (default): Replaces content inside the target.
        • outerHTML: Replaces the entire target element (including its own tags). Useful for updating an item itself or removing it.
        • afterbegin, beforeend, afterend, beforebegin: Inserts content relative to the target.
    • Check: Use the “Elements” tab in your browser’s developer tools. After an HTMX interaction, observe which HTML elements are changing. If the target is wrong, you’ll see the changes in an unexpected place or no changes at all.
  3. Backend Not Returning HTML (or Returning Bad HTML):

    • Symptom: HTMX requests are sent, the backend processes them, but the frontend doesn’t update correctly. You might see strange characters or nothing.
    • Fix: HTMX expects HTML fragments by default. Ensure your Flask routes that respond to HTMX requests are returning render_template() calls with the appropriate partials (_todo_list.html or _todo_item.html). If you accidentally return JSON, HTMX won’t know what to do with it for a standard swap.
    • Check: In the “Network” tab of your browser’s developer tools, click on the HTMX request. Look at the “Response” tab. Is it valid HTML? Is it the HTML you expected to be swapped into the page?
  4. global Keyword in Flask (Python Specific):

    • Symptom: When trying to delete or entirely replace the todos list in app.py, you might get an UnboundLocalError (e.g., local variable 'todos' referenced before assignment).
    • Fix: Remember that when you reassign a global variable inside a function (like todos = [new_list]), you need to explicitly declare it as global at the beginning of the function: global todos. This tells Python you’re not creating a new local variable, but modifying the global one. If you’re just changing items within the list (e.g., todo['completed'] = True), you don’t need global.

By keeping these common issues in mind and making good use of your browser’s developer tools, you’ll be able to quickly diagnose and fix most problems that come your way!

Summary: You’re Building Dynamic Web Apps!

Congratulations! You’ve successfully built your first dynamic web application using HTMX and a Flask backend. This is a huge milestone! Let’s recap what you’ve achieved:

  • Backend Setup: You’ve set up a basic Flask application, serving an initial HTML page and handling dynamic requests.
  • HTMX Integration: You’ve included the HTMX library and understood its role in making your HTML interactive.
  • Creating Todos: You used a form with hx-post to send data to the server and update the list dynamically.
  • Listing Todos: You implemented hx-get to fetch an HTML partial from the server and display your todo list.
  • Toggling Completion: You leveraged hx-put to update the status of individual todo items, using hx-target and hx-swap="outerHTML" to replace just that item’s HTML.
  • Deleting Todos: You used hx-delete to remove items, again employing hx-target and hx-swap="outerHTML" with an empty server response to remove the element from the DOM.
  • Modular HTML: You learned the power of Jinja2 templates and partials (_todo_item.html, _todo_list.html) for cleaner, reusable HTML.

You’ve seen how HTMX empowers you to build rich, interactive user experiences without the complexity of traditional JavaScript frameworks. This approach simplifies development, reduces context switching, and leverages the power of HTML and the server where they shine.

What’s Next?

In the upcoming chapters, we’ll build upon this foundation. We’ll explore:

  • More Advanced HTMX Patterns: Modals, dependent selects, pagination, and more.
  • Error Handling and User Feedback: How to gracefully handle errors and provide visual cues to the user.
  • Styling and UX Improvements: Integrating CSS frameworks more deeply and refining the user experience.
  • Interacting with External APIs: Fetching data from third-party services.

Keep practicing, keep building, and get ready to unlock even more of HTMX’s potential!