12

I've seen simple ways to read contents from a file input in JavaScript using HTML5 File API.

This is my view method, inside a small fable-arch app:

let view model =
    div [
        attribute "class" "files-form"
    ] [
        form [
            attribute "enctype" "multipart/form-data"
            attribute "action" "/feed"
            attribute "method" "post"
        ] [
            label [ attribute "for" "x_train" ] [ text "Training Features" ]
            input [
                attribute "id" "x_train"
                attribute "type" "file"
                onInput (fun e -> SetTrainingFeaturesFile (unbox e?target?value)) 
            ]
        ]
        model |> sprintf "%A" |> text
    ]
  • Is there a simple way to capture the file content directly from F#?
  • What is the minimum amount of interop fable code necessary to accomplish this?
0

3 Answers 3

12

In the latest version of Fable, we now have access to Browser.Dom.FileReader and avoid using interop.

It is possible to write something like:

input 
    [ 
        Class "input"
        Type "file"
        OnInput (fun ev -> 
            let file = ev.target?files?(0)

            let reader = Browser.Dom.FileReader.Create()

            reader.onload <- fun evt ->
                dispatch (SaveFileContent evt.target?result)

            reader.onerror <- fun evt ->
                dispatch ErrorReadingFile

            reader.readAsText(file)
        ) 
    ]

Live demo

Sign up to request clarification or add additional context in comments.

Comments

10

I couldn't find a way to not write plain JavaScript mainly because I couldn't import/instantiate FileReader from Fable. If someone can do it, then the solution can probably improve.

Reading the file is asynchronous. This means that the view should generate a delayed model update. Since that can only be done in the model update function, I had to forward a JavaScript File handle inside.

The plain JavaScript is just an export hack

// file interops.js, can I get rid of this?
export var getReader = function() { return new FileReader(); }

In the view

// view code
input [
    attribute "id" "x_train"
    attribute "type" "file"
    onInput (fun e -> FromFile (SetField, e?target?files?(0)))
]

So the message is actually a "Delayed Message with File Content". Here's the action and update code:

type Action =
    | SetField of string
    | FromFile of (string -> Action) * obj

let update model action =
    match action with
    | SetField content ->
        { model with Field = content}, []
    | FromFile (setAction, file) ->
        let delayedAction h =
            let getReader = importMember "../src/interops.js"
            let reader = getReader()
            reader?onload <- (fun () ->  h <| setAction !!reader?result)
            reader?readAsText file |> ignore
        model, delayedAction |> toActionList

The FromFile is a complex action because I want to use it to set more than one field. If you only need one, you can make it just an of obj.

1 Comment

Thanks @MangelMaxime for all the suggestions in the Fable channel. :-)
1

Here's my take on Maxime's answer, using Fable.Elmish.React v3.0.1. I'm not familiar with the ? operator, but I was able to cast some types using the :?> one instead.

input [
          Class "input"
          Type "file"
          OnInput (fun ev ->
            let file = (ev.target :?> Browser.Types.HTMLInputElement).files.Item(0)
            let reader = Browser.Dom.FileReader.Create()
            reader.onload <- fun evt ->
              (*
                Negotiate/assume the onload target is a FileReader
                Result is a string containg file contents:
                https://developer.mozilla.org/en-US/docs/Web/API/FileReader/readAsText
              *)
              dispatch (Set (string (evt.target :?> Browser.Types.FileReader).result))

            reader.onerror <- fun evt ->
              dispatch (Set "Error")
            
            reader.readAsText(file))]

1 Comment

? operator comes from the open Fable.Core.JsInterop

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.