htmlx - ghdrako/doc_snipets GitHub Wiki
- https://four.htmx.org/announcements/2026-08-28-htmx-4.0.0-is-released
- https://semaphoreci.com/blog/htmx-react
- https://www.builder.io/blog/htmx-vs-react
Python:
-
https://github.com/paveldedik/ludic framwork with htmlx
Popularne polaczenie z pythonem dla niewielkich projektow, zeby nie trzeba bylo projektowac interfejsow w json miedzy frontend a backend jak w React. Wszystko jest w kodzie Pythona.
Przyklad
from fastapi import FastAPI
from fastapi.responses import HTMLResponse
app = FastAPI()
# Nasza "baza danych"
MOVIES = [
"Matrix", "Mroczny Rycerz", "Incepcja",
"Interstellar", "Władca Pierścieni", "Gladiator", "Gwiezdne Wojny"
]
# 1. Główny widok zwracający stronę HTML
@app.get("/", response_class=HTMLResponse)
def home():
return """
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/[email protected]"></script>
</head>
<body style="font-family: sans-serif; padding: 20px;">
<h2>Wyszukiwarka filmów (htmx + Python)</h2>
<!-- Magia htmx w jednym tagu input: -->
<input type="text"
name="q"
placeholder="Zacznij wpisywać tytuł..."
hx-get="/search"
hx-trigger="keyup changed delay:300ms"
hx-target="#wyniki">
<ul id="wyniki">
<!-- Tutaj htmx dynamicznie wklei wyniki z serwera -->
</ul>
</body>
</html>
"""
# 2. Endpoint wyszukiwania (zwraca HTML, NIE JSON!)
@app.get("/search", response_class=HTMLResponse)
def search(q: str = ""):
if not q:
return "" # Jeśli pole jest puste, zwracamy pusty string, żeby wyczyścić listę
# Szukamy filmów pasujących do zapytania
znalezione = [m for m in MOVIES if q.lower() in m.lower()]
if not znalezione:
return "<li><i>Brak wyników</i></li>"
# Python generuje i zwraca gotowe elementy <li> HTML
html_wynik = ""
for film in znalezione:
html_wynik += f"<li>{film}</li>"
return html_wynikPrzyklad z jinja template
mój_projekt/
├── main.py
└── templates/
├── index.html (Główna, pełna strona)
└── partials/
└── results.html (Tylko fragment z wynikami)
templates/index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Wyszukiwarka htmx</title>
<script src="https://unpkg.com/[email protected]"></script>
</head>
<body style="font-family: sans-serif; padding: 20px;">
<h2>Wyszukiwarka filmów (Jinja2)</h2>
<input type="text"
name="q"
placeholder="Zacznij wpisywać tytuł..."
hx-get="/search"
hx-trigger="keyup changed delay:300ms"
hx-target="#wyniki">
<ul id="wyniki">
<!-- Zostawiamy puste. Htmx wklei tutaj zawartość z results.html -->
</ul>
</body>
</html>
templates/partials/results.html
{% if not q %}
<!-- Jeśli zapytanie jest puste, nie wyświetlamy nic (czyści listę) -->
{% elif znalezione %}
<!-- Jeśli znaleziono filmy, pętla generuje tagi <li> -->
{% for film in znalezione %}
<li>{{ film }}</li>
{% endfor %}
{% else %}
<!-- Jeśli wpisano tekst, ale nic nie znaleziono -->
<li><i>Brak wyników dla hasła "{{ q }}"</i></li>
{% endif %}
main.py
from fastapi import FastAPI, Request
from fastapi.responses import HTMLResponse
from fastapi.templating import Jinja2Templates
app = FastAPI()
# Konfiguracja Jinja2 - wskazujemy folder z szablonami
templates = Jinja2Templates(directory="templates")
MOVIES = [
"Matrix", "Mroczny Rycerz", "Incepcja",
"Interstellar", "Władca Pierścieni", "Gladiator", "Gwiezdne Wojny"
]
@app.get("/", response_class=HTMLResponse)
async def home(request: Request):
# Zwracamy pełną stronę
return templates.TemplateResponse(
"index.html",
{"request": request}
)
@app.get("/search", response_class=HTMLResponse)
async def search(request: Request, q: str = ""):
# Logika wyszukiwania
if not q:
znalezione = []
else:
znalezione = [m for m in MOVIES if q.lower() in m.lower()]
# Zwracamy TYLKO MAŁY FRAGMENT (partial) z odpowiednimi zmiennymi
return templates.TemplateResponse(
"partials/results.html",
{"request": request, "q": q, "znalezione": znalezione}
)test_main.py
from fastapi.testclient import TestClient
from bs4 import BeautifulSoup
from main import app # Importujesz aplikację z pliku main.py
client = TestClient(app)
def test_strona_glowna_zawiera_formularz():
response = client.get("/")
assert response.status_code == 200
# Sprawdzamy, czy poprawnie wpięliśmy atrybuty htmx
assert 'hx-get="/search"' in response.text
def test_wyszukiwanie_znajduje_film():
# Symulujemy zapytanie wysłane przez htmx
response = client.get("/search?q=mat")
assert response.status_code == 200
# Zamiast asercji na strukturze JSON, sprawdzamy zwrócony HTML (partial)
assert "<li>Matrix</li>" in response.text
assert "<li>Mroczny Rycerz</li>" not in response.text
def test_wyszukiwanie_brak_wynikow():
response = client.get("/search?q=avengers")
assert response.status_code == 200
assert "Brak wyników dla hasła" in response.text
def test_wyszukiwanie_puste_zapytanie():
response = client.get("/search?q=")
assert response.status_code == 200
# Puste zapytanie zgodnie z logiką zwraca pusty string
assert response.text.strip() == ""
def test_wyszukiwanie_zwraca_odpowiednia_liczbe_elementow():
response = client.get("/search?q=w")
soup = BeautifulSoup(response.text, "html.parser")
# Weryfikujemy, czy wygenerowały się dokładnie 2 elementy <li> (Władca Pierścieni, Gwiezdne Wojny)
elementy_li = soup.find_all("li")
assert len(elementy_li) == 2