Now that we have a working setup and can compile elm code into javascript, let's focus on the actual application we will develop over the next few chapters.
The initial goal is to have a simple web site that we can use to query The Movie Database for actors names. The main challenge in this will be to send requests to the REST api of TMDB (BTW I chose it instead of IMDB because querying the API is easier than with the more popular IMDB). Later on we will extend this to search for actors who have starred in movies together and finally extend this into a little game where you have to create chains of actors and link them via movies they have done together. But one step at a time - let's start with learning how to create a simple UI in Elm!
Let's collect what we need for the initial user interface: we need a text field to enter the name, a button to submit the search, and a list that displays the possible matches.
If we write a minimal version of this down in html, it might look like this (already showing a single stand-in search result):
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Actor search</title>
</head>
<body>
<h3>Actor search</h3>
<input/>
<button>Search!</button>
<h3>Results</h3>
<ul>
<li>Barbara Stanwyck</li>
</ul>
</body>
</html>
If we want to be able to create user interfaces in Elm so that the content that is displayed can change, we need to put Elm in control of all the content inside a certain tag. For now, let's just handle everything inside the body tag with Elm. If you have worked with React in Javascript before, the following will seem familiar:
We need to keep a skeleton HTML file, but with everything inside the body tag removed (you can be more selective but for this example we'll do it like this). Then, we need to introduce a script tag that will load the final, compiled javascript file that is generated by the elm compiler. Finally, we need another script tag to fire up our App and tell it to take over the body tag and run our program:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Actor search</title>
</head>
<body>
</body>
<script src="elm-movie-domino.js"></script>
<script>
var app = Elm.Main.fullscreen();
</script>
</html>
Ok, now let's turn to the Elm side of things. In chapter two we already saw the text
function. We used it like this:
import Html exposing (text, Html)
main : Html a
main =
text "Hello from the MovieSearch app!"
We now need some other functions from the Html module to render different tags - in particular the h3, input, button, ul and li functions that render tags of the same name. If you look at the documentation of each of these functions, you will see that they all have the same type signature:
List (Attribute a) -> List (Html a) -> Html a
What does this mean? Each of them takes a list some type Argument a
and a List of some Html a
and returns a Html a
. Since the same type variable a
is used in the two parameters and the return type, the same concrete type will have to be used here. The thing to fill in here will be our own application's message type so that if an event occurs because the user does something (e.g. he clicks somewhere), we will be able to get a message we understand. We will look into this in more detail in the next chapter, I am just stating this here so you have a vague idea of what the 'a' is here for.
What do the Attribute
and Html types
represent? They represent DOM node attributes and (child) DOM nodes respectively (DOM is the Document Object Model, the logical structure of a website that is rendered by your browser. HTML is the textual language that can be used to serialize or write down the important part of the DOM). In our example above, we didn't use any attributes and we only had one instance where we nested a tag within another one, namely the li tag inside the ul tag. Since we have to satisfy the type signature for each function, we will have to pass two lists to each of these functions, even if they are empty - not giving a value at all is not the same as giving an empty list for Elm!
So here is a first attempt to use these 5 functions to create a similar DOM represenation using Elm as the original HTML draft:
import Html exposing (text, Html, input, button, ul, li, h3)
import Html.Attribute exposing (type)
main : Html a
main =
h3 [] [ text "Actor search" ]
input [] []
button [] [ text "Search!" ]
h3 [] [ text "Results" ]
ul
[]
[ li [] [ text "Barbara Stanwyck" ]
]
Don't compile it just yet - do you notice something? In the chapter about functions I told you that a function always has to evaluate to one expression but here we have 5 - h3 ..., input ..., button ..., h3 ... and ul ... (Why not 6? because the li is nested inside the ul - the result of this function will be put into a list that is the second argument to the ul function call. The ul function call is thus one expression).
You may think back to let...in, but if this is the structure we want, what would let ... in help us? The problem is that we have 5 dom nodes we want to return, but main can't be a List (Html a)
- it can only be a Html a
(or two other things as we saw in a previous chapter).
The answer is to wrap the entire thing in one DOM node, e.g. a div
, so that we can return a singular node from main. Does this mean that you really have to have a singular top level node in Elm? Yes indeed. In the functions you define yourself we will shortly see that it is perfectly alright to return a List (Html a)
, but the main entry point needs a single DOM node, period. So let's do this. What is the type signature of div? Interestingly, the same as that of h3, input, button, ul and li - in fact, practically all functions in the Html namespace have this signature (text being one of the only counter examples):
import Html exposing (text, Html, input, button, ul, li, div, h3)
import Html.Attribute exposing (type)
main : Html a
main =
div []
[ h3 [] [ text "Actor search" ]
, input [] []
, button [] [ text "Search!" ]
, h3 [] [ text "Results" ]
, ul
[]
[ li [] [ text "Barbara Stanwyck" ]
]
]
The many square brackets may confuse you a little, but just keep in mind that most of these functions (all except text), want two arguments: the list of attributes (that in this case is usually empty) and the list of child elements. Now we have satisfied the Elm compiler - our types check out and, if we compile this with elm make Main.elm --output elm-movie-domino.js
or running elm reactor
we should see an (ugly) little user interface if we open the html file - but this time, the content of body is rendered by evaluating our Elm code.
In the next chapter, we'll introduce some interactivity, but there are two things we should look at before that - first, how to extract some of the code we wrote here into another function to make it easier to read, and second how we could model the data we need for this as a type and then construct a value of this type and pass that to our function to render the output based on some data. This second part will be a great preparation for when we add interactivity in the next chapter and actually want to change some data and see the visual representation be updated "automatically".
First, it may make sense to extract the list of results into a function. We basically want to take the ul
... part out and replace it by calling a new function. Like so:
resultList : Html a
resultList =
ul
[]
[ li [] [ text "Barbara Stanwyck" ]
]
main : Html a
main =
div []
[ h3 [] [ text "Actor search" ]
, input [] []
, button [] [ text "Search!" ]
, h3 [] [ text "Results" ]
, resultList
]
That was easy! Ok, how about modelling the data? Well, we will want a List String
for the actor names. We will then change resultList from a function that takes no arguments to one that takes a List String
:
testData : List String
testData =
[ "Barbara Stanwyck" ]
resultList : List String -> Html a
resultList actors =
ul
[]
[ li [] [ text ?? ]
]
main : Html a
main =
div []
[ input [] []
, button [] [ text "Search!" ]
, resultList testData
]
The basic mechanism is all in place - at the top we declare a new value testData
of type List String
. For now, it only contains one Actress. The function resultList
now takes one argument, also a List String
, but I left out the implementation for now and inserted some question marks. Finally, in main, we call resultList
now with one argument, the testData
value we defined above.
So how could we implement resultList
so that it runs a List String
into a single ul element that in turn contains, for each string element in the actors argument, one li
element with the name of that actor as a html element produced by the text
function?
The answer lies in the map
function of the List module. It's type signature is:
map : (a -> b) -> List a -> List b
What does that tell us? map
takes two arguments. The first is a function (the parenthesis in the type signature make that clear!) that takes a value of type a
and returns a value of type b
- i.e. it transforms or projects a value of some type to another. The second argument is a List
of that same type a
that can be the input to the function that is passed in the first parameter. Finally, the return type is a List
of b
, where that b
is what the first argument evaluates to. So what map does is it takes a function that does something to one element, and a list of elements, and it returns a new list where each element has been "run through" that function.
This is how you use map to turn a List Int
into a List String
:
map toString [1, 2, 3]
-- the result would be ["1", "2", "3"]
This is just what we need in our example above. We have a List of String and we want to turn it into a List of Html. Like so:
listEntry : String -> Html a
listEntry name =
li [] [ text name ]
resultList : List String -> Html a
resultList actors =
ul
[]
( List.map listEntry actors )
For constructing a resultList
, we now take a list of actors, create ul node with an empty list of attributes and as child elements we use List.map
to map all elements of the actors
list to a list of Html a
nodes by applying the function listEntry
to each actors name. listEntry
then just gets one name at a time and turns it into a li
node. The function call of List.map
is wrapped in parenthesis so that Elm correctly parses the function call as one expression that should be evaluated and then used as the second parameter in the call to ul
.
Great Success so far! We don't have any interactivity yet, but we have used functions from the Html module to mirror a html structure we had previously designed. If you need to this for some larger chunk of HTML, a great tool to use is the Html -> Elm converter. You paste in some well formed Html (with a single root node!) and it gives you back the Elm code to generate the equvalent DOM nodes via functions from the Html module.
In the next chapter we will start using interactivity and use a more complex model than just a List of Strings.