Let's build a Random Quote Machine in Elm - Part 3
Tags: elm, beginners, tutorial, webdev
Published: 2019-07-31 09:03:28

---
title: Let's build a Random Quote Machine in Elm - Part 3
published: true
description: 
tags: elm, beginners, tutorial, webdev
series: Build a Random Quote Machine in Elm
cover_image: https://thepracticaldev.s3.amazonaws.com/i/jtlq619x1pg6heurxi39.png
---

[Yesterday](https://dev.to/dwayne/let-s-build-a-random-quote-machine-in-elm-part-2-49cl) we ported the HTML to Elm and refactored the code.

Today we're going to make the "New quote" button work, fetch quotations from a remote source when the app initially loads  and we'll make the URL for the remote source configurable by setting a flag.

Let's get started.

## Make the "New quote" button work

When the "New quote" button is clicked a new quotation will be displayed and the color of certain elements will change.

### Prepare for randomness

To work with randomness in our app we'll need to be able to
[command](https://guide.elm-lang.org/effects/) Elm's runtime system to generate random values for us.

[Browser.element](https://package.elm-lang.org/packages/elm/browser/1.0.1/Browser#element) allows us to work with [commands](https://guide.elm-lang.org/effects/).

Let's edit `src/Main.elm` to use [Browser.element](https://package.elm-lang.org/packages/elm/browser/1.0.1/Browser#element).

```elm
module Main exposing (main)


import Browser


main : Program () Model msg
main =
  Browser.element
    { init = init
    , view = view
    , update = update
    , subscriptions = always Sub.none
    }


-- MODEL


type alias Model =
  { quote : Quote
  }


init : () -> (Model, Cmd msg)
init _ =
  ( { quote = defaultQuote
    }
  , Cmd.none
  )


-- UPDATE


update : msg -> Model -> (Model, Cmd msg)
update _ model =
  ( model
  , Cmd.none
  )


-- VIEW


view : Model -> Html msg
view { quote } =
  div [ class "background" ]
    [ div []
        [ viewQuoteBox quote
        , -- ...
        ]
    ]
```

### Display a random quote

Install [elm/random](https://package.elm-lang.org/packages/elm/random/1.0.0/).

```sh
$ elm install elm/random
```

And then, make the following edits to `src/Main.elm`:

```elm
import Random


main : Program () Model Msg


type alias Model =
   { quote : Quote
   , quotes : List Quote
   }


init : () -> (Model, Cmd msg)
init _ =
  ( { quote = defaultQuote
    , quotes = allQuotes
    }
  , Cmd.none
  )


allQuotes : List Quote
allQuotes =
  [ defaultQuote
  , { content = " Transferring your passion to your job is far easier than finding a job that happens to match your passion."
    , author = "Seth Godin"
    }
  , { content = "Less mental clutter means more mental resources available for deep thinking."
    , author = "Cal Newport"
    }
  , { content = "How much time he saves who does not look to see what his neighbor says or does or thinks."
    , author = "Marcus Aurelius"
    }
  , { content = "You do not rise to the level of your goals. You fall to the level of your systems."
    , author = "James Clear"
    }
  ]


type Msg
  = ClickedNewQuote
  | NewQuote Quote


update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
  case msg of
    ClickedNewQuote ->
      ( model
      , Random.generate NewQuote (Random.uniform defaultQuote model.quotes)
      )

    NewQuote newQuote ->
      ( { model | quote = newQuote }
      , Cmd.none
      )


view : Model -> Html Msg


viewQuoteBox : Quote -> Html Msg
viewQuoteBox quote =
  div [ class "quote-box" ]
    [ -- ...
    , div [ class "quote-box__actions" ]
        [ -- ...
        , -- ...
        , div []
            [ button
                [ -- ...
                , onClick ClickedNewQuote
                ]
                [ text "New quote" ]
            ]
        ]
    ]
```

When the "New quote" button is clicked the `ClickedNewQuote` message is
created by Elm's runtime system and it eventually calls our `update` function with that message.

The `ClickedNewQuote` branch of our `update` function is selected and it sends a command to Elm's runtime that tells the runtime to select a random quotation from our list of quotations and to wrap it in a `NewQuote` message.

When the `NewQuote` message is created by the runtime, the runtime eventually calls our `update` function with that message. The `NewQuote` branch is selected and our model gets updated with the new quotation.

### Change colors randomly

Here are the edits you need to make:

```elm
import Html.Attributes exposing (..., style, ...)


type alias Model =
  { -- ...
  , color : Color
  }


type alias Color = String


init : () -> (Model, Cmd msg)
init _ =
  ( { -- ...
    , color = defaultColor
    }
  , Cmd.none
  )


defaultColor : Color
defaultColor =
  "#333"


allColors : List Color
allColors =
  [ "#16a085"
  , "#27ae60"
  , "#2c3e50"
  , "#f39c12"
  , "#e74c3c"
  , "#9b59b6"
  , "#fb6964"
  , "#342224"
  , "#472e32"
  , "#bdbb99"
  , "#77b1a9"
  , "#73a857"
  ]


type Msg
  = ClickedNewQuote
  | NewQuoteAndColor (Quote, Color)


update msg model =
  case msg of
    ClickedNewQuote ->
      ( model
      , Random.generate NewQuoteAndColor <|
          Random.pair
            (Random.uniform defaultQuote model.quotes)
            (Random.uniform defaultColor allColors)
      )

    NewQuoteAndColor (newQuote, newColor) ->
      ( { model | quote = newQuote, color = newColor }
      , Cmd.none
      )


view { quote, color } =
  div
    [ class "background"
    , style "background-color" color
    ]
    [ div []
        [ viewQuoteBox quote color
        , -- ...
        ]
    ]


viewQuoteBox : Quote -> Color -> Html Msg
viewQuoteBox quote color =
  div
    [ class "quote-box"
    , style "color" color
    ]
    [ -- ...
    , div [ class "quote-box__actions" ]
        [ div []
            [ viewIconButton "twitter" (twitterUrl quote) color ]
        , div []
            [ viewIconButton "tumblr" (tumblrUrl quote) color ]
        , div []
            [ button
                [ -- ...
                , style "background-color" color
                , onClick ClickedNewQuote
                ]
                [ text "New quote" ]
            ]
        ]
    ]


viewIconButton : String -> String -> Color -> Html msg
viewIconButton name url color =
  a [ -- ...
    , style "background-color" color
    ]
    [ i [ class ("fa fa-" ++ name) ] [] ]
```

Now, when the "New quote" button is clicked both a quotation and a color are randomly selected.

### Add color transitions

Finally, let's add some color transitions to make the colors change smoothly.

Add two new classes to `assets/styles.css`:

```css
/* Transitions */

.has-color-transition {
  transition: color 2s;
}

.has-background-color-transition {
  transition: background-color 2s;
}
```

And then, add the `has-color-transition` class to the quote box:

```elm
viewQuoteBox quote color =
  div
    [ class "quote-box has-color-transition"
    , style "color" color
    ]
    [ -- ...
    ]
```

And, add the `has-background-color-transition` class to the background and the buttons:

```elm
div
  [ class "background has-background-color-transition"
  , style "background-color" color
  ]
  [ -- ...
  ]


button
  [ -- ...
  , class "button has-background-color-transition"
  , style "background-color" color
  , -- ...
  ]
  [ text "New quote" ]


a [ -- ...
  , class "icon-button has-background-color-transition"
  , style "background-color" color
  ]
  [ i [ class ("fa fa-" ++ name) ] [] ]
```

For more details, go [here](https://github.com/dwayne/elm-random-quote-machine/blob/tutorial/tutorial/step-05.md).

## Get quotations from a remote source

The quotations we'll use will be taken from [here](https://gist.githubusercontent.com/dwayne/ff832ab1d4a0bf81585870369f984ebc/raw/46d874a29e9efe38006ec9865ad67b054ef312a8/quotes.json).

## Decode the quotations

The quotations are returned in the JSON format in the following form:

```json
{
  "quotes": [
    { "content": "Life isn't about getting and having, it's about giving and being.", "author": "Kevin Kruse" },
    { "content": "Whatever the mind of man can conceive and believe, it can achieve.", "author": "Napoleon Hill" }
  ]
}
```

We'll need to install [elm/json](https://package.elm-lang.org/packages/elm/json/1.1.3/) to decode the response.

```sh
$ elm install elm/json
```

Let's write the decoders.

```elm
import Json.Decode as D


-- DECODERS


quotesDecoder : D.Decoder (List Quote)
quotesDecoder =
  D.field "quotes" (D.list quoteDecoder)


quoteDecoder : D.Decoder Quote
quoteDecoder =
  D.map2 Quote
    (D.field "content" D.string)
    (D.field "author" D.string)
```

The `quoteDecoder` can decode JSON in the format:

```json
{
  "content": "...what matters in the long run is sticking with things and working daily to get better at them.",
  "author": "Angela Duckworth"
}
```

And, the `quotesDecoder` can decode JSON in the format:

```json
{
  "quotes": [
    {
      "content": "...what matters in the long run is sticking with things and working daily to get better at them.",
      "author": "Angela Duckworth"
    },
    {
      "content": "Don't judge. Teach. It's a learning process.",
      "author": "Carol S. Dweck"
    }
  ]
}
```

### GET the quotes

Install [elm/http](https://package.elm-lang.org/packages/elm/http/2.0.0/).

```sh
$ elm install elm/http
```

And, write the following to fetch the quotes when the app initially loads:

```elm
import Http


init : () -> (Model, Cmd Msg)
init _ =
  ( -- ...
  , getQuotes "https://gist.githubusercontent.com/dwayne/ff832ab1d4a0bf81585870369f984ebc/raw/46d874a29e9efe38006ec9865ad67b054ef312a8/quotes.json"
  )


type Msg
  = -- ...
  | GotQuotes (Result Http.Error (List Quote))


update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
  case msg of
    -- ...

    GotQuotes (Ok remoteQuotes) ->
      ( { model | quotes = remoteQuotes }
      , Cmd.none
      )

    GotQuotes (Err _) ->
      ( model, Cmd.none )


-- COMMANDS


getQuotes : String -> Cmd Msg
getQuotes url =
  Http.get
    { url = url
    , expect = Http.expectJson GotQuotes quotesDecoder
    }
```

### Pass the URL for the remote source via a flag

To make the URL easy to configure we'll pass it into the app via a
[flag](https://guide.elm-lang.org/interop/flags.html).

Edit `index.html`.

```js
Elm.Main.init({
  node: document.getElementById("app"),
  flags: "https://gist.githubusercontent.com/dwayne/ff832ab1d4a0bf81585870369f984ebc/raw/46d874a29e9efe38006ec9865ad67b054ef312a8/quotes.json"
});
```

Edit `src/Main.elm`.

```elm
main : Program String Model Msg
main =
  -- ...


init : String -> (Model, Cmd Msg)
init url =
  ( -- ...
  , getQuotes url
  )
```

### Select a random quote when the app initially loads

```elm
init : String -> (Model, Cmd Msg)
init url =
  ( -- ...
  , Cmd.batch
      [ generateNewQuoteAndColor allQuotes
      , getQuotes url
      ]
  )


update msg model =
  case msg of
    ClickedNewQuote ->
      ( model
      , generateNewQuoteAndColor model.quotes
      )

    -- ...


generateNewQuoteAndColor : List Quote -> Cmd Msg
generateNewQuoteAndColor quotes =
  Random.generate NewQuoteAndColor <|
    Random.pair
      (Random.uniform defaultQuote quotes)
      (Random.uniform defaultColor allColors)
```

Congratulations! You've reached the end. Review the [demo](https://dwayne.github.io/elm-random-quote-machine/) to check that you were able to follow along and recreate the app.

For more details, go [here](https://github.com/dwayne/elm-random-quote-machine/blob/tutorial/tutorial/step-06.md).

Now go and use what you learned in this tutorial to build out your next app. Feel free to reach out to me for advice or help if you get stuck.

Happy coding!