Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Elm: attribute "onerror" adds "data-onerror" attribute instead

Tags:

elm

In Elm I have a simple image, and I want it to be replaced by some 'missing' image onerror. So I added an "onerror" attribute:

img
    [ src "broken-link.png"
    , attribute "onerror" "this.onerror=null;this.src='missing.png';"
    ] []

However, when I look at the generated html, the img doesn't get an onerror attribute, but rather gets an data-onerror, and ofcourse this doesn't work.

Why is this? And how do I fix it?

Here is a little example I made with my friend Bulbasaur to illustrate the problem: https://ellie-app.com/3Yn8Y6Rmvrqa1

like image 674
The Oddler Avatar asked Nov 24 '18 09:11

The Oddler


2 Answers

Why is this?

This seems to be a built-in undocumented safety feature of Elm.

Checking source code of Elm, Html.attribute is defined as (source)

attribute : String -> String -> Attribute msg
attribute =
    VirtualDom.attribute

and VirtualDom.attribute is defined as (source):

attribute : String -> String -> Attribute msg
attribute key value =
    Elm.Kernel.VirtualDom.attribute
        (Elm.Kernel.VirtualDom.noOnOrFormAction key)
        (Elm.Kernel.VirtualDom.noJavaScriptOrHtmlUri value)

Your attribute name onclick is passed to Elm.Kernel.VirtualDom.noOnOrFormAction which is defined in JavaScript as (source):

function _VirtualDom_noOnOrFormAction(key)
{
    return /^(on|formAction$)/i.test(key) ? 'data-' + key : key;
}

So if attribute name starts with on, or is string formAction, then it is renamed to be a data-attribute.

How do I fix it?

One way I know how to fix this is to write the code in Elm without JavaScript. Here's full working example with main parts copied below: (This is based on accepted answer here about detecting image load failure.)

1) Keep current URL in model

type alias Model =
    { src : String
    }

init : Model
init =
    { src = "http://example.com" }

2) Change event handler to send Elm message on image load error

img
    [ src model.src
    , on "error" (Json.Decode.succeed ImageError)
    , alt "Should be Bulbasaur"
    ] []

3) Change URL in update on error

update : Msg -> Model -> Model
update msg model =
    case msg of
        ImageError ->
            { model
                | src = "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/1.png"
            }
like image 109
Markus Laire Avatar answered Nov 12 '22 14:11

Markus Laire


I'd solve this problem by writing a custom event handler for image load error event. The custom handler will be look a like:

onImageLoadError : msg -> Html.Attribute msg
onImageLoadError message =
    on "error" (JD.succeed message)

I'll define the Model, Msg as below:

type Msg
    = ImageLoadError


type alias Model =
    ImageSrc

I'll also define a custom type to wrap the good and default url.

type ImageSrc
    = Good String
    | Default String

The onImageLoadError will be fired and send the ImageLoadError msg to the update function, and the update function will then set the default url as:

update : Msg -> Model -> Model
update msg model =
    case msg of
        ImageLoadError ->
            Default "https://developer.mozilla.org/static/img/web-docs-sprite.22a6a085cf14.svg"

And here is the full runnable code:

module Main exposing (main)

import Browser
import Html exposing (Html, div, img, text)
import Html.Attributes exposing (alt, src)
import Html.Events exposing (on)
import Json.Decode as JD


type Msg
    = ImageLoadError


type ImageSrc
    = Good String
    | Default String


type alias Model =
    ImageSrc


update : Msg -> Model -> Model
update msg model =
    case msg of
        ImageLoadError ->
            Default "https://developer.mozilla.org/static/img/web-docs-sprite.22a6a085cf14.svg"


imageSrcVal : ImageSrc -> String
imageSrcVal src =
    case src of
        Good url ->
            url

        Default url ->
            url


view : Model -> Html Msg
view model =
    div []
        [ div [] [ text "Broken image, that should be replaced by Bulbasaur: " ]
        , img
            [ src (imageSrcVal model)
            , alt "Should be Bulbasaur"
            , onImageLoadError ImageLoadError
            ]
            []
        , div [] [ text "instead it does nothing, and the 'onerror' attribute is 'data-onerror' (use inspect element to see)" ]
        ]


onImageLoadError : msg -> Html.Attribute msg
onImageLoadError message =
    on "error" (JD.succeed message)


main : Program () Model Msg
main =
    Browser.sandbox
        { init = Good "https://developer.mozilla.org/static/arrows/arrow-right.cbc8b4f075cc.svg"
        , view = view
        , update = update
        }

Ellie App.

I init the model with a good url "https://developer.mozilla.org/static/arrows/arrow-right.cbc8b4f075cc.svg", but if you somehow make it a bad url like "https://bad.developer.mozilla.org/static/arrows/arrow-right.cbc8b4f075cc.svg", you will the default image there.

like image 43
Arup Rakshit Avatar answered Nov 12 '22 15:11

Arup Rakshit