Generating HTML

The Html module is for more than displaying plain text. It lets you make anything in Elm that you could make in HTML, but with a cleaner, more powerful syntax.

We're going to make a bare-bones to-do list app with three main sections:

  • Three filter buttons: "All", "Pending", and "Completed"
  • A text input field with an "Add Task" button
  • A unordered list of tasks, with completed tasks crossed out

It would be straightforward to express this in HTML. It would look something like this:

<div>
  <button>All</button>
  <button>Pending</button>
  <button>Completed</button>
</div>

<div>
  <input type="text" />
  <button>Add Task</button>
</div>

<ul>
  <li style="text-decoration: line-through">get milk</li>
  <li>call Mom</li>
  <li>learn Elm<li>
</ul>

Let's see what this looks like directly translated into Elm.

Tags become function calls

If you look through the docs for the Html module, you'll find that there's a function corresponding to basically any HTML tag you could think of. There's a div function for making <div> tags, an a function for making links, a button tag for making <button>s, and on and on.

Using any of these functions isn't very different from using an HTML tag. So you can see what I mean, let's write out that "Completed" button in Elm.

completedButton = Html.button [ ] [ Html.text "Completed" ]

-- preview it in the browser by changing your main value:
-- main = completedButton

You'll notice some things right away. We passed in two arguments, both Lists. This came as no surprise if you took a look at the type signature for those Html functions:

List (Attribute msg) -> List (Html msg) -> Html msg

(Don't get thrown off by the msg bits there--they just tell us that these items can respond to user input (messages in Elm speak), which we'll cover soon.)

Our "Completed" button has no attributes, so our first argument was an empty List. As for its children, it only has one plain text child. Since that second argument is a List (Html msg), we passed in "Completed" to that text function we saw in the last section.

Nesting elements

Now we can write out that <div> of filter buttons in full.

filterButtons =
  Html.div [ ] [
    Html.button [ ] [ Html.text "All" ],
    Html.button [ ] [ Html.text "Pending" ],
    Html.button [ ] [ Html.text "Completed" ]
  ]

-- preview it in the browser by changing your main value:
-- main = filterButtons

Don't let the formatting fool you. This is still just a function call. Since our <div> has three buttons as children, we call Html.button three times. Each child button has its own line to make things easier to read, but this is still a List like any other. Therefore, unlike in raw HTML, each child button is separated by a comma.

Attributes

The Html.Attributes module in the Elm HTML package contains functions for making attributes.

Since this is a new module, we'll have to add an import statement at the top of our file:

import Html.Attributes

Just like the functions in the Html module, their names correspond closely to the names you're used to from HTML. The only difference I know of is the name of the function for dealing with type attributes. We'll need it to translate our input field into Elm:

Html.input [ Html.Attributes.type' "text" ] [ ]

The reason it's written type' is that type has a special meaning in Elm. The ' prime mark helps differentiate things for us, just like with number and number'.

Following the example of our filter buttons above, we can now complete our new task form.

taskForm =
  Html.div [ ] [
    Html.input [ Html.Attributes.type' "text" ] [ ],
    Html.button [ ] [ Html.text "Add Task" ]
    ]

-- preview it in the browser by changing your main value:
-- main = taskForm

More attributes

The functions in Html.Attributes are different than raw HTML attributes in that you won't always set their values with plain text. This makes it easier to deal with things like boolean attributes, for instance. (If you've ever scripted HTML attributes in a JavaScript library like jQuery or React, this practice should feel familiar.)

One special attribute function is Html.Attributes.style. Instead of a single String, it takes a List (String, String). As you can see in the docs, each pair of Strings corresponds to a CSS property/value pair. So here's what the style attribute in our first <li> looks like translated into Elm:

Html.Attributes.style [ ("text-decoration", "line-through") ]

The whole list, then, looks like this:

taskList =
  Html.ul [ ] [ 
    Html.li [ Html.Attributes.style [ ("text-decoration", "line-through") ] ] [ Html.text "get milk" ],
    Html.li [ ] [ Html.text "call Mom" ],
    Html.li [ ] [ Html.text "learn Elm" ]
    ]

-- preview it in the browser by changing your main value:
-- main = filterButtons

Clean up your code by exposing a module's functions

We're now ready to write our complete app skeleton in Elm. But it's a pain to always write Html. and Html.Attributes. over and over. That certainly doesn't make for clean code. But there's an easy fix for this:

-- in ToDoApp.elm
import Html exposing (..)
import Html.Attributes exposing (..)

Now, we can call all our functions in Html and Html.Attributes without any qualifiers! The end result ends up looking quite nice:

-- after your import statements

main =
  body [ ] [
    filterButtons,
    taskForm,
    taskList
    ]


filterButtons =
  div [ ] [
    button [ ] [ text "All" ],
    button [ ] [ text "Completed" ],
    button [ ] [ text "Pending" ]
    ]


taskList =
  ul [ ] [ 
    li [ style [ ("text-decoration", "line-through") ] ] [ text "get milk" ],
    li [ ] [ text "call Mom" ],
    li [ ] [ text "learn Elm" ]
    ]


taskForm =
  div [ ] [
    input [ type' "text" ] [ ],
    button [ ] [ text "Add Task" ]
    ]

If you've been dealing with HTML for a long time, this syntax may take some getting used to. But there's no denying that, compared to the raw HTML version, this Elm version looks strikingly clean. All the noise from those angle brackets, slashes, repeated tag names, and the rest is gone!

Notice how we gave each section a name, only putting them together when we kick things off in main. In addition to better legibility, this gives us the option to easily rearrange our layout if we feel like it. Say we wanted to move our filter buttons to the bottom of our layout. All we need to do is change two lines:

main =
  body [ ] [
    taskForm,
    taskList,
    filterButtons
    ]

And that's it! Try achieving the same thing in the raw HTML and you'll see the advantage here.

This is just the tip of the iceberg when it comes to the benefits of using a full-blown programming language in the place of a simple markup language. We'll cover those in depth in throughout the rest of this chapter.

results matching ""

    No results matching ""