Skip to content

Instantly share code, notes, and snippets.

@ajmas
Last active March 11, 2025 17:43
Show Gist options
  • Save ajmas/5710ac15c48b74b806bee9c2073b44dc to your computer and use it in GitHub Desktop.
Save ajmas/5710ac15c48b74b806bee9c2073b44dc to your computer and use it in GitHub Desktop.

Revisions

  1. ajmas revised this gist Mar 11, 2025. 2 changed files with 32 additions and 0 deletions.
    13 changes: 13 additions & 0 deletions upload-dnd.css
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,13 @@
    .dropzone.dropzone-active {
    box-shadow: 0px 0px 15px 0px rgba(73, 245, 54, 0.75);
    }

    .dropzone.dropzone-badtype {
    cursor: no-drop;
    box-shadow: 0px 0px 15px 0px rgba(248, 19, 19, 0.75);
    content: url("./white-x-icon.svg");
    }

    #error_message {
    color: red;
    }
    19 changes: 19 additions & 0 deletions white-x-icon.svg
    Loading
    Sorry, something went wrong. Reload?
    Sorry, we cannot display this file.
    Sorry, this file is invalid so it cannot be displayed.
  2. ajmas revised this gist Mar 11, 2025. 1 changed file with 3 additions and 1 deletion.
    4 changes: 3 additions & 1 deletion upload-dnd.js
    Original file line number Diff line number Diff line change
    @@ -1,5 +1,7 @@
    /**
    This is a rudementary implementation to allow drag an drop upload on an HTML page.
    This is a rudementary implementation to allow drag an drop upload on an HTML page. It
    was made for a project I was working on, but thought I'd share it for anyone interested
    and for eventual feedback.
    - Supports limiting:
    - file type
  3. ajmas created this gist Mar 11, 2025.
    137 changes: 137 additions & 0 deletions upload-dnd.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,137 @@
    /**
    This is a rudementary implementation to allow drag an drop upload on an HTML page.
    - Supports limiting:
    - file type
    - file size
    - number of files uploaded
    - Disables dropping files elsewhere on the page
    - What you need to provide:
    - an element to act as drop zone and adjust vlaue of `dropZoneSelector`
    - file input element and ajdust the value of `fileInputSelector`
    - error element and adjust the value of `errorSelector`
    Note, due to browser security model, we can't get the file size during dragging. It is only available
    once the files are dropped.
    */

    const supportedFileTypes = ['image/jpeg', 'image/png', 'video/mp4', 'application/zip'];
    const maxFileCount = 7;
    const maxFileSizeMB = 24;
    const dropZoneSelector = '.dropzone';
    const fileInputSelector = '#id_file';
    const errorSelector = '#error_message';

    function toBytes (sizeInMegaBytes) {
    return sizeInMegaBytes * 1024 * 1024;
    }

    function onDragZoneChange () {
    document.querySelector(dropZoneSelector).classList.remove('dropzone-active');
    document.querySelector(dropZoneSelector).classList.remove('dropzone-badtype');
    }

    function isValidFileItem (fileItem) {
    return !!(supportedFileTypes.indexOf(fileItem.type) > -1);
    }

    function onFileDrop (event) {
    event.preventDefault();
    onDragZoneChange();

    const errorOutput = document.querySelector(errorSelector);
    if (errorOutput) {
    errorOutput.innerHTML = ' ';
    }

    const maxFileSize = toBytes(maxFileSizeMB);
    let dataTransfer = new DataTransfer();
    let tooBig = false;
    let tooManyFiles = false;

    if (event.dataTransfer.files) {
    [...event.dataTransfer.files].forEach((file) => {

    if (file.size > maxFileSize) {
    tooBig = true;
    return;
    } else if (dataTransfer.items.length > maxFileCount) {
    tooManyFiles = true;
    return;
    }

    if (isValidFileItem(file) && dataTransfer.items.length < maxFileCount) {
    dataTransfer.items.add(file);
    }
    });
    }

    const fileInput = document.querySelector(fileInputSelector);
    fileInput.files = dataTransfer.files;
    fileInput.dispatchEvent(new Event('change', { bubbles: true }));

    if (errorSelector) {
    const messages = [];

    if (tooBig) {
    messages.push(`Files larger than ${maxFileSizeMB} MB were ignored`);
    }
    if (tooManyFiles) {
    messages.push(`Uploads are limited to ${maxFileCount} files per submission`);
    }

    errorOutput.innerHTML = messages.join('; ');
    }
    }

    function onDragOver (event) {
    event.preventDefault();
    event.stopPropagation();

    document.querySelector(dropZoneSelector).classList.add('dropzone-active');

    let permitted = true;

    if (event.dataTransfer.items) {
    [...event.dataTransfer.items].forEach((item) => {
    if (!isValidFileItem(item)) {
    permitted = false;
    }
    });
    } else {
    [...event.dataTransfer.files].forEach((file) => {
    if (!isValidFileItem(item)) {
    permitted = false;
    }
    });
    }

    if (!permitted) {
    document.querySelector(dropZoneSelector).classList.add('dropzone-badtype');
    event.dataTransfer.dropEffect = 'none';
    event.dataTransfer.effectAllowed = 'none';
    }
    }

    document.addEventListener('DOMContentLoaded', () => {
    const dropZone = document.querySelector(dropZoneSelector);
    if (dropZone) {
    dropZone.addEventListener('drop', onFileDrop);
    dropZone.addEventListener('dragenter', onDragOver);
    dropZone.addEventListener('dragover', onDragOver);
    dropZone.addEventListener('dragleave', onDragZoneChange);
    }

    // logic to prevent items being dropped elsewhere in the page
    const body = document.querySelector('body');
    body.addEventListener('dragover', (event) => {
    event.preventDefault();
    event.stopPropagation();

    event.dataTransfer.dropEffect = 'none';
    event.dataTransfer.effectAllowed = 'none';
    });
    })