Daily Archives: 18 July 2016


An Introduction to Elm Series: Solution to ‘Pair of Counters’ example

In http://guide.elm-lang.org/architecture/modularity/counter_pair.html, we are given an application that reuses the counter application that was converted into a module for the purpose of this tutorial.

We were asked to make a swap between the two instances of the counter. That was easy due to the nature of the updates that create a new version of the model each time instead of updating it. So, we just swapped the instances of the two counters in the update.

Later, we were asked to extend the counter to log some statistics, since the parent module was not using the model directly it was very easy for us to extend the model and implement the changes needed in the update and the view of the model. No signatures were changed and init was modified to initialize all statistics along with the value.

Parent Module (1-counter-pair.elm)

import Counter
import Html exposing (Html, button, div, text)
import Html.App as App
import Html.Events exposing (onClick)



main =
  App.beginnerProgram
    { model = init 0 0
    , update = update
    , view = view
    }



-- MODEL


type alias Model =
  { topCounter : Counter.Model
  , bottomCounter : Counter.Model
  }


init : Int -> Int -> Model
init top bottom =
  { topCounter = Counter.init top
  , bottomCounter = Counter.init bottom
  }



-- UPDATE


type Msg
  = Reset
  | Top Counter.Msg
  | Bottom Counter.Msg
  | Swap


update : Msg -> Model -> Model
update message model =
  case message of
    Reset ->
      init 0 0

    Top msg ->
      { model | topCounter = Counter.update msg model.topCounter }

    Bottom msg ->
      { model | bottomCounter = Counter.update msg model.bottomCounter }

    Swap ->
      { model | bottomCounter = model.topCounter, topCounter = model.bottomCounter }


-- VIEW


view : Model -> Html Msg
view model =
  div
    []
    [ App.map Top (Counter.view model.topCounter)
    , App.map Bottom (Counter.view model.bottomCounter)
    , button [ onClick Reset ] [ text "RESET" ]
    , button [ onClick Swap ] [ text "Swap" ]
    ]

Counter Module (Counter.elm)

module Counter exposing (Model, Msg, init, update, view)

import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events exposing (onClick)



-- MODEL


type alias Model =
  { count : Int
  , max' : Int
  , min' : Int
  , increment : Int
  , decrement : Int
  }


init : Int -> Model
init count =
  Model count 0 0 0 0

-- UPDATE


type Msg
  = Increment
  | Decrement


update : Msg -> Model -> Model
update msg model =
  case msg of
    Increment ->
      { model |
        count = model.count + 1
        , max' = Basics.max (model.count + 1) model.max'
        , increment = model.increment + 1
      }

    Decrement ->
      { model |
        count = model.count - 1
        , min' = Basics.min (model.count - 1) model.min'
        , decrement = model.decrement + 1
      }



-- VIEW


view : Model -> Html Msg
view model =
  div []
    [ button [ onClick Decrement ] [ text "-" ]
    , div [ countStyle ] [ text (toString model.count) ]
    , button [ onClick Increment ] [ text "+" ]
    , div [] [ text ("Max " ++ (toString model.max')) ]
    , div [] [ text ("Min " ++ (toString model.min')) ]
    , div [] [ text ("Increment " ++ (toString model.increment)) ]
    , div [] [ text ("Decrement " ++ (toString model.decrement)) ]
    ]


countStyle : Attribute msg
countStyle =
  style
    [ ("font-size", "20px")
    , ("font-family", "monospace")
    , ("display", "inline-block")
    , ("width", "50px")
    , ("text-align", "center")
    ]

 

You can download the solution from here 1-counter-pair (compressed) (304 downloads)


An Introduction to Elm Series: Solution to ‘Time’ example

In http://guide.elm-lang.org/architecture/effects/time.html, we are given an application that resembles a clock and we are asked to add a button to pause it and add additional hands.

What we did in the following code is:

  • We created a normal clock that shows the time in UTC
  • A toggle button that pauses/resumes it was added
  • We added the second, minute and hour hands. Which we gave them different lengths and colors
  • We created utilities to convert time to angle in degrees both for minutes/seconds and for hours
  • A new function that adds the leading zero to values when we print the time in digital format was created
import Html exposing (Html, br, div, button)
import Html.Events exposing (onClick)
import Html.App as Html
import Svg exposing (..)
import Svg.Attributes exposing (..)
import Time exposing (Time, second)



main =
  Html.program
    { init = init
    , view = view
    , update = update
    , subscriptions = subscriptions
    }



-- MODEL


type alias Model =
  { time : Time
  , state : Bool
  }


init : (Model, Cmd Msg)
init =
  (Model 0 True, Cmd.none)



-- UPDATE


type Msg
  = Tick Time
  | Toggle


update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
  case msg of
    Tick newTime ->
      ({ model | time = newTime }, Cmd.none)

    Toggle ->
      (toggleState model, Cmd.none)


-- UTILITIES
toggleState : Model -> Model
toggleState model =
  {model | state = not(model.state)}

printWithLeadingZero : Int -> String
printWithLeadingZero number =
  if number < 10 then
    "0" ++ (toString number)
  else
    toString number

degressCorrection : Float
degressCorrection =
  90.0 -- The correction we must do on our analog clock to show 12 pointing up instead to the right

degressForHour : Float
degressForHour =
  360.0 / 12.0 -- We divide the total degrees of a full circle by the full hours of the day to get the degrees per hour

degreesForMinute : Float
degreesForMinute =
  360.0 / 60.0 -- We divide the total degrees of a full circle by the full minutes of the hour to get the degrees per minute

convertToDegrees : Int -> Float -> Float
convertToDegrees value degreesPerPoint =
  degrees (((toFloat value) * degreesPerPoint) - degressCorrection)

minutesToDegrees : Int -> Float
minutesToDegrees minutes =
  convertToDegrees minutes degreesForMinute

hoursToDegrees : Int -> Float
hoursToDegrees hours =
  convertToDegrees hours degressForHour

-- SUBSCRIPTIONS


subscriptions : Model -> Sub Msg
subscriptions model =
  case model.state of
    True ->
      Time.every second Tick
    False ->
      Sub.none



-- VIEW


view : Model -> Html Msg
view model =
  let


    second' =
        Time.inSeconds model.time

    secondOfMinute =
        truncate second' `rem` 60

    secondAngle =
      minutesToDegrees secondOfMinute

    secondHandX =
      toString (50 + 40 * cos secondAngle)

    secondHandY =
      toString (50 + 40 * sin secondAngle)

    minute' =
      Time.inMinutes model.time

    minuteOfHour =
      truncate minute' `rem` 60

    minuteAngle =
      minutesToDegrees minuteOfHour

    minuteHandX =
      toString (50 + 35 * cos minuteAngle)

    minuteHandY =
      toString (50 +35 * sin minuteAngle)

    hour' =
      Time.inHours model.time

    hourOfDay =
      truncate hour' `rem` 24

    hourAngle =
      hoursToDegrees hourOfDay

    hourHandX =
      toString (50 + 30 * cos hourAngle)

    hourHandY =
      toString (50 + 30 * sin hourAngle)

    currentTime =
      (printWithLeadingZero hourOfDay) ++ ":" ++ (printWithLeadingZero minuteOfHour) ++ ":" ++ (printWithLeadingZero secondOfMinute)

  in
    div []
      [ div [] 
      , svg [ viewBox "0 0 100 100", width "300px" ]
        [ circle [ cx "50", cy "50", r "45", fill "#0B79CE" ] []
        , line [ x1 "50", y1 "50", x2 secondHandX, y2 secondHandY, stroke "#ee0000" ] []
        , line [ x1 "50", y1 "50", x2 minuteHandX, y2 minuteHandY, stroke "#0000ee" ] []
        , line [ x1 "50", y1 "50", x2 hourHandX, y2 hourHandY, stroke "#023963" ] []
        ]
      , br [] []
      , button [onClick Toggle] 
      ]

You can download the solution from here 6-clock (compressed) (319 downloads)

 


An Introduction to Elm Series: Solution to ‘HTTP’ example 5

In the http://guide.elm-lang.org/architecture/effects/http.html, we were given a sample that loads random images from a web application on another service thought HTTP requests.

We were asked to print the error message of the request (if any) and update the UI to allow changing the topic (a parameter of the web call) either through an input field or a drop down list.

We wrote a message that accepts as string the new topic, that message is used both in the case of the input and in the case of the select list. Finally, we made sure that the ‘Please Wait’ image is shown while loading the new image from the remote server. The path for the ‘Please Wait’ image was added to a custom function to emulate a public static string value.

import Html exposing (..)
import Html.App as Html
import Html.Attributes exposing (..)
import Html.Events exposing (..)
import Http
import Json.Decode as Json
import Task



main =
  Html.program
    { init = init "cats"
    , view = view
    , update = update
    , subscriptions = subscriptions
    }



-- MODEL


type alias Model =
  { topic : String
  , gifUrl : String
  , error : String
  }

waitingImage : String
waitingImage =
  "5-http/waiting.png"

init : String -> (Model, Cmd Msg)
init topic =
  ( Model topic waitingImage ""
  , getRandomGif topic
  )



-- UPDATE


type Msg
  = MorePlease
  | FetchSucceed String
  | FetchFail Http.Error
  | NewTopic String


update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
  case msg of
    MorePlease ->
      ({ model | gifUrl = waitingImage }, getRandomGif model.topic)

    FetchSucceed newUrl ->
      (Model model.topic newUrl "", Cmd.none)

    FetchFail error ->
      (Model model.topic waitingImage (toString error), Cmd.none)

    NewTopic newTopic ->
      ({ model | topic = newTopic }, Cmd.none)


-- VIEW


view : Model -> Html Msg
view model =
  div []
    [ h2 [] 
    , input [ type' "text", placeholder "Topic", onInput NewTopic ] []
    , select [ onInput NewTopic ]
      [ option [] [ text "Pokemon" ]
      , option [] [ text "SuperCars" ]
      , option [] [ text "Cyprus" ]
      , option [] [ text "Cats" ]
      , option [] [ text "Dogs" ]
      ]
    , button [ onClick MorePlease ] [ text "More Please!" ]
    , br [] []
    , img [src model.gifUrl] []
    , br [] []
    , div [] 
    ]



-- SUBSCRIPTIONS


subscriptions : Model -> Sub Msg
subscriptions model =
  Sub.none



-- HTTP


getRandomGif : String -> Cmd Msg
getRandomGif topic =
  let
    url =
      "//api.giphy.com/v1/gifs/random?api_key=dc6zaTOxFJmzC&tag=" ++ topic
  in
    Task.perform FetchFail FetchSucceed (Http.get decodeGifUrl url)


decodeGifUrl : Json.Decoder String
decodeGifUrl =
  Json.at ["data", "image_url"] Json.string

You can download the solution from here 5-http (compressed) (298 downloads)