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 List
s. 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 String
s 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.