Elm: Web Development without Html, CSS or JS
Part 1: Getting Things Up and Running
Introduction
Webdevelopment is cumbersome. Different and fast changing technologies, unclear standards that not every player respects, questionable technical decisions for very import parts of the ecosystem probably are the reason for this.
Elm tries to go a different route. As a compiled language it is free to leave the mistakes of the past behind.
In combination with the library elm-ui
one can achieve complete independence from Html, CSS and Javascript.
But Elm is also something new and very unfamiliar and thus the barrier to entry is quite high. This tutorial will help you understand how Elm works and how you can leverage it to build fast webapps that are reliable and maintainable.
We have been using Elm in production for about two years now and are very happy with it. Some projects would not have been possible at their respective budget without it.
This is a multipart tutorial that will show you how to set up Elm, understaand it’s architecture and learn it’s strong and weak sides.
Part 1: Getting things up and running
This part covers:
- installing and setting up Elm
- basic compiler usage
- an Elm primer
- how to use and install community packages
- install and show off
elm-ui
You can follow the code on GitHub.
First things first:
Install Elm
For windows and mac please use the respective installers.
You can install elm on other systems through npm:
npm install -g elm
Check if Elm is correctly installed by running elm --version
.
0.19.0
Lets create an empty Elm project:
cd ~/Documents
mkdir -p elm-tutorial/part1
cd elm-tutorial/part1
elm init
This will create an elm.json
file and a src
directory.
For now thats all we need to know, later we will have a closer look at the elm.json
.
If you do not want to install Elm as of now, you can also use Ellie an Elm online editor.
Setting up an editor
For the sake of this tutorial we will only explain how to setup VS Code with Elm. In case you want to use a different editor check out the list of available plugins in the official Elm guide.
If you haven’t already installed VS Code check out how to do it here.
In VS Code, use the open folder menu to navigate to the elm-tutorial/part1
folder we created in the previous section. Navigate to the extensions menu and install the elm
extension.
At the time of writing it has version 0.25.0
.
How to use the compiler
Let’s find out how to use the compiler.
Add a Main.elm
file in the src
directory and fill it with:
module Main exposing (main)
import Browser
import Html exposing (h1, text)
main =
Browser.sandbox
{ init = ()
, update = \_ _ -> ()
, view = \_ -> h1 [] [ text "Hello World!" ]
}
No need to understand anything at the moment. This is just to a minimal example to be able to show you what you can do with the compiler.
You can now invoke the compiler to generate a HTML-file and have a look at it:
# in ~/Documents/elm-tutorial/part1
elm make src/Main.elm
firefox index.html
Tada! Hello world!
There are other ways to use the compiler but we will touch that later.
Functional Programming: Basics
Let’s extend our Main.elm
to be a bit more meaningful and understandable:
module Main exposing (main)
import Browser
import Html exposing (Html, h1, text)
import Html.Events exposing (onClick)
init : Model
init =
{ name = "Hans"
, age = 37
}
type alias Model =
{ name : String
, age : Int
}
type Msg
= Click
update : Msg -> Model -> Model
update msg model =
case msg of
Click ->
{ model | age = model.age + 1 }
view : Model -> Html Msg
view model =
h1 [ onClick Click ]
[ text
("Hello "
++ model.name
++ ". You seem to be "
++ String.fromInt model.age
++ " years old."
)
]
main =
Browser.sandbox
{ init = init
, update = update
, view = view
}
If you have never seen functional code, this probably seems alien to you. So let’s go through it line by line.
Modules
module Main exposing (main)
This tells the compiler, that the module in this file is called Main
and that we only expose one function to the outside world (main
). The name of the module and the filename have to be identical.
If any other module wanted to use other functions or types from our module, we would have to list them in the exposing brakets. E.g.:
module Main exposing (Model, main, init)
You can also tell the compiler to expose everything with the following:
module Main exposing (..)
Imports
Imports work in a similar manner. When a module is imported all it exposes can be accessed through the module name. In our example:
import Browser
-- later
Browser.sandbox
You can also directly import functions or types with the exposing syntax.
Here we do so with h1
and text
.
Also it is possible to alias modules. For example we could import Browser
as B
like that:
import Browser as B
-- later
B.sandbox
Functions
If you are not familiar with functional programming the function syntax is probably gonne be the most confusing at the beginning.
A function has two parts:
- The declaration:
init: Model
- The definition:
init = { name = "Hans", age = 37 }
The declaration just states the types and is completely optional.
Examples
five: Int
five = 5
The declaration states, that the return type is an Int. And we define the return value of five
to be 5
.
double: Int -> Int
double x =
x + x
Lot’s of new syntax here.
Int -> Int
says that when we give the function an Int it gives us back an Int.
In the definition we give a name to our first parameter: x
.
We add x
and x
. The result will automatically be returned.
Here is a typescript implementation as a comparison:
function double(x: number): number {
return x + x;
}
With more parameters functions look like this:
add: Int -> Int -> Int
add x y =
x + y
Init
As we now know, init
takes no arguments and returns a value of type Model
.
Model
is constructed by record syntax, which is similar to the way to declare record type.
Types
There are three different kinds of types in Elm: custom types, record types and tuples. We will deal with custom types and tuples later and will for now only explain record types.
Record types, like Model
, have named fields which can be trivially accessed and updated.
You can update fields by using the update syntax:
{ record | field = value }
Check out the update
function. Here we increment model.age
by one with the following statement:
{ model | age = model.age + 1 }
And this also shows you how to access record fields:
Simply use a dot: model.age
as in
h1 [] [ text ("Hello " ++ model.name ++ ". You seem to be " ++ toString model.age ++ " years old.") ]
Updating the Model
In Elm all state lies in one model, irrespective of the complexity of the app. For our case this is just a simple record with a name
and an age
. We will deal with the problem of how to handle larger and more complex models in a later part of this tutorial.
For now, it is important to realize another fact: the model can only be changed in one place: the update
function.
In our case, we want to increment the age
value when Click
is triggered:
Click -> { model | age = model.age + 1 }
Please ignore possibly confusing syntax for now.
Emmitting Html
Even if we won’t write Html ourselves, of course the basis of every webapp is Html. Elm takes a different approach than most other frameworks or tools in the area.
Instead of templating, i.e. having Html with interspersed code, in Elm Html is build with functions.
The h1
in our view
is just a function. It takes as argument a list of attributes and a list of child elements and returns value of type Html msg
.
The runtime then makes sure that the Html we built with functions actually get inserted in the DOM.
Wiring up the runtime
The Elm runtime needs to know a few things about our application to wire it up correctly.
You can find all ways to wire up your application in the Browser
-package.
Here we use the sandbox
function for the most simple of wiring up.
The sandbox expects three arguments: init
,view
and update
.
We already discussed theses functions and can now just wire them up:
Browser.sandbox
{ init = init
, update = update
, view = view
}
Don’t get confused: the init
, update
and view
on the right-handside are the functions we just implemented and discussed.
Ways to get the app running
We already learned how to use elm make
.
Now lets check out a different way to get the app running. Navigate to the project directory and execute
elm reactor
Go to <http://localhost:8000> to see your project dashboard.
Now navigate to http://localhost:8000/src/Main.elm in your browser. You should see this:
You can click on the text and, as expected, the age will increment.
Community Packages
Elm, like most modern languages shippes with a package-manager. Direct and indirect dependencies are listed in the elm.json
we generated right at the beginning of this tutorial.
You can use the package-catalog to search for packages and read their documentation.
We want to teach you how to write web-apps without HTML, CSS and JS. But in the first example, even though we do not write Html directly, we still use h1
which is clearly Html. To get rid of Html completly we a need different way to generate Html than the previously used elm/html
package.
We will use mdgriffith/elm-ui
instead.
To add it to elm.json
run
elm install mdgriffith/elm-ui
Now we can use everything elm-ui
provides.
elm-ui
elm-ui liberates you from using Html and Css. It operates on a higher level of abstraction. Our code until now can be easily rewritten to use elm-ui:
module Main exposing (main)
import Browser
import Html exposing (Html)
import Element exposing (el, text, paddingXY)
import Element.Events exposing (onClick)
import Element.Font as Font
import Element.Region as Region
init : Model
init =
{ name = "Hans"
, age = 37
}
type alias Model =
{ name : String
, age : Int
}
type Msg
= Click
update : Msg -> Model -> Model
update msg model =
case msg of
Click ->
{ model | age = model.age + 1 }
view : Model -> Html Msg
view model =
Element.layout []
(el
[ Region.heading 1
, Font.size 32
, Font.bold
, paddingXY 8 20
, onClick Click
]
(text
("Hello "
++ model.name
++ ". You seem to be "
++ String.fromInt model.age
++ " years old."
)
)
)
main =
Browser.sandbox
{ init = init
, update = update
, view = view
}
As you probably expected, only the view function changes.
Element.layout
is a function to transform elm-ui-Views back to Html.
Otherwise we are doing nothing fancy here.
Start the reactor and visit localhost:8000/src/Main.elm. The result is stunningly identical.
Showing off what elm-ui can do
Just as a little tidbit at the end of this tutorial we will show off a little something elm-ui is great at.
Putting things where you want them to be. Vertical and horizontal centering is far from trivial in CSS.
With elm-ui it’s just a centerX
and centerY
Change view
to
view : Model -> Html Msg
view model =
Element.layout []
(el
[ Element.centerX
, Element.centerY
, Region.heading 1
, Font.size 36
, Font.bold
, onClick Click
]
(text
("Hello "
++ model.name
++ ". You seem to be "
++ String.fromInt model.age
++ " years old."
)
)
)
Future Parts
In Part 2 we will cover basic concepts of elm-ui, custom types and how to embed your elm app in an existing app. Follow us on twitter to get updates.
Exercise
While waiting on part 2 we have a little exercise for you:
Add a second line which says ‘No, i’m not!'. When clicking on this the age should decrement.
Hint: you’ll need Element.column
and you will need to extend the Msg
-type.
A solution will be posted on Monday (June 3rd) on our twitter.
Interested in working with us?
CurrySoftware is now offering Elm consulting services and we would love to support you on your path using Elm! Please contact us if you are interested!