Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Getting file input content with Fable

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?
like image 315
villasv Avatar asked Jan 01 '17 21:01

villasv


3 Answers

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.

like image 143
villasv Avatar answered Nov 18 '22 16:11

villasv


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

like image 26
Maxime Mangel Avatar answered Nov 18 '22 16:11

Maxime Mangel


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))]
like image 1
adam strickland Avatar answered Nov 18 '22 17:11

adam strickland