Navigation

    The simple way would be to serve a bunch of different HTML files. Going to the home page? Load new HTML. Going to elm/core docs? Load new HTML. Going to elm/json docs? Load new HTML.

    Until Elm 0.19, that is exactly what the package website did! It works. It is simple. But it has some weaknesses:

    1. Blank Screens. The screen goes white every time you load new HTML. Can we do a nice transition instead?
    2. Redundant Code. The home page and the docs share a lot of functions, like Html.text and Html.div. Can this code be shared between pages?

    We can improve all three cases! The basic idea is to only load HTML once, and then be a bit tricky to handle URL changes.

    Instead of creating our program with Browser.element or Browser.document, we can create a Browser.application to avoid loading new HTML when the URL changes:

    It extends the functionality of Browser.document in three important scenarios.

    When the application starts, init gets the current from the browsers navigation bar. This allows you to show different things depending on the Url.

    When someone clicks a link, like <a href="/home">Home</a>, it is intercepted as a UrlRequest. So instead of loading new HTML with all the downsides, onUrlRequest creates a message for your update where you can decide exactly what to do next. You can save scroll position, persist data, change the URL yourself, etc.

    When the URL changes, the new Url is sent to onUrlChange. The resulting message goes to update where you can decide how to show the new page.

    We will start with the baseline Browser.application program. It just keeps track of the current URL. Skim through the code now! Pretty much all of the new and interesting stuff happens in the update function, and we will get into those details after the code:

    The update function can handle either LinkClicked or UrlChanged messages. There is a lot of new stuff in the branch, so we will focus on that first!

    Whenever someone clicks a link like <a href="/home">/home</a>, it produces a UrlRequest value:

    The Internal variant is for any link that stays on the same domain. So if you are browsing https://example.com, internal links include things like settings#privacy, /home, https://example.com/home, and //example.com/home.

    The External variant is for any link that goes to a different domain. Links like https://elm-lang.org/examples, https://static.example.com, and http://example.com/home all go to a different domain. Notice that changing the protocol from https to http is considered a different domain!

    Whichever link someone presses, our example program is going to create a LinkClicked message and send it to the update function. That is where we see most of the interesting new code!

    Most of our update logic is deciding what to do with these UrlRequest values:

    The particularly interesting functions are Nav.load and Nav.pushUrl. These are both from the module which is all about changing the URL in different ways. We are using the two most common functions from that module:

    • load loads all new HTML. It is equivalent to typing the URL into the URL bar and pressing enter. So whatever is happening in your Model will be thrown out, and a whole new page is loaded.
    • changes the URL, but does not load new HTML. Instead it triggers a UrlChanged message that we handle ourselves! It also adds an entry to the “browser history” so things work normal when people press the BACK or FORWARD buttons.

    There are a couple ways to get UrlChanged messages. We just saw that pushUrl produces them, but pressing the browser BACK and FORWARD buttons produce them as well. And like I was saying in the notes a second ago, when you get a LinkClicked message, the pushUrl command may not be given immediately.

    So the nice thing about having a separate UrlChanged message is that it does not matter how or when the URL changed. All you need to know is that it did!

    We are just storing the new URL in our example here, but in a real web app, you need to parse the URL to figure out what content to show. That is what the next page is all about!

    Note: I skipped talking about Nav.Key to try to focus on more important concepts. But I will explain here for those who are interested!

    A navigation Key is needed to create navigation commands (like pushUrl) that change the URL. You only get access to a Key when you create your program with Browser.application, guaranteeing that your program is equipped to detect these URL changes. If Key values were available in other kinds of programs, unsuspecting programmers would be sure to run into some and learn a bunch of techniques the hard way!

    As a result of all that, we have a line in our for our Key. A relatively low price to pay to help everyone avoid an extremely subtle category of problems!