In Model View Update - Part 1, we used a very simple example to learn the fundamental concepts behind Elm Architecture. In this section, we’ll build a slightly more complex app to reinforce our understanding of the Model View Update pattern.
Building a Sign-Up Form
Most web applications today require users to create a new account, so let’s build a sign-up form in Elm. It will be a good exercise to see how the Elm Architecture holds up when our application isn’t just a simple counter. It’ll also give us an opportunity to learn how to style our app using different techniques including an external CSS framework. Here’s how the sign-up form will look after we’re done implementing it.
When building a new app, we often don’t know where to start. One approach is to define the model first, write some view code to present that model to the user, and figure out what messages the user can send to our app. We can then use those messages to transform the current model into a new one. This is exactly the approach we took in the Model View Update - Part 1 section when we built a simple counter app. We’ll use the same approach here too.
First, let’s think about what information does our app need to keep track of. To create a new account, we need the user’s name, email, and password. We also need to keep track of whether or not the user is logged in. Based on that, here is what the model looks like:
Create a new file called
Signup.elm in the
beginning-elm/src directory and add the above code to it.
Our model is a record with four fields. The user entered information will be stored in
loggedIn will indicate whether or not the user has been authenticated to use the app. It’s quite common to use a record for defining a model in Elm as we’ve done here.
We called our model
User. Elm doesn’t require us to use the word
Model, so we can give it whatever name we want. It makes sense to call our model
User since we’re using it to track information about the user. The model for our sign-up app is more complex compared to the counter app. Here’s the counter app model again for comparison:
Now that we’ve defined the structure of our model, we need to create an initial version of it. The initial model will be given to the app when it gets launched. Add the following code to the bottom of
The next step is to present the initial model to the user. We do that by passing the model to the
view function. Add the following code to the bottom of
We already know what the
button functions do. The new functions —
input — represent the
<input> HTML tags respectively. The
id function represents the
id attribute of an HTML tag. Finally, the
type_ function defines which type of button we want to use. We can also use
type_ to specify the type for other tags such as
Notice the underscore in the
type_ function’s name. That’s because the
type keyword is already taken in Elm. Earlier in the Naming Constants section, it was suggested that we should try to replace an underscore with a meaningful word like this:
Following that suggestion, the creators of the
Html module could have renamed the
type_ function to
buttonType, but then they’d have to create separate functions for specifying the type attribute of other tags too such as
<style>. That seems a bit superfluous, so the use of an underscore is justified in this case.
All of the functions used in
view are defined in the
Html.Attributes modules, so go ahead and import them in
Notice how we had to use the
Html prefix before
form in the
view function —
Html.form. That’s because we exposed everything from the
Html.Attributes modules and both of them define a function called
form. If we hadn’t added the prefix, the Elm would get confused and show us the following error.
Generally speaking, we should avoid exposing everything from a module due to reasons explained in the Qualified vs Unqualified Import section. However, if we have a family of modules that often get used together such as
Html.Attributes where the chances of name clashes are minimal and the function names are self-documenting then it’s safe to expose everything. If you don’t feel comfortable exposing everything at all, an alternative is to expose each value individually like this:
Application Entry Point
To display the view, we need to define an entry point to our app. Add the following code to the bottom of
elm reactor from the
beginning-elm directory in terminal if it’s not running already, and go to this URL in your browser:
http://localhost:8000/src/Signup.elm. You should see the sign-up form.
Styling Our View
Our sign-up form looks a bit ugly. Let’s add some style. There are multiple ways to style a page in Elm:
- Using inline styles
- Using the
- Using an external CSS file
- Using a CSS framework
We’ll use all of those approaches to style the sign-up form. That way you’ll have a better sense of which one feels more comfortable and maintainable to you.
1. Using Inline Styles
We can use the
style function from the
Html.Attributes module to add an inline style. Let’s add some padding to the header. In the
view function, modify the line that applies the
h1 function like this:
style function takes two arguments: CSS property name and value. Here’s how its type signature looks:
Next, we’ll style the
button tags. Add the following code below the
Apply these styles to the respective tags in the
Refresh the page at
http://localhost:8000/src/Signup.elm and you should see a much better looking sign-up form.
Note: The best practice for working with HTML suggests that the styles should primarily be specified in CSS files. Therefore, we shouldn’t use too many inline styles.
2. Using the
It’s convenient to style elements inline. However, if we make a mistake typing one of the properties, the Elm compiler won’t catch it because they’re just plain old strings. Wouldn’t it be great if we could use Elm’s type system to detect errors in our CSS too? That’s exactly what the
elm-css package offers.
Let’s re-implement the styles we applied above using functions defined in the
Css module included in the
elm-css package. Before we can do that though, we need to install the package. Run the following command from the
beginning-elm directory in terminal.
y when asked to add
rtfeldman/elm-css and other packages to
elm-css is a complex package with more than a dozen modules in it, but we’re interested in only three of them:
Css module enables us to apply CSS styles using plain old Elm functions. For example, if we want to give a blue background to a button, we can use the
hex functions defined in the
Css module like this:
For comparison, here is how we used the
style function earlier to do exactly that:
If we make a mistake while typing a function name from the
CSS module, the Elm compiler will throw an error. However, if we introduce a typo in the CSS property name passed to the
style function, the compiler can’t help us. By taking advantange of Elm’s powerful type system, we can get rid of so many CSS related bugs during compile time. Isn’t that nice? Let’s import the
Css module in
Html.Styled is a drop-in replacement for the
Html module from the
elm/html package. All it does is return a styled version of elements defined in the
Html module. For example, here is how the
Html.text function is implemented.
Remember all HTML elements in Elm are virtual DOM nodes behind the scenes. Let’s compare the implementation above with that of
As you can see, the only difference is that
Html.Styled.text returns a styled node whereas
Html.text returns a plain old node. Let’s import
Signup.elm and remove the line that imports the
We can now create styled elements in our app like this:
Add that code below
Signup.elm and also remove the
buttonStyle functions while we’re at it. To style an element using
elm-css, we need to use the
styled function defined in the
Html.Styled module. Here is how its type signature looks:
The first argument is an HTML element from the
Html.Styled module. We can’t use the one defined in
Html. The second argument is a list of CSS styles. Each style is represented by the
Style type defined in the
Css module we covered earlier. Now that we have styled the
button elements separately, let’s use them in our
To style the header, we’ll use the
css function from the
Html.Styled.Attributes module like this:
We’re still inlining our style here, but we’re using an Elm function to specify the
padding-left CSS property. Here’s how the
css function’s type signature looks:
Html.Styled.Attributes is a drop-in replacement for the
Html.Attributes module from the
elm/html package. Let’s import it in
Signup.elm and remove the line that imports
type_ attributes used inside the
view function are now from
Html.Styled.Attributes instead of
Rendering Styled Elements
All of our HTML elements and their attributes now come from the
Html.Styled.Attributes modules respectively. Elm doesn’t know how to render them. Luckily, the
Html.Styled module provides a function called
toUnstyled for converting the styled elements to something Elm can render. Here is how its type signature looks:
Signup.elm to this:
main’s type is now
VirtualDom.Node msg. Before using
elm-css it was
Html msg. The definition of the
Html type reveals that it’s just a type alias for
Because of this, we are allowed to return a virtual DOM node directly from
main. Let’s install the
elm/virtual-dom package by running the following command from the
Actually, that package is already installed as an
indirect dependency. Answer
y to make it a
direct dependency. Now import the
VirtualDom module in
Finally, we’re ready to run our app. Refresh the page at
http://localhost:8000/src/Signup.elm and you should see the exact same form as before.
3. Using an External CSS File
The third option is to create a regular CSS file and use it in our Elm app. Unfortunately,
elm reactor doesn’t provide an easy way to load external CSS files, so we’ll have to compile the
Signup.elm file manually. Create a new file called
signup-style.css in the
beginning-elm directory and add the code below to it.
Next, we’ll use
elm make to compile the
Signup.elm file. Before we do that, we need to remove all traces of
elm-css from that file. Remove the
styledButton functions and modify
view to this:
We’re back to where our sign-up form had no style at all. Remove
main and also change its type signature.
And replace the import list in
Signup.elm with the following.
We’re now ready to compile
beginning-elm directory in terminal.
elm make will create a new file called
signup.js in the
beginning-elm directory. If it already exists, its contents will be overwritten. The final step is to create an HTML file that loads the
signup.js files. Create a new file called
signup.html in the
beginning-elm directory and add the code below to it.
The code for loading the
signup-style.css file is straightforward, but the one that loads our Elm app is slightly more complex. First, we need to load the
signup.js file includes not only the code we wrote, but also the entire Elm runtime and all the other Elm packages we installed.
We created a
div and gave it an
id. We then asked the Elm runtime to load our app inside the
elm-code-is-loaded-here div. When we call the
Elm.Signup.init function, the runtime looks for the
main function inside the
Signup module and uses it as an entry point. If the
Signup module is missing, or if the
Signup module exists, but doesn’t have the
main function, we’ll get an error. If you open the
beginning-elm/signup.html file in a browser, you should see the exact same form as before.
Loading CSS from an external file forced us to take a few more steps, but it also enabled us to use any CSS file in our Elm app. This approach will come in handy if you are planning to introduce Elm into your existing app that already contains numerous CSS files.
4. Using a CSS Framework
It’s also possible to use a CSS framework with Elm apps. It’s actually very similar to using an external CSS file. Back in the Building a Simple Page with Elm section, we used the popular Bootstrap framework to style the home page of Dunder Mifflin. Let’s use it here again to style our sign-up form. Modify the
signup.html file to include the Bootstrap framework instead of the
Next, we need to specify which CSS classes we want to use in our Elm code. Modify the
view function in
Signup.elm like this:
Bootstrap defines many CSS classes such as
form-group that have special meaning. We won’t try to understand what each of these classes mean in this book, but you can learn all about them from the official site. Once you know how Bootstrap works, it’s pretty easy to understand the code above.
The next step is to re-compile
Signup.elm. Run the following command from the
beginning-elm directory in terminal.
Now, if you open the
signup.html file in a browser, you should see this form:
One big disadvantage of using an external CSS file or framework in this way is that we lose the ability to detect errors in our CSS code during compile time. The good news is, the Elm community members are working hard to provide Elm packages for making the use of external frameworks such as Bootstrap and Material Design more reliable through Elm’s type system and other features. The
elm-mdl packages give us the ability to design our pages with Bootstrap and Material Design respectively without sacrificing Elm’s type safety. You should definitely check them out.
For the rest of this section, we’ll use
elm-css, so let’s revert
Signup.elm back to the following code. This will allow us to keep using
elm reactor instead of recompiling our code with
elm make every time we make a small change.
Now that we have presented the initial model to users, what can they do with it? They can enter their name, email, and password into the form and click Create my account to sign up. The first thing we need to figure out is how to store name, email, and password entered by the user in
User model. The
Html.Events module from the
elm/html package defines a function called
onInput that allows us to specify which message should be triggered when the user starts typing into an input text box. Here’s an example:
Html.Styled.Events module from the
elm-css package also defines the
onInput function. Just like
Html.Styled.Events module is a drop-in replacement for
Don’t type the above code yet; we’ll need to add the
onInput attribute to all of our
input fields. We’ll do that after we’ve defined the
SaveName and all other messages needed in our app.
Add the following type definition right above
SavePassword messages will be triggered when the user enters name, email, and password respectively. The
Signup message will be triggered when the Create my account button is clicked.
We need to tell Elm what to do with these messages. This is where the
update function comes in. Whenever Elm receives a message, it checks to see if there is a pattern in the
update function that matches the incoming message. If a pattern does exist, it executes the code for that pattern. Otherwise, the application crashes. Therefore, it’s important to handle all possible incoming messages. Add the
update function above
update function takes a message and a model (
User in our case) and performs some operation on that model. It then returns the resulting model. So when users type their name in a text field, Elm receives the
SaveName message. It finds a pattern that matches this message:
All we are doing here is saving the name that came in as a payload with the
SaveName message into the
User model using the update record syntax. The code for handling messages originated from the
SavePassword text fields looks very similar. The branch for the
Signup message simply sets the
loggedIn flag to
True. The actual code for handling this message requires us to know how to send HTTP requests to a server in Elm. We haven’t learned that yet, so we’ll have to wait until chapter 6 to fully implement it.
We’ve created a mechanism for handling messages, but we still don’t have a way to create them. Let’s take care of that. Add
styledButton respectively so that they can fire messages to the runtime.
Previously, the HTML returned by
view wasn’t creating any messages, so we just used
msg as a placeholder in its type annotation.
view uses the
onClick functions from the
Html.Styled.Events module to generate messages of type
Msg. That’s why we needed to change
view’s type annotation. Let’s import
Wiring Everything Up
Here is how our
main looks right now in
view was enough when we were just displaying a static view. To allow interactivity, we need to add
update in the mix. Modify
main to this:
And import the
Browser module in
When we compare the
main function in
Signup.elm to the one in
Counter.elm from the Model View Update - Part 1 section, we notice two differences.
Difference #1: The type annotation for
User instead of
Model. That’s because we decided to name our model
User to make it more descriptive.
toUnstyled is applied before rendering the view in
As mentioned earlier in the Rendering Styled Elements section, Elm doesn’t know how to render elements and attributes defined in the
Html.Styled.Attributes modules from the
elm-css package. Therefore, we need to use the
toUnstyled function to convert them to something Elm can render.
We assigned an anonymous function to the
view field in
Browser.sandbox. Anonymous functions are a little hard to read by nature. We can simplify
Signup.elm by replacing the anonymous function with
>> like this:
>> is a built-in operator for composing multiple functions. You can think of
func1 >> func2 as equivalent to this:
We’re now ready to run our app once again. Refresh the page at
http://localhost:8000/src/Signup.elm and you should see the sign-up form.
When the user enters all information and clicks Create my account, the resulting model will look like this:
We don’t get any visual feedback when the Create my account button is clicked because we aren’t sending the user data to a server. We’ll learn how to do that in chapter 6.
In this section, we reinforced our understanding of how the Model View Update pattern works by building a slightly more complex app that allowed users to enter their name, email, and password.
The following diagram shows the interaction between various parts of the sign-up form app which is very similar to the interaction between different components in the counter app we built in Model View Update - Part 1.
We also learned how to style our views in different ways using inline CSS, the
elm-css package, an external CSS file, and an external framework such as Bootstrap. Here is the entire code for building a sign-up form using