I'm starting to work with Elm's Browser.application
which is a lot of fun, but one thing that (as far as I can tell) is never really explained is the Browser.Navigation.Key
type.
The elm docs say
A navigation Key is needed to create navigation commands that change the URL. That includes pushUrl, replaceUrl, back, and forward.
And I think at this point I think I pretty much get how to use it from following examples in the elm guide, but I am very curious, just academically:
I haven't used Elm in a while but was curious about this myself.
At first glance, the key mechanism solves a problem outlined here: https://github.com/elm/browser/blob/1.0.2/notes/navigation-in-elements.md —or at least has something to do with that contention.
The immediate takeaway is that they wanted to prohibit you from accessing the URL-changing API unless you created your app with Browser.application
, so one way to do that would be to require a dummy type like Key
as input that you can only get if you use Browser.application
. But there's more to the key than that.
Browser.application
, unlike Browser.document
and Browser.element
, gives you additional handlers: onUrlRequest
and onUrlChange
.
So, how is it communicated to your application when the URL changes? How is it implemented?
The source code here explains a lot: https://github.com/elm/browser/blob/53e3caa265fd9da3ec9880d47bb95eed6fe24ee6/src/Elm/Kernel/Browser.js#L142
In particular, this is what's held in the Key
opaque type:
var key = function() { key.__sendToApp(onUrlChange(_Browser_getUrl())); };
When you use Browser.application
, the initialization code wires url-change listeners to call key()
:
_Browser_window.addEventListener('popstate', key);
_Browser_window.addEventListener('hashchange', key);
And URL-changing functions also call key()
:
var _Browser_pushUrl = F2(function(key, url)
{
return A2(__Task_perform, __Basics_never, __Scheduler_binding(function() {
history.pushState({}, '', url);
key();
}));
});
The key mechanism is a simple way for the API know where to route URL-change updates to: simple, the code requires that you give it the key
function that will call app.onUrlChange(...)
on the app that created it.
This still doesn't seem to completely answer one technical part of the question to me: Why do you need to pass key to Navigation.{go,push,replace}? Doesn't the code above (_Browser_window.addEventListener('popstate', key)
) already call key()
when the URL changes?
It turns out that the popstate
event only fires for UI interactions like clicking the back button. The event isn't called when you use history.{push,replace}State(..., url)
directly which of course is what this library is doing: https://developer.mozilla.org/en-US/docs/Web/API/WindowEventHandlers/onpopstate
In other words, it seems the key mechanism exists as a simple stateless way for the go
, pushUrl
, replaceUrl
functions (https://github.com/elm/browser/blob/53e3caa265fd9da3ec9880d47bb95eed6fe24ee6/src/Elm/Kernel/Browser.js#L190-L212) to have a way to call the currently running application's onUrlChange
. Else the API could have probably gotten away with just the _Browser_window.addEventListener('popstate', key)
line in Browser.application
initialization. But that event is never emitted when the URL is changed from Javascript, so they needed a way to call app.onUrlChange()
directly inside those functions, aka key()
.
And, of course, it has the additional pay-off of scoping the URL API to only applications created with Browser.application
. Bit of a rambling answer, but I didn't have time to write a shorter one. ;)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With