I started using Elm relatively recently, only for a few weeks now, and it’s my first foray into functional programming. As I was working on a small feature to learn the language, I came across a situation where I wanted to simultaneously grab multiple values from a field. Well Html.Events.onSubmit wasn’t cutting it so I ended up creating my own event listener to do what I wanted.
I’m sharing this example here in case it is useful to anyone else, but also for feedback. It seems to work well but perhaps there are some pitfalls or gotchas with this approach that I’m not aware, as opposed to what I’ve seen before with updating the model using onInput with every keypress for each each field.
This approach seemed better for my case where I really don’t care what’s in the field until the user chooses to submit it.
Neat! If you find yourself doing this a lot you might want to write some higher level primitives for decoding the data. Like “decodeField : String -> Decoder String”. That function could use D.andThen to check the name of the field and decode by name instead of position, which would make the decoder independent of the view order.
Ah, so that’s how it’s done. I had thought of that, keying off the name field instead, recognizing that what I’ve got now could be pretty fragile, but didn’t know how to accomplish it. Thanks for the tip, that would definitely be better.
I think I’m making some progress on this approach, but I’m a bit stuck. I can see how I can first decode by name, and then use andThen to return the proper decoder based on the decoded name.
I’m having two issues, however:
As I have it, it’s still dependent upon order, to a degree. If I moved the input[type=submit] element to be the first child I’d miss out on one of the input fields I want.
I can’t figure out what decoder I need to return once I know the name. The issue I have is that the field I want, value is a sibling of name.
Can’t work through it completely right now, but what you probably want to do is decode target.elements to a Dict of field values and use that to get the field you want. Something close-ish to this maybe:
D.at ["target", "elements"] decodeFieldDict
|> D.andThen (\fields -> D.succeed (Dict.get fieldLookingFor fields)) -- But also handle the error case
You might have to decode target.elements to a list of tuples before you can get it into a Dict…
That’s just off the top of my head though. I can work through it more later today if you’re still having issues.
This didn’t end up turning out anything like I thought it would.
The value at target.elements isn’t an array - it’s an HTMLFormControlsCollection which looks to Elm like an object: {"0": ..., "1": ..., "2": ...}. Decoding that thing would be interesting. You’d probably have to decode it as a dict, convert that to a list, and iterate over that pulling out what you’re looking for. But maybe someone else here has a better idea.
Luckily there’s a much easier way. Fields with names are available right on the form object. So this works:
That’s very clever. I had spent the afternoon pursuing the first path you mentioned, to no avail. That was going to be a deep rabbit hole that I’d have had to set aside more time for.
Does your second solution work because Json.Decoder.at doesn’t require that each item in the list is a direct descendant of the previous one? The idea of using a variable had crossed my mind but I wouldn’t have arrived at such a simple solution.
Thanks for your time on this, it’s been a great learning experience for me.
No, D.at does require each item in the list be a direct descendant of the previous one. Input fields with name attributes are available as properties of the HTMLFormElement object. See the documentation, at the bottom of the Properties section.