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
]
I couldn't find a way to not write plain JavaScript mainly because I couldn't import/instantiate
FileReaderfrom 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.
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
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))]
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With