Webapi and input files

am I correct in assuming the only way to get the files from a file input is to use %raw?

basically

const reader = new FileReader();
    reader.onload = (e) => {
      img.src = e.target.result;
    };
    reader.readAsDataURL(file);

I can query the Dom with Webapi and get the input element, but how do I read the files so I can pass it to a FileReader
I just spent an hour chasing my tail. asking chatgpt (useless btw) and got no where.

chatgpt :

let handleFileInputChange = (event: Dom.Event.t) => {
  let target = Dom.Event.target(event);
  switch (Dom.HtmlInputElement.files(target) {

but there’s no such function HtmlInputElement.files. also its missing a closing ) lol

I’ve adapted my code (which parses a CSV file) to your requirements.

module File = {
  type t
}

module FileReader = {
  type t

  type event = [
    | #load
    | #abort
  ]

  @new external make: unit => t = "FileReader"

  @send external readAsDataURL: (t, File.t) => unit = "readAsDataURL"

  @send
  external addEventListener: (t, event, unit => unit) => unit =
    "addEventListener"

  @get external result: t => string = "result"
}

@react.component
let make = () => {
  <label>
    <input
      accept="csv"
      name="csvImport"
      id="csvImport"
      onChange={ev => {
        let input = ev->JsxEvent.Form.currentTarget
        let files: array<File.t> = input["files"]
        files->Array.forEach(f => {
          let reader = FileReader.make()
          reader->FileReader.addEventListener(#load, () => {
            Console.log(FileReader.result(reader))
          })
          reader->FileReader.readAsDataURL(f)
        })
      }}
      type_="file"
    />
    {"Import statement from a CSV file"->React.string}
  </label>
}

The compiled JavaScript code as seen in this playground link reads fine (but I haven’t run it).

I haven’t used rescript-webapi (if that’s what you’ve referred to in your question) though. You can make a judgment call on whether to integrate the above code with WebApi’s types and bindings or not.

Thanks. That clears up the filereader. However I’m not using react or jsx. If it wasn’t clear by my need for webapi to get the input element. It’s for a chrome extension. I don’t own the input element. So I’m still stuck trying to get at the files to pass to a filereader. Beyond dipping into raw js which is… fine.

Ok. That should be a matter of making the right bindings.

Here is an attempt that adds a binding to the existing webapi bindings, combining it with the File.t module I defined in my previous post:

@get external files: t_htmlInputElement => array<File.t> = "files"

I’m not sure how you would be able to access t_htmlInputElement though. @spyder may be able to help you out there.

%raw is not a bad stop gap solution if you’re pressed for time.

Meanwhile, here is an attempt to build minimal bindings to file input, independent of rescript-webapi.

// File APIs. Also borrow the FileReader bindings from my previous post.

module File = {
  type t
}

// We want to get to event.target.files
// So we need a binding to event.target.files
// where event.target is our input element.
// A bindign to Event.target should give us our InputElement.
// A binding to InputElement.files should give us our files.
// I've also added a binding to addEventListener specific for InputElement.

type evType = [
  | #change
]

module InputElement = {
  type t

  @get external files: t => array<File.t> = "files"

  // You can add other API bindings for properties like .value, .disabled, etc.

  module Event = {
    type event
    @get external target: event => t = "target"
    @get external currentTarget: event => t = "currentTarget"
  }

  @send
  external addEventListener: (t, evType, Event.event => unit) => unit =
    "addEventListener"
}

// NOTE: FAKE constructor and creation of InputElement, just to verify the compiled JS code for event listener.
@new external make: unit => InputElement.t = "InputElement"
let ele = make()

ele->InputElement.addEventListener(#change, ev => {
  let target = ev->InputElement.Event.target
  Console.log(target->InputElement.files)
})

The above code compiles to the following JS code (only the relevant portion):

ele.addEventListener("change", (function (ev) {
        var target = ev.target;
        console.log(target.files);
      }));
1 Like

The tests, while many of them are incomplete, show a rough guideline of how to start using the HtmlInputElement api (this pattern actually works for all element types):

The binding @bhoot suggested would be fine to PR to the repo, actually.

Also note that there is an ongoing effort to replace webapi, due to it’s importance to the ecosystem and my lack of time to work on what is now a very old codebase.

4 Likes

Also note that there is an ongoing effort to replace webapi…

ya that’s why I was just gonna raw js it lol. I just wanted to make sure I wasn’t missing something from the current Webapi.

Thank you both! this really helps my understanding of bindings.