elm / browser

Create Elm programs that run in browsers!
https://package.elm-lang.org/packages/elm/browser/latest/
BSD 3-Clause "New" or "Revised" License
311 stars 64 forks source link

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

Open ChristophP opened 6 years ago

ChristophP commented 6 years ago

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 commented 5 years ago

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.

turboMaCk commented 3 years ago

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.

layflags commented 2 years ago

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

ChristophP commented 2 years ago

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