SPA in Elm: Part 2

Screen Based Routing with Navigation

T0ha
3∑: Elm, Erlang & Elixir
4 min readApr 26, 2018

--

Previously we’ve created a simple, but working elm application. In this part we’ll add some code to make it multiscreen. Except that, we’ll add global counter that will show button clicks on all screens of our app.

Creating screens for basic application

Let’s take our simple app we’ve created in previous part and create screens from it. To do it create three folders in our source code root directory:

  • First
  • Second
  • Third

This is our screens. Folder names must start with capital letters, as far as they are Elm module names. Now let’s copy our Model.elm, View.elm and Update.elm to each folder. As a result, our directory structure will be the following:

For the purpose of this article, I omit Subscription and Port modules, as we don’t need them.

Making One Application form 3 Screens

This part contains of 4 steps:

  1. Changing app Model to incorporate all screen Models with minimal changes, store stuff for routing and common data.
  2. Add Routing stuff that will switch screens based on url and do some additional stuff
  3. Proxy messages to <screen>.Update.update when necessary
  4. Add main menu
  5. Make small fixes to screens to use global counter and return to screens menu

Application model

It’s right time to look deeper and fix our main Model. The Model includes 3 main parts:

  • routing data
  • common data
  • screen data for submodules

Routing uses Model.screen to determine which screen of our app it should render. This process takes place in View and Route modules and we’ll discus them in the next part.

Common data is some variables that is used in several or even all screens. In our case it is Model.counter which is counter for total button clicks on any screen. For online shop it can be cart. Sometimes some app settings are needed.

Screen data is placed in Model.<screen> fields and defined for each screen separately in <screen>.Model module. It’s bad idea to use one data for one screen from another.

Routing and rendering

Routing itself is held in 2 modules: View and Router.

First one renders <screen>.View.view according to Model.screen value. It wraps messages from screens for us as well so we can avoid thinking about it in entire screen code. We’ll discuss it nextly.

Router module defines your screens in 2 ways: as Screen type and as list of Route structures in screens function. As well, it has helpers for translating screens into urls and vice versa.

Translating screen messages

As we’ve discussed in previous part all events in Elm are translated into messages that are received and handled by Update. But the magic here is that we have 2 types of messages:

  • global that are sent by app and received by Update
  • screen specific that are sent from screen UI and should be received by <screen>.Update module.

To make all this happen we need to wrap screen local messages into global transit packets, so global Update can determine if it should handle massage itself or proxy to <screen>.Update. This is what wrapScreen functions from Update and View modules does for us. When Update.update function receives <screen>Event message it just unpacks it and calls <screen>.Update.update function to take actions with it.

Fixing screens

Now we need to change a bit each screen modules. What is needed to be changed?

  1. Module declaration as far as now it is submodule.
  2. Page header to visually understand which screen are we looking at.
  3. Add “Button pushed in all screens: “ block for displaying global counter.
  4. Fix “Button pushed in this screen: “ to display model.<screen>.counter, not global one.
  5. Add “Back” button to return to main menu from each screen.

First 4 points looks rather straightforward, contact me, if I’m wrong.

But the fifth needs a bit of discussion. This link uses Router helper to get url for main screen. It’s very helpful, because if you change url scheme, you don’t need to fix links everywhere, but only in Router module. So don’t hardcode links, just use helpers.

Code

All code from this article can be found on GitHub. It has one branch for each part. You are welcome with to fork, use and star it as you like.

PS

Dear readers, thank you for your interest and feedback. I was very glad to see so much interest about my Intro and Part 1. If you like this one you are welcome to clap, subscribe and connect me. I’m waiting for new feedback!

You can contact me on Twitter (@t0ha666), Reddit (/u/t0ha), Telegram (t.me/war1and) or Email (t0hashvein@gmail.com).

--

--

T0ha
3∑: Elm, Erlang & Elixir

#Founder & #developer focused on #startups rocketing and #socialimpact.