I have a Elmish-React project and this dropzone
OnDrop (fun e ->
let file = e.dataTransfer.files.item 0
e.preventDefault()
e.stopPropagation()
)
how do I load the content of this file?
I have a Elmish-React project and this dropzone
OnDrop (fun e ->
let file = e.dataTransfer.files.item 0
e.preventDefault()
e.stopPropagation()
)
how do I load the content of this file?
The file
object (MDN) has a method text
that returns the file contents as a Promise<string>
. This method appears to be missing from the Fable bindings. However, we can use the ?
operator from Fable.Core.JsInterop
to access it.
file?text()
In Elmish, we wrap promises inside of commands in order to keep the application logic pure.
First we introduce a message type:
type Message =
| FileDropped of Browser.Types.File
| FileContent of Result<string, Exception>
FileDropped
is dispatched when the user drops a file onto the drop zone. FileContent
is dispatched when the content of a file is ready.
Here is the command for loading file content:
let readFile (file : File) =
Cmd.OfPromise.either
(fun () -> file?text())
()
(fun content ->
FileContent (Ok content))
(fun exn ->
FileContent (Error exn))
And here is a complete working example of how to load the contents of a drag-and-dropped file and display it in an Elmish view.
module App
open System
open Fable.Core.JsInterop
open Browser.Types
open Fable.React
open Elmish
open Elmish.React
type Model =
{
File : File option
Content : Result<string, Exception> option
}
type Message =
| FileDropped of File
| FileContent of Result<string, Exception>
let init () =
{
File = None
Content = None
}, Cmd.none
module Cmd =
let readFile (file : File) =
Cmd.OfPromise.either
(fun () -> file?text())
()
(fun content ->
FileContent (Ok content))
(fun exn ->
FileContent (Error exn))
let update (message : Message) state =
match message with
| FileDropped file ->
{
state with
File = Some file
Content = None
}, Cmd.readFile file
| FileContent content ->
{
state with
Content = Some content
}, Cmd.none
let view state dispatch =
div
[]
[
div
[
Props.Style
[
Props.MinHeight "300px"
Props.BackgroundColor "gray"
]
Props.OnDragOver
(fun e ->
e.preventDefault())
Props.OnDrop
(fun e ->
e.preventDefault()
e.stopPropagation()
if e.dataTransfer.files.length > 0 then
let file = e.dataTransfer.files.item 0
dispatch (FileDropped file))
]
[
str "Drop a file here"
]
h1 [] [ str "File Meta" ]
ul
[]
[
match state.File with
| Some file ->
li [] [ str $"Name: %s{file.name}" ]
li [] [ str $"Type: %s{file.``type``}" ]
li [] [ str $"Size: %i{file.size}" ]
li [] [ str $"Last Modified: %A{DateTimeOffset.FromUnixTimeMilliseconds (int64 file.lastModified)}" ]
| None -> ()
]
h1 [] [ str "Content" ]
div
[]
[
match state.Content with
| Some (Ok content) -> str content
| Some (Error exn) -> str $"%A{exn}"
| None -> ()
]
]
Program.mkProgram init update view
|> Program.withReactBatched "root"
|> Program.run
Note that the following packages were used:
<ItemGroup>
<PackageReference Include="Elmish" Version="4.0.0" />
<PackageReference Include="Fable.Browser.Event" Version="1.5.0" />
<PackageReference Include="Fable.Elmish.React" Version="4.0.0" />
<PackageReference Include="Fable.Promise" Version="3.2.0" />
<PackageReference Include="Fable.React" Version="9.1.0" />
</ItemGroup>