Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What *is* a Browser.Navigation.Key in Elm?

Tags:

elm

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:

  • What sort of information does this type hold?
  • How is that information used by elm's navigation internally?
like image 921
Eleanor Holley Avatar asked Feb 22 '21 01:02

Eleanor Holley


1 Answers

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. ;)

like image 183
danneu Avatar answered Nov 18 '22 23:11

danneu