Calling DOM methods from Elm

I’m completely new to Elm and are reading through the guide. In the Maybe chapter there is an exercise where one task is to put a red border around an invalid input.

I would like to use HTMLInputElement.setCustomValidity(String) instead of using CSS.

Unfortunately elm/html does not have a DOM method primitive.
The problem with using Html.Attributes.property "setCustomValidity" (JSON.Encode.string e), is that Html.Attributes.property generates a setter that not only is not working but overwrites setCustomValidity() so it does not work for any JS on the page.

I’ve read Properties vs. Attributes that does not mention DOM methods and Exploits allowing arbitrary JS code execution via Elm code which indicate that all escape hatches has been closed.

Is there any simple way to call a DOM method on an Element in Elm?

PS. In case you want to see how I want to use setCustomValidity, my attempt is on line 161 in celsiusToFahrenheit/src/Main.elm.

I think it will require using ports or a custom element to access that API. A custom element could wrap up the logic nicely, but for this use case I would recommend making your own error view in vanilla Elm if that is an option.

The ports are a decent bit of cognitive overhead, and the custom element can be too if you don’t already use them in the app, and the error view is much simpler.

Thank you for your suggestion.

I think that, with my current speed of learning, ports and custom elements are a few days out. But I can revisit my code when I get to those.

I also searched a bit for Elm packages and there is quite a few that are validation oriented. But I could not understand, with my current knowledge, if they would enable reading / setting validity.

Do you know any packages for that? I rather start out with a package than spend time re-inventing the wheel, if someone already worked on this.

For now, I can definitely use Elm and CSS but I like to use DOM API’s as they generally work much better across devices (e.g. mobile, watch etc).

I like https://package.elm-lang.org/packages/rtfeldman/elm-validate/latest for validating the data itself, but for viewing the errors I’ve rolled my own to fit in the application.

Unfortunately I don’t think you will find any packages that use the validity API as it will require ports, the main options from the Elm side are going to be the pattern and required attributes, as well as ARIA for assisive technologies.

If you are already familiar with Javascript using ports or a custom element to accomplish this will not be too difficult, check out https://guide.elm-lang.org/interop/custom_elements.html for an example of one.

Elm is not treating DOM objects as the mutable nodes they are. Elm has this immutable abstraction of a node and it’s doing the mutations in a virtual dom implementation.

So, anything that looks like a mutation of the DOM is going to be achieved either by having the view be different in such a way that the virtual DOM generates that mutation OR through ports.

Your use-case here is actually more challenging than expected.

A custom elements approach is running into this virtual dom issue.

However, a ports approach where validity is updated on input change works.

My experience with HTML5 validation in JS projects is that it sounds super nice first, but then never works exactly how you’d like and is full of edge cases and workarounds. :man_shrugging:

2 Likes

This is just for fun. Usual disclaimers apply that this is not the intended or supported way of doing things.

If there is a method in the DOM that really should be a property, nothing is stopping you from just making it one: https://ellie-app.com/8LXM8GVGxWWa1

I see.

I do not fully understand. So what you are demonstrating is that it does not work?

This actually looks very nice. The only added line, to a normal Elm app, is port setValidity : { id : String, validity : String } -> Cmd msg as far as I can see. On the JS side, the amount of code is similar, or a little less, to what I would have written without Elm.

Sorry for the confusion. Yes, in that example I’ve shown you how it would have looked if it would have worked. As you can see… it is not a lot of code. To be honest, I fully expected that example to work and when it didn’t, I researched a little bit and I’ve found the issue that I linked. None of the custom elements that I use in my app are extending already existing input elements.

I have had a somewhat similar experience but categorize it into two different cases.

  1. There is a designer who has designed the validation experience. E.i. colors, popups. etc. Or I want to play the designer and display a custom error design.
  2. No error handling has been designed at all.

In the first case, always use text input and perhaps password but nothing else. If you do, then you will be fighting a long, long and surprising battle against browser behavior and CSS. Especially the CSS will not be documented and you might not even be able to override it… And then some Samsung browser comes along and does it a little bit different but enough to break the design. I have even used <div> with great success and implemented everything from scratch. No browser can f*** that up! :slight_smile:

In the second case, use all of the HTML5 goodies. Save a week from not re-implementing that datepicker in corporate branding and simplify everything for the back-end folks. Have great accessibility across desktops, game consoles, watches, mobile, TV - you name it! Screen readers? Colorblind? WCAG 2? Keyboard shortcuts? Bring up the right smartphone keypad? Impossible form factors? No sweat - it is all taken care of. But accept this: YOU DO NOT CONTROL HOW IT WILL LOOK.

I appreciate your ingenious (but brittle) way of extending the DOM! :smiley: It works fine in gecko while blink is ignoring it.

It is also very nice for me to see examples like this (and all of the examples in this thread). I feel it provide’s me real world examples of some of the Elm operators, that so far, has not been covered in the Elm guide.

I will save a list of all of them for future reference - thanks!

I’m currently collecting similar examples like this for a reference page! I’ll probably post a link on Discourse once I wrote some real documentation (mostly warnings) for these.

I use Firefox as my main browser, but I checked it before in Chrome. It seems to set the properties correctly (validationnMessage and validity). Maybe Chrome is not applying default styling to it? The MDN example also does not seem to have styling in Chrome

1 Like

Yet a way of doing it would be do add data-validity="Whatever message" to your <input>, and call .setCustomValidity with the contents of that attribute via a MutationObserver. I use this at work to call .showModal() on <dialog data-state="modal|closed"> elements.

Nice! I have never used MutationObserver but this seems like a candidate case for using it.

I’d recommend against using the is syntax if you need to support Safari as well, they don’t support customizing built-ins.

What we have been doing at work for elements that don’t need to actually add/replace children is render the elements inside the custom element, and then look them up.

Updated ellie: https://ellie-app.com/8MdS5ytWnyqa1

image

For this module it would probably go in ValidInput.elm and you might expose it with an API using the “unAttr” pattern like Ellie does to restrict the public API

validInput: Model -> Html Msg
validInput model =]
    ValidInput.view 
        [ ValidInput.validity "custom validity message"
        , ValidInput.value model.value
        , ValidInput.onChange SetValue
        ]

The ValidInput module would make sure that an input is rendered, that attributes/properties are encoded correctly, etc.

3 Likes

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