Creating a todo app in Elm
Tags: elm, webdev
Published: 2019-12-30 12:46:02

> I'm going to do something really different - I'm going to learn a new language in the open, and learn by trying to teach what I've learned step by step. The language I'm trying to learn is Elm.

In the last article in this series, we went through setting up an Elm developer environment, as well as creating a very "hello world"-y counter app.

In this article, we're going to create something a tiny bit more advanced - **the trusty old todo app**.

## Start with the model

I think Elm does a lot of things well, but one of my favorite things is that it makes you think about how you're going to model your state. Since we're creating a todo app, it makes sense to start out with modeling a todo:

```elm
type alias Todo =
    { text : String
    , completed : Bool
    }
```

That is - a todo is a record (similar to a JavaScript object) with a descriptive text and a completed flag.

Now, we don't want to handle a single todo, but a list of them. So our model might look like this:

```elm
type alias Model = 
    List Todo
```

A `List` in Elm is a linked list implementation of an array, and is [well documented](https://package.elm-lang.org/packages/elm/core/latest/List). It's what is created when you write code like `list = [1,2,3]`, so it looked like just what I needed.

Our model is still lacking, though. In order to add todos, we need to keep track of the text in our "add todo" input as well. Therefore, we need to use a record!

```elm
type alias Model = 
    { todos: List Todo
    , inputText: String
    }
```

Now we're cooking! 

## Mapping out the available actions

So we've been able to create a state model. Next up is creating a list of possible actions that might happen in our application. Let's create a type that enumerates all those possibilities.

```elm
type Message 
    = AddTodo
    | RemoveTodo Int
    | ToggleTodo Int
    | ChangeInput String
```

Here, we create four distinct actions - and most accept an argument as well. So the `AddTodo` message will accept no arguments, while the `RemoveTodo` will accept the index to remove as an argument and so forth. I think this is what you call a parameterized custom type, but don't let that bother you for a second 😄 Just know that the word following the message type is the type of the first argument. If you added a second type after that, it would indicate that the message would expect two arguments, and so forth!

> ### type vs type alias
> If you paid meticulous attention to the above examples, you noticed that we wrote `type alias` when we specified our model, and `type` when we specified our message type. Why is that?
> 
> If I've understood the [FAQ](http://faq.elm-community.org/#what-is-the-difference-between-type-and-type-alias) correctly, a `type alias` is  a "shortcut" for a particular type, while a `type` is an actual distinct type. I think you could pattern match a type, but not a type alias. We specify the type of functions like `update` and `view` with type aliases.

## Writing the business logic

I giggle every time I call the logic in my todo app for "business logic", but I guess it's what it is. No matter what you call it though, we should implement it via the `update` method. 

If you don't remember from the last post of our series, you can think of this method as the "reducer" of a Redux application. It gets called whenever you trigger an action in your app, receives the old state model and expects you to return the updated state model.

We're going to handle each of the possible messages with a `case .. of` expression - which is a way to "pattern match". I still call it a "fancy switch statement". For our app, it looks like this:

```elm
update : Message -> Model -> Model
update message model
    = case message of
        AddTodo -> 
            { model 
                | todos = addToList model.inputText model.todos
                , inputText = ""
            }
        RemoveTodo index -> 
            { model | todos = removeFromList index model.todos }
        ToggleTodo index ->
            { model | todos = toggleAtIndex index model.todos }
        ChangeInput input ->
            { model | inputText = input }
```

This is a tad bit simplified, so let's step through it one case at a time.

### Handling AddTodo

First, we handle the `AddTodo` message. We use the `{ model | something }` syntax to copy the existing model, and then overriding any fields to the right of the `|`. In this particular instance, we wouldn't have needed it, since we change the entire state - but by doing it anyways, we make our model easier to extend at a later point in time.

We get the new `todos` value by calling this mystical function `addToList`, which is called with the input text and the existing todos list. But how does that function look like?

```elm
addToList : String -> List Todo -> List Todo
addToList input todos =
    todos ++ [{ text = input, completed = False }]
```

`addToList` accepts a string input text and a list of todos, and returns a new list of todos. We append the old list with a list containing the new todo by using the `++` operator.

In JavaScript, this function would've looked like this:

```js
const addToList = input => todos => [
  ...todos, 
  { text: input, completed: true },
];
```

We could've inlined this as well, but extracting a function looked a bit cleaner to me 🤷‍♂️

### Handling RemoveTodo

The next message to handle is `RemoveTodo`. We're passed the `index` as an argument, and we pass both the index and the existing list as arguments to the `removeAtIndex` function. It looks like so:

```elm
removeFromList : Int -> List Todo -> List Todo
removeFromList index list =
    List.take index list ++ List.drop (index + 1) list
```

Here, we use two list functions called [`List.take`](https://package.elm-lang.org/packages/elm/core/latest/List#take) and [`List.drop`](https://package.elm-lang.org/packages/elm/core/latest/List#drop) to construct two new lists - one that includes all items up to (but not including) the index specified, and one that includes all items from the item after the provided index. Finally, we concatenate the two lists with the `++` operator.

I'm sure there are more clever ways to do this, but that's what I came up with. 🙈

### Handling ToggleTodo

`ToggleTodo` is pretty similar to the previous one. It calls the `toggleAtIndex` function, which looks like this:

```elm
toggleAtIndex : Int -> List Todo -> List Todo
toggleAtIndex indexToToggle list =
    List.indexedMap (\currentIndex todo -> 
        if currentIndex == indexToToggle then 
            { todo | completed = not todo.completed } 
        else 
            todo
    ) list
```

Here, we use the `indexedMap` list function to loop through all the items, and toggling the completed flag. Note that we're passing an anonymous function to the `indexedMap` function - those have to be prefaced by a `\` (backslash). Supposedly, the backslash was chosen because it resembles a `λ` character - and it denotes a lambda function. It might not make a lot of sense, but it's a nice way to remember to add it! 😄

The JavaScript version of the same could look like this:

```js
const toggleAtIndex = indexToToggle => todos => 
  todos.map((todo, currentIndex) => 
    indexToToggle === currentIndex 
      ? { ...todo, completed: !todo.completed } 
      : todo
  );
```

### Handling ChangeInput

The last message to handle is the simplest one, really. The `ChangeInput` receives the updated input as an argument, and we return the model with an updated `inputText` field in response.

## Implementing the view

We've designed a good state model, outlined all possible actions and implemented how they will change the model. Now, all that's left to do is to put all of this on screen!

As with the counter example in the last article, we implement the `view` function. It looks like this:

```elm
view : Model -> Html Message
view model =
    Html.form [ onSubmit AddTodo ]
        [ h1 [] [ text "Todos in Elm" ]
        , input [ value model.inputText, onInput ChangeInput, placeholder "What do you want to do?" ] []
        , if List.isEmpty model.todos then
            p [] [ text "The list is clean 🧘‍♀️" ]
          else
            ol [] List.indexedMap viewTodo model.todos
        ]
```

Here, we create a form with an `h1` tag, an input for adding new todos, and a list of todos. If there isn't any todos in your list, we let you know you're done for now.

We've pulled the "render a todo" logic into its own helper function, `viewTodo`. We call it for each of the todos in `model.todos` with the `List.indexedMap` utility we used earlier. It looks like this:

```elm
viewTodo : Int -> Todo -> Html Message
viewTodo index todo =
    li
        [ style "text-decoration"
            (if todo.completed then
                "line-through"
             else
                "none"
            )
        ]
        [ text todo.text
        , button [ type_ "button", onClick (ToggleTodoCompleted index) ] [ text "Toggle" ]
        , button [ type_ "button", onClick (RemoveTodo index) ] [ text "Delete" ]
        ]
```

Here, we create a list item with the todo text, and buttons for toggling and removing the list. There's a few things here I thought I'd explain:

First off, notice that attributes that happen to be a reserved word in Elm is suffixed with `_` - like `type_` in the buttons.

Second, notice how you can specify inline styles. It's very verbose, but you could refactor most of that if it's becoming bothersome. For now that's fine.

Speaking of attributes, I want to do bring your attention to the fact that all HTML attributes are functions! That took me a bit by surprise to begin with, but once you "get it", the syntax makes a lot more sense!

## Adding a feature: Filters!

In a typical project, you don't write complete new UIs - you add features to them. So let's add one right now.

I want to filter out which tasks are done, and what's remaining. Let's start by creating the type definition for a filter, with all its possible state.

```elm
type Filter
    = All
    | Completed
    | Remaining
```

Next, let's add a field to our model!

```elm
type alias Model = 
    { todos: List Todo
    , inputText: String
    , filter: Filter
    }
```

The `init` function complains that we haven't specified an initial value for the new `filter` value, so let's add that as well:

```elm
init =
    { todos = []
    , inputText = ""
    , filter = All
    }
```

We also need to specify a new message for changing the filter!

```elm
type Message
    = AddTodo
    | RemoveTodo Int
    | ToggleTodo Int
    | ChangeInput String
    | ChangeFilter Filter
```

Now our `update` function complains that we haven't handled all possible cases for the `Message` type. Implementing it is pretty similar to the `ChangeInput` case!

```elm
update : Message -> Model -> Model
update message model
    = case message of
        -- all the other cases are truncated for brevity
        ChangeFilter filter ->
            { model | filter = filter }
```

Finally, we need to change the UI a bit. First, let's create few functions for creating the "select a filter" UI:

```elm
type alias RadioWithLabelProps =
    { filter : Filter
    , label : String
    , name : String
    , checked : Bool
    }


viewRadioWithLabel : RadioWithLabelProps -> Html Message
viewRadioWithLabel config =
    label []
        [ input 
            [ type_ "radio"
            , name config.name
            , checked config.checked
            , onClick (ChangeFilter config.filter) 
            ] []
        , text config.label 
        ]


viewSelectFilter : Filter -> Html Message
viewSelectFilter filter =
    fieldset []
        [ legend [] [ text "Current filter" ]
        , viewRadioWithLabel 
            { filter = All
            , name = "filter"
            , checked = filter == All
            , label = "All items" 
            }
        , viewRadioWithLabel 
            { filter = Completed
            , name = "filter"
            , checked = filter == Completed
            , label = "Completed items" 
            }
        , viewRadioWithLabel 
            { filter = Remaining
            , name = "filter"
            , checked = filter == Remaining
            , label = "Remaining items"
            }
        ]

```

Woah, that was a lot! Let's go through it step by step:

First, let's look at the `viewSelectFilter` function. It accepts the current filter, and returns a fieldset with a legend and three new "nested views" I've named `viewRadioWithLabel`. Each of these radio buttons are passed a record with four different arguments.

The `viewRadioWithLabel` function is pretty simple, too. It renders a label with an input inside of it, as well as the actual label text. It sets the correct attributes on the `<input />` element, and adds an `onClick` event listener that triggers the `ChangeFilter` message.

Note that we gave the `viewRadioWithLabel` function a single record as its argument (complete with its own type alias), instead of currying four different arguments. I think that makes it much easier to reason about - even if you can't partially apply stuff the same way. 

Finally, we add the `viewSelectFilter` to our main `view` function, and apply the actual filtering to our list!

```elm
view : Model -> Html Message
view model =
    Html.form [ onSubmit AddTodo ]
        [ h1 [] [ text "Todos in Elm" ]
        , input [ value model.inputText, onInput ChangeInput, placeholder "What do you want to do?" ] []
        , viewSelectFilter model.filter
        , if List.isEmpty model.todos then
            p [] [ text "The list is clean 🧘‍♀️" ]
          else
            ol [] model.todos
                |> List.filter (applyFilter model.filter)
                |> List.indexedMap viewTodo
        ]
```

See what's happened where we list out our todos? We're using this new fancy operator `|>`, which lets us apply several functions to our list one at a time. 

Let's look at the `applyFilter` function as well - it's pretty straight forward.

```elm
applyCurrentFilter : Filter -> Todo -> Bool
applyCurrentFilter filter todo =
    case filter of
        All ->
            True

        Completed ->
            todo.completed

        Remaining ->
            not todo.completed
```

And that's it! We've now added a completely new feature to our application!

## So what have we learned?

Elm is starting to show its strengths as we make our applications more complex. There is a lot of lines of code to deal with, but it's all pretty easy once you get used to the syntax.

Creating complex UIs can be simplified by splitting out view functions as you need them. They're kind of like React components in many ways, but more specialized and less "reusable" by design. I like it!

While working through this app, I got a lot of assistance from the lovely people on the Elm Slack. I just gotta give it to this community - it's filled with so much love, compassion and helpful people. Thank you for taking the time to explain a lot of functional concepts to a complete beginner!

So what's next? I think it's about time to look into fetching some data and displaying it somehow. I feel I have gone through enough of the syntax so that I feel comfortable diving into some of the more exciting parts of Elm!

Did you learn anything from following along? Do you have questions? Or comments on how I could drastically simplify something? Please let me know, and I'll try my best to reply.