How can one upload files using Elm?

Does any of you have a simple but complete example of how to handle uploading some binary file (it can be an image or a PDF)?

Can the http post request be made in Elm or this can only be achieved by creating the http in JS in ports code?

2 Likes

We’ve used the technique described in this blog post at work. The summary of how it works is:

  1. Create an input in Elm that passes its own element id to a port when the change event is fired (a file is selected).
  2. The port uses the FileReader API to read the contents of the file and sends it back to Elm as a base64-encoded data URL.
  3. Elm receives the contents of the file as a string and makes an HTTP request.

We have used this for CSV files that are up to 100KB in size without any issue. Cannot speak how well this would work for significantly larger files (e.g. a 10MB image or a 100MB zip file).

2 Likes

Thank you for the reply.

To give more information. I’m trying to figure out how to handle S3 uploads. I need to send some PDF files to Amazon S3 and I’m trying to figure out if this can be achieved with the Http library of Elm.

I saw this link on slack. Sounds like it might be useful for you.
https://simonh1000.github.io/2016/12/elm-s3-uploads/

That solution uses Native code and I would prefer a solution that uses ports or some other way of handling the JS part (e.g. MutationObservers).

In this module, I have made buttons to load single or multiple file as JS Value(s). It’s wrapped for style-elements but I think you can pretty easily get the gist of it and keep only what matters to you.

That will get you until "I have a file now as a JavaScript Value". Then I believe you can’t send it yet with elm http APIs, except if you stringify as base64 or other method the binary Value, but to do that I believe you need to go through ports anyway. So what I would do is just send the raw JS Value representing the file through a port and ask JS to make an HTTP request with it as binary.

I have played a little bit with MutationObservers and I got as far as being able to get the contents of the file as a Value into elm using regular elm events but I got stuck at the sending stage. This is why I asked.

Of course, I can do the sending stage in JS too but then I would have to introduce subscriptions and all the trouble of managing the reply of the server which is what I’m trying to avoid.

I wonder if FileReader.readAsBinaryString() can be used to convert a file to a string and then send it with Elm using Http.stringPart. I will need to implement image upload in the project I’m working on soon, and this is an idea I’m thinking about.

According to MDN, " The best way to send binary content (e.g. in file uploads) is by using an ArrayBufferView or Blob in conjunction with the send() method."

1 Like

Very interested in this discussion!

I am going to need to handle file uploads to Amazon at some point for my project too, so I would be very interested to know if anyone has figured out a nice way already! I am rubbish at JavaScript so I am putting this off until I absolutely have to do it.

I did once get something working by using the simonh1000 package and following the excellent https://elmseeds.thaterikperson.com/upload-to-s3 tutorial. However, I scrapped that implementation because I don’t want to rely on native code.

In general, I wonder if it would make sense to build up a sort of catalogue of “how to do X in Elm with ports & JavaScript” blog-posts for topics like file uploads, Phoenix Channels, etc. (i.e. the most common use cases where people are currently using packages that include native code). This might help to keep people moving forward until a proper, 100% Elm solution can be found.

3 Likes

In the old forum there was a thread about support for binary data. And apparently there was some work on this at that time.

https://groups.google.com/d/msg/elm-discuss/spr621OlUeo/srUEyeeQCAAJ

Folks, I suppose the topic still actual as 0.19 out. Can anyone have a link to small repo with examples uploading big files without native modules but only ports? Thank you

1 Like

It would be awesome to get official guidance on this!

2 Likes

If you are not afraid by old school hacks, the pre-AJAX “hidden iframe form upload” trick seems to be the only way to send binary files directly (no base64 encoding) and get a “decodable” (kind of) answer from the server in pure elm without ports:

Example for elm 0.19:

module Main exposing (..)

import Browser
import Html exposing (..)
import Html.Attributes exposing (action, enctype, id, method, name, src, style, target, title, type_)
import Html.Events exposing (on)
import Http
import Json.Decode as Decode exposing (Decoder)

type Msg
    = UploadCompleted String


type alias Model =
    String


view : Model -> Html Msg
view model =
    div []
        [ form
            [ action "/upload"
            , target "iframe"
            , enctype "multipart/form-data"
            , method "POST"
            ]
            [ input
                [ name "fileInput"
                , type_ "file"
                ]
                []
            , iframe
                [ name "iframe"
                , id "iframe"
                , style "display" "none"
                , on "load" resultDecoder
                ]
                []
            , button []
                [ text "Submit" ]
            ]
        , div []
            [ text model
            ]
        ]


resultDecoder : Decoder Msg
resultDecoder =
    Decode.at [ "target", "contentDocument", "body", "textContent" ] Decode.string
        |> Decode.andThen (Decode.succeed << UploadCompleted)


update : Msg -> Model -> Model
update msg model =
    case msg of
        UploadCompleted result ->
            result


main : Program () Model Msg
main =
    Browser.sandbox
        { init = ""
        , update = update
        , view = view
        }

It works (not for S3 though) but it’s weak because the server response HTTP headers cannot be decoded.

:warning: Don’t try this at home :rofl:

the optimist in me would like to think that now that we have the Browser module it will be easier to add FileReader support because it can be partitioned off from e.g. a Node way of reading files (and a WASM way if that exists)

1 Like

@catz From my experience, the method described in the blog post linked by @christian using readAsDataURL and only ports allows to handle big files (several tenth of MB) without much issues in spite of the overhead. Here is a 0.18 repo with the code (0.19 version should be very close):

(if you test it with big files, just remove the image preview code in view).

As handling HTTP requests from Elm is simpler and safer, I would seriously benchmark this solution before handling the requests from JS world.

Note: this example does not handle S3 uploads.

1 Like

This topic was automatically closed 10 days after the last reply. New replies are no longer allowed.