Vores første Elm-hjemmeside
Så skal vi bygge hjemmesider!
Vi kan ikke skrive en hjemmeside ved at skrive i elm repl. elm repl er ikke rigtig lavet til at skrive kode, der fylder mere end en linje ad gangen - men det gør hjemmesider: Avancerede hjemmesider kan sagtens fylde 10.000 linjer kode! Derfor har vi har brug for at kunne skrive mange linjer efter hinanden i et tekstdokument. Her kommer Sublime Text ind i billedet igen.
Det første eksempel
Enhver Elm-hjemmeside følger en bestemt struktur. Den vil vi forklare nu, og vi skal også se et eksempel på en Elm-hjemmeside.
Forestil jer en meget simpel hjemmeside: Der er en knap og et tal. På knappen står der "Forøg". Tallet starter med at være 0. Når man trykker på "Forøg"-knappen, så vokser tallet med 1. Det kunne se for eksempel sådan her ud:
Ovenstående hjemmeside er dynamisk, fordi tallet på siden ændrer sig når vi interagerer med siden (det vil sige: når vi trykker på knappen). Tænk på det, der foregår sådan her: Hjemmesiden starter med at se ud på en bestemt måde: Der er en knap, som ikke ændrer sig, og så er der et tal, som starter med at være 0. Tallet kan ændre sig. Når vi trykker på knappen, sender knappen en besked til tallet om at vokse med 1. Derefter bliver browserens vindue opdateret, og så bliver det nye tal vist til os.
Nu dykker vi ned i koden bag ovenstående eksempel. Vi skal forstå præcis, hvad der sker, og hvorfor det virker.
Elm-kode
Lad os springe ud i det og se på den Elm-kode, som genererer ovenstående hjemmeside.
Elm
module NumberGame exposing (main)
import Browser
import Html exposing (Html)
import Html.Attributes
import Html.Events
-- MODEL
type alias Model = Int
init : Model
init = 0
-- UPDATE
type Msg
= AddOne
update : Msg -> Model -> Model
update message model =
case message of
AddOne ->
(model + 1)
-- VIEW
view : Model -> Html Msg
view model =
Html.div []
[ Html.div []
[ Html.h3 []
[ Html.text (String.fromInt model) ]
]
, Html.div []
[ Html.button [ Html.Events.onClick AddOne ]
[ Html.text "Forøg" ]
]
]
Wow, det føles måske som meget kode for så simpel en hjemmeside, men det er faktisk ikke så slemt, når man lige får fod på det! Lad os prøve at gå igennem koden - bid for bid. Koden er lige nu delt op i tre dele, adskilt af linjer, som starter med dobbelt bindestreg:
-- MODEL-- UPDATE-- VIEW
Vi gennemgår nu delene!
Før -- MODEL
Det der står over-- MODEL kommer vi ikke til at røre ved. Det er en beskrivelse af hvilke moduler vi skal hente for at få adgang til de funktioner, vi skal bruge i vores kode. Som eksempel får vi brug for nogle HTML-agtige funktioner.
-- MODEL
Næste afsnit, som står mellem -- MODEL og -- UPDATE er hjemmesidens hjerte. Her definerer vi formen på den dynamiske data, som skal vises på hjemmesiden. I eksemplet ovenfor er den dynamiske data det tal som bliver vist. Vi kan altså repræsentere hele hjemmesidens dynamiske indhold med ét tal! Afsnittet her forklarer, hvordan vi gør det.
Afsnittet -- MODEL består af tre linjer:
-- MODEL
type alias Model = Int
init : Model
init = 0
Første linje begynder med type alias, som er den måde hvorpå vi kan lave forkortelser for allerede eksisterende typer. Lige nu er vores Model egentlig bare et tal af typen Int, men vi omdøber typen til Model for at understrege, at det er hjemmesidens data-model. Når senere skal de vise sig, når vi skal bygge avancerede hjemmesider, vil vores Model ofte være en record som består af mange felter.
Det sidste vi gør er at specificere en startværdi til modellen. Startværdien kalder vi init, som står for "initialize". Vi skriver at den skal have typen Model, og den skal have værdien 0 når hjemmesiden åbnes.
-- VIEW
Lad os lige hoppe ned til det sidste afsnit efter -- VIEW.
-- VIEW
view : Model -> Html Msg
view model =
Html.div []
[ Html.div []
[ Html.h3 []
[ Html.text (String.fromInt model) ]
]
, Html.div []
[ Html.button [ Html.Events.onClick AddOne ]
[ Html.text "Forøg" ]
]
]
Her sker der en hel masse!
Afsnittet er bygget op omkring funktionen view, som har til opgave at vise hjemmesiden i browseren. Lad os se nærmere på view:
view : Model -> Html Msg
View tager en værdi af typen Model som input. Når hjemmesiden åbnes er den konkrete værdi, det bliver puttet ind i view-funktionen den værdi vi har defineret med init. I vores eksempel er det altså tallet 0. Det giver rigtig god mening, at view skal have den nuværende model som input, fordi den skal netop vise dataen i modellen!
view-funktionens output er straks mere kompliceret! Outputtet har typen Html Msg, som vi ikke er støt på indtil nu. Det er en lidt svær sag, som det kan være lidt svært at få sit hoved rundt om! Du kan tænke på værdier af typen Html Msg som noget Html-kode, som browseren kan forstå, og det er netop pointen: Outputtet af view-funktionen skal være en Html Msg som afspejler den HTML vi gerne vil vise i browseren. Groft sagt skal vi altså "bare" skrive noget HTML-kode i vores view-funktion.
view-funktionen ovenfor ikke ligner det HTML-kode, vi tidligere har skrevet! Det er helt rigtigt, men ved nærmere eftersyn er der en klar sammenhæng: Hvis vi gerne vil skrive et <div>-tag direkte i HTML, ville vi skrive:
<div class = "highlighted"> Her er tekst. </div>
I Elm skriver vi i stedet:
Html.div [Attributes.class = "highlighted"] [Html.text "Her er tekst."]
Logikken her er at Html.div faktisk er en funktion!
Html.div : List (Html Attribute) -> List (Html Msg) -> Html Msg
Html.div tager altså to lister som input: Den første liste består af alle de attributter, som vi vil knytte til div-elementet. Den anden liste består af alle de HTML-elementer, som skal være efterkommere af denne <div>.
På samme måde er Html.h3 og Html.button funktioner, der hver tager to lister som input:
Html.h3 : List (Html Attribute) -> List (Html Msg) -> Html Msg
Html.button : List (Html Attribute) -> List (Html Msg) -> Html Msg
Måske har du allerede luret den: for hvert eneste HTML-element er der en funktion i Elm, nemlig Html.TAGNAME, hvor TAGNAME står for navnet på det til HTML-elementet hørende HTML-tag.
Html.text-funktionen. Denne funktion genererer ikke noget HTML, og derfor tager den heller ikke hverken attributter eller efterkommere. Html.text-funktionen genererer bare helt almindelig tekst, og tager derfor kun en String som input.
Html.text : String Html Msg
Den følgende Elm (view-funktion)
view : Model -> Html Msg
view model =
Html.div []
[ Html.h3 [] [ Html.text "Overskrift" ]
, Html.p [] [Html.text "Her er en lang tekst."]
]
HTML (i browseren)
<div>
<h3>Overksrift</h3>
<p>Her er en lang tekst.</p>
</div>
Vi mangler kun at forstå to små dele af view-funktionen nu. Pyha, næsten i mål!
Html.text kan kun modtage en String, så vi får brug for at lave vores Model om fra en Int til en String. Det gør funktionen String.fromInt, som er defineret sådan her:
String.fromInt : Int -> String
På knappen skal der stå Forøg, og det er Html.text "Forøg", der sørger for det. Men der skal også ske noget, når vi trykker på knappen! Det funktionen Html.Events.onClick, der sørger for det. Når der sker noget på en hjemmeside kaldes det for et event. Et eksepel på et event er, når nogen trykker på en knap med musen. Funktionen Html.Events.onClick sætter en attribut på et HTML-element, som angiver hvilken besked, der skal sendes, når nogen trykker på HTML-elementet.
Html.Events.onClick : Msg -> Html Attribute
Inputtet AddOne er den besked, som knappen skal sende, når der bliver trykket på det. Man hvad er AddOne egentlig? Vi har jo slet ikke kigget på beskeder endnu! Det leder os videre til det sidste afsnit af koden.
-- UPDATE
Dette afsnit består af to dele.
-- UPDATE
type Msg
= AddOne
update : Msg -> Model -> Model
update message model =
case message of
AddOne ->
(model + 1)
Den første del definerer typen Msg, som fortæller præcis hvilke beskeder, browseren må sende. Lige nu er der kun én besked, nemlig AddOne. På en avanceret hjemmeside kan der ske mange forskellige events, så vi skal kunne sende mange forskellige slags beskeder. Efterhånden som vi udbygger hjemmesiden med flere events, tilføjer vi også flere varianter til typen Msg.
Den anden del er selve update-funktionen. Funktionen tager to input: en besked af typen Msg og den nuværende model, der selvfølgelig har typen Model.
update : Msg -> Model -> Model
Når vi gør noget på hjemmesiden (f.eks. trykker på en knap), så bliver der sendt en besked af typen Msg til update-funktionen. Udover beskeden får update også den nuværende model som input. Ud fra beskeden skal update-funktionen nu opdatere den nuværende model, og dens output er en ny, opdateret model. Det er præcis det der sker her:
update : Msg -> Model -> Model
update message model =
case message of
AddOne ->
(model + 1)
Læg mærke til, at vi har lavet en case ... of, selvom der på nuværende tidspunkt kun er én mulig besked. Det er fordi, at vi allerede har gjort klar til, at der snart kommer flere beskeder til!
Overblik
Elm-programmer kører altid efter følgende illustration:

Vi ved allerede hvad en browser er, og ovenfor har vi selv lavet update og view i diagramet. Men hvad er Elm Runtime? Elm Runtime er hjernen bag det hele: det er det, der får det hele til løbe rundt. Når vi åbner vores hjemmeside, så starter kredsløbet, og det forløber sådan her:
- Elm Runtime sender den model, vi har specificeret med
init, tilview-funktionen. view-funktionen tager imod den nuværende model og genererer enHtml Msgog sender den tilbage til Elm Runtime.- Elm Runtime fortolker den
Html Msg, genererer en visualisering og sender HTML videre til Browseren (der står DOM på tegningen, men det er i virkeligheden bare HTML der bliver sendt til browseren). Browseren forstår HTML-kode og viser det på din skærm. - Browseren venter på, at du gør noget: Du kan for eksempel være, at du trykker på en knap eller skriver noget i et felt. Når/hvis det sker, så sender browseren en besked til Elm Runtime.
- Elm Runtime sender nu beskeden videre til
update-funktionen. Sammen med beskeden sender Elm Runtime den nuværende model, såupdate-funktionen både ved, hvad der er sket i browseren (beskeden) og hvordan hjemmesiden ser ud lige nu (modellen).update-funktionen outputter en ny model baseret på den besked den har fået. Denne nye model bliver sendt tilbage til Elm Runtime. - Herefter sender Elm Runtime den nye model til
view-funktionen, og så kører loopet!
Ovenstående forklaring er måske lidt abstrakt, så her følger en gennemgang med udgangspunkt i vores eksempel ovenfor.
- Elm Runtime sender modellen
0tilview-funktionen. view-funktionen tager0som input og genererer følgendeHtml Msg, som sendes tilbage til Elm Runtime:Html.div [] [ Html.div [] [ Html.h3 [] [ Html.text (String.fromInt model) ] ] , Html.div [] [ Html.button [ Html.Events.onClick AddOne ] [ Html.text "Forøg" ] ] ]- Elm Runtime fortolker den
Html Msg, genererer følgende HTML, som sendes til brwoseren:<div> <div> <h3>0</h3> </div> <div> <button>Forøg</button> </div> </div> - Browseren venter på, at du trykker på knappen. Når/hvis det sker, så sender browseren beskeden
AddOnetil Elm Runtime. - Elm Runtime sender nu beskeden
AddOneog den nuværende model, som er0, videre tilupdate-funktionen.update-funktionen outputter en ny model, som er1. Denne nye model bliver sendt tilbage til Elm Runtime. - Herefter sender Elm Runtime den nye model til
view-funktionen, og så kører loopet!
Nu er du klar til selv at programmere i Elm! Giv den gas med øvelserne herunder, og når du er færdig med dem, så venter der en spændende udfordring i kapitel 6.
Vi har snydt hjemmefra. Vi har allerede programmeret en hjemmeside, der ser ud som beskrevet ovenfor. Følg denne guide for at åbne hjemmesiden.
- Vi har forberedt en lille .zip-fil med nogle filer, som I får brug for i resten af forløbet. Tryk på det her link for at downloade mappen: Link
- Find .zip-filen i mappen Overførsler eller Downloads på jeres computer. Højreklik på den og vælg Udpak Alle. Vælg et passende sted på jeres computer, f.eks. Dokumenter eller Skrivebord.
- Find nu mappen i Dokumenter eller på Skrivebord. Gå ind i mappen, og find den mappe der hedder
src. Gå ind i den mappe og åbn filen der hedderNumberGame.elmi Sublime Text. Det er en ægte Elm-fil, og den indeholder det eksempel, som vi har gennemgået ovenfor. - Vend tilbage til mappen og gå ud af
src-mappen. Der skulle gerne ligge to mapper og fire filer, f.eks. table og elm. Imens I holder Ctrl og Shift nede, højreklikker (ikke på filerne eller mapperne) I inde i mappen og vælger 'Åbn Powershell-vinduet her'. - Powershell er en avanceret terminal, og den kan cirka det samme som den normale sorte terminal. Skriv blot
elm reactorog tryk Enter. - Gå nu ind i jeres browser og skriv i adressefeltet:
localhost:8000. - Her ligger de filer som I kunne se i mappen fra før. Tryk på mappen
srcog tryk så på filenNumberGame. Så skulle I gerne kunne se den hjemmeside vi har beskrevet ovenfor.
Følg ovenstående vejledning og åbn filen
NumberGame.elm i browseren.
Åbn filen NumberGame.elm i Sublime Text. Tjek at der står samme kode som i kodevinduet ovenfor. Vi vil gerne tilføje en ny knap til eksemplet, hvor der står Formindsk, og når vi trykker på den knap skal tælleren på hjemmesiden mindskes med 1.
- Start med at tilføje en ekstra variant til
Msg, og kald denMinusOne. - Tilføj en linje ekstra i
update-funktionen, så tælleren falder med 1, nårupdatemodtager enMinusOne-message. - Tilføj en ekstra linje i
Html-div'en inde iview-funktionen, hvor du tilføjer en knap mere. Teksten på knappen skal væreFormindsk, og når vi trykker på knappen, skal der sendes enMinusOne-message.
Tag udgangspunkt i koden fra øvelsen ovenover, hvor der er to knapper på hjemmesiden. Vi vil gerne tilføje en ny knap som skal nulstille tælleren, når man trykker på knappen.
- Start med at tilføje en ekstra variant af
Msg, som hedderReset. - Tilføj en linje ekstra i
update-funktionen, så tælleren sættes til 0, nårupdatemodtager enReset-message. - Tilføj en ekstra linje i
Html-div'en inde iview-funktionen, hvor du tilføjer en knap mere. Teksten på knappen skal væreReset, og når vi trykker på knappen, skal der sendes enReset-message.
Tilføj en knap mere som øger tælleren med 10.