# How I uploaded a file

**URL:** <https://discourse.elm-lang.org/t/how-i-uploaded-a-file/3309>\
**Category:** Show and Tell\
**Created:** [March 13, 2019, 9:07am UTC](https://discourse.elm-lang.org/t/how-i-uploaded-a-file/3309 "2019-03-13T09:07:27Z")\
**Posts on this page:** 2\
**Page:** 1

<div class="post-metadata">

**Author:** ![Hermanverschooten](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/hermanverschooten/32/1883_2.png) [@Hermanverschooten](https://discourse.elm-lang.org/u/Hermanverschooten)\
**Post date:** [March 13, 2019, 9:07am UTC](https://discourse.elm-lang.org/t/how-i-uploaded-a-file/3309/1 "2019-03-13T09:07:27Z")

</div>

# How I uploaded a file

I was migrating some old rails (trailblazer) code, turning the front into Elm and all communications into JSON. One of the forms contains a required file select to attach a PDF.  
I started looking at the docs for `elm/file` and `elm/http`, but they weren’t all too clear.  
I understood easily how to upload the file, but what about the rest of my _form_, `Http.filePart` was easy to understand, but `Http.stringPart` made no sense.  
How do I fit my JSON into it?

To find out, I had to resort to WireShark and look at what the current form sends when I attach a file. When you use a `Http.multipartBody`, every _field_ becomes a separate `Http.stringPart`.

## Code

```auto
module Upload exposing (main)

import File exposing (File)
import File.Select as Select
import Html exposing (..)
import Html.Attributes exposing (type_, value, checked)
import Html.Events exposing (onClick, onInput)
import Http
import Json.Decode as Decode
import Json.Encode as Encode

--- MODEL

type alias Model =
  { name: String
  , age: Int
  , checked: Bool
  , pdf : Maybe File
  }

init: () -> (Model, Cmd Msg)
init _ =
  (
    { name = ""
    , age = 0
    , checked = False
    , pdf = Nothing
  }

--- API

encode : Model -> List (String, Encode.Value)
encode model =
  [("name", Encode.string model.name)
  ,("age", Encode.int model.age)
  ,("checked", Encode.bool model.checked)
  ]

save : Model -> Cmd Msg
save model =
  let
    body =
      case model.pdf of
        Nothing -> 
          Http.jsonBody (Encode.object [("person", Encode.object (encode model) )])
     
       Just file ->
         Http.multipartBody
                        (Http.filePart "person[pdf]" file
                            :: List.map
                                (\( l, v ) ->
                                    Http.stringPart ("person[" ++ l ++ "]") (Encode.encode 0 v)
                                )
                                (encode model)
                        )
  in
  Http.post
    { url = "/persons"
    , body = body
    , expect = Http.expectJson Saved Decode.int
    }

--- UPDATE

Type Msg
  = SelectFile 
  | FileSelected File
  | ClearFile
  | Save
  | Saved (Result Http.Error Int)
  | ChangedName String
  | ChangedAge String
  | Toggled

update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
  case msg of
    SelectFile ->
      ( model, Select.file ["application/pdf"] FileSelected )
    FileSelected f ->
      ( {model | pdf = Just f}, Cmd.none)
    ClearFile ->
      ( {model | pdf = Nothing}, Cmd.none)
    Save ->
      ( model, save model)
    Saved (Ok i) ->
      ( {model | pdf = Nothing}, Cmd.none)
    Saved (Err _) ->
      ( model, Cmd.none)
    ChangedName str ->
      ({ model | name = str}, Cmd.none)
    ChangedAge str ->
      ({ model | age = String.toInt str |> Maybe.withDefault 0 }, Cmd.none)
    Toggled ->
      ({ model | check = not model.check}, Cmd.none)

--- VIEW 

view : Model -> Html Msg
view model =
  let
    pdfBtn =
       case model.pdf of
         Nothing ->
                   button [onClick SelectFile] [text "Add PDF"]
         Just pdf ->
                   button [onClick ClearFile] [text ("Remove " ++ File.name pdf)]
  in
  div []
    [label [] [text "Name"]
    , input [type_ "text", value model.name, onInput ChangedName] []
    , label [] [text "Age"]
    , input [type_ "number", value (String.fromInt model.age), onInput ChangedAge] []
    , label []
       [ text "Check this if you dare "
       , input [type_ "checkbox", checked model.checked, onClick Toggled]
       ]
    , pdfBtn
    , button [onClick Save] [text "Save"]
  ]

--- MAIN

main : Program () Model Msg
main =
  Browser.element
    { init = init
    , update = update
    , view = view
    , subscriptions = always Sub.none
   }

```

## So what did I do?

I used the `elm/file` `File.Select`, triggered from a `button`, to allow the user to choose a PDF file. On receiving this file, I store it in the model. When the save button is clicked, the code checks for the presence of a `File`, if there is none, we use a regular `Http.jsonBody` and `Http.post` it. If however, there is a `File`, we use `Http.multipartBody`, add the file using `Http.filePart` and then loop over the fields, convert them into strings for the `Http.stringPart`. Both `filePart` and `stringPart` take a `String` that is the field name, by using the `person[fieldname]` construction, Rails (Rack) receives this as fields in `params[:person]`. `Encode.encode 0 v` allows me to use the `Json.Encode` functions to turn my fields into valid strings.

_Disclaimer_  
I distilled this code from my working solution, don’t shoot me over typos or certain omissions.

Herman

---

<div class="post-metadata">

**Author:** ![system](https://canada1.discourse-cdn.com/flex035/uploads/elm_lang/original/1X/50a05e53677a2c3b47776d7abd0f113eb50193a1.png) [@system](https://discourse.elm-lang.org/u/system)\
**Post date:** [March 23, 2019, 9:07am UTC](https://discourse.elm-lang.org/t/how-i-uploaded-a-file/3309/2 "2019-03-23T09:07:31Z")

</div>

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