tl;dr
Tired of writing so many ports and subscriptions to request and retrieve values from JavaScript, I made a package that just automates the whole thing internally so you dont have to wire up any incoming or outgoing ports. Its called Chadtech/mail
, and you can see an example project here. Below is the most basic example code:
mailLogin : Model -> Mail Msg
mailLogin model =
[ ( "username", Encode.string model.username )
, ( "password", Encode.string model.password )
]
|> Encode.object
|> Mail.letter "login"
|> Mail.expectResponse loginDecoder LoginResult
|> Mail.send
This code is saying "Pass along this Json.Encode.Value
, to the javascript function named "login"
, and decode its response value with loginDecoder
, which will be handled by the Msg
LoginResult
". Thats it. Theres no outgoing port that needs to get called, and you dont need to set up the subscription to listen for the result.
Background
@splodingsocks gave that great talk on ports at Elm conf, where he showed everyone a code technique of having only one incoming and outgoing port. Its a really great idea that excited a whole lot of us. At my job, we ended up implementing it in our Elm apps. @pdamoc made Elm Ports Driver, which are an Elm and Npm package for this technique too.
I ended up talking about it with enough people that I wrote this gist, summarizing how I do it. I ended up talking to James Hopkins, a new member of the Elm slack, about his project using ports to access firebase. Somehow an idea emerged between us in our conversation:
What if ports were like http requests, and you didnt have to wire up any subscriptions at all? Instead you could just pack up an address, a value, and an expected return value and send it off.
So thats what I set out to make, and thats what Chadtech/mail
is. The whole one-incoming-and-outgoing-port thing is still there, but its entirely encapsulated inside this new Mail.Program
type. Its all automated behind the scenes. On the JavaScript side you have functions with address
s, and you can send Letter
s to those addresses
just by passing along a String
with a Json.Encode.Value
. The JS function also gets a call back, and if you mailed your Letter
along with a Decoder a
and a Result String a -> msg
, Chadtech/mail
decodes the return value and passes the Msg
right into your update function.
The whole thing ended up being really intensive
I didnt realize this at the onset, but to make this work I had to kind of overhaul everything. You know how theres Html.program
, but separately theres also Navigation.program
? I had to make a Mail.program
. The most fundamental change from Html.program
s is that Mail.Program
update functions return (Model, Mail Msg)
instead of (Model, Cmd Msg)
. Its just an extension of Cmd
s, regular old Cmd Msg
s can become Mail Msg
s by means of Mail.cmd : Cmd Msg -> Mail Msg
and they work just how they did before. Thats the most fundamental difference.
Thats my project. Im curious for any feedback, or if you think this might be useful. I was really just trying to get a proof of concept together but now I have some hope this could actually be practical. Let me know if you dont think so. Seeing as this was a lot more of a fundamental change in the Elm architecture than I thought it would be, it seems to me that the best implementation of this would be in the language itself, and not as a package. It really would just be making a Ports.send : String -> Letter msg -> Cmd msg
function, along with a callback inside app.ports.f.subscribe
functions.