8. Elm - Eine funktionale Programmiersprache zur Entwicklung von Web-Apps

8.3 Benutzerinput

Als einfachstes Beispiel sehen Sie hier Website03Counter.elm; diese Beispiel ist von elmprogramming.com. Der Zustand Ihrer Webseite ist ein Objekt vom Typ Model. Dies ist ein Typ, den Sie selbst in Ihrem Code definieren. Da wir hier nur ein Integer speichern, ist Model nur ein Alias für Int, nämlich per type alias Model = Int. Im Allgemeinen wird Model viel komplexere Daten enthalten. Das Verhalten Ihrer Webseite wird von den folgenden Funktionen festgelegt:

  1. der Funktion view : Model -> Html Message, die beschreibt, wie aus den Daten des Modells die Html-Seite geschaffen werden soll;
  2. der Funktion update : Message -> Model -> Model, die beschreibt, wie eine hereinkommende Nachricht das Modell verändern soll;
  3. der Konstante init : Model, die den Wert festlegt, den das Modell am Anfang, also beim Laden der Seite haben soll.

Manche Html-Elemente müssen wissen, dass Sie unter Umständen eine Nachricht schicken sollen. Zum Beispiel

button [ onClick Increment ] [ text "+" ]

Dadurch wird festgelegt, dass auf Knopfdruck die Nachricht Increment geschickt werden soll. Increment ist ein von uns selbst definierter Wert vom Typ Message. Daher muss Html ein parametrisierter Typ mit Typ-Variable msg sein. Genau wie es List a heißt, weil wir im Allgemeinen nicht wissen, welche Typen unsere Liste speichern soll und daher die Typenvariable a als Platzhalter verwenden, so weiß der Typ Html ja nicht, welcher Typ von Nachrichten verschickt wird, und deswegen muss es Html msg heißen.

Ein etwas komplexeres Beispiel finden Sie unter Website03ExpandList.elm.

Übungsaufgabe Passen Sie Website03ExpandList.elm an, so dass es nicht ein <ul>-Element erschafft, sondern die Werte in einer Tabelle mit zwei Spalten anzeigt. Die linke Spalte soll die Indizes \(n\) enthalten, die rechte die entsprechenden Fibonacci-Zahlen \(F_n\).

Offener Input: Textfelder etc.

In der Anwendung Website04EchoString.elm sehen Sie eine Seite, die den eingegebenen String in Kleinbuchstaben umwandelt. Die Codezeile
input [ onInput ValueChanged, value model.input ] []

erschafft ein HTML-Input-Element, das jedes Mal, wenn sein Wert sich ändert, die Message ValueChanged an unseren Code schickt. Allerdings nützt uns die Nachricht allein nichts. Wie müssen ja auch den neuen Wert wissen; die Nachricht braucht also eine Payload. Sehen Sie, Sie können in Elm aus fundamentalen Gründen nichts wie inputField.getValue() schreiben; das wäre nicht funktional: die Funktion würde, abhängig vom Kontext, andere Werte zurückliefern. Wie wird dieses Problem in Elm gelöst? Erforschen wir, worum es sich bei onInput denn handelt:

elm repl                            
import Html.Events
Html.Events.onInput
<function> : (String -> msg) -> Html.Attribute msg
Also: onInput will als Argument keine Message, sondern eine Funktion, die aus einem String eine Message baut. Wie zum Beispiel unseren Datenkonstruktur ValueChanged:
import Website04EchoString                            
Website04EchoString.ValueChanged
<function> : String -> Website04EchoString.Message
Nochmal: obwohl ValueChanged ist zwar streng gesehen keine Funktion, da sie nichts "ausführt"; es ist ein Datenkonstruktur, der sich aber syntaktisch und semantisch wie eine Funktion verhält. Der Ausdruck ValueChanged "Auf Wiedersehen" macht aus dem String "Auf Wiedersehen" die Message ValueChanged "Auf Wiedersehen". Das Ergebnis des "Funktionsaufrufes" ist also der Aufruf selbst. Gewöhnungsbedürftig, aber elegant.

Die Semantik der Zeile input [ onInput ValueChanged, value model.input ] [] ist nun, dass, wenn sich der Inhalt des Textfeldes verändert (z.B. durch User-Input) und x der neue Inhalt ist, dann schickt die Laufzeitumgebung an unseren Elm-Code die Nachricht ValueChanged x. In der Funktion update fangen wir den Wert x in den Zeilen

    case message of
        ValueChanged newString ->
            { model | input = newString }

auf und können ihn weiterverarbeiten.

Übungsaufgabe Schreiben Sie eine einfache Webseite, wo der User eine Zahl in ein Eingabefeld eingeben kann; die entsprechende Fibonacci-Zahl wird dann ausgerechnet und angezeigt.

Hinweis. Die zusätzliche Herausforderung liegt jetzt darin, den Eingabe-String in ein Int zu verwandeln. Dies geht mit der Funktion String.toInt. Was geschieht, wenn der eingegebe String kein Integer darstellt?