How does OnChangedUrl get caught from inner update?

Hi all
I wrote the following code, but how does it work, it seems for me a mystery:

port module Security.Data exposing (..)


import Security.Env as Env
import Security.Keycloak as Keycloak
import Browser.Navigation as Nav
import Url
import Json.Decode exposing (..)
import Http

port doPubKc : () -> Cmd msg

port onSubKc : (Value -> msg) -> Sub msg 


type alias Model = {
        env: Env.Model,
        kc: Keycloak.Model
    }

init : Model 
init = Model Env.init Keycloak.init

type Msg 
    = OnEnv (Result Http.Error String)
    | OnSubKc Keycloak.Token

update :  Url.Url -> Nav.Key -> Msg -> Model -> (Model, Cmd Msg)
update url key msg model =
    case msg of
        OnEnv res ->
            case res of 
                Ok succ ->
                    ({model | env = succ}, Cmd.none)
                Err fail ->
                    (model, Nav.pushUrl key (Url.toString (buildErrorUrl url)))
        OnSubKc value ->
            case value of 
                Ok succ -> 
                    ({model | kc = succ }, Env.reqEnv OnEnv )
                Err fail -> 
                    (model, Nav.pushUrl key (Url.toString (buildErrorUrl url)))
            
            

buildErrorUrl : Url.Url -> Url.Url
buildErrorUrl old =
    Url.Url old.protocol old.host old.port_ "/error" old.query old.fragment

As you can see, when an error occurs, it will redirect to /error path and it will trigger the onUrlChange event.
The onUrlChange will be caught in the Main.elm file, that contains the following code:

module Main exposing (Model, Msg(..), init, main, update, view)

import Browser
import Browser.Navigation as Nav
import Home.Page as Home
import Html exposing (..)
import Html.Attributes exposing (..)
import Json.Decode as Decode
import NotFound.Page as NotFound
import Url.Parser as Parser exposing (..)
import Url
import Http
import Menu.Page as Menu
import Security.Data as Security
import Security.Keycloak as Keycloak
import Error.Page as Error

---- MODEL ----

type Page
    = NotFound NotFound.Model
    | Error Error.Model
    | Home Home.Model
--    | AccountPage AccountPage.Model


type alias Model =
    { url : Url.Url
    , key : Nav.Key
    , security : Security.Model
    , page : Page
    }


init : Decode.Value -> Url.Url -> Nav.Key -> ( Model, Cmd Msg )
init flags url key =
    (Model url key Security.init (Home "Hello Foo"), Cmd.none)


---- UPDATE ----


type Msg
    = OnUrlChange Url.Url
    | OnUrlRequest Browser.UrlRequest
    | OnSecurity Security.Msg
    | NotFoundMsg NotFound.Msg
    | ErrorMsg Error.Msg
    | HomeMsg Home.Msg
    | MenuMsg Menu.Msg


update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        OnUrlRequest urlRequest ->
            case urlRequest of
                Browser.Internal url ->
                    ( model, Nav.pushUrl model.key (Url.toString url) )
                Browser.External href ->
                    ( model, Nav.load href )
        OnUrlChange url ->
            navigate url model
        OnSecurity secMsg ->
            handleSecurity model (Security.update model.url model.key secMsg model.security)
        HomeMsg homeMsg ->
            case model.page of 
                Home homeModel ->
                    navToHome model (Home.update homeMsg homeModel)
                _ ->
                    (model, Cmd.none)
        NotFoundMsg notFoundMsg ->
            case model.page of 
                NotFound notFoundModel ->
                    navToNotFound model (NotFound.update notFoundMsg notFoundModel)
                _ ->
                    (model, Cmd.none)
        ErrorMsg errorMsg ->
            (model, Cmd.none)
        MenuMsg menuMsg ->
            (model, Cmd.none)  

-- UPDATE HANDLERS --
handleSecurity : Model -> (Security.Model, Cmd Security.Msg) -> (Model, Cmd Msg) 
handleSecurity model (secModel, secMsg) =
    ({model | security = secModel}, Cmd.map OnSecurity secMsg)


--- NAVIGATION ---
navToHome : Model -> (Home.Model, Cmd Home.Msg) -> (Model, Cmd Msg)
navToHome model (homeModel, homeMsg) =
    ({model | page = Home homeModel},
     Cmd.map HomeMsg homeMsg )

navToNotFound : Model -> (NotFound.Model, Cmd NotFound.Msg) -> (Model, Cmd Msg)
navToNotFound model (notFoundModel, notFoundMsg) =
    ({model | page = NotFound notFoundModel},
     Cmd.map NotFoundMsg notFoundMsg )

navToError : Model -> (Error.Model, Cmd Error.Msg) -> (Model, Cmd Msg)
navToError model (errorModel, errorMsg) =
    ({model | page = Error errorModel},
     Cmd.map ErrorMsg errorMsg)


navigate : Url.Url -> Model -> (Model, Cmd Msg)
navigate url model = 
    let
        parser = 
            oneOf [
                route top ( navToHome model Home.init),
                route (Parser.s "error") (navToError model Error.init)
            ]
    in
    case Parser.parse parser url of
        Just page ->
            page
        Nothing -> 
            navToNotFound model (NotFound.init "Can not find the page")


route : Parser a b -> a -> Parser (b -> c) c
route parser handler =
    Parser.map handler parser

--- SUBSCRIPTIONS ---


subscriptions : Model -> Sub Msg
subscriptions _ =
    Sub.batch [
        Sub.map OnSecurity (Security.onSubKc (\value -> Security.OnSubKc (Keycloak.validate value)))
    ]


---- VIEW ----
content : Model -> Html Msg
content model =
    main_ []
        [ case model.page of
            Home homeModel ->
                Html.map HomeMsg (Home.view homeModel)
            Error errorModel ->
                Html.map ErrorMsg (Error.view errorModel)
            NotFound notFoundModel ->
                Html.map NotFoundMsg (NotFound.view notFoundModel)
        ]


body : Model -> List (Html Msg)
body model =
    [ Html.map MenuMsg Menu.content,
      content model
    ]


view : Model -> Browser.Document Msg
view model =
    { title = "Cockpit"
    , body = body model
    }


---- FUNCTIONS ----


---- PROGRAM ----


main : Program Decode.Value Model Msg
main =
    Browser.application
        { init = init
        , view = view
        , update = update
        , subscriptions = subscriptions
        , onUrlChange = OnUrlChange
        , onUrlRequest = OnUrlRequest
        }

The question is, how does the event OnUrlChange get caught in the Main.elm, since OnUrlChange does not get caught in the update function in Security.Data module and it is wrapped with OnSecurity.

Thanks

1 Like

OnUrlChange message is generated by Elm runtime and send to the update-function you define in main = ..., i.e. your Main.update.

It doesn’t matter where in your code you trigger it with Nav.pushUrl, as all messages from runtime will be sent to your main update, which then can pass them on to other update functions.

1 Like

Wow…Thanks a lot. Now it is clear.

This topic was automatically closed 10 days after the last reply. New replies are no longer allowed.