最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Getting file input content with Fable - Stack Overflow

programmeradmin2浏览0评论

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?

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?
Share Improve this question edited May 23, 2017 at 12:13 CommunityBot 11 silver badge asked Jan 1, 2017 at 21:19 villasvvillasv 6,8315 gold badges48 silver badges83 bronze badges 0
Add a comment  | 

3 Answers 3

Reset to default 11

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

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))]
发布评论

评论列表(0)

  1. 暂无评论