How can I access uploaded files using JavaScript?

Looks like LiveView JS is removing uploaded files from input.files. I need to access them to read the width, height, and coordinates.

I found a way to read it from phx private object but it doesn’t seem right using “private” functions.

Is there any recommended way to do so? If not, maybe I could create a PR with some official way of accessing upload files on the client?

@josevalim in the GH issue you recommended tracking uploads by adding own JS handlers. What did you mean?

input.addEventListener('change', () => {
  input.files // returns empty FileList
})

Using this I’m getting an empty file list.

Hi @caspg! Can you describe a bit more about what you want to do with the image metadata?

Generally our guidance here is that you cannot trust data that comes from the client without verifying it on the server. So if you send width/height/etc from the client you still need some way to verify the information.

Are you uploading to your web server or to a cloud storage provider?

Using this I’m getting an empty file list.

I am a little surprised by this. LiveView listens to form events on window, so I would expect any files added by the change event to still be available until LiveView consumes the input event.

1 Like

I’m sending the width and height and storing them in DB. Images are stored in S3.

I know that we can’t trust things that are coming from the client. In my case, it’s not really an issue even if someone messes with it. The worst thing that can happen is that photo will render incorrectly. I’m also reading GPS data from Exif info.

I could send an image to the server and read all of that there but that would require bigger servers. Right now, I’m using the cheapest Heroku dyno and serving ~80k monthly unique users. When I was processing images on the server I was running out of memory from time to time.

Okay, I tested it more and an empty file list is only when using drag and drop. When I’m selecting files using “regular” input the list is populated.

EDIT:
It looks that drag and drop is dispatching “input” event not “change”. But still file list is empty.

input.addEventListener('input', () => {
  input.files // returns empty FileList
})

Ah, yes, drag-and-drop is another beast entirely :slight_smile:

I haven’t verified this yet, but you should be able to listen for the drop event on the drop target element and get the files from there:

dropTarget.addEventListener("drop", (e) => {
  let files = Array.from(e.dataTransfer.files || [])
  // todo: get metadata from files[]
})
2 Likes

Nice, thanks :grinning: Why we don’t assign files to input.files on drag and drop anyway?

Right now, I will need two listeners that are reading files differently. Or is it an expected behavior in drag and drop? It’s first time I’m implementing this :grimacing:

This is the expected behavior– they are completely separate DOM events. Check out File drag and drop - Web APIs | MDN for more information.

In the LiveView client we listen for ‘drop’ events on phx-drop-target elements, collect the files, and then emit an ‘input’ event to trigger the form change to the server. We maintain our own file list for bookkeeping purposes, otherwise we risk input.files getting out-of-sync with uploads to the server.

1 Like

Got ya. I’m still trying to wrap my head around this :slight_smile: but when input.files could get out-of-sync with uploads? If some JS code modifies it?

Yes input.files is a property on an HTMLInputElement whose type is file (Getting information about selected file(s) | MDN). It is updated each time a user selects files from the input. So if we only relied on it we would lose any existing files whenever the user selected new files! LiveView maintains its own list so we can do things like add files from drop targets as well as safely handle enhancements like auto-uploads, live image preview, and allow users to build up a “queue” of files before submitting.

2 Likes

Thanks for all the explanations!

1 Like