Package: pdamoc/elm-ports-driver

Hi there,

I have created a proof of concept of an idea I had for a long time.

http://package.elm-lang.org/packages/pdamoc/elm-ports-driver/latest

The basic idea is to use convention and encapsulate frequently used JS code inside a driver that can receive commands from Elm and can reply to Elm with the results of certain commands (if there is such a need).

Right now it is only a minimal proof of concept that I needed for a project that needs the FileReader API. I have also added a command to change the title and a command to mount some CSS in the head (they were low hanging fruits). All three pieces are demonstrated in the example folder.

For the next version I’m planning to add localStorage support.

Please take a look and provide feedback.

5 Likes

I think it’s a great proof of concept! Let me summarize the things I said on slack

  • The Driver type should be opaque, with its fields being functions that take a Driver argument
  • The Msg type should be opaque, with the File constructor instead being a function file : Msg -> Maybe (String, String, Contents)
  • Perhaps allow for the names of ports to be specified in elm_ports_driver.install()

For file inputs

  • Selection of multiple files
  • Chunked file reading
1 Like

This looks really cool! Just had a small suggestion.

I think it would be helpful to at least have a version of the file commands that don’t rely on an input id and instead expect a File object. So the functions would be of type Value -> Cmd msg and the user would have to decode a file from an event. I’ve had to do this to implement a drag and drop file uploader, since there is no input element with file info in that case.

Do you have some open source code that I can look at that functionality?

Sure thing, I used it in this project: https://github.com/mcordova47/payback.

Here are the relevant snippets:

Main.elm > update:

UploadFile file ->
    ( { model | dragging = False }, Ports.upload file )

Decoder:

dropEventDecoder : Decoder Value
dropEventDecoder =
    Decode.at [ "dataTransfer", "files", "0" ] Decode.value

File Drag/Drop Element:

fileDrop : Maybe String -> Bool -> Html Msg
fileDrop message dragging =
    Html.div
        [ Events.onWithOptions "drop"
            { stopPropagation = False, preventDefault = True }
            (Decode.map UploadFile dropEventDecoder)

Ports.elm:

port upload : Value -> Cmd msg

index.js:

app.ports.upload.subscribe(file => {
  const fileReader = new FileReader()
  fileReader.onload = () => app.ports.readFile.send(fileReader.result)
  fileReader.readAsText(file)
})
4 Likes

I have just published version 3.0.0 of the package (npm version 2.1.0)

What I changed:

  • rather than have a monolith command processor in JS, I have made it so that install receives a list of “plugins” where each knows how to handle one or more messages. I believe that this will help with DCE.

  • in Elm, instead of having one driver record now there are a commands creators that take a config object.

  • the subscriptions takes the config and a list of message decoders and if the incoming message does not matches any of them, it will return the fail msg.

  • the onFile replaces onFileChange. The msg creator receives a FileRef for the file selected. FileRef is then used to request the read. FileRef is just a Value so, FileRef from other sources (the above drop example) should be just fine.

I have also implemented the localStorage support.

Multiple file support and chunked file read are delayed for a future version.

If any of you wants to help in any way, PR are more than welcome.

The first priority is a stable convention for how to do this ports driver. Once we have this, extension should be easier. I think this version is in much better shape than the first version.

Please take a look and provide feedback as to how I can improve this.

4 Likes