diff --git a/examples/Example.elm b/examples/Example.elm index befe70f..c1d83c3 100644 --- a/examples/Example.elm +++ b/examples/Example.elm @@ -21,8 +21,11 @@ main = , view = view , update = update , subscriptions = subscriptions - , onUrlRequest = UrlRequest - , onUrlChange = UrlChange + , onUrlRequest = UrlRequest -- This should be a function of type `Browser.UrlRequest -> Msg`. + -- It isn't a handler like in JS, but a message that `update` can + -- process. + , onUrlChange = UrlChange -- Similarly, this is also a function that returns a message. However, + -- it accepts a `Url`, not a `Browser.UrlRequest`. } @@ -31,11 +34,15 @@ main = type alias Model = - { history : List (Maybe Route) + { history : List (Maybe Route) -- Keep track of the routes. The Maybe is because parsing can (and will) fail. , key : Nav.Key } +-- Nav.Key is needed for controlling navigation, it is provided by `init` and we store it in the `Model` +-- If you want to understand what its actually doing, the docs for it are linked below: +-- + init : () -> Url -> Nav.Key -> ( Model, Cmd Msg ) init _ url key = ( Model [ P.parse routeParser url ] key @@ -47,20 +54,83 @@ init _ url key = -- URL PARSING -type Route - = Home - | BlogList (Maybe String) - | BlogPost Int - +type Route -- A custom type to describe a route. This is how our parser will give us its results. + = Home -- "/" + | BlogList (Maybe String) -- "/blog?search=..." (`Just ...`) or "/blog" (`Nothing`) + | BlogPost Int -- "/blog/" +-- A parser is built by starting with simple builtin parsers, like "the root path" or "this directory", +-- and then these parsers are combined with *parser combinators*, like and , which are explained +-- more concretely and in greater detail below. routeParser : Parser (Route -> a) a routeParser = - P.oneOf - [ P.map Home top - , P.map BlogList (s "blog" Q.string "search") - , P.map BlogPost (s "blog" P.int) + P.oneOf -- This is the first parser combinator, it takes a list + -- of parsers, and if one of the parsers fails, it just + -- tries the next one given. The URL parser it returns + -- can go through all of the different patterns specified + -- and generate the correct representation of the URL. + + [ P.map Home top -- `top : Parser a a` and `s : String -> Parser a a` + -- are the two main "starting points" for building a + -- basic parser. top represents the root page "/", + -- and s is used to represent a particular directory. + + -- Just like Maybe.map allows you to have a function + -- run in the context of the Maybe, P.map allows us to + -- insert a function "into" the parser in a similar manner. + -- Here, we are using the type constructor `Home` to turn + -- basic `top` parser into one that, when given a matching + -- URL ("/" in this case), will return Home. + + , P.map BlogList (s "blog" Q.string "search") -- The parser combinator is used to combine a + -- standard URL parser (Parser ...) with a + -- query string parser (Q.Parser ...). + + -- Here, the `Q.string : String -> Q.Parser (Maybe String)` + -- function is used as a basic query string parser. + -- `Q.String` will parse the given query parameter, so this + -- is going to extract the query parameter "search". + -- Note that when mapping over a parser created with , + -- the the value passed into the mapping function (here + -- `BlogList`), will be the result from the query parser. + -- Since `Q.String "..."` is a `Q.Parser (Maybe String)`, + -- the mapping function should take a `Maybe String`. + + , P.map BlogPost (s "blog" P.int) -- Simply converting the directory name to an int + -- wouldn't always work, and we would have to handle the + -- Maybe ourselves. Instead of having to deal with a + -- `BlogPost (Maybe Int)`, or having to get the parser to + -- fail on non-integers ourselves, we can use the builtin + -- P.int do handle the directory name as an integer. + -- Note that this is in contrast to the standard query + -- parser, where the parser returns a `Maybe String`. + -- There, not finding a particular query parameter is not + -- indicative of failure, but just is something should be + -- dealt with as an optional value. + + -- The parser combinator is used just like , + -- but is used to combine two URL parsers, where + -- instead of the second parsing query parameters after + -- the first, it will parse subdirectories found after + -- the first. ] +-- You could use `Debug.toString : a -> String` here, but that is not recommended for production use +routeToString : Route -> String +routeToString route = + case route of + BlogList (Just name) -> + "BlogList (Just " ++ name ++ ")" + + BlogList Nothing -> + "BlogList Nothing" + + BlogPost num -> + "BlogPost " ++ String.fromInt num + + Home -> + "Home" + -- UPDATE @@ -75,20 +145,29 @@ update : Msg -> Model -> ( Model, Cmd Msg ) update msg model = case msg of UrlChange url -> - ( { model | history = P.parse routeParser url :: model.history } + ( { model | history = P.parse routeParser url :: model.history } -- The main part of this is, + -- `P.parse routeParser url`, + -- where we are parsing the URL + -- with the parser we just built. + -- Then, we use the `::` operator + -- to add it to the history list. , Cmd.none ) UrlRequest request -> - case request of + case request of -- onUrlRequest doesn't actually provide a Url, but a Browser.UrlRequest. + -- It isn't just a URL, it needs to carry some additional information. + -- It is explained on the docs for Browser.UrlRequest, here: + -- https://package.elm-lang.org/packages/elm/browser/latest/Browser#UrlRequest Browser.Internal url -> ( model - , Nav.pushUrl model.key (Url.toString url) + , Nav.pushUrl model.key (Url.toString url) -- Change the URL, but do not trigger a page load. + -- Adds a new entry to the browser history. ) Browser.External url -> ( model - , Nav.load url + , Nav.load url -- Basically a redirect (always a page load) ) @@ -124,9 +203,9 @@ viewLink url = viewRoute : Maybe Route -> Html msg viewRoute maybeRoute = - case maybeRoute of + case maybeRoute of -- URL parsing can succeed or fail, we must handle both cases. Nothing -> - li [] [ code [] [ text "Uknown URL" ] ] + li [] [ code [] [ text "Unknown URL" ] ] Just route -> - li [] [ code [] [ text (Debug.toString route) ] ] + li [] [ code [] [ text (routeToString route) ] ]