Going through Elm guide and doing exercises.
Converter.elm
| -- https://ellie-app.com/3P9hcDhdsc5a1 | |
| module Main exposing (Model, Msg(..), init, main, update, view, viewConverter) | |
| import Browser | |
| import Html exposing (Attribute, Html, div, input, span, text) | |
| import Html.Attributes exposing (..) | |
| import Html.Events exposing (onInput) | |
| -- MAIN | |
| main = | |
| Browser.sandbox { init = init, update = update, view = view } | |
| -- MODEL | |
| type alias Model = | |
| { celsius : String | |
| , fahrenheit : String | |
| , inches : String | |
| } | |
| init : Model | |
| init = | |
| { celsius = "", fahrenheit = "", inches = "" } | |
| -- UPDATE | |
| type Msg | |
| = ChangeCelsius String | |
| | ChangeFahrenheit String | |
| | ChangeInches String | |
| update : Msg -> Model -> Model | |
| update msg model = | |
| case msg of | |
| ChangeCelsius newInput -> | |
| { model | celsius = newInput } | |
| ChangeFahrenheit newInput -> | |
| { model | fahrenheit = newInput } | |
| ChangeInches newInput -> | |
| { model | inches = newInput } | |
| -- VIEW | |
| view : Model -> Html Msg | |
| view model = | |
| div [ style "display" "flex", style "flex-direction" "column" ] | |
| [ viewCelsius model | |
| , viewFahrenheit model | |
| , viewInches model | |
| ] | |
| viewCelsius : Model -> Html Msg | |
| viewCelsius model = | |
| case String.toFloat model.celsius of | |
| Just celsius -> | |
| viewConverter model.celsius ChangeCelsius "°C" "°F" "blue" (celsiusToFahrenheit celsius) | |
| Nothing -> | |
| viewConverter model.celsius ChangeCelsius "°C" "°F" "red" "???" | |
| viewFahrenheit : Model -> Html Msg | |
| viewFahrenheit model = | |
| case String.toFloat model.fahrenheit of | |
| Just fahrenheit -> | |
| viewConverter model.fahrenheit ChangeFahrenheit "°F" "°C" "blue" (fahrenheitToCelsius fahrenheit) | |
| Nothing -> | |
| viewConverter model.fahrenheit ChangeFahrenheit "°F" "°C" "red" "???" | |
| viewInches : Model -> Html Msg | |
| viewInches model = | |
| case String.toFloat model.inches of | |
| Just inches -> | |
| viewConverter model.inches ChangeInches "in" "m" "blue" (inchesToMeters inches) | |
| Nothing -> | |
| viewConverter model.inches ChangeInches "in" "m" "red" "???" | |
| celsiusToFahrenheit : Float -> String | |
| celsiusToFahrenheit celsius = | |
| String.fromFloat (celsius * 1.8 + 32) | |
| fahrenheitToCelsius : Float -> String | |
| fahrenheitToCelsius fahrenheit = | |
| String.fromFloat (fahrenheit / 1.8 - 32) | |
| inchesToMeters : Float -> String | |
| inchesToMeters inches = | |
| String.fromFloat (inches * 0.0254) | |
| viewConverter : String -> (String -> Msg) -> String -> String -> String -> String -> Html Msg | |
| viewConverter userInput convert convertFrom convertTo color equivalentTemp = | |
| span [] | |
| [ input [ value userInput, onInput convert, style "width" "40px", style "border" ("1px solid " ++ color) ] [] | |
| , text (convertFrom ++ " = ") | |
| , span [ style "color" color ] [ text equivalentTemp ] | |
| , text convertTo | |
| ] | 
| module Main exposing (Model, Msg(..), init, main, update, view, viewInput, viewValidation) | |
| import Browser | |
| import Html exposing (..) | |
| import Html.Attributes exposing (..) | |
| import Html.Events exposing (onInput) | |
| main = | |
| Browser.sandbox { init = init, update = update, view = view } | |
| type alias Model = | |
| { name : String | |
| , password : String | |
| , passwordAgain : String | |
| } | |
| init : Model | |
| init = | |
| Model "" "" "" | |
| type Msg | |
| = Name String | |
| | Password String | |
| | PasswordAgain String | |
| update : Msg -> Model -> Model | |
| update msg model = | |
| case msg of | |
| Name name -> | |
| { model | name = name } | |
| Password password -> | |
| { model | password = password } | |
| PasswordAgain password -> | |
| { model | passwordAgain = password } | |
| view : Model -> Html Msg | |
| view model = | |
| div [] | |
| [ viewInput "text" "Name" model.name Name | |
| , viewInput "password" "Password" model.password Password | |
| , viewInput "password" "Re-enter Password" model.passwordAgain PasswordAgain | |
| , viewValidation model | |
| ] | |
| viewInput : String -> String -> String -> (String -> msg) -> Html msg | |
| viewInput t p v toMsg = | |
| input [ type_ t, placeholder p, value v, onInput toMsg ] [] | |
| type ValidationResult | |
| = ShortPassword | |
| | NoDigitsInPassword | |
| | NoUppercaseCharacters | |
| | NoLowercaseCharacters | |
| | PasswordsNotMatch | |
| | Valid | |
| viewValidation : Model -> Html msg | |
| viewValidation model = | |
| case validate model of | |
| ShortPassword -> | |
| viewValidationError "Password should be longer than 8 characters" | |
| NoDigitsInPassword -> | |
| viewValidationError "Password should contain one numeric character" | |
| NoUppercaseCharacters -> | |
| viewValidationError "Password should contain one upper case character" | |
| NoLowercaseCharacters -> | |
| viewValidationError "Password should contain one lower case character" | |
| PasswordsNotMatch -> | |
| viewValidationError "Passwords do not match!" | |
| Valid -> | |
| div [ style "color" "green" ] [ text "OK" ] | |
| viewValidationError : String -> Html msg | |
| viewValidationError errorMessage = | |
| div [ style "color" "red" ] [ text errorMessage ] | |
| validate : Model -> ValidationResult | |
| validate model = | |
| if String.length model.password < 8 then | |
| ShortPassword | |
| else if not (String.any Char.isDigit model.password) then | |
| NoDigitsInPassword | |
| else if not (String.any Char.isUpper model.password) then | |
| NoUppercaseCharacters | |
| else if not (String.any Char.isLower model.password) then | |
| NoLowercaseCharacters | |
| else if model.password /= model.passwordAgain then | |
| PasswordsNotMatch | |
| else | |
| Valid | 
| import Browser | |
| import Html exposing (Html, button, div, text, input) | |
| import Html.Events exposing (onClick, onInput) | |
| import Html.Attributes exposing (..) | |
| main = | |
| Browser.sandbox { init = init, update = update, view = view } | |
| type alias Model = | |
| { counter : Int | |
| } | |
| init : Model | |
| init = | |
| Model 10 | |
| type Msg = Reset | Change String | |
| update : Msg -> Model -> Model | |
| update msg model = | |
| case msg of | |
| Reset -> | |
| init | |
| Change newCounter -> | |
| { model | counter = Maybe.withDefault model.counter (String.toInt newCounter) } | |
| view : Model -> Html Msg | |
| view model = | |
| div [] | |
| [ div [] [ text (String.fromInt model.counter) ] | |
| , input [ type_ "range", placeholder "Type counter", value (String.fromInt model.counter), onInput Change ] [] | |
| , viewButton "Reset" Reset | |
| , viewSnowman model | |
| ] | |
| viewButton : String -> msg -> Html msg | |
| viewButton t toMsg = | |
| button [ onClick toMsg ] [ text t ] | |
| viewSnowman : Model -> Html msg | |
| viewSnowman model = | |
| div | |
| [ style "display" "flex" | |
| , style "flex-direction" "column" | |
| , style "align-items" "center" | |
| , style "width" "50%" | |
| ] | |
| [ viewShape model 1 | |
| , viewShape model 2 | |
| , viewShape model 3 | |
| ] | |
| viewShape : Model -> Int -> Html msg | |
| viewShape model position = | |
| div | |
| [ style "width" ((String.fromInt (model.counter * position)) ++ "px") | |
| , style "height" ((String.fromInt (model.counter * position)) ++ "px") | |
| , style "background-color" (getColor model.counter) | |
| , style "border-radius" "50%" | |
| , style "transition" "background-color 300ms linear" | |
| ] | |
| [] | |
| getColor : Int -> String | |
| getColor counter = | |
| if counter <= 50 then | |
| "green" | |
| else | |
| "red" |