使用 Fable 获取文件输入内容

Getting file input content with Fable

我看到 simple ways 使用 HTML5 文件 API.

从 JavaScript 中输入的文件中读取内容

这是我的 view 方法,在一个小型 fable-arch 应用程序中:

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 FileReader from Fable. If someone can do it, then the solution can probably improve.

读取文件是异步的。这意味着视图应该生成延迟的模型更新。由于这只能在模型更新功能中完成,我不得不在里面转发一个 JavaScript 文件句柄。

普通的 JavaScript 只是一个导出 hack

// file interops.js, can I get rid of this?
export var getReader = function() { return new FileReader(); }

在视图中

// view code
input [
    attribute "id" "x_train"
    attribute "type" "file"
    onInput (fun e -> FromFile (SetField, e?target?files?(0)))
]

所以消息实际上是 "Delayed Message with File Content"。这是操作和更新代码:

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

FromFile 是一项复杂的操作,因为我想用它来设置多个字段。如果您只需要一个,可以将其设为 of obj

在最新版本的寓言中,我们现在可以访问 Browser.Dom.FileReader 并避免使用互操作。

可以这样写:

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

这是我对 Maxime 的回答的看法,使用 Fable.Elmish.React v3.0.1。我不熟悉?运算符,但我可以使用 :?> 来转换某些类型。

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))]