Tutorial: F# mit SAFE-Stack – Teil 4

Keine Kommentare

Eine Anwendung mit nur einer (funktionalen) Programmiersprache entwickeln

Polieren(https://unsplash.com/photos/P4H2wo6Lo7s)

Willkommen zurück zum vierten Teil der Serie.

Im ersten Teil der Serie haben wir das Grundgerüst für eine einfache Todo-Anwendung gebaut, die zunächst nur eine feste Liste von Aufgaben anzeigen konnte. Im zweiten Teil haben wir sie dann erweitert, um neue Aufgaben hinzufügen zu können. Am Ende des dritten Teils war die Anwendung dann bereits feature complete, da existierende Aufgaben auch als abgeschlossen markiert werden können.

Die Web-Anwendung ist vollständig in F# geschrieben und benutzt die Bibliotheken des SAFE-Stacks, insbesondere:

  • Giraffe (für die Bearbeitung von HTTP-Requests)
  • Fable (F# nach Javascript und React-UI-Elemente)
  • Elmish (für das Model-Update-View-Pattern)

Natürlich würde man in einer echten, produktiven Anwendung eine Aufgaben auch noch ändern und löschen können wollen. Aber um das zu bauen, benötigen wir keine neue (Sprach-)Elemente und Patterns, also habe ich das aus diesem Tutorial herausgelassen.

Trotzdem sind wir noch nicht fertig.

Heute wollen wir uns nämlich um das Aussehen unserer Anwendung kümmern. Ehrlich gesagt ist das bisherige Frontend optisch echt nicht besonders ansprechend. Alle Texte stehen einfach eng untereinander, Schriftgröße und Buttonformen entsprechen nicht dem heutigen Standard.

Aber zum Glück können wir das ändern. Unsere Oberfläche besteht aus HTML-Elementen, die gestylt werden können.

Frontend-Spezialisten könnten jetzt eine eigene CSS-Datei erstellen und an den einzelnen UI-Objekten in den view-Funktionen mit Class "xxx" die jeweiligen Styles zuweisen.

Ich bin leider nicht besonders gut in CSS. Daher habe ich einen anderen Weg gewählt. Schauen wir es uns an.

Den Code vom letzten Mal findet ihr hier.

Den vollständigen Code von heute gibt es hier.

Bulma

Auf der Website bulma.io findet man einen Anbieter einer CSS-Datei, in der eine Vielzahl von möglichen UI-Elementen bereits vorbereitet ist. Darunter sind Styles für Buttons und andere Komponenten, aber auch Layout-Informationen für mehrspaltige Seiten, Header und Footer und viele weitere einfach einzusetzende Klassen.

Diese Bibliothek werden wir für unser Projekt nutzen. Eine Möglichkeit wäre es, sie dem Projekt hinzuzufügen und – wie oben beschrieben – die CSS-Klassen direkt an den HTML-Elemente in den view-Funktionen zu nutzen. Das finde ich aber nicht besonders elegant. Ich möchte doch eigentlich die gesamte Anwendung in F# schreiben. Und obwohl Class-Angaben natürlich gültiger F#-Code sind, wünsche ich mir doch eine besser integrierte Lösung.

Und zum Glück gibt es die in Form der Bibliothek Fulma.

Fulma

Die ähnlichen Namen kommen nicht von ungefähr. Fulma ist ein F#-Wrapper um die Bulma-Bibliothek. Sprich: Mit Fulma können wir fertige UI-Komponenten so nutzen, wie wir das bisher mit den Fable-React-Komponenten — zum Beispiel h1 oder div – gemacht haben. Und Fulma kümmert sich darum, die richtigen HTML-Elemente mit den entsprechenden CSS-Klassen zu erstellen.

Damit können wir weiterhin bei unserem deklarativen Stil für die UI bleiben, ohne den Code mit einer Vielzahl von Elementen mit dedizierten Class-Angaben zu stilistisch zu verwässern.

Bulma und Fulma ins Projekt einbauen

Der einfachste Weg, Bulma ins Projekt zu bringen, ist, die bulma.css-Datei in das Verzeichnis src/Client/public zu kopieren. Natürlich kann man Bulma auch per npm in das node-Projekt einbinden, aber das schien mir die Mühe nicht wert.

Fulma hingegen ist eine F# Bibliothek und muss dem .NET-Projekt hinzugefügt werden. Dazu wechseln wir in das Verzeichnis src/Client. Dort fügen wir die Bibliothek unserem Projekt hinzu:

dotnet add package Fulma.Extensions.Wikiki.Tooltip
dotnet build

Ab jetzt sind wir bereit und können die neue Funktionen nutzen.

Natürlich müssen wir heute ausschließlich unser Frontend anpassen.

Index.fs

Den ganzen oberen Abschnitt unseres Codes mit den Messages und der update-Funktion lassen wir unverändert. Änderungen gibt es erst weiter unten, wo die view-Funktionen stehen.

In dem dortigen Bereich von open-Anweisungen fügen wir eine weitere hinzu:

open Fulma

Ab jetzt stehen uns neue UI-Komponenten zur Verfügung. Die Dokumentation auf der Fulma-Website ist leider nicht besonders ausführlich, aber wenn man sich ein bisschen eingelesen hat, reicht es später meistens aus, die Bulma-Dokumentation zu lesen. Wie die Komponenten dann in Fulma heißen, ist meistens intuitiv klar, bzw. kann mithilfe der Code-Completion herausgefunden werden.

Also verändern wir jetzt unsere erste große Funktion todoView für die Darstellung eines todo:

let todoView todo dispatch =
    Level.level [] [
        Level.left [] [
            Level.item [] [
                input [
                    Type "checkbox"
                    Checked todo.Completed
                    OnClick (fun _ -> todo.Id |> ToggleCompleted |> dispatch)
                ]
            ]
            Level.item [] [
                div [
                    match todo.Completed with
                    | true -> Style [ CSSProp.TextDecoration "line-through"  ]
                    | false -> Style []
                ] [
                    Label.label [ Label.Modifiers [
                        if todo.Completed then
                            Modifier.TextColor IsSuccess
                            Modifier.TextWeight TextWeight.Normal
                    ] ] [ str todo.Description ]
                ]
            ]
        ]
    ]

Ok, das ist jetzt sehr viel umfangreicher als die vorherige Version (hier im Vergleich):

let todoView todo dispatch =
    div [] [
        input [
            Type "checkbox"
            Checked todo.Completed
            OnClick (fun _ -> todo.Id |> ToggleCompleted |> dispatch)
        ]
        label [ ] [str todo.Description]
    ]

Aber wir möchten ja auch, dass unsere Anwendung hübscher wird. Also verwenden wir das Fulma-Element Level. Und dieses erfordert (leider), dass man einige Elemente schachtelt.

Im ersten item befindet sich die Checkbox. Es gibt zwar eine Fulma-Checkbox, ich habe mich aber für die »normale« entschieden, damit wir sehen können, dass Fulma problemlos mit »normalen« HTML-Komponenten gemischt werden kann. Das ist auch kein Wunder, denn letztlich erzeugen diese Fulma-Funktionen auch nur HTML-Elemente, nur eben mit vordefinierten Class-Eigenschaften.

Das zweite item enthält ebenfalls einen »normalen« div. Wir verwenden ihn dafür, den Text durchzustreichen – eine Option, die in der Dokumentation zu Fulma oder Bulma nicht gefunden habe. In dem div steckt dann allerdings ein Fulma-Label mit entsprechenden Style-Attributen.

Unsere nächste Funktion ist deutlich einfacher:

let todosView model dispatch =
    Box.box' [] ( model.Todos |> List.map (fun each ->
        todoView each dispatch))

Letztlich verwenden wir hier nur eine Box, um einen Rahmen mit Schattierung um alle Aufgaben zu zeichnen. (Das Zeichen ' bei Box.box' ist kein Tippfehler. Den genauen Grund dafür kenne ich nicht – ich vermute, der der Name box bereits irgendwie vergeben war und sich die Entwickler daher für box' entschieden haben.)

Unsere Eingabezeile für neue Aufgaben ist wieder etwas aufwändiger:

let descriptionView model dispatch =
    Columns.columns [ ] [

        Column.column [ ] [
            Input.text [
                Input.Placeholder "What is to be done?"
                Input.IsRounded
                Input.Value (model.Description)
                Input.OnChange (fun ev -> !!ev.target?value |> DescriptionChanged |> dispatch)
            ]
        ]

        Column.column [ Column.Width (Screen.All, Column.IsNarrow) ] [
            Button.button [
                Button.Color IsPrimary
                Button.OnClick (fun _ -> AddTodo |> dispatch)
           ] [ str "Add" ]
        ]
    ]

Aber wirklich kompliziert ist der Code nicht. In zwei Spalten nebeneinander stehen der Text und der Button. Die Konstruktion Column.Width (Screen.All, Column.IsNarrow) dient dazu, dass der Button nicht die gleiche Größe hat wie das Eingabefeld. Screen.All ist dabei der Platzhalter für alle Arten von Screens (Desktop, Tablet, Mobilephone …), damit die Eigenschaft stets richtig  gewählt wird. Hier scheint eine Stärke von Bulma/Fulma durch, denn die sich ergebene Website ist responsive und reagiert auf Veränderungen z. B. in der Breite des Browser-Fensters.

Zuletzt die eigentliche view-Funktion.

let view model dispatch =
    div [ ] [
        Columns.columns [  ] [
            Column.column [
                Column.Width (Screen.All, Column.IsHalf)
                Column.Offset (Screen.All, Column.IsOneQuarter)
            ] [
                Section.section [
                    Section.Props [ Style [ TextAlign TextAlignOptions.Center ] ]
                ] [ 
                    img [ Src "favicon.png" ]
                    Heading.h1 [] [ str "Todo list" ]
                ]
                
                descriptionView model dispatch
                errorView model
                todosView model dispatch            
            ]
        ]
    ]

Die Columns am Anfang sorgen für die Zentrierung des restlichen Inhalts: Der Inhalt ist einen halben Screen breit und hat links einen Rand von einem Viertel Screen.

Auf diese Weise haben wir jetzt eine deutliche Verbesserung der Optik unserer kleinen Anwendung erreicht.

Vorher:Screenshot alte VersionNachher:Screenshot neue Version

Zusammenfassung

In diesem – erneut recht kurzen – Teil der Serie haben wir eine Möglichkeit kennengelernt, eine F#-Anwendung optisch zu verbessern. Es gibt noch eine Vielzahl von weiteren Optionen – insbesondere ist es möglich, eigene Komponenten mit einer dedizierten CSS-Datei zu erstellen.

Alles, was man dafür braucht, sind Funktionen, die HTML-Elemente zurückgeben und diese sinnvoll stylen.

Sicher gebe ich zu, dass auch diese Anwendung kein perfektes UI-Design besitzt, eine wirklich ansprechende Oberfläche zu erstellen, überlasse ich dann aber euch.

Bulma und Fulma sind wie gesagt nur eine Möglichkeit, eine Web-Anwendung aufzuhübschen. Aber diese Bibliotheken sind in der SAFE-Welt sehr verbreitet. Wenn ihr nämlich ein neues SAFE-Projekt aufsetzen möchtet und diesmal nicht mit dem minimalen sondern dem normalen Template beginnt, dann werdet ihr feststellen, dass dort Bulma und Fulma bereits integriert sind. Auch in vielen anderen Beispielen zu SAFE habe ich sie gefunden. Man kann also behaupten, dass es sich hierbei um so etwas wie einen de facto Standard für F# mit SAFE-Stack handelt.

Einen letzten Teil habt ihr noch vor euch. Nächstes Mal fügen wir noch automatisierte Unit-Tests hinzu. Damit wären wir dann vollständig.

Ich freue mich auf euch.

Goetz Markgraf

Goetz hat Wirtschaftsinformatik studiert und viele Jahre als Softwareentwickler und Project Manager gearbeitet. Sein Fokus liegt dabei immer auf dem Verständnis für die Situation und Herausforderungen der Kunden und darin, dies für Entwickler verständlich zu machen.
Seit 2018 ist er Consultant bei der codecentric AG.

Über 1.000 Abonnenten sind up to date!

Die neuesten Tipps, Tricks, Tools und Technologien.
Jede Woche direkt in deine Inbox.

Kostenfrei anmelden und immer auf dem neuesten Stand bleiben!
(Keine Sorge, du kannst dich jederzeit abmelden.)

Kommentieren

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.