I'm curious what your problem is with ports? In my experience it's a nice API for interacting with the outside world. Or are you referring to DOM features that haven't been mirrored in Elm yet?
I'm curious what your problem is with ports? In my experience it's a nice API for interacting with the outside world. Or are you referring to DOM features that haven't been mirrored in Elm yet?
math.js has a nice `math.evaluate` function for this, that in theory would have the type `String -> Result ParseError Float`, if I could directly assign Elm types to a JS function. However, I can't actually use the function like that. I must treat it as a pub-sub mechanism and define the requisite messages, state, update functions, etc. in order to use it. This turns what should've been a one line call into potentially dozens of lines that also forces an architectural change in how the calling code actually calls the function.
The only other real alternative is to rewrite the functionality I need in Elm.
There should be some built-in asyn request/response mechanism for ports, in the style of the Http module where you specify a Msg for the response. This would work for synchronous stuff too.
As far as I can tell the only reason Evan doesn't include something like task ports is to purposely make interop less convenient (but no more safe) than it needs to be, in order to encourage people to write elm. However, I think this decision will end up biting him at the end of the day.
For instance, we have a text editor that wraps Quill so that we can support things like mentions, hashtags, and emojis. We can have multiple instances of the editor on the page at once, so the port needs a way to uniquely identify which instance the event is for, and handle delivering any responses to the correct instance of the editor. I find it a lot easier to partially apply the ID of the editor to the message so that everything is defined where it is used in the code. It is also possible to forget to add the necessary subscriptions when adding the editor to a new page, whereas with a custom element it is all handled internally.
Another case where ports had a bit of an impedance mismatch is on using existing browser APIs, like the Intl module (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...). We wanted to use it to format an epoch time into the user's locale. The only way to call the Intl module from Elm itself is through ports, and the wiring makes the code a lot more complex because you have to send the request out through a port, wait for the response, and then render it. With a custom element it becomes a lot simpler conceptually, we have something like
DateTime.view posix
We also like to wrap up the custom elements with a type-safe Elm wrapper so that we can control what options are available. We have some feeds that have infinite-scroll on them to load more content as you reach the bottom, it uses a custom element that wraps the IntersectionObserver API and fires events that Elm can listen to, so it makes it really easy to use and understand how it is working, in the code it looks like InfiniteScroll.view
{ onScrollIntersect = LoadMore
, state =
case feed of
Done ->
InfiniteScroll.Done
Failure ->
InfiniteScroll.Failure
Loading ->
InfiniteScroll.Loading
}
Anywhere you want to respond to scroll position you can just add that element in and it will work its magic.I don't know if there is a formal name for this pattern, but we've been calling in the "un-attr" pattern, you define an opaque type like
type Attribute msg
= Attr (Html.Attribute msg)
And then expose functions that allow you to set attributes you whitelist. In the JS you use setters to hook into when the attribute changes and respond to it (https://github.com/ellie-app/ellie/blob/master/assets/src/El...)