elm-route-url icon indicating copy to clipboard operation
elm-route-url copied to clipboard

Using Anchors?

Open Sushisource opened this issue 6 years ago • 2 comments

Hi there!

Liking this package a lot - but I've run into a bit of trouble. I'm not sure anchors are compatible with the way the hash URL currently works, but maybe I'm just doing something stupid.

I have an app with the router set up like so (a bunch of it elided for readability)

main =
  RouteUrl.program
    { view = view
    , update = update
    , init = init
    , subscriptions = subscriptions
    , delta2url = delta2url
    , location2messages = hash2messages
    }

init = (defaultModel, Cmd.none)
subscriptions : Model -> Sub Msg
subscriptions model = Sub.none

delta2url : Model -> Model -> Maybe UrlChange
delta2url previous current = Maybe.map Builder.toHashChange <| delta2builder previous current

delta2builder : Model -> Model -> Maybe Builder
delta2builder previous current =
    Just (current |> extractClNum |> \l -> Builder.appendToPath l builder)

hash2messages : Location -> List Msg
hash2messages location = builder2messages (Builder.fromHash location.href)

builder2messages : Builder -> List Msg
builder2messages builder =
  case Builder.path builder of
    first :: rest ->
      let
        _ = Debug.log "url first: " first
        _ = Debug.log "url rest: " rest
        subBuilder = Builder.replacePath rest builder
      in
        -- This is a bit hacky but haven't figured out a better way to avoid freaking out when
        -- clicking an anchor.
        if (startsWith "ev" first) then [] else [ChangeCL first]
    [] -> []

Basically the only thing I'm doing is, after the hostname/elm.html#!/ I have a number which is some ID for stuff I need to fetch from the server and render. So my url normally looks like hostname/elm.html#!/123456 for example.

As it stands, the anchors work, but they mess up the URL to be hostname/elm.html#ev-1

When what I want is hostname/elm.html#!/123456#ev-1

The problem being this breaks history/refresh/url sharing.

I think I may be doing something wrong insofar as instead of setting href #ev-1 on my links, I should be having onClick generate some action which appends to the url - but I'm not 100% on how to do that without adding a "current anchor" field to my model or something, which feels a little overkill.

That said, maybe that's what I've got to do, given that providing a bookmark like this doesn't really work since the element doesn't exist until pageload is complete, which is a general elm problem and not specific to this package.

It feels a touch clunky to me so I'm wondering if there's a prettier way to do this or if that's just what I need to go ahead and do.

In any case, there is one thing that definitely seems like it's not working: If I manually enter hostname/elm.html#!/123456#ev-1 into my url bar and hit enter, nothing about #ev-1 shows up in the console:

url rest: : []  
url first: : "123456" 

Thanks!

Sushisource avatar Oct 09 '17 23:10 Sushisource

Single-page app routing with the hash essentially "hijacks" the usual purpose of the hash, so you can't then easily also use the hash to navigate to an anchor on your page in the usual manner. There is, after all, only one hash (from the browser's point of view) -- there's no (simple) way to tell the browser to ignore the first part of the hash and navigate to an anchor represented by the second part of the hash.

In fact, look at the docs for RouteUrl.Builder.fromHash -- that describes how a "hash within a hash" is encoded. (Since you can only have one hash, really).

So, to support navigating to a particular spot within a page (i.e. what a hash usually does), you've basically got two choices, I think:

  1. You could try putting the "elm-route-url" routing stuff in the URL itself (not the hash), and use the hash for its usual purpose, to refer to an anchor. In this case, you'd need to parse the path for elm-route-url (instead of the hash), and your server would have to know which page to send (i.e. the same page, even though the path differs.

    Though, I don't think that can be made to work in a straight-forward way, because (as you note) the anchor doesn't exist immediately -- it only exists when the Elm code constructs it. So, the browser can't actually go the anchor, at least if you're refreshing the page, because the anchor won't exist yet at the moment the browser looks for it. I suppose you could create a port which, when it receives a message, navigates to the hash, and send that port a message on a delay (so that the anchor exists by then).

  2. So, the better way is to handle it programmatically. But this is actually awkward too, because I don't think that elm-lang/dom has the equivalent of Javascript's scrollIntoView, which is the sort of thing you'd need. You can find it in https://github.com/gdotdesign/elm-dom, so that would be an option.

    In any event, the idea there would be that you would parse the URL using elm-route-url, and part of your url scheme (in whatever way you like) would indicate an anchor on the page to go to, and so you could generate a message that would scroll the relevant element into view.

    In fact, on second thought, you could create a port that uses scrollIntoView in order to implement the message (so you wouldn't necessarily need something like https://github.com/gdotdesign/elm-dom).

Anyway, I hope that puts you on the right track -- basically, it's not possible to use the hash to go to an anchor in the usual way, but there are some other approaches that can be made to work.

rgrempel avatar Oct 10 '17 01:10 rgrempel

Thanks! That definitely helps. I'll see about using the hash-less version with the port for scrolling, that seems pretty reasonable. Might be worth putting some of this into the docs for the curious - something about not expecting anchors to work by default I expect would be useful. Happy to open a PR with something reasonably concise if that'd help.

Sushisource avatar Oct 10 '17 16:10 Sushisource