5. Web-Apps mit Elm

5.3 Auf Benutzerinput reagieren

Speichern Sie Web04ComputeFib.elm in Ihrem Verzeichnis elm/web. Werfen Sie elm reactor an und laden die Datei. Sie sehen hier ein extrem einfaches Beispiel einer Webseite, die mit Nutzerinput agiert. Der Nutzer kann etwas eingeben (eine Zahl \(n\)), und auf Knopfdruck wird dann die Fibonacci-Zahl \(F_n\) berechnet.

Auch wenn diese Webseite extrem einfach ist, illustriert sie bereits, wodurch das Verhalten einer Webseite definiert wird, und welche Design-Entscheidungen wir treffen müssen.

  1. Was ist der interne "Zustand" der Webseite, also die Daten, die gespeichert werden? In Web04ComputeFib.elm wäre das der Nutzerinput und der berechnete Output.
  2. Welche Art von User-Input soll es eben? Im obigen Beispiel wäre das (1) der Nutzer ändert den Inhalt des Textfeldes und (2) der Nutzer klickt den Knopf.
  3. Wie übersetzt sich der interne Zustand in die Darstellung der Webseite in einem Browser? Welche Elemente der Webseite können wie und wann welchen User-Input entgegennehmen?
  4. Wie verändert der Benutzerinput den internen Zustand?

In Elm werden diese Fragen wie folgt spezifiziert:

  1. Der interne Zustand der Seite, auch Modell genannt, ist ein Objekt vom Typ Model. Diesen müssen Sie selbst definieren. In unserem Beispiel bietet es sich an, Model als Record mit zwei String zu definieren, da wir ja Inhalt des Textfeldes und den Outputsring speichern müssen. Im allgemeinen kann Model sehr komplex werden. Es bietet sich bei der Entwicklung einer neuen App auch an, mit einem einfachen Modell zu beginnen und es schrittweise zu erweitern.
  2. Welche Art von User-Input es gibt, ist durch den Datentyp Msg (Message) festgelegt. Diesen Datentyp müssen Sie auch selbst entwerfen. In unserem Beispiel gibt es nur zwei Messages: ButtonClicked und InputChanged.
  3. Wie sich der Zustand in eine Darstellung im Browser übersetzt, wird in der Funktion view festgelegt. Hier legen Sie auch fest, welche Html-Elemente (z.B. button) User-Input entgegennehmen können und welche Msg dann an den Elm-Code geschickt werden soll. Daher hat der Datentyp Html auch eine Typenvariable, die eben offen lässt, von welchem Typ die Messages sind: in unserem Beispiel eben Html Msg. Die Signatur von view ist daher auch view : Model -> Html Msg.
  4. Wie sich der Zustand, also das Modell, bei Benutzerinput verändert, legt die Funktion update fest. Hier beschreiben Sie, wie aus dem alten Model beim Empfang einer Msg ein neues Model berechnet wird. Daher die Signatur update: Msg -> Model -> Model.

Es ist Standard, diese Datentypen Model und Msg zu nennen und die Funktionen view und update. Theoretisch können Sie beliebige Namen verwenden; es empfiehlt sich aber, sich an Konventionen zu halten.

Es gibt noch einen fünften Punkt, um das Verhalten einer Webseite zu spezifizieren, den wir unter den Teppich gekehrt haben: den Anfangszustand, also ein Objekt initialModel : Model. Die ganze Verhalten Webseite wird dann eben durch initialModel, view und update spezifiziert, daher die Codezeilen

    main = 
        Browser.sandbox
            { init = initialModel
            , view = view
            , update = update
            }

Lassen Sie sich nicht von Zeilen wie view = view verwirren! Das "rechte" view ist der Name unserer Funktion, die wir implementiert haben (in Zeile 33). Das "linke" view ist der Name der Variable im Record

    { init : model
    , update : msg -> model -> model
    , view : model -> Html msg }

den die Funktion Browser.sandbox als Input nimmt. Diese Namen können Sie nicht verändern, da sie nicht von Ihnen festgelegt worden sind, sondern irgendwo (wahrscheinlich im Modul Browser) stehen.

Event-Listener

Betrachten Sie folgende Code-Zeile von Web04ComputeFib.elm:

        , Html.button [ Html.Events.onClick ButtonClicked ] [ Html.text "compute fib" ]

Mit Html.Events.onClick ButtonClicked erzeugen wir ein Attribut, dessen Bedeutung ist: wenn der Nutzer den Knopf drüctk, schicke uns bitte die Nachricht ButtonClicked. Die Laufzeitumgebung ruft dann update mit ButtonClicked und dem derzeigen Modell auf, um das sich daraus ergebende Modell zu berechnen.

Leicht komplexer wird es, wenn der Nutzer Text eingibt:

        [ Html.input [ Html.Events.onInput InputChanged ] []

Die Funktion Html.Events.onInput baut uns ein Attribut, dass spezifiziert, dass jedes Mal, wenn der Nutzer den Inhalt des Textfeldes ändert, eine Nachricht geschickt werden soll. Allerdings braucht nun die Nachricht eine Payload: einen String, nämlich den neuen Inhalt des Textfeldes. Dieser wird als Payload der Nachricht angehängt. Daher übergeben wir hier auch InputChanged, was einen String als Payload hat.

Übungsaufgabe Schreiben Sie eine App, bei der der Nutzer eine Zahl \(n\) eingeben kann. Auf Knopfdruck soll dann eine Tabelle erzeugt werden, die die Menge \(\{0,1\}^n\) anzeigt. Die Tabelle soll also \(2^n\) Zeilen haben und alle Bitstrings der Länge \(n\) auflisten, von \(00\dots 0\) bis \(11 \dots 1\). So ähnlich wie die Wahrheitstabelle im zweiten Programmierprojekt.

Nehmen Sie als Vorlage die Datei Web01Table.elm.