Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to drive Elm app with existing navigation constructed outside of the Elm app?

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.

like image 566
scoots Avatar asked Dec 15 '16 16:12

scoots


1 Answers

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)
like image 53
toastal Avatar answered Nov 16 '22 04:11

toastal