loading...

Elm Journey - Avoiding HTML inputs

kspeakman profile image Kasey Speakman ・5 min read

I have started trying to think of ways to avoid using HTML inputs in my apps when using Elm. I wanted to share more about this and where I have gotten so far.

Why?

HTML inputs have their own independent state, separate from your own Elm Model. When these differ, you can observe some confusing and difficult-to-trace behavior.

The first examples of this that we ran into were radio buttons. If you give several radio buttons the same name, then the browser will treat them as one group and keep track of which one the user last selected. However, Elm is not aware of this unless you setup click events and appropriately update the model. This caused some initial confusion because it looks like everything works even though the model is not getting updated. A couple of us with Angular experience had trouble following what was happening because we were so used to two-way binding.

Another common example of the problem is text inputs where the user types very fast. This not a problem in most cases. But we ran into it with USB barcode scanners which enter numbers very quickly.

For example, if the user types "abc" very quickly, the "b" will get dropped. Here is why.

  1. User types "a", browser changes input value to "a"
  2. UserEntered "a" sent to Elm
  3. User types "b", browser changes input value to "ab"
  4. UserEntered "ab" sent to Elm
  5. Elm processes UserEntered "a", updates model, rerenders
  6. Elm changes the input value to "a" (replacing "ab")
  7. User types "c", browser changes input value to "ac"
  8. UserEntered "ac" sent to Elm
  9. "ab" will get processed, but will later be overwritten by "ac"
  10. To the user it looks like "b" got dropped.

Issues like these arise because HTML inputs have their own state which can be updated from many different places at once. In Elm apps, we would prefer to be the keeper of the state.

This is unfortunately not a solved problem for text inputs, as you will see below, but other types of inputs can be replaced with display-only HTML.

Radio buttons and Checkboxes

Once you realize the problem you could continue to use these inputs... just don't set the name attribute (nor is value necessary). But for styling flexibility, I tend to prefer to replace these with display-only elements. For example, you can use an icon and text. Here is a checkbox replacement example using Semantic UI.

active toggle switch

div []
  [ i [ class "input-sized blue toggle on icon" ] []
  , b [ class "gap left" ] [ text " Active" ]
  ]

inactive toggle switch

div []
  [ i [ class "input-sized horizontally flipped grey toggle on icon" ] []
  , span [ class "gap left" ] [ text " Inactive" ]
  ]

I convert the model property to HTML pretty simply like this:

if something.isActive then
  activeToggle
else
  inactiveToggle

Semantic UI has an off toggle icon, but I did not want to use it because it was so deemphasized that it no longer looked like a control. So I used the on toggle flipped and turned grey.

The CSS classes in the above snippets are from Semantic UI. With the exception of a two extra styles I used for minor visual tweaks.

.gap.left { margin-left: 3px; }

/* same size as other inputs, i.e. on same line */
i.input-sized.icon, i.input-sized.icons { font-size: 2.7em; line-height: 1; vertical-align: middle }

Select

There are tons of different styled drop-down list replacements. Most UI styling kits (including Semantic, Bootstrap, and Zurb Foundations) have one. One of the reasons I love Semantic is because I can style all its states entirely with HTML classes (no JS), which meshes perfectly with Elm. However, one thing to bear in mind is performance. If you have the scenario where you need a lot of data in a drop-down or a lot of drop-downs on a page *, a javascript-rendered drop down is going to be significantly slower than a browser-rendered select.

* these are not great design choices, but sometimes needs must.

I generally hated styled drop-down list replacements in the past. But the reason then was because I was still depending on using the state directly from the HTML element. And these replacements had really hokey ways of keeping state which usually required extra code on my part. Whereas getting the state from native HTML elements "just worked". However since I only care about the display aspects when using Elm, a styled replacement is fine... provided it is robust enough.

Text input

This one is a much harder problem, and not a solved one. Initially I thought about using a div and handling onKeyUp, but then how do you handle cursor positioning (use clicks in between letters to move cursor)? Do you really want to have to handle backspace, delete, arrow keys, etc? "contenteditable" is no help either as it has the same statefulness problem of input.

For now I work around the fast input problem mentioned above using the defaultValue attribute. Browsers display defaultValue if value has not been set. However, defaultValue faces new problems where you have a lot of dynamically generated text inputs. Elm's virtual DOM implementation apparently has a bug which does not properly handle this case. In that case, I just use value and most users can't type fast enough to experience the problem.

If I were to try to solve this problem, I was thinking I would use a text input for user entry, but have it hidden (opacity 0%) in front of the display-only div. Then have the div styled to look like an input and receive updates from Elm as the user enters text. So the input is only used as a "source" but what is displayed is the div's contents rendered from Elm. But that's just a vague idea at this stage.

Other inputs: number, date, etc.

I generally find these are inconsistently implemented across browsers. So rather than writing a bunch of code to resolve that inconsistency, I use text inputs for everything.

Conclusion

This is still a work in progress for me. The main revelation is that I don't need HTML elements to keep state for me when using Elm. And in fact, using stateful elements has caused the proverbial "wild goose chase" on the team more than once. In an ideal scenario I want to use HTML only for visual purposes. However, text input continues to be a sticking point since it handles so many important dimensions of data entry for me. If anybody has any further ideas there, I would love to hear them.

/∞

Discussion

pic
Editor guide
Collapse
dawehner profile image
Daniel Wehner

Do you know how your approach of not using native elements works in context of accessibility?

Collapse
kspeakman profile image
Kasey Speakman Author

ARIA attributes seem to be the path forward there.

I must admit this is not something we currently code for, and thusfar no one has brought it up. However I am glad you mentioned it, and I am putting a story on our backlog for it.

Collapse
wking profile image
William King

Also, feel free to jump into the Elm Slack when you run into these kind of problems! There are a lot of people and teams in the community with Elm in production that have answers for all the problems you face and they are always happy to help! elmlang.herokuapp.com/

Collapse
kspeakman profile image
Kasey Speakman Author

Thanks for the comments.

I have already researched the mentioned issues with text input. You can find them recorded here:

Unreliable value due to requestAnimationFrame #107
Html.input defaultValue does not update properly when switching pages #152

The second one is why defaultValue does not work when there are multiples in a dynamic list. I tried Html.Keyed.node, but I cannot remember the outcome of that test, and it is more of a pain to use.

Collapse
wking profile image
William King

Yeah dynamic lists with Virtual DOM is always a fun problem! Html.Keyed is a great solution but yeah it is more code and more to keep track of. 👌

Collapse
wking profile image
William King

Did you get a chance to give onInput a try for text inputs? This event function specifically passes the value of the input no matter what key is pressed, or deleted, etc. package.elm-lang.org/packages/elm-...

Collapse
kspeakman profile image
Kasey Speakman Author

Thanks for the comment. Yes onInput was used. See my answer to your other comment for more details.