Getting file contents when using DropzoneJS
Asked Answered
R

3

14

I really love the DropZoneJS component and am currently wrapping it in an EmberJS component (you can see demo here). In any event, the wrapper works just fine but I wanted to listen in on one of Dropzone's events and introspect the file contents (not the meta info like size, lastModified, etc.). The file type I'm dealing with is an XML file and I'd like to look "into" it to validate before sending it.

How can one do that? I would have thought the contents would hang off of the file object that you can pick up on many of the events but unless I'm just missing something obvious, it isn't there. :(

Revisory answered 14/11, 2015 at 16:48 Comment(0)
R
8

Ok, I've answer my own question and since others appear interested I'll post my answer here. For a working demo of this you can find it here:

https://ui-dropzone.firebaseapp.com/demo-local-data

In the demo I've wrapped the Dropzone component in the EmberJS framework but if you look at the code you'll find it's just Javascript code, nothing much to be afraid of. :)

The things we'll do are:

  • Get the file before the network request

    The key thing we need become familiar with is the HTML5 API. Good news is it is quite simple. Take a look at this code and maybe that's all you need:

    /**
     * Replaces the XHR's send operation so that the stream can be
     * retrieved on the client side instead being sent to the server.
     * The function name is a little confusing (other than it replaces the "send"
     * from Dropzonejs) because really what it's doing is reading the file and
     * NOT sending to the server.
     */
    _sendIntercept(file, options={}) {
      return new RSVP.Promise((resolve,reject) => {
        if(!options.readType) {
          const mime = file.type;
          const textType = a(_textTypes).any(type => {
            const re = new RegExp(type);
            return re.test(mime);
          });
          options.readType = textType ? 'readAsText' : 'readAsDataURL';
        }
        let reader = new window.FileReader();
        reader.onload = () => {
          resolve(reader.result);
        };
        reader.onerror = () => {
          reject(reader.result);
        };
    
        // run the reader
        reader[options.readType](file);
      });
    },
    

    https://github.com/lifegadget/ui-dropzone/blob/0.7.2/addon/mixins/xhr-intercept.js#L10-L38

    The code above returns a Promise which resolves once the file that's been dropped into the browser has been "read" into Javascript. This should be very quick as it's all local (do be aware that if you're downloading really large files you might want to "chunk" it ... that's a more advanced topic).

  • Hook into Dropzone

    Now we need to find somewhere to hook into in Dropzone to read the file contents and stop the network request that we no longer need. Since the HTML5 File API just needs a File object you'll notice that Dropzone provides all sorts of hooks for that.

    I decided on the "accept" hook because it would give me the opportunity to download the file and validate all in one go (for me it's mainly about drag and dropping XML's and so the content of the file is a part of the validation process) and crucially it happens before the network request.

    Now it's important you realise that we're "replacing" the accept function not listening to the event it fires. If we just listened we would still incur a network request. So to **overload* accept we do something like this:

    this.accept = this.localAcceptHandler; // replace "accept" on Dropzone
    

    This will only work if this is the Dropzone object. You can achieve that by:

    • including it in your init hook function
    • including it as part of your instantiation (e.g., new Dropzone({accept: {...})

    Now we've referred to the "localAcceptHandler", let me introduce it to you:

    localAcceptHandler(file, done) {
      this._sendIntercept(file).then(result => {
        file.contents = result;
        if(typeOf(this.localSuccess) === 'function') {
          this.localSuccess(file, done);
        } else {
          done(); // empty done signals success
        }
      }).catch(result => {
        if(typeOf(this.localFailure) === 'function') {
          file.contents = result;
          this.localFailure(file, done);
        } else {
          done(`Failed to download file ${file.name}`);
          console.warn(file);
        }
      });
    }
    

    https://github.com/lifegadget/ui-dropzone/blob/0.7.2/addon/mixins/xhr-intercept.js#L40-L64

    In quick summary it does the following:

    • read the contents of the file (aka, _sendIntercept)
    • based on mime type read the file either via readAsText or readAsDataURL
    • save the file contents to the .contents property of the file
  • Stop the send

    To intercept the sending of the request on the network but still maintain the rest of the workflow we will replace a function called submitRequest. In the Dropzone code this function is a one liner and what I did was replace it with my own one-liner:

    this._finished(files,'locally resolved, refer to "contents" property');

    https://github.com/lifegadget/ui-dropzone/blob/0.7.2/addon/mixins/xhr-intercept.js#L66-L70

  • Provide access to retrieved document

    The last step is just to ensure that our localAcceptHandler is put in place of the accept routine that dropzone supplies:

    https://github.com/lifegadget/ui-dropzone/blob/0.7.2/addon/components/drop-zone.js#L88-L95

Revisory answered 18/11, 2015 at 20:21 Comment(0)
P
24

This worked for me:

Dropzone.options.PDFDrop = {
    maxFilesize: 10, // Mb
    accept: function(file, done) {
        var reader = new FileReader();
        reader.addEventListener("loadend", function(event) { console.log(event.target.result);});
        reader.readAsText(file);
    }
};

could also use reader.reaAsBinaryString() if binary data!

Parrett answered 19/2, 2017 at 11:53 Comment(3)
I agree. Keeping it simple: not adding done() in the accept function prevents the upload.Impudicity
Using vue-clip, my file object has no content, whatever I do I always get this error: "TypeError: Argument 1 of FileReader.readAsText does not implement interface Blob"Allemande
So anyone found a solution for that TypeError: Argument 1 of FileReader.readAsText does not implement interface Blob ?Rubie
R
8

Ok, I've answer my own question and since others appear interested I'll post my answer here. For a working demo of this you can find it here:

https://ui-dropzone.firebaseapp.com/demo-local-data

In the demo I've wrapped the Dropzone component in the EmberJS framework but if you look at the code you'll find it's just Javascript code, nothing much to be afraid of. :)

The things we'll do are:

  • Get the file before the network request

    The key thing we need become familiar with is the HTML5 API. Good news is it is quite simple. Take a look at this code and maybe that's all you need:

    /**
     * Replaces the XHR's send operation so that the stream can be
     * retrieved on the client side instead being sent to the server.
     * The function name is a little confusing (other than it replaces the "send"
     * from Dropzonejs) because really what it's doing is reading the file and
     * NOT sending to the server.
     */
    _sendIntercept(file, options={}) {
      return new RSVP.Promise((resolve,reject) => {
        if(!options.readType) {
          const mime = file.type;
          const textType = a(_textTypes).any(type => {
            const re = new RegExp(type);
            return re.test(mime);
          });
          options.readType = textType ? 'readAsText' : 'readAsDataURL';
        }
        let reader = new window.FileReader();
        reader.onload = () => {
          resolve(reader.result);
        };
        reader.onerror = () => {
          reject(reader.result);
        };
    
        // run the reader
        reader[options.readType](file);
      });
    },
    

    https://github.com/lifegadget/ui-dropzone/blob/0.7.2/addon/mixins/xhr-intercept.js#L10-L38

    The code above returns a Promise which resolves once the file that's been dropped into the browser has been "read" into Javascript. This should be very quick as it's all local (do be aware that if you're downloading really large files you might want to "chunk" it ... that's a more advanced topic).

  • Hook into Dropzone

    Now we need to find somewhere to hook into in Dropzone to read the file contents and stop the network request that we no longer need. Since the HTML5 File API just needs a File object you'll notice that Dropzone provides all sorts of hooks for that.

    I decided on the "accept" hook because it would give me the opportunity to download the file and validate all in one go (for me it's mainly about drag and dropping XML's and so the content of the file is a part of the validation process) and crucially it happens before the network request.

    Now it's important you realise that we're "replacing" the accept function not listening to the event it fires. If we just listened we would still incur a network request. So to **overload* accept we do something like this:

    this.accept = this.localAcceptHandler; // replace "accept" on Dropzone
    

    This will only work if this is the Dropzone object. You can achieve that by:

    • including it in your init hook function
    • including it as part of your instantiation (e.g., new Dropzone({accept: {...})

    Now we've referred to the "localAcceptHandler", let me introduce it to you:

    localAcceptHandler(file, done) {
      this._sendIntercept(file).then(result => {
        file.contents = result;
        if(typeOf(this.localSuccess) === 'function') {
          this.localSuccess(file, done);
        } else {
          done(); // empty done signals success
        }
      }).catch(result => {
        if(typeOf(this.localFailure) === 'function') {
          file.contents = result;
          this.localFailure(file, done);
        } else {
          done(`Failed to download file ${file.name}`);
          console.warn(file);
        }
      });
    }
    

    https://github.com/lifegadget/ui-dropzone/blob/0.7.2/addon/mixins/xhr-intercept.js#L40-L64

    In quick summary it does the following:

    • read the contents of the file (aka, _sendIntercept)
    • based on mime type read the file either via readAsText or readAsDataURL
    • save the file contents to the .contents property of the file
  • Stop the send

    To intercept the sending of the request on the network but still maintain the rest of the workflow we will replace a function called submitRequest. In the Dropzone code this function is a one liner and what I did was replace it with my own one-liner:

    this._finished(files,'locally resolved, refer to "contents" property');

    https://github.com/lifegadget/ui-dropzone/blob/0.7.2/addon/mixins/xhr-intercept.js#L66-L70

  • Provide access to retrieved document

    The last step is just to ensure that our localAcceptHandler is put in place of the accept routine that dropzone supplies:

    https://github.com/lifegadget/ui-dropzone/blob/0.7.2/addon/components/drop-zone.js#L88-L95

Revisory answered 18/11, 2015 at 20:21 Comment(0)
W
0

using the FileReader() solution is working amazingly good for me:

Dropzone.autoDiscover = false;
var dz = new Dropzone("#demo-upload",{
   autoProcessQueue:false,
   url:'upload.php'
});

dz.on("drop",function drop(e) {
              var files = [];
              for (var i = 0; i < e.dataTransfer.files.length; i++) {
                files[i] = e.dataTransfer.files[i];
              }


var reader = new FileReader();
reader.onload = function(event) {
    var line = event.target.result.split('\n');
    for ( var i = 0; i < line.length; i++){
        console.log(line);
    }
};
reader.readAsText(files[files.length-1]);
Westhead answered 20/10, 2020 at 11:47 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.