In the Model View Update - Part 1 section, we used a very simple example to learn the fundamental concepts behind the 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 using 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/elm-examples directory and add the above code to it.
Our model is a record with four properties.
password will store the information entered by the user.
loggedIn will indicate whether or not the user has been authenticated to use our 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 the 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 function 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
We’ve already defined the
view functions. Let’s define the
update function next. Add the following code right above the
main function in
update function simply returns the initial model. We’ll expand it to respond to various messages generated by the UI elements a little bit later. Now, we’re ready to render the view on a screen. Run
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/elm-examples/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 try each approach from the above list so that you can compare them. 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 a list of tuples that contain the CSS property names and values. Here’s how its type signature looks:
The line that contains the
h1 in the
view function is a bit difficult to read with all the parentheses and square brackets. Let’s extract the style info into a separate function. Add the following function definition right below the
Now we can simply apply the
headerStyle function like this:
Next, we’ll style the
button tags. Add the following function definitions right below the
Apply these styles to the respective tags in the
If you refresh the page at
http://localhost:8000/elm-examples/Signup.elm, you should see a much better looking sign-up form.
2. Using the
Although being able to style elements inline is convenient, it’s a bit cumbersome to use a list of tuples for specifying the CSS properties and values. More importantly, 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? That’s exactly what the
elm-css package offers.
Let’s re-implement the styles we applied above using the functions defined in the
Css module included in the
elm-css package. Before we can do that though, we need to install the package. From the
beginning-elm directory in terminal, run the following command.
y when asked to add
rtfeldman/elm-css as a dependency to the
elm-package.json file and approve the installation plan. After that, import the
Css module in
Next, we’ll rewrite the
buttonStyle functions using the functions defined in the
Css module. Replace the contents of those functions in
Signup.elm like this:
As you can see, we’re now using plain old Elm functions to declare CSS properties. Instead of using the
style function defined in the
Html.Attributes module, we used our own function called
styles which takes the CSS properties and converts them into key-value pairs that can be passed to the
Since we didn’t expose the functions when importing the
Css module, we had to use a prefix which made the code hard to read. The reason we didn’t expose them all was due to the fact that the
Css module defines some functions that are also found in the
Html.Attributes modules, for example
width. A better solution is to extract all CSS related code to a separate module. Let’s do that. Create a new file called
SignupStyle.elm in the
Add the following code to the
The CSS code looks much cleaner now. We need to import the
SignupStyle module from the
Don’t forget to remove the
import Css line from
Signup.elm. Also, remove the
buttonStyle functions from that file. If you refresh the page at
http://localhost:8000/elm-examples/Signup.elm, 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 ourselves. Create a new file called
signup-style.css in the
Add the following CSS code to the
Next, we’ll use
elm-make to compile the
Signup.elm file. Before that, we need to remove all traces of the
SignupStyle module from that file. Remove the line that imports the
We also need to stop applying the
buttonStyle functions in
Now we’re back to where our sign-up form had no style at all. Run the following command from the
beginning-elm directory to compile
elm-make will create a new file called
signup.js in the
beginning-elm directory. If it already exists, its content will be overwritten. The final step is to create an HTML file that loads the
signup.js files. Create a file called
signup.html in the
Add the following code to the
The code for loading the
signup-style.css file is straightforward, but the one that loads the 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.embed function, the Elm 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 Inc. 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 the Bootstrap framework works, it’s pretty easy to understand the above code.
The next step is to re-compile the
Signup.elm file using
elm-make. 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.
Let’s revert the
view function back to where it used the styles defined in the
SignupStyle.elm file so that we can keep using
elm-reactor to test our changes instead of recompiling our code with
elm-make every time we make a small change.
We also need to import the
SignupStyle module again in the
Now that we have presented a view to the users, what can they do with it? They can enter their name, email, and password into the form and click the Create my account button to initiate the creation of a new account. The first thing we need to figure out is how to store the name, email, and password into the
User model. The
Html.Events module 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:
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 the
SavePassword messages will be triggered when the user types in the 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. Modify the
update function like this:
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 the user types his or her 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 the HTTP Requests section to fully implement it.
Not sure if you noticed, but the type annotation of the
update function now uses the
Msg type instead of the
msg type variable.
We also need to replace the
msg type variable with the
Msg type in the
main functions’ type annotations.
Next, we need to add the
onClick attributes to all input fields and Create my account button respectively in the
onClick functions are defined in the
Html.Events modules, we need to import that in
If you refresh the page at
http://localhost:8000/elm-examples/Signup.elm, you should see the sign-up form once again.
After the user has typed all three pieces of information and clicked the Create my account button, the resulting model will look like this:
We don’t get any visual feedback when the Create my account button is clicked, but we’ll fix that later in the Http Requests section.
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 the name, email, and password information. The interaction between various parts of the sign-up form app is very similar to how the components in the counter app from the Model View Update - Part 1 section interacted with each other.
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.