Listings Grotz/Elm

Listing 1: Immutable Records als Basis der Datenmodellierung
-- Typ definieren
type alias MyRecord =
    { text : String
    , count : Int
    }

-- Record erstellen
aRecord =
    { text = "Lorem Ipsum"
    , count = 42
    }

-- es wird auch ein Record-Konstruktor erzeugt
anotherRecord = MyRecord "Lorem Ipsum" 42

-- veränderte Kopie erzeugen
changedCopy = { aRecord | count = 7 }

--------

Listing 2: Custom Types zur Strukturierung unterschiedlicher Daten
type alias CardInfo = { holder : String, number : String }
type alias Account = { username : String, email : String }

type PaymentMethod
    = Cash
    | CreditCard CardInfo
    | PayPal Account

--------

Listing 3: Partial Application, Higher-Order Functions und Funktionsverkettung
-- Addition zweier Zahlen
add a b = a + b

-- Partial Application, "a" ist als 2 festgelegt, "b" ist noch offen
add2 = add 2

filteredList =
    -- 2 zu jeder Zahl in der Liste addieren
    List.map add2 [ 1, 2, 3, 5, 8, 13 ]
      -- Neue Liste an List.filter übergeben und via anonymer Filterfunktion nur Zahlen kleiner 10 behalten
      |> List.filter (\input -> input < 10)
      -- Ergebnis: [3, 4, 5, 7]

--------

Listing 4: Pattern Matching zur Verarbeitung von Custom Types
pay : Amount -> PaymentMethod -> Bool
pay amount paymentMethod =
    case paymentMethod of
      Cash ->
        payWithCash amount

      CreditCard cardInfo ->
        payWithCard amount cardInfo

      PayPal { username, _email } ->
        payViaPayPal amount username

--------

Listing 5: HTML-Rendering in der View-Funktion
view : Model -> Html Msg
view model =
    let
      -- HTML-Element für leeren Stern
      emptyStar index =
          span [ style "cursor" "pointer", onClick (StarClicked index) ] [ text "☆" ]

      -- HTML-Element für gefüllten Stern
      filledStar index =
          span [ style "cursor" "pointer", style "color" "yellow", onClick (StarClicked index) ] [ text "★" ]

      -- Wie viele Sterne von welcher Art werden benötigt?
      { numberOfFilledStars, numberOfEmptyStars } =
          ratingToStarCounts model.rating

      -- alle DOM-Elemente für die gefüllten Sterne
      renderedFilledStars =
          -- Liste mit Zahlen: [1..n]
          List.range 1 numberOfFilledStars
              -- Für jede Zahl einmal die HTML-Element-Funktion mit der Zahl als zusätzliches Argument aufrufen
              |> List.map filledStar

      -- alle DOM-Elemente für die leeren Sterne
      renderedEmptyStars =
          List.range (numberOfFilledStars + 1) (numberOfFilledStars + numberOfEmptyStars)
              |> List.map emptyStar

      -- Liste mit allen notwendigen Stern-DOM-Elementen erzeugen
      allRenderedStars =
          renderedFilledStars ++ renderedEmptyStars
    in
    div [ class "elm-rating__wrapper" ]
      [ h2 [] [ text model.title ]
      , div [ class "elm-rating__star-container" ] allRenderedStars
      ]

--------

Listing 6: Nachrichtentyp für Benutzerinteraktion
type Msg
    = StarClicked Int
type alias StarCounts =
    { numberOfFilledStars : Int
    , numberOfEmptyStars : Int
    }

ratingToStarCounts : Maybe Rating -> StarCounts
ratingToStarCounts rating =
    case rating of
      Nothing -> StarCounts 0 5
      Just One -> StarCounts 1 4
      Just Two -> StarCounts 2 3
      Just Three -> StarCounts 3 2
      Just Four -> StarCounts 4 1
      Just Five -> StarCounts 5 0

--------

Listing 7: Die notwendigen Styledefinitionen
.elm-rating__wrapper {
    background-color: lightgrey;
    width: 15rem;
    display: flex;
    flex-direction: column;
    align-items: center;
}

.elm-rating__star-container {
    display: flex;
    font-size: 3rem;
}

--------

Listing 8: Aktualisierung des Models
numberToRating : Int -> Maybe Rating
numberToRating number =
    case number of
      1 -> Just One
      2 -> Just Two
      3 -> Just Three
      4 -> Just Four
      5 -> Just Five
      _ -> Nothing

update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
      StarClicked clickedIndex ->
        let
          newRating =
            numberToRating clickedIndex
        in
        ( { model | rating = newRating }, Cmd.none )

--------

Listing 9: Einbettung der Bibliothek in der index.js-Datei
import elmWebComponents from '@teamthread/elm-web-components'

elmWebComponents.configure('0.19');

elmWebComponents.register('elm-rating', Elm.Main, {
    setupPorts: ports => {
      ports.ratingChanged.subscribe(data => {
        alert(JSON.stringify(data));
      })
    },
    onSetupError: error => {
      alert('elmWebComponents -> Something went wrong', error)
    }
});

--------

Listing 10: Web-Components-Attribute entgegennehmen
type alias Flags =
    { title : String
    , rating : Maybe String
    }

init : Flags -> ( Model, Cmd Msg )
init flags =
    let
      parsedRating =
        let
          parsedString =
            Maybe.andThen String.toInt flags.rating
        in
        Maybe.andThen numberToRating parsedString
    in
    ( { title = flags.title
      , rating = parsedRating
      }
    , Cmd.none
    )

--------

Listing 11: Änderungen nach außen geben
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        StarClicked clickedIndex ->
            let
                newRating =
                    numberToRating clickedIndex

                ratingAsString =
                    Maybe.map ratingToString newRating

                ratingChangedPortCmd =
                    ratingChanged (Flags model.title ratingAsString)
            in
            ( { model | rating = newRating }, ratingChangedPortCmd )

--------

Listing 12: Alle Bausteine miteinander verknüpfen
main : Program Flags Model Msg
main =
    Browser.element
        { view = view
        , init = init
        , update = update
        , subscriptions = always Sub.none
        }




