Is it possible to restore the browser default behavior on fragment links without ports?

I have a page (in a Browser.application) where the navigation inside the page is done with fragment links. Is it possible to restore the browser default behavior without ports?

here is some sample code that shows this problem:

module Main exposing (Model, Msg(..), init, main, subscriptions, update, view, viewLink)

import Browser exposing (Document)
import Browser.Navigation as Nav
import Html exposing (..)
import Html.Attributes exposing (..)
import Lorem
import Url



-- MAIN


main : Program () Model Msg
main =
    Browser.application
        { init = init
        , view = view
        , update = update
        , subscriptions = subscriptions
        , onUrlChange = UrlChanged
        , onUrlRequest = LinkClicked
        }



-- MODEL


type alias Model =
    { key : Nav.Key
    , url : Url.Url
    }


init : () -> Url.Url -> Nav.Key -> ( Model, Cmd Msg )
init flags url key =
    ( Model key url, Cmd.none )



-- UPDATE


type Msg
    = LinkClicked Browser.UrlRequest
    | UrlChanged Url.Url


update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        LinkClicked urlRequest ->
            case urlRequest of
                Browser.Internal url ->
                    ( model, Nav.pushUrl model.key (Url.toString url) )

                Browser.External href ->
                    ( model, Nav.load href )

        UrlChanged url ->
            ( { model | url = url }
            , Cmd.none
            )



-- SUBSCRIPTIONS


subscriptions : Model -> Sub Msg
subscriptions _ =
    Sub.none



-- VIEW


type Section
    = Home
    | About
    | Services
    | ContactUs


sections : List Section
sections =
    [ Home, About, Services, ContactUs ]


sectionToString : Section -> String
sectionToString section =
    case section of
        Home ->
            "Home"

        About ->
            "About"

        Services ->
            "Services"

        ContactUs ->
            "Contact Us"


view : Model -> Document Msg
view model =
    { title = "Title"
    , body = viewLinks :: List.map viewSection sections
    }


viewLinks : Html msg
viewLinks =
    ul [] <|
        List.map viewLink sections


viewLink : Section -> Html msg
viewLink section =
    let
        name =
            sectionToString section
    in
    li []
        [ a [ href ("#" ++ Url.percentEncode name) ]
            [ text name ]
        ]


viewSection : Section -> Html msg
viewSection section =
    let
        name =
            sectionToString section
    in
    div [ id (Url.percentEncode name) ] <|
        h1 [] [ text name ]
            :: (Lorem.paragraphs 10 |> List.map (p [] << List.singleton << text))

and the elm.json file:

{
    "type": "application",
    "source-directories": [
        "src"
    ],
    "elm-version": "0.19.0",
    "dependencies": {
        "direct": {
            "elm/browser": "1.0.1",
            "elm/core": "1.0.2",
            "elm/html": "1.0.0",
            "elm/url": "1.0.0",
            "ohanhi/lorem": "1.0.2"
        },
        "indirect": {
            "elm/json": "1.1.3",
            "elm/time": "1.0.0",
            "elm/virtual-dom": "1.0.2"
        }
    },
    "test-dependencies": {
        "direct": {},
        "indirect": {}
    }
}

I think I saw some time ago that Navigation.load works for the same path with a hash.

On the other hand the elm package website has that functionality to link to specific doc functions/items, and from what I can gather it includes the fragment in the parser, and then manually triggers setViewport with the appropriate coordinates. See:

So it seems doable, and it lets you control in the urls what paths you want to consider fragments for, and what to do with it (browser-like scrolling or something else custom).

I think if you set a custom target attribute on the link you can skip elm’s handling of the URL and get the functionality back. https://github.com/elm/browser/blob/67140e3da6e90df96ade0e32a868e28de1e77296/src/Elm/Kernel/Browser.js#L157

I tested it out and the target does restore the functionality, though it does seem a little hacky.

I might wrap it in another element to make it less awkward

anchor: String -> List (Attribute msg) -> List (Html msg) -> Html msg
anchor name attrs children = 
    a (target "asdf" :: href ("#" ++ Url.percentEncode name) :: attrs) children
1 Like

This doesn’t work on my computer. It opens the link into another browser window.

This did it:


scrollTo : String -> Cmd Msg
scrollTo tag =
    Task.attempt ScrollAttempted
        (Dom.getElement tag
            |> Task.andThen (\info -> Dom.setViewport 0 info.element.y)
        )
1 Like

This topic was automatically closed 10 days after the last reply. New replies are no longer allowed.