Search code examples
url-routinghrefelm

a elements without href, or with an empty href, causes page reload when using Browser.application in Elm 0.19


Empty links, i.e. a elements without a href attribute or with href "" causes a page reload if used with Browser.application in Elm 0.19.

Here's a minimal, complete and verifiable example:

module Main exposing (main)

import Browser
import Browser.Navigation
import Html exposing (..)
import Url


type alias Model =
    ()


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


init () _ _ =
    ( (), Cmd.none )


update msg model =
    case msg of
        UrlRequested (Browser.Internal _) ->
            ( model, Cmd.none )

        UrlRequested (Browser.External url) ->
            ( model, Browser.Navigation.load url )

        UrlChanged _ ->
            ( model, Cmd.none )


view model =
    { title = ""
    , body = [ a [] [ text "click to reload" ] ]
    }


main =
    Browser.application
        { init = init
        , view = view
        , update = update
        , subscriptions = \_ -> Sub.none
        , onUrlRequest = UrlRequested
        , onUrlChange = UrlChanged
        }

This is non-standard behavior, as such links should not be considered hyperlinks by the user agent, and a lot of code depend on this behavior, among them elm-bulma.


Solution

  • This is caused by Elm attaching onclick event listeners to all a elements to be able to intercept and handle routing internally. It parses the URL and categorizes it as either Internal or External, where an empty or omitted href is apparently considered External. Elm will then create a 'Msg' using the type constructor passed to Browser.application via onUrlRequest, run update passing this Msg, and this is where we can intercept and handle it appropriately.

    The solution is to add another pattern to update which matches on empty external URL, where we simply do nothing instead of trying to load the URL as we normally would with other external URLs. In regards to the question's example the following updated update function should do the trick:

    update msg model =
        case msg of
            UrlRequested (Browser.Internal _) ->
                ( model, Cmd.none )
    
            UrlRequested (Browser.External "") ->
                ( model, Cmd.none )
    
            UrlRequested (Browser.External url) ->
                ( model, Browser.Navigation.load url )
    
            UrlChanged _ ->
                ( model, Cmd.none )