Routing, authentication etc. still on my 3rd Elm project.

196 views
Skip to first unread message

Rupert Smith

unread,
Sep 28, 2016, 11:36:40 AM9/28/16
to Elm Discuss
So I have been sidetracked a bit learning how to handle REST calls in Elm and figuring out what help functions I can write around that to try and wrap up a service + boilerplate in a way that provides the simplest interface to a consumer of it.

I have a screen that displays user accounts and lets an admin take actions around those accounts. Prior to displaying that screen a list of accounts (paginated) needs to be fetched from the server. So I thought a bit about how to initialize each screen.

I could initialize in a global router for the whole application - or perhaps more neatly delegate the initialization to each module that needs it. I decided to go for the second option. I needed somewhere to trigger the initialization action, and also inevitably this routing logic gets mixed up with handling the current authenticated or not authenticated state.

If not authenticated - the current location is changed to '#welcome', which is the login page. I also save the location that was trying to be accessed, so it can be put back after a login. Before introducing the concept of an initializer for each location, I did not have to do that, as the location in the url simply stayed put on the welcome screen. But now that I want an initializer for each route, I need to explicitly change the location to '#welcome' to login, then back to the protected route after, in order to trigger its initializer.

The initializers are set up as events on each module, and I used Cmd.Extra.message to turn them into Elm commands and invoke the update function for the module. Code is below.

What I like about this vs what I have experienced previously with Angular - my router is just a function. It is not config that I pass to some other angular router implementation. I built my own function and make it work in the way that I want routing to work without that being dictated or limited by what a framework offers me. I haved used elm-route-url, but that just seems to offer a convenient way of directing route changes as Msgs which are directed to the update function, which feels like the right way to do it. The function you see below called 'selectLocation' is just a convenience function that I split out of the update function to stop it getting too big.

All in all, I feel that Elm was not overly difficult to work with - the types of the functions in elm-route-url pretty much guide you to how it is used - and very flexible in allowing me to implement whatever routing functionality I need. Very nice.

'filterNothing' removes the Maybe.Nothings from a list - I probably could have avoided using Maybes at all and built the list of Cmds more directly. I think I will refactor to achieve that.

{-
   This is the main router for the application, invoked on all url location changes.

   When not logged in and not already on the welcome page, this will forward to the welcome
   page to log in. The location being requested will be saved in the auth forward location, so
   that it can be forwarded to upon succesfull login.

   When forwarding to a location with an 'Init' event available, this will be triggered
   in order that a particular location can initialize itself.
-}


selectLocation : Model -> String -> ( Model, Cmd Msg )
selectLocation model location =
    let
        -- Flag indicating whether the welcome location should be navigated to.
        jumpToWelcome =
            not model.auth.authState.loggedIn && location /= "welcome"

        -- Maybe a command to jump to the welcome location.
        jumpToWelcomeCmd =
            if jumpToWelcome then
                Navigation.newUrl "#welcome" |> Just
            else
                Nothing

        -- Saves the location as the current forward location on the auth state.
        forwardLocation authState =
            { authState | forwardLocation = "#" ++ location }

        -- When not on the welcome location, the current location is saved as the
        -- current auth forwarding location, so that it can be restored after a
        -- login.
        jumpToWelcomeModel =
            if location /= "welcome" then
                { model | auth = forwardLocation model.auth }
            else
                model

        -- Choses which tab is currently active.
        tabNo =
            Dict.get location Main.View.urlTabs
                |> Maybe.withDefault -1

        -- Maybe a command to trigger the 'Init' event when navigating to a location
        -- with such an event.
        initCmd =
            if not jumpToWelcome then
                case location of
                    "accounts" ->
                        Cmd.Extra.message (AccountsMsg Accounts.Types.Init) |> Just

                    x ->
                        Nothing
            else
                Nothing

        -- The model updated with the currently selected tab.
        selectTabModel =
            { jumpToWelcomeModel | selectedTab = tabNo }
    in
        ( selectTabModel, Cmd.batch (filterNothing [ jumpToWelcomeCmd, initCmd ]) )

Wouter In t Velt

unread,
Sep 29, 2016, 7:26:17 AM9/29/16
to Elm Discuss
Very useful to read your experiences with Elm. I am in a more-or-less similar point on the learning curve.
Small aside: The Elm Exts (extentions) package has a simple function called catMaybes to filter out Nothings:
catMaybes : List (Maybe a) -> List a


Rupert Smith

unread,
Sep 29, 2016, 11:48:15 AM9/29/16
to Elm Discuss
Thanks, loads of useful stuff in Elm.Exts. 
Reply all
Reply to author
Forward
0 new messages