1.0.32 • Published 2 years ago

dropzone-frontend v1.0.32

Weekly downloads
2
License
MIT
Repository
github
Last release
2 years ago

dropzone-frontend

Turns an html5 - component in an amazing dropzone for fileupload.

  • There is a fallback to an html5 with input type file provided.
  • It can directly be consumed as a typescript library.
  • Styling can be changed through changing of scss variables or through css - overrides.
  • You have to provide the handlers for uploading, downloading, deleting yourself offcourse. So, there are no strings attached to any backend technology.
  • The dropzone can be populated with current state. It can be used for a readonly file output object.
  • i18n: you can add custom labels, or translate labels with the i18n dropzone resource.
  • configure the accepted droppable/uploadable types with fileTypeSpecifiers. ( https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#Unique_file_type_specifiers )
  • The current setup needs a scss compilation step.
  • can be used for uploading on drop, or deleting on delete pressed
  • can be used for uploading after an external trigger (for example a button press)
  • IE11 support (include polyfills)

dropzone-example-giffy

Installation

npm i dropzone-frontend

How to use

TypeScript setup with scss

  • Include scss in scss file

      @import '../../node_modules/dropzone-frontend/lib/styles/export/dropzone-lib';
  • Html file: add a container on your Html file

      <div id="myDropzone">
      </div>
      
  • TypeScript file: turn the html-container in a dropzone

Meta flow

  • turn the html element in a dropzone
      const dropzone: Dropzone = new Dropzone(window.document.querySelector('#myDropzone')/*,
          optionally a dropzoneProps - object can be added,  
          optionally an i18n - override can be added */);
      
  • add a listener for a file drop (give feedback with the progress, succesCallback and errorCallback - functions)

      dropzone.addOnFileDroppedEventListener((file: DropzoneFile,
          successCallback: (createdDropzoneFile: DropzoneFile) => any | void,
          errorCallback: (error?: any) => any,
          progress: (uploadPercentage: number) => void) => {
          // <- upload the file anyway you want
  • add a listener when the user wants to download the file

      dropzone.addDownloadFileEventListener((dropzoneFile: DropzoneFile) => { 
          // <- download the file any way you want
        
  • add a delete listener

      dropzone.addOnDeleteFileEventListener((file: DropzoneFile,
         successCallback: (deletedFile?: any) => any,
         errorCallback: (error?: any) => void) => {
         // <- delete the file, any way you want.
                                 
  • popupulate the dropzone with the current state. (Expected type: DropzoneFile)

      dropzone.setDropzoneFiles(jsonData); // put the loaded files on the dropzone

Full example with a node backend running on port 3000.

import {DropzoneFile} from "dropzone-frontend/lib/scripts/model/dropzone-file";
import {Dropzone} from "dropzone-frontend/lib/scripts/dropzone";

const dropzone: Dropzone = new Dropzone(window.document.querySelector('#myDropzone')/*, 
    optionally a dropzoneProps - object can be added, 
    optionally an i18n - override can be added */); 

// upload the file when it is dropped.
dropzone.addOnFileDroppedEventListener((file: DropzoneFile,
    successCallback: (createdDropzoneFile: DropzoneFile) => any | void,
    errorCallback: (error?: any) => any,
    progress: (uploadPercentage: number) => void) => {
  const formData = new FormData();
  formData.append('file', file.file); 
    // <- the file is in the file.file attribute.
  
  if (xhr.upload) {
    xhr.upload.onprogress = (e: any) => {
      const done = e.position || e.loaded,
        total = e.totalSize || e.total;
      const percentage = Math.floor((done / total) * 1000) / 10;
      progress(percentage); 
        // <- give progress feedback by calling progress with percentage
    };
  }
  
  xhr.onerror = () => {
    errorCallback(); // <- if something goes wrong with the upload, let it know at the dropzone with the error callback function
  };
  
  xhr.onreadystatechange = (e: any) => {
    if (xhr.readyState === 4 && xhr.status === 200) {
      successCallback(file); 
        // <- if everything goes well notify the dropzone by the successCallback
        // the successCallback excepts an object of type DropzoneFile
    }
  };
  xhr.open('post', 'http://localhost:3000/api/upload-file', true);
    xhr.send(formData); // <- upload the file anyway you want
  });

// handle the download file action
dropzone.addDownloadFileEventListener((dropzoneFile: DropzoneFile) => {
  window.open(`http://localhost:3000/api/upload-file/${dropzoneFile.fileName}`); 
    // <- download the file any way you want
});

// When the user want's to delete the file, do the delete
dropzone.addOnDeleteFileEventListener((file: DropzoneFile,
   successCallback: (deletedFile?: any) => any,
   errorCallback: (error?: any) => void) => {
  const formData = new FormData();
  formData.append('file', file.file);
  fetch(`http://localhost:3000/api/upload-file/${file.id}`, {
    method: 'DELETE', // *GET, POST, PUT, DELETE, etc.
    mode: 'cors', // no-cors, cors, *same-origin
    cache: 'no-cache', // *default, no-cache, reload, force-cache, only-if-cached
    credentials: 'same-origin', // include, *same-origin, omit
    redirect: 'follow', // manual, *follow, error
    referrer: 'no-referrer', // no-referrer, *client
    body: formData, // body data type must match "Content-Type" header
  })
    .then(data => {
      successCallback(data as any);
    }) // JSON-string from `response.json()` call
    .catch(error => {
      errorCallback(error);
    });
});

// load all the files
fetch('http://localhost:3000/api/upload-file', {
  method: 'GET', // *GET, POST, PUT, DELETE, etc.
  mode: 'cors', // no-cors, cors, *same-origin
  cache: 'no-cache', // *default, no-cache, reload, force-cache, only-if-cached
  credentials: 'same-origin', // include, *same-origin, omit
  headers: {
    'Content-Type': 'application/json',
  },
  redirect: 'follow', // manual, *follow, error
  referrer: 'no-referrer', // no-referrer, *client
  body: undefined, // body data type must match "Content-Type" header
})
  .then(data => {
    data.json().then(jsonData => {
      jsonData.forEach((aJson: any) => {
        if (aJson.thumbnail) {
          aJson.thumbnailUrl = 'http://localhost:3000/api/upload-file/' + aJson.thumbnail;
        }
      });
      dropzone.setDropzoneFiles(jsonData); // put the loaded files on the dropzone
    });
  }) // JSON-string from `response.json()` call
  .catch(error => {
    console.error('error during read', error);
  });
  

Upload after an external trigger (buttonpress): Full example with a node backend running on port 3000.

  • Html file: add a container on your Html file

      <h1>Attachments  but no autoupload</h1>
      <div class="m-dropzone--no-autoupload">
      </div>
      <button id="saveNotUploadDropzoneButton">SAVE THEM</button>
      
  • TypeScript file: turn the html-container in a dropzone Keep track of the items to save/delete. Use the isSaved - flag of the DropzoneFile - model.

const notAutoUploadOnDropDropzone: Dropzone = new Dropzone(
    window.document.querySelector('.m-dropzone--no-autoupload'),
    {
        readonly: false,
    },
    {
        uploadProgressLabel: 'upload progress',
        uploadErrorLabel: 'upload error',
        uploadCompleteLabel: 'De upload is voltooid',
        browseLabel: 'browse.',
        dropFilesLabel: 'Sleep bestanden om bij te voegen of ',
    },
);

let filesToSave: DropzoneFile[] = []; // <- keep state of filesToSave
let filesToDelete: DropzoneFile[] = []; // <- keep state of filesToDelete

notAutoUploadOnDropDropzone.addOnFileDroppedEventListener((file, successCallback, errorCallback, progress) => {
    // <- do not save to backend on drop! but update state.
    filesToSave.push(file);
    file.canBeDeleted = true;
    if (file.file.type.indexOf('image/') === 0) {
        file.thumbnailObjectURL = URL.createObjectURL(file.file);
        // <- optionally provide a thumbnailObjectURL for an image type
    }
    notAutoUploadOnDropDropzone.updateDropzoneFile(file);
});

notAutoUploadOnDropDropzone.addDownloadFileEventListener((dropzoneFile: DropzoneFile) => {
    if (dropzoneFile.isSaved) {
        window.open(`http://localhost:3000/api/upload-file/${dropzoneFile.fileName}`);
    } else {
        window.alert('This file cannot be donwloaded yet, because it is not saved yet!');
    }
});


notAutoUploadOnDropDropzone.addOnDeleteFileEventListener((file, successCallback, errorCallback) => {
    // <- do not persist to backend, but keep track of the filesToSave and delete state.
    if (file.isSaved) { // <- use the isSaved flag to keep your state nice and clean.
        filesToDelete.push(file);
    } else if (filesToSave.indexOf(file) !== -1) {
        // <- item is not yet saved, so it doesn't need to be persisted to backend later.
        filesToSave = filesToSave.filter(aFileToSave => {
            return aFileToSave.id !== file.id;
        });
        
    }
    successCallback(file); // <- successCallback removes the item visually from the dropzoe
});

// <- init the dropzone with your file data.
fetch('http://localhost:3000/api/upload-file', {
    method: 'GET', // *GET, POST, PUT, DELETE, etc.
    mode: 'cors', // no-cors, cors, *same-origin
    cache: 'no-cache', // *default, no-cache, reload, force-cache, only-if-cached
    credentials: 'same-origin', // include, *same-origin, omit
    headers: {
        'Content-Type': 'application/json',
    },
    redirect: 'follow', // manual, *follow, error
    referrer: 'no-referrer', // no-referrer, *client
    body: undefined, // body data type must match "Content-Type" header
})
    .then(data => {
        data.json().then(jsonData => {
            jsonData.forEach((aJson: any | DropzoneFile) => {
                if (aJson.thumbnail) {
                    aJson.thumbnailUrl = 'http://localhost:3000/api/upload-file/' + aJson.thumbnail;
                }
                aJson.isSaved = true;
            });
            notAutoUploadOnDropDropzone.setDropzoneFiles(jsonData);
        });
    }) 
    .catch(error => {
        console.error('error during read', error);
    });

// <- add a button and handle the file upload/delete there.
// off course you can prefer to send them all at once to the backend. Suit yourself!

document.querySelector('#saveNotUploadDropzoneButton').addEventListener('click', () => {
    filesToDelete.forEach(aFileToDelete => {
        const formData = new FormData();
        formData.append('file', aFileToDelete.file);

        fetch(`http://localhost:3000/api/upload-file/${aFileToDelete.id}`, {
            method: 'DELETE', // *GET, POST, PUT, DELETE, etc.
            mode: 'cors', // no-cors, cors, *same-origin
            cache: 'no-cache', // *default, no-cache, reload, force-cache, only-if-cached
            credentials: 'same-origin', // include, *same-origin, omit
            //headers: {
            //    'Content-Type': 'multipart/form-data', https://muffinman.io/uploading-files-using-fetch-multipart-form-data/
            //},
            redirect: 'follow', // manual, *follow, error
            referrer: 'no-referrer', // no-referrer, *client
            body: formData, // body data type must match "Content-Type" header
        })
            .then(data => {
                console.log('file is deleted!');
            })
            .catch(error => {
                window.alert('An error did occur while deleting file ' + aFileToDelete.fileName + '. The file is added again to the dropzone.');
                notAutoUploadOnDropDropzone.appendDropzoneFile(aFileToDelete);
            });
        filesToDelete = [];
    });

    filesToSave.forEach(aFileToSave => {
        const formData = new FormData();
        formData.append('file', aFileToSave.file);

        const xhr = new XMLHttpRequest();

        xhr.onerror = () => {
            console.log('** An error occurred during the transaction');
            window.alert('An error did occur while saving file ' + aFileToSave.fileName + '.');
            notAutoUploadOnDropDropzone.appendDropzoneFile(aFileToSave);
            filesToSave.push(aFileToSave);
        };

        xhr.onreadystatechange = (e: any) => {
            if (xhr.readyState === 4 && xhr.status === 200) {
                aFileToSave.canBeDeleted = true;
                aFileToSave.canBeDownloaded = true;
                aFileToSave.isSaved = true;
                notAutoUploadOnDropDropzone.updateDropzoneFile(aFileToSave);
            }
        };
        xhr.open('post', 'http://localhost:3000/api/upload-file', true);
        // xhr.setRequestHeader("Content-Type","multipart/form-data");
        xhr.send(formData);
    });
    filesToSave = [];
});

configure accepted filetypes

import {DropzoneFile} from "dropzone-frontend/lib/scripts/model/dropzone-file";
import {Dropzone} from "dropzone-frontend/lib/scripts/dropzone";
import {DefaultDropzoneProps} from './dropzone/model/dropzone-props';

const dropzoneProps = new DefaultDropzoneProps();
// dropzoneProps.acceptedFileTypeSpecifiers = ['.pdf', 'video/*', 'image/png'];
dropzoneProps.acceptedFileTypeSpecifiers = ['application/pdf'];

let dropzone: Dropzone = new Dropzone(window.document.querySelector('.m-dropzone--default'), dropzoneProps, {
    uploadProgressLabel: 'upload progress',
    uploadErrorLabel: 'upload error',
    uploadCompleteLabel: 'De upload is voltooid',
    browseLabel: 'browse.',
    dropFilesLabel: 'Sleep bestanden om bij te voegen of ',
    invalidTypeText: 'Gelieve enkel pdf - bestanden bij te voegen.'
});

IE11 support with polyfills

You can make the dropzone work on IE11 with an array polyfill.

For example install the polyfill package: npm i core-js

And then include next polyfill.

import 'core-js/es/array';
1.0.32

2 years ago

1.0.31

3 years ago

1.0.30

4 years ago

1.0.28

4 years ago

1.0.27

4 years ago

1.0.25

4 years ago

1.0.22

5 years ago

1.0.21

5 years ago

1.0.20

5 years ago

1.0.19

5 years ago

1.0.18

5 years ago

1.0.17

5 years ago

1.0.16

5 years ago

1.0.15

5 years ago

1.0.14

5 years ago

1.0.13

5 years ago

1.0.12

5 years ago

1.0.10

5 years ago

1.0.9

5 years ago

1.0.8

5 years ago

1.0.7

5 years ago

1.0.6

5 years ago

1.0.5

5 years ago

1.0.4

5 years ago

1.0.3

5 years ago

1.0.2

5 years ago

1.0.1

5 years ago

1.0.0

5 years ago