Getting file input content with Fable
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
.
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))]
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