Eduardo Mior
Eduardo Mior

Reputation: 244

Is it possible to get the filename in dragover or dragenter event?

I'm creating a Dropzone component.

I would like to make my <div> red when the user tries to drag and drop an invalid file type.

Currently through dragstart event I can get the MimeType of the file, but I can't get the file name or extension.

The accept property of the Input type=file allows filtering files either by MimeType (example: image/png) or by extension (example: .png). I wish my Dropzone could filter both by MimeType and by extension, just like Input type=file.

Upvotes: 1

Views: 1796

Answers (1)

Kom N
Kom N

Reputation: 143

So, keep in mind that the extension is not actually something you can or need to validate on. The raw file data is simply the bytes whereas the extension is used by the operating system to try and pick the correct application to present that set of bytes.

Also, the accept value for the input element doesn't actually limit the file types which can be entered into the field. HTML attribute: accept - HTML: HyperText Markup Language | MDN

The accept attribute doesn't validate the types of the selected files; it provides hints for browsers to guide users towards selecting the correct file types. It is still possible (in most cases) for users to toggle an option in the file chooser that makes it possible to override this and select any file they wish, and then choose incorrect file types.

What I'm reading is that you're looking for validating that the document they are trying to drag into your dropzone is the correct file type which can only be done based on the mime-type of the document itself which is possible and should be performed by both your front-end and backend applications. Common MIME types - HTTP | MDN

In the DragEvent under dataTransfer.items, you can convert that to an array using Array.from then iterate over each item, you have exposed the item.kind and item.type.

  • The item.kind is either file or text
  • The item.type when the kind is file this will give you a hint to the mime-type which the user is attempting to drag into your dropzone.

You would need to write a parser that would be able to take your accept-mime-type and validate this value.

I've mocked up something which could help accomplish this for you.

Keep in mind:

  • This is NOT production-ready, and could be optimized
  • This just demonstrates a very simple check
  • You will want to validate onDrop using a similar method and also onChange if using an <input type="file" /> for manual uploads
// this is your original accept tag
// which is a comma-separated list of types
const accept = "image/*";

// Just do this step once as the event fires pretty often
const acceptArr = accept
    .split(",") // get the individual values
    .map(a => {
        // each value is now "*/*" (or the extension - not recommended)
        // split the value further into the array parts

        // if you are using extension, you could reference common extensions and convert them to a mime-type prior to doing the next step
        return a.split("/");
    });
// The resulting array would look something like this
/*
   [
      [ "image", "*" ], // <- from image/*
      [ "application", "pdf"], // <- from application/pdf
      [ ".doc", "" ] // <- if you used extensions - this cannot be validated, but you could potentially create your own map of extensions to mime-type
   ]
*/

/*
 * @param e {DragEvent} the original drag event (this is React.DragEvent<HTMLDivElement> in typescript/react
 */
function onDragOver(e) {
    if (e.dataTransfer?.items) {
        const valid = 
            // the "accept" value was not supplied
            !accept ||
            // the "accept" value was a wild card
            accept === "*/*" ||
            accept === "*" ||
            // the "dataTransfer.items" is not iterable in older ES versions
            // therefore, we need to convert it into an Array
            // Thankfully since it has a "length" property, we can use Array.from
            Array
                .from(e.dataTransfer.items)
                // every item must return "true"
                .every(item => {
                    const { kind, type } = item;
                    if (kind === "file") {
                        // the type is */* format
                        const [ namespace, friendlyName ] = type.split("/");

                        // now iterate over the accepted file types
                        return acceptArr
                            .every(accepted => {
                                const [ acceptedNs, acceptedName ] = accepted;

                                return
                                    (acceptedNs === "*" || acceptedNs === namespace) &&
                                    (acceptedName === "*" || acceptedName === friendlyName) 
                            });
                    } else {
                        return false; // not a file
                    }
                });
    }
}

Upvotes: 2

Related Questions