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:
- Blank Screens. The screen goes white every time you load new HTML. Can we do a nice transition instead?
- Redundant Code. The home page and the docs share a lot of functions, like
Html.text
andHtml.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 yourModel
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 theBACK
orFORWARD
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 (likepushUrl
) that change the URL. You only get access to aKey
when you create your program withBrowser.application
, guaranteeing that your program is equipped to detect these URL changes. IfKey
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!