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.elmwä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,Modelals Record mit zweiStringzu definieren, da wir ja Inhalt des Textfeldes und den Outputsring speichern müssen. Im allgemeinen kannModelsehr 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:ButtonClickedundInputChanged. - Wie sich der Zustand in eine Darstellung im Browser übersetzt, wird in der Funktion
viewfestgelegt. Hier legen Sie auch fest, welche Html-Elemente (z.B.button) User-Input entgegennehmen können und welcheMsgdann an den Elm-Code geschickt werden soll. Daher hat der DatentypHtmlauch eine Typenvariable, die eben offen lässt, von welchem Typ die Messages sind: in unserem Beispiel ebenHtml Msg. Die Signatur vonviewist daher auchview : Model -> Html Msg. -
Wie sich der Zustand, also das Modell, bei Benutzerinput verändert, legt die
Funktion
updatefest. Hier beschreiben Sie, wie aus dem altenModelbeim Empfang einerMsgein neuesModelberechnet 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.