Hotwire - use case'y

W tym wątku będę opisywał, jak zaimplementować typowe use case’y za pomocą HotWire.

Use Case 1 - Formularz z walidacją (na przykładzie formularza logowania)

Bardzo typowy przypadek - wypełniamy formularz i wciskamy submit. Jeżeli jest wypełniony poprawnie, to przechodzimy do nowego widoku. Jeżeli nie, to w formularzu wyświetlana jest customowa wiadomość (lub wiele wiadomości, dla każdego pola z osobna).

Ten ficzer da radę zaimplementować przy użyciu samego TurboDrive’a (część Hotwire). Nie trzeba pisać żadnego JS-a na froncie. Omówię to na podstawie przykładu formularza logowania z Sealiousem na backendzie.

Sposób działania

Zanim wkleję kod, opiszę, jak taki schemat działałby w przypadku tradycyjnej, wręcz old-schoolowej, niewzbogaconej hotwire-m, renderowanej po stronie serwera strony.

  1. Użytkownik robi GET na /login. Zwracamy mu formularz z metodą POST na /login.
  2. Użytkownik wypełnia formularz i wciska submit. Przeglądarka robi POST na /login i w body daje wartości pól z formularza.
  3. Serwer sprawdza poprawność loginu i hasła.
    • Jeżeli jest niepoprawne, to w odpowiedzi na zapytanie zwraca ponownie formularz logowania, z doklejoną wiadomością, np. “Hasło niepoprawne”. Dla wygody użytkownika wartość pola username jest wpychana do wysyłanego inputa w HTML-u, aby nie musiał jej wpisywać ponownie
    • Jeżeli logowanie przebiegło pomyślnie, to serwer ustawia cookie i robi redirect na /user. Endpoint /user zwraca użytkownikowi informację o tym, jako który użytkownik jest zalogowany.

W apce która wykorzystuje funkcje hotwire’a kod takiego formularza prezentuje się nastepująco:

function LoginForm(username = "", error_message?: string): string {
  return /* HTML */ `
    <turbo-frame id="login">
      <form method="POST" action="/login" data-turbo-frame="_top">
        ${error_message ? `<div>${error_message}</div>` : ""}
        <label for="username">
          Nazwa użytkownika:
          <input
            id="username"
            name="username"
            type="text"
            value="${username}"
            required
          />
        </label>
        <label for="password"
          >Hasło:
          <input
            id="password"
            name="password"
            type="password"
            value=""
            required
        /></label>
        <input type="submit" value="Zaloguj" />
      </form>
    </turbo-frame>
  `;
}
router.get("/login", async (ctx) => {
  ctx.body = html(LoginForm());
});

router.post("/login", Middlewares.parseBody(), async (ctx) => {
  try {
    const session_id = await ctx.$app.collections.sessions.login(
      ctx.$body.username as string,
      ctx.$body.password as string
    );
    ctx.cookies.set("sealious-session", session_id, {
      maxAge: 1000 * 60 * 60 * 24 * 7,
      secure: ctx.request.protocol === "https",
      overwrite: true,
    });
    ctx.redirect("/user");
  } catch (e) {
    ctx.status = 422;
    ctx.body = html(
      LoginForm(ctx.$body.username as string, (e as Error).message)
    );
  }
});

Jak widać, kod renderujący HTML jest wyciągnięty do osobnej funkcji - LoginForm. Można o niej myśleć trochę jak o takim komponencie Reactowym - dostaje propsy i zwraca HTML/DOM. Po prostu zamiast zarządzać stanem za pomocą JS-a na froncie, wyręcza nas w tym Hotwire i tradycyjne mechanizmy działania formularzy w aplikacjach webowych.

Formularz ma ustawiony param data-turbo-frame="_top". W ten sposób mówimy Turbo, aby po pomyślnym wypełnieniu formularza zamienić całą aktualnie widoczną stronę na HTML zwrócony przez serwer. Gdyby nie ten argument, to nastąpiłoby domyślne dla Turbo zachowanie, które (w dużym skrócie) polega po prostu na podmianie tylko contentu frame’a, w którym znajduje się formularz.

Gdy login lub hasło są nieprawidłowe, zwracamy formularz ponownie i do jego HTML-a dopisujemy komunikat z treścią błędu. Ustawiamy wtedy HTTP status na 422. Sygnalizuje to Turbowi, żeby tylko podmienił zawartość aktualnego frame’a na tę zwróconą przez serwer.

Mając mindset nastawiony i wytrenowany do myślenia w SPA powyższe rozwiązanie może wydawać się egzotyczne, ale w dużej mierze tak działał internet przez dobre dwie dekady zanim przyszła rewolucja na froncie :wink:

Kilka obserwacji:

  1. Powyższy kod zadziała z wyłączonym JS oraz bez Hotwire.
  2. Hotwire podmienia zapytania z form i wykonuje zamiast nich odpowiadające im zapytania AJAX w tle, więc bez SPA osiągamy to, co przyciągnęło ludzi do SPA - strona nie “miga” przy przechodzeniu z jednego widoku w drugi
2 Likes