Elm: Navigation

Intro

I recently killed a whole day trying to figure out how to implement navigation within an Elm web application. I finally figured it out due to some helpful blogs and GitHub repos. In summary, I learned that the key to performing navigation using Elm’s Navigation package is by referencing the Location record of the Navigation module.
 
I come from a native app development background. As a result, I was not clear on what a SPA really meant and why the Navigation package was tightly coupled to it.

My current understanding is that navigation within Elm apps tend to rely on SPAs (Single Page Applications). Thus, I believe within a SPA application, we are actually manipulating the HTML DOM to display various UIs for the end-user.
 

Configuration

Several procedures enabled me to finally get navigation to work on an app

First, let’s install Elm’s navigation package:

elm-package install elm-lang/navigation

Second, let’s import the Navigation module that our code will need to reference:

import Navigation exposing (..)

Next, let’s establish the following code for our main Elm page that enables navigation:

main =
    Navigation.program UrlChange
        { init = model
        , view = view
        , update = update
        , subscriptions = (\_ -> Sub.none)
        }

 

The code above is for bootstrapping the web app to run. Observe that we invoke the function Navigation.program and supply the URLChange message as an argument.

The URLChange definition can be found here:

type Msg = UrlChange Navigation.Location

 
Once the above items have been coded, we can then define our model type:

type alias Model =
   { currentRoute : Navigation.Location }

 

Observe that our model will now maintain navigation context information. In this case, we are storing the location (i.e. URL data) within our model.

We can then initialize our model with the following function:

model : Navigation.Location -> ( Model, Cmd Msg )
model location =

( { currentRoute = location } , Cmd.none )

 
If we look at the annotation of this function, we will observe that the function has a Navigation.Location parameter.

Thus, as noted earlier, we actually called this function with the following code that we defined earlier:

main =

Navigation.program UrlChange

{ init = model

, view = view

, update = update

, subscriptions = (\_ -> Sub.none)

}

 

The init function is assigned the result of the model function.

Our update function is defined as follows:

update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
   case msg of
      UrlChange location ->
         ( { model | currentRoute = location }, Cmd.none )

Whenever the URL changes, the URLChange message will be dispatched and the update function will return a new model with the latest URL that our view function will need for rendering.

This now leads us to our view function which is the following:

view : Model -> Html Msg
view model =
    let
        routePath =
            fromUrlHash model.currentRoute.hash
    in
        case routePath of
            [] ->
                homePage model

            [ "home" ] ->
                homePage model

            [ "contributor", id ] ->
                Html.map Contributor <| Contributor.view <| Contributor.Model (Id "") [] [] [] []

            _ ->
                notFoundPage

 
Our view function simply returns html. However, in order for the appropriate html to be retuned, our view function needs to parse the URL that our model references as seen above.

The following functions return html:

homePage : Model -> Html Msg
homePage model =
    div []
        [ header []
            [ label [] [ text "Nikeza" ]
            , model |> loginUI
            ]
        , div [] [ contributorsUI ]
        , footer [ class "copyright" ]
            [ label [] [ text "(c)2017" ]
            , a [ href "" ] [ text "GitHub" ]
            ]
        ]

notFoundPage : Html Msg
notFoundPage =
    div [] [ text "Not Found" ]

 
At last, we define our URL parsing code as follows:

-- NAVIGATION

type alias RoutePath =
   List String

fromUrlHash : String -> RoutePath
fromUrlHash urlHash = 
   urlHash |> String.split "/" |> List.drop 1

 

Conclusion

In conclusion, I attempted to document my understanding of Elm’s Navigation package. I learned that the key to performing navigation using this package is by referencing the Location record of the Navigation module. A complete example can be found on GitHub.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: