Let's build a Random Quote Machine in Elm - Part 2
Tags: elm, beginners, tutorial, webdev
Published: 2019-07-30 10:04:47

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

[Yesterday](https://dev.to/dwayne/let-s-build-a-random-quote-machine-in-elm-part-1-3n64) we got the structure (HTML) and styles (CSS) completed.

Today we're going to port the HTML to Elm and organize the code in such a way that it will be convenient to add features moving forward.

## Port the HTML to Elm

Go to your project's root directory and run `elm init`.

```sh
$ cd path/to/random-quote-machine
$ elm init
```

Press ENTER at the prompt to have an `elm.json` file and an empty `src` directory created for you.

The `elm.json` file tracks dependencies and other metadata for your app.

The `src` directory is where you'll place all the Elm files comprising your app. In this case you'll need one file, `src/Main.elm`.

**N.B.** *You can read https://elm-lang.org/0.19.0/init to learn more about `elm init`.*

Go ahead and create that file now.

```sh
$ touch src/Main.elm
```

And, edit it to contain the following:

```elm
module Main exposing (main)


import Html exposing (Html, a, blockquote, button, cite, div, footer, i, p, span, text)
import Html.Attributes exposing (autofocus, class, href, target, type_)


main : Html msg
main =
  div [ class "background" ]
    [ div []
        [ div [ class "quote-box" ]
            [ blockquote [ class "quote-box__blockquote"]
                [ p [ class "quote-box__quote-wrapper" ]
                    [ span [ class "quote-left" ]
                        [ i [ class "fa fa-quote-left" ] [] ]
                    , text "I am not a product of my circumstances. I am a product of my decisions."
                    ]
                , footer [ class "quote-box__author-wrapper" ]
                    [ text "\u{2014} "
                    , cite [ class "author" ] [ text "Stephen Covey" ]
                    ]
                ]
            , div [ class "quote-box__actions" ]
                [ div []
                    [ a [ href "https://twitter.com/intent/tweet?hashtags=quotes&text=%22I%20am%20not%20a%20product%20of%20my%20circumstances.%20I%20am%20a%20product%20of%20my%20decisions.%22%20%E2%80%94%20Stephen%20Covey"
                        , target "_blank"
                        , class "icon-button"
                        ]
                        [ i [ class "fa fa-twitter" ] [] ]
                    ]
                , div []
                    [ a [ href "https://www.tumblr.com/widgets/share/tool?posttype=quote&tags=quotes&content=I%20am%20not%20a%20product%20of%20my%20circumstances.%20I%20am%20a%20product%20of%20my%20decisions.&caption=Stephen%20Covey&canonicalUrl=https%3A%2F%2Fwww.tumblr.com%2Fdocs%2Fen%2Fshare_button"
                        , target "_blank"
                        , class "icon-button"
                        ]
                        [ i [ class "fa fa-tumblr" ] [] ]
                    ]
                , div []
                    [ button
                        [ type_ "button"
                        , autofocus True
                        , class "button"
                        ]
                        [ text "New quote" ]
                    ]
                ]
            ]
        , footer [ class "attribution" ]
            [ text "by "
            , a [ href "https://github.com/dwayne/"
                , target "_blank"
                , class "attribution__link"
                ]
                [ text "dwayne" ]
            ]
        ]
    ]
```

An Elm file is called a module. The first line names the module, `Main`, and makes the `main` function available for use by the outside world.

The import lines import various functions from the [Html](https://package.elm-lang.org/packages/elm/html/1.0.0/Html) and [Html.Attributes](https://package.elm-lang.org/packages/elm/html/1.0.0/Html-Attributes) modules for use in your `Main` module.

The `Html` and `Html.Attributes` modules exist in the [elm/html](https://package.elm-lang.org/packages/elm/html/1.0.0/) package. If you look in your `elm.json` file you'd see that `elm init` has already set you up with the `elm/html` package as a direct dependency. This means you won't need to install it.

The `main` function contains mostly what we'd find in `index.html` except that instead of HTML tags and attributes we have function calls.

In general,

```html
<foo attr1="a" attr2="b">bar</foo>
```

is translated into:

```elm
foo [ attr1 "a", attr2 "b" ] [ text "bar" ]
```

where `foo` and `text` are in `Html` and `attr1` and `attr2` are in
`Html.Attributes`.

The only minor difference is our use of the Unicode code point `\u{2014}` instead of the `&mdash;` HTML entity.

### Compile to JavaScript

```sh
elm make src/Main.elm --output=assets/app.js
```

The `Main` module is compiled to JavaScript and saved in `assets/app.js`.

### Load it

Edit `index.html` and replace the `body` with the following:

```html
<body>
  <div id="app"></div>
  <script src="assets/app.js"></script>
  <script>
    Elm.Main.init({
      node: document.getElementById("app")
    });
  </script>
</body>
```

View `index.html` in a browser and observe that absolutely nothing has changed. That's a good thing. It means you faithfully converted the HTML to Elm.

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

## Refactor the Elm code

We'll extract some useful functions and add a new type.

### Extract the `viewQuote` function

From the `main` function, take the "HTML" that comprises the `blockquote` and wrap it in a function named `viewQuote` that takes two arguments.

```elm
viewQuote : String -> String -> Html msg
viewQuote content author =
  blockquote [ class "quote-box__blockquote"]
    [ p [ class "quote-box__quote-wrapper" ]
        [ span [ class "quote-left" ]
            [ i [ class "fa fa-quote-left" ] [] ]
        , text content
        ]
    , footer [ class "quote-box__author-wrapper" ]
        [ text "\u{2014} "
        , cite [ class "author" ] [ text author ]
        ]
    ]
```

Then, from `main` call `viewQuote`.

```elm
viewQuote
  "I am not a product of my circumstances. I am a product of my decisions."
  "Stephen Covey"
```

### Extract the `viewIconButton` function

Notice that

```elm
a [ href "https://twitter.com/intent/tweet?hashtags=quotes&text=%22I%20am%20not%20a%20product%20of%20my%20circumstances.%20I%20am%20a%20product%20of%20my%20decisions.%22%20%E2%80%94%20Stephen%20Covey"
  , target "_blank"
  , class "icon-button"
  ]
  [ i [ class "fa fa-twitter" ] [] ]
```

and this

```elm
a [ href "https://www.tumblr.com/widgets/share/tool?posttype=quote&tags=quotes&content=I%20am%20not%20a%20product%20of%20my%20circumstances.%20I%20am%20a%20product%20of%20my%20decisions.&caption=Stephen%20Covey&canonicalUrl=https%3A%2F%2Fwww.tumblr.com%2Fdocs%2Fen%2Fshare_button"
  , target "_blank"
  , class "icon-button"
  ]
  [ i [ class "fa fa-tumblr" ] [] ]
```

are quite similar.

Write a function named `viewIconButton` to generalize the pattern you see.

```elm
viewIconButton : String -> String -> Html msg
viewIconButton name url =
  a [ href url
    , target "_blank"
    , class "icon-button"
    ]
    [ i [ class ("fa fa-" ++ name) ] [] ]
```

Go back to `main` and replace the links with the relevant function calls.

For Twitter use:

```elm
viewIconButton "twitter" "https://twitter.com/intent/tweet?hashtags=quotes&text=%22I%20am%20not%20a%20product%20of%20my%20circumstances.%20I%20am%20a%20product%20of%20my%20decisions.%22%20%E2%80%94%20Stephen%20Covey"
```

And, for Tumblr use:

```elm
viewIconButton "tumblr" "https://www.tumblr.com/widgets/share/tool?posttype=quote&tags=quotes&content=I%20am%20not%20a%20product%20of%20my%20circumstances.%20I%20am%20a%20product%20of%20my%20decisions.&caption=Stephen%20Covey&canonicalUrl=https%3A%2F%2Fwww.tumblr.com%2Fdocs%2Fen%2Fshare_button"
```

I hope you see that the Twitter and Tumblr URLs will change based on the quotation's content and author. Hence, you'll need a way to generate the URLs given that information.

### Extract functions to generate the Twitter and Tumblr URLs

Install [elm/url](https://package.elm-lang.org/packages/elm/url/1.0.0/). It provides the functions we need to build the URLs.

```sh
$ elm install elm/url
```

From the [Url.Builder](https://package.elm-lang.org/packages/elm/url/1.0.0/Url-Builder) module import [crossOrigin](https://package.elm-lang.org/packages/elm/url/1.0.0/Url-Builder#crossOrigin) and [string](https://package.elm-lang.org/packages/elm/url/1.0.0/Url-Builder#string).

**N.B.** *The [string](https://package.elm-lang.org/packages/elm/url/1.0.0/Url-Builder#string) function ensures that the query parameter's value is [percent-encoded](https://tools.ietf.org/html/rfc3986#section-2.1).*

```elm
import Url.Builder exposing (crossOrigin, string)
```

To generate the Twitter URL write the `twitterUrl` function:

```elm
twitterUrl : String -> String -> String
twitterUrl content author =
  let
    tweet = "\"" ++ content ++ "\" \u{2014} " ++ author
  in
    crossOrigin "https://twitter.com"
      [ "intent", "tweet" ]
      [ string "hashtags" "quotes"
      , string "text" tweet
      ]
```

And, to generate the Tumblr URL write the `tumblrUrl` function:

```elm
tumblrUrl : String -> String -> String
tumblrUrl content author =
  crossOrigin "https://www.tumblr.com"
    [ "widgets", "share", "tool" ]
    [ string "posttype" "quote"
    , string "tags" "quotes"
    , string "content" content
    , string "caption" author
    , string "canonicalUrl" "https://www.tumblr.com/docs/en/share_button"
    ]
```

Then, update the arguments to the `viewIconButton` function calls.

For Twitter use:

```elm
viewIconButton "twitter" (twitterUrl "I am not a product of my circumstances. I am a product of my decisions." "Stephen Covey")
```

For Tumblr use:

```elm
viewIconButton "tumblr" (tumblrUrl "I am not a product of my circumstances. I am a product of my decisions." "Stephen Covey")
```

### Extract the `viewQuoteBox` function

```elm
viewQuoteBox : String -> String -> Html msg
viewQuoteBox content author =
  div [ class "quote-box" ]
    [ viewQuote content author
    , div [ class "quote-box__actions" ]
        [ div []
            [ viewIconButton "twitter" (twitterUrl content author) ]
        , div []
            [ viewIconButton "tumblr" (tumblrUrl content author) ]
        , -- ...
        ]
    ]
```

Use it in `main`.

```elm
main : Html msg
main =
  div [ class "background" ]
    [ div []
        [ viewQuoteBox
            "I am not a product of my circumstances. I am a product of my decisions."
            "Stephen Covey"
        , -- ...
        ]
    ]
```

## Add the `Quote` record

```elm
type alias Quote =
  { content : String
  , author : String
  }


defaultQuote : Quote
defaultQuote =
  { content = "I am not a product of my circumstances. I am a product of my decisions."
  , author = "Stephen Covey"
  }
```

You'll need to update `main`, `viewQuoteBox`, `viewQuote`, `twitterUrl` and `tumblrUrl` to all work with the `Quote` record.

```elm
main =
  div [ class "background" ]
    [ div []
        [ viewQuoteBox defaultQuote
        , -- ...
        ]
    ]

viewQuoteBox : Quote -> Html msg
viewQuoteBox quote =
  div [ class "quote-box" ]
    [ viewQuote quote
    , div [ class "quote-box__actions" ]
        [ div []
            [ viewIconButton "twitter" (twitterUrl quote) ]
        , div []
            [ viewIconButton "tumblr" (tumblrUrl quote) ]
        , div []
            [ button
                [ type_ "button"
                , autofocus True
                , class "button"
                ]
                [ text "New quote" ]
            ]
        ]
    ]


viewQuote : Quote -> Html msg
viewQuote { content, author } =
  -- ...


twitterUrl : Quote -> Html msg
twitterUrl { content, author } =
  -- ...


tumblrUrl : Quote -> Html msg
tumblrUrl { content, author } =
  -- ...
```

Here's the final version of the code after all the refactoring is completed:

```elm
module Main exposing (main)


import Html exposing (Html, a, blockquote, button, cite, div, footer, i, p, span, text)
import Html.Attributes exposing (autofocus, class, href, target, type_)
import Url.Builder exposing (crossOrigin, string)


type alias Quote =
  { content : String
  , author : String
  }


defaultQuote : Quote
defaultQuote =
  { content = "I am not a product of my circumstances. I am a product of my decisions."
  , author = "Stephen Covey"
  }


main : Html msg
main =
  div [ class "background" ]
    [ div []
        [ viewQuoteBox defaultQuote
        , footer [ class "attribution" ]
            [ text "by "
            , a [ href "https://github.com/dwayne/"
                , target "_blank"
                , class "attribution__link"
                ]
                [ text "dwayne" ]
            ]
        ]
    ]


viewQuoteBox : Quote -> Html msg
viewQuoteBox quote =
  div [ class "quote-box" ]
    [ viewQuote quote
    , div [ class "quote-box__actions" ]
        [ div []
            [ viewIconButton "twitter" (twitterUrl quote) ]
        , div []
            [ viewIconButton "tumblr" (tumblrUrl quote) ]
        , div []
            [ button
                [ type_ "button"
                , autofocus True
                , class "button"
                ]
                [ text "New quote" ]
            ]
        ]
    ]


viewQuote : Quote -> Html msg
viewQuote { content, author } =
  blockquote [ class "quote-box__blockquote"]
    [ p [ class "quote-box__quote-wrapper" ]
        [ span [ class "quote-left" ]
            [ i [ class "fa fa-quote-left" ] [] ]
        , text content
        ]
    , footer [ class "quote-box__author-wrapper" ]
        [ text "\u{2014} "
        , cite [ class "author" ] [ text author ]
        ]
    ]


twitterUrl : Quote -> String
twitterUrl { content, author } =
  let
    tweet = "\"" ++ content ++ "\" \u{2014} " ++ author
  in
    crossOrigin "https://twitter.com"
      [ "intent", "tweet" ]
      [ string "hashtags" "quotes"
      , string "text" tweet
      ]


tumblrUrl : Quote -> String
tumblrUrl { content, author } =
  crossOrigin "https://www.tumblr.com"
    [ "widgets", "share", "tool" ]
    [ string "posttype" "quote"
    , string "tags" "quotes"
    , string "content" content
    , string "caption" author
    , string "canonicalUrl" "https://www.tumblr.com/docs/en/share_button"
    ]


viewIconButton : String -> String -> Html msg
viewIconButton name url =
  a [ href url
    , target "_blank"
    , class "icon-button"
    ]
    [ i [ class ("fa fa-" ++ name) ] [] ]
```

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

Tomorrow we'll make the "New quote" button work such that when it is clicked a new random quotation will be displayed and the color of certain elements will change. Then, we'll arrange to have quotations fetched from a remote source when the app initially loads. Finally, we'll make sure the URL is easily configurable by passing it in via a flag.