I'm working with an existing Rails app where the navigation must continue to be constructed on the backend (due to complexity and time limitations). The intended result is to have some of the pages generated with Elm, and some with Rails, using no hashes, and no full page reloads (at least for the Elm pages). The simplified version of the navigation looks like this:
<nav>
<a href="rails-page-1">...
<a href="rails-page-2">...
<a href="elm-page-1">...
<a href="elm-page-2">...
</nav>
<div id="elm-container"></div>
I've experimented with the Elm navigation package, and the elm-route-url, possibly coming close with the latter unless I'm fundamentally misunderstanding the package's capability.
Is there a way to accomplish this? I've gotten it working using hash tags, but no luck without them.
using hash tags
Well you got a chuckle out of me.
I have this guy in my Helpers.elm
file that I can use in lieu of Html.Events.click
.
{-| Useful for overriding the default `<a>` behavior which
causes a refresh, but can be used anywhere
-}
overrideClick : a -> Attribute a
overrideClick =
Decode.succeed
>> onWithOptions "click"
{ stopPropagation = False
, preventDefault = True
}
So on an a [ overrideClick (NavigateTo "/route"), href "/route" ] [ text "link" ]
which would allow middle-clicking the element as well as using push state to update the navigation.
What you're needing is something similar on the JavaScript that works with pushState, and you don't want to ruin the middle-click experience. You can hijack all <a>
s,preventDefault
on its event to stop the browser from navigating, and push in the new state via the target's href. You can delegate the navigation's <a>
s in the event handler. Since the Navigation
runtime doesn't support listening to history changes externally (rather it appears to be using an effect manager), you'll have to push the value through a port -- luckily if you're using the Navigation
package, you should already have the pieces.
On the Elm end, use UrlParser.parsePath
in conjuction with one of the Navigation
programs. Create a port to subscribe to using the same message that is used for it's internal url changes.
import Navigation exposing (Location)
port externalPush : (Location -> msg) -> Sub msg
type Msg
= UrlChange Location
| ...
main =
Navigation.program UrlChange
{ ...
, subscriptions : \_ -> externalPush UrlChange
}
After the page load, use this:
const hijackNavClick = (event) => {
// polyfill `matches` if needed
if (event.target.matches("a[href]")) {
// prevent the browser navigation
event.preventDefault()
// push the new url
window.history.pushState({}, "", event.target.href)
// send the new location into the Elm runtime via port
// assuming `app` is the name of `Elm.Main.embed` or
// whatever
app.ports.externalPush.send(window.location)
}
}
// your nav selector here
const nav = document.querySelector("nav")
nav.addEventListener("click", hijackNavClick, false)
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