In this article, we’ll be adding ports, which allow our Elm apps to work with JavaScript. In this case, we’ll use them to save our model state in localStorage.

Quickstart Elm abstract art

Note: Examples in this article use Elm 0.19.

The complete app

Here’s the full working app:

The important thing to remember here is that you might come into the following error on Ellie:

Exception Thrown in Output

Uncaught Error: Problem with the flags given to your Elm program on initialization. Problem with the given value: undefined Expecting an OBJECT with a field named 'todos'

If you’re trying to run the app locally, from VS Code for example, the error might look a bit different:

Your `main` program wants an extended record from JavaScript.

211| main =
     ^^^^^

But the exact shape of the record must be known at compile time. No type variables!

While most of the time, the Elm complier gives out nice error messages, this one is rather cryptic. The issue is that you haven’t added the communication from the HTML / JavaScript side:

<body>
  <main></main>
  <script>
    var app = Elm.Main.init({ node: document.querySelector('main'), flags:{todos: []} })
    // you can use ports and stuff here
  </script>
</body>
</html>

Now everything should work.

But we’re still not saving anything in localStorage. For that, we need to update the HTML in our Ellie.

Here’s the full embedded app:

With this update, our localStorage tracks changes, as can be seen below: Ellie app saving changes to localStorage

Marking a todo as completed

To mark a todo as completed, we’ll change them from strings to records, and we’ll add the completed property to the Todo record. It’s a Bool, tracking whether or not a user completed the specific todo.

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

The todos in the Model are no longer a List String, now they are List Todo.

type alias Model =
    { text : String
    , todos : List Todo
    , editing : Maybe TodoEdit
    }