browser icon indicating copy to clipboard operation
browser copied to clipboard

Jumping to fragments does not work in a good way in `Browser.application`

Open ChristophP opened this issue 7 years ago • 4 comments

For a Browser.application the docs recommend to handle UrlRequest like this

case msg of
    ClickedLink urlRequest ->
      case urlRequest of
        Internal url -> ( model, Nav.pushUrl model.key (Url.toString url))
        External url -> ( model, Nav.load url )

However, this won't do anything with links like the following <a href="#someAnchor">Jump</a> (to jump to <a name="someAnchor"></a>) since they will count as Internal UrlRequests. It is possible to jump to fragments using Nav.load but the suggested code above will only change the url with Nav.pushUrl.

Notes: There are a couple of cases to consider, links like:

  1. #myAnchor or /samePage#myAnchor Links pointing to anchors on the same page can be used to safely jump to the anchor immediately using the browsers default behavior, because the page is already rendered.
  2. /newPageAnchor#anchorOnDifferentPage However when navigating to an new page an anchor on a new page, SPAs behave differently than server rendered pages. The url changes first and only after some async data is loading the page containing the anchor is actually rendered. Only once the page is rendered it is possible to jump to the anchor. This case introduces a time gap between clicking the link and being able to navigate to the anchor.

It would be nice to have a solution for the first case that is close to browser behavior, but the preventDefault on every link seems to make it tough right now. The second case (navigating to a new page) doesn't seem to be elm specific but a general SPA "problem". Other frameworks and routers have similar issues https://github.com/rafrex/react-router-hash-link/tree/react-router-v2/3#react-router-hash-link-scroll and seem to use some kind of polling of the DOM to check if the anchor has been rendered.

ChristophP avatar Sep 20 '18 23:09 ChristophP

Update

After giving this a lot of thought, I would like to suggest to add an additional check in the vdom to prevent the default on link clicks only when this is also true:

domNode.getAttribute('href')[0] !== '#' 

This will preserve regular browser behavior for links which just reference an anchor and won't send the msg to the Browser.application. This will take care of the most common case for using anchor (go to anchor on same route) which is case 1 talked about above. Of course there are also other cases like going to a new page with an anchor (case 2 above) but those will need to be handled anyway in SPAs and the jump to the anchor needs to be delayed until the anchor is rendered. This suggested fix could be a good way to get some of the browser fragment link behavior back that is now taken away by the prevent default.

ChristophP avatar Nov 03 '18 19:11 ChristophP

I would advice against that. In our application we have custom logic which also allows us to scroll nested scrolled containers. All you need to do is to define the url parser in a way it parses fragments and do implement the logic appropriate to what you want to do. If elm would start ignoring these links it would be much harder for us to implement the scrolling in anything other than window.

turboMaCk avatar Mar 30 '21 13:03 turboMaCk

By setting target "_self" on links, Browser.application seems to ignore them for navigation. So, that's a simple workaround which also works for mailto: and tel: links! BTW: Thanks to @ChristophP for solving this issue on his own ;)

layflags avatar Sep 23 '22 09:09 layflags

Yup, it's this line for reference https://github.com/elm/browser/blob/master/src/Elm/Kernel/Browser.js#L157

ChristophP avatar Sep 23 '22 10:09 ChristophP