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.
- 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. - 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.
- 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?
- Wie verändert der Benutzerinput den internen Zustand?
In Elm werden diese Fragen wie folgt spezifiziert:
- 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 zweiString
zu definieren, da wir ja Inhalt des Textfeldes und den Outputsring speichern müssen. Im allgemeinen kannModel
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. - 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
undInputChanged
. - 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 welcheMsg
dann an den Elm-Code geschickt werden soll. Daher hat der DatentypHtml
auch eine Typenvariable, die eben offen lässt, von welchem Typ die Messages sind: in unserem Beispiel ebenHtml Msg
. Die Signatur vonview
ist daher auchview : Model -> Html Msg
. -
Wie sich der Zustand, also das Modell, bei Benutzerinput verändert, legt die
Funktion
update
fest. Hier beschreiben Sie, wie aus dem altenModel
beim Empfang einerMsg
ein neuesModel
berechnet wird. Daher die Signaturupdate: 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.