Skip to content

Hotwire — HTML-driven UI Layer

Hotwire je interaktivní UI vrstva admin panelu. Umožňuje AJAX navigaci, odesílání formulářů a JSON akce bez psaní JavaScriptu — vše se řídí data-* atributy přímo v HTML.


Analogie s HTMX a Hotwire (Rails)

Název vychází z podobné filosofie jako populární knihovny, ale implementace je vlastní a minimální.

Koncept HTMX Hotwire (Rails) Petrovo CMS Hotwire
Navigace bez JS hx-get Turbo Drive data-content
JSON akce hx-post + trigger Turbo Streams data-json
Formuláře hx-post na form Turbo Frames data-form
Filtry hx-trigger="change" data-filter
Callback hx-on / events Stimulus controller data-after
Potvrzení hx-confirm data-confirm data-confirm
Cílový element hx-target Frame ID vždy #content

Klíčový rozdíl: HTMX a Hotwire jsou obecné knihovny s desítkami atributů. Petrovo CMS Hotwire je záměrně minimální — pouze atributy, které admin panel reálně potřebuje. Žádná abstrakce navíc.


Filosofie

  • HTML-driven: Logika je v HTML atributech, ne v JS souborech
  • Progressive: Stránka funguje i bez JS (fallback na href)
  • No layers: Přímé fetch callsites bez interceptorů nebo middleware
  • Transparent: Co vidíš v HTML je přesně to, co se stane

Architektura

src/admin/js/hotwire/
├── index.js          # Centrální re-export
├── setup.js          # Inicializace a orchestrace
├── actions.js        # Event delegation — všechny data-* handlery
├── fetch_content.js  # GET/POST → HTML → inject do #content
├── fetch_json.js     # POST/DELETE → JSON + message + callback
├── callbacks.js      # Window-level callback funkce
└── core/
    ├── request.js    # Low-level fetch wrapper (CSRF, timeout, session)
    └── response.js   # JSON parsing a normalizace

Tok dat

HTML (data-* atributy)
         ↓
  setupContentLoading()       ← zavolá se jednou při startu
         ↓
   actions.js
   event delegation
         ↓
  ┌──────┼──────────────┐
  ↓      ↓              ↓
data-   data-json    data-form
content              data-filter
  ↓      ↓              ↓
  └──────┴──────────────┘
         ↓
  fetchContent()  nebo  fetchJson()
         ↓
       request()          ← CSRF header, timeout, session check
         ↓
  HTML → inject #content
  JSON → message + callback

Inicializace

setupContentLoading() se volá jednou v main.js při startu admin panelu:

import { setupContentLoading } from './hotwire/index.js';

setupContentLoading();

Co setup provede:

  1. Inicializuje history.replaceState() pro aktuální URL
  2. Zavolá setupActions() — registruje všechny event listenery
  3. Přidá popstate listener (tlačítka Zpět/Vpřed → location.reload())

Po každém načtení HTML do #content se automaticky zavolá afterContentLoad(), která inicializuje tab systém a autofocus na formulářích.


Moduly

actions.js — Event delegation

Jedno místo pro všechny HTML atributy. Registruje tři globální listenery na document:

  1. Click — sleduje poslední stisknuté button[name] (pro CRUD formy)
  2. Click — zpracovává [data-content] a [data-json]
  3. Change — auto-submit pro [data-filter] select
  4. Submit — zpracovává [data-filter] a [data-form]

fetch_content.js — HTML akce

GET nebo POST → HTML odpověď → inject do #content.

Používá se pro navigaci (list, detail, formulář). Po úspěšném načtení volá afterContentLoad() a aktualizuje URL přes history.pushState().

fetch_json.js — JSON akce

POST nebo DELETE → JSON odpověď → zobrazí message, volá callback.

Používá se pro akce bez přechodu na jinou stránku (smazání záznamu, toggle stavu). Pokud odpověď obsahuje data.listUrl, automaticky načte HTML a aktualizuje #content.

core/request.js — HTTP vrstva

Low-level fetch wrapper. Přidává:

  • CSRF header z state.csrfToken
  • Automatický timeout (konfigurovatelný přes data-timeout nebo data-unlimited)
  • Detekci vypršení session — pokud odpověď obsahuje login formulář, přesměruje na /ADMIN/{lang}/login
  • Auto-detekci JSON vs HTML odpovědi

core/response.js — Normalizace

Normalizuje odpověď do jednotného formátu {type: 'html', html} nebo {type: 'json', ...}. Pokud JSON parse selže, vrátí raw HTML (graceful fallback).