HTML5 and Javascript file handling

In summary: Time()); } ); // evt.preventDefault(); // evt.stopPropagation(); // Close the FileReader. // evt.dataTransfer.done = true; // Set the FileReader's done flag to true.
  • #1
jjc
21
0
I am quite new to the JavaScript and HTML5 world, and I am having trouble with some code that I was hacking together (based on demo's from other people). I am trying to take a file as input (a binary image) and base64 encode it. I think I have the right function for the base64 encoding, I think it is just the handling of the file itself that I seem to be having trouble with. But...don't really know if that is ALL of my trouble. :)

Here is the code:

Code:
<script>
    //  From:  http://www.html5rocks.com/en/tutorials/file/dndfiles/
    //  JC update:  changing the handleFileSelect() function to do the base64 Processing
  
  function base64Encode(aFile) {
      /*
       * base64.js - Base64 encoding and decoding functions
       * See: http://developer.mozilla.org/en/docs/DOM:window.btoa
       *      http://developer.mozilla.org/en/docs/DOM:window.atob
       * Copyright (c) 2007, David Lindquist <david.lindquist@gmail.com>
       * Released under the MIT license
       *
       * JC, update:  Removed the 'atob' section of code; only need ENcoding, not DEcoding.
       */

      if (typeof btoa == 'undefined') {
          function btoa(str) {
              var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
              var encoded = [];
              var c = 0;
              while (c < str.length) {
                  var b0 = str.charCodeAt(c++);
                  var b1 = str.charCodeAt(c++);
                  var b2 = str.charCodeAt(c++);
                  var buf = (b0 << 16) + ((b1 || 0) << 8) + (b2 || 0);
                  var i0 = (buf & (63 << 18)) >> 18;
                  var i1 = (buf & (63 << 12)) >> 12;
                  var i2 = isNaN(b1) ? 64 : (buf & (63 << 6)) >> 6;
                  var i3 = isNaN(b2) ? 64 : (buf & 63);
                  encoded[encoded.length] = chars.charAt(i0);
                  encoded[encoded.length] = chars.charAt(i1);
                  encoded[encoded.length] = chars.charAt(i2);
                  encoded[encoded.length] = chars.charAt(i3);
              }
              return encoded.join('');
          }
      }
  }
  
  function handleFileSelect(evt) {
    evt.stopPropagation();
    evt.preventDefault();
    var files = evt.dataTransfer.files; // FileList object - a FileList of File objects.
    var fReader = new FileReader () ;  
    var output = [];
    for (var i = 0, f; f = files[i]; i++) {
      if ( !f.type.match('image.*')) { continue; }    //To skip non-image files
      fReader.onLoad = (function (aFile) { return base64Encode(aFile); } ) (f);
      output.push('<li><strong>', escape(f.name), '</strong> (', f.type || 'n/a', ') - ',
                  f.size, ' bytes, last modified: ',
                  f.lastModifiedDate ? f.lastModifiedDate.toLocaleDateString() : 'n/a',
                  '<br><br>' , fReader.readAsBinaryString(f) , '<br><br>', '</li>');
     //This defines the 'onLoad' behavior/function...I think.
    
    }
    document.getElementById('list').innerHTML = '<ul>' + output.join('') + '</ul>';
  }

  function handleDragOver(evt) {
    evt.stopPropagation();
    evt.preventDefault();
    evt.dataTransfer.dropEffect = 'copy'; // Explicitly show this is a copy.
  }
  
  // Setup the dnd listeners.  [Slightly modified by JC]
  var dropZone = document.getElementById('drop_zone');
  dropZone.addEventListener('dragover', handleDragOver, false);
  dropZone.addEventListener('drop', handleFileSelect, false);
</script>

Any pointers would be appreciated.

Thanks,
J
 
Technology news on Phys.org
  • #2
To narrow things down a bit, I guess my two main questions come from these lines:

Code:
fReader.onLoad = (function (aFile) { return base64Encode(aFile); } ) (f);
output.push('<li><strong>', escape(f.name), '</strong> (', f.type || 'n/a', ') - ',
     f.size, ' bytes, last modified: ',
     f.lastModifiedDate ? f.lastModifiedDate.toLocaleDateString() : 'n/a',
     '<br><br>' , fReader.readAsBinaryString(f) , '<br><br>', '</li>');

In the first line, "fReader.onLoad...", I am not really certain what is going on with the (what I am guessing is) anonymous function and the trailing "...(f)...". Then in the second line, the calling of the file reader at that stage. And of course, the arguments of each call being in the right sequence.

There are probably other areas that I have messed up, but those are the ones that jump to mind.

Thanks,
J
 
Last edited by a moderator:
  • #3
In the first line, as you suspect, an anonymous function is being defined. This function is defined to have a single arg. The trailing (f) part is the actual argument to this function, probably the file that you intend to convert. This function is being assigned to the onLoad property of fReader, which suggests to me that it will be called when the page loads.

I'm not sure what's happening in the 2nd line. Could it be that fReader.readAsBinaryString causes onLoad to be called?

Anyway, that's what I think is happening.
 
  • #4
Ah, thanks! That trailing (f) was befuddling me.

So, to trace thing outloud...

The "fReader.OnLoad..." portion is the function definition (i.e. not actually doing anything at that time). The trailing "(f)" becomes the parameter to the function. What defines "f"? Does "f" come from the calling context, i.e. f = the file variable in the FOR loop? And then does the "f" correspond with the "aFile" variable/parameter in the function definition itself? In other words, the "f" is what I am passing to the function from the outer context, and "aFile" is the reference to that parameter used within the inner/function-definition context.

The "fReader.readAsBinaryString(f)" definition, I think, is what actually slurps up the file to read. When it is done reading, it should raise an 'onload' event, which would call that onload function just defined. I think. And then it is the 'onload' function which ends up running the encoding function I found.

Thanks for helping out; still a few more foggy bits to try and work out.

-- J
 
  • #5
OK, so here is my updated code, and down below that, the sample code that I am using as my template. There is a curious structure going on there that I don't quite get, and had disassembled slightly in my earlier version of code originally posted here. This new version of my code much more closely mimics the sample, but still doesn't work. Still kind of fumbling around in the dark; any suggestions appreciated.

My current code:

Code:
<script>
    //  From:  http://www.html5rocks.com/en/tutorials/file/dndfiles/
    //  JC update:  changing the handleFileSelect() function to do the base64 Processing
  
  function b64Enc(str) {
      {/*
       * base64.js - Base64 encoding and decoding functions
       * See: http://developer.mozilla.org/en/docs/DOM:window.btoa
       *      http://developer.mozilla.org/en/docs/DOM:window.atob
       * Copyright (c) 2007, David Lindquist <david.lindquist@gmail.com>
       * Released under the MIT license
       *
       * JC, update:  Removed the 'atob' section of code; only need ENcoding, not DEcoding.
       *     Removed 'btoa' naming wrapper-function; just expose the code directly in b64Enc()
       */}
      var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
      var encoded = [];
      var c = 0;
      while (c < str.length) {
          var b0 = str.charCodeAt(c++);
          var b1 = str.charCodeAt(c++);
          var b2 = str.charCodeAt(c++);
          var buf = (b0 << 16) + ((b1 || 0) << 8) + (b2 || 0);
          var i0 = (buf & (63 << 18)) >> 18;
          var i1 = (buf & (63 << 12)) >> 12;
          var i2 = isNaN(b1) ? 64 : (buf & (63 << 6)) >> 6;
          var i3 = isNaN(b2) ? 64 : (buf & 63);
          encoded[encoded.length] = chars.charAt(i0);
          encoded[encoded.length] = chars.charAt(i1);
          encoded[encoded.length] = chars.charAt(i2);
          encoded[encoded.length] = chars.charAt(i3);
      }
      alert ( "Done" ) ;   //OK, it got here...but no output.
      return encoded.join('');
  }
  
  function handleFileSelect(evt) {
    evt.stopPropagation();
    evt.preventDefault();
    var inputFiles = evt.dataTransfer.files; // Gets a FileList object - a list of File objects.
    var fReader = new FileReader () ;  
    var output = [];
    for (var i = 0, f; f = inputFiles[i]; i++) {
      if ( !f.type.match('image.*')) { continue; }    //Breaks out of current loop iteration, i.e. skip non-image files
      alert ( "Image file found!");
      
      fReader.onLoad = (
        function (aFile) {
          return function (e) { output.push('<li><strong>', escape(f.name), '</strong> (', f.type || 'n/a', ') - ',
                  f.size, ' bytes, last modified: ',
                  f.lastModifiedDate ? f.lastModifiedDate.toLocaleDateString() : 'n/a',
                  '<br><br>' , b64Enc(e.target.result) , '<br><br>End File', '</li>');
          };
        }
      ) (f);
      
      fReader.readAsText(f);
    }
    document.getElementById('list').innerHTML = '<ul>' + output.join('') + '</ul>';
  }

  function handleDragOver(evt) {
    evt.stopPropagation();
    evt.preventDefault();
    evt.dataTransfer.dropEffect = 'copy'; // Explicitly show this is a copy.
  }
  
  // Setup the dnd listeners.  [Slightly modified by JC]
  var dropZone = document.getElementById('filedrag');
  dropZone.addEventListener('dragover', handleDragOver, false);
  dropZone.addEventListener('drop', handleFileSelect, false);
</script>

And the original sample that I am using as a template:

Code:
//////////////////////
//  from http://www.html5rocks.com/en/tutorials/file/dndfiles/
//////////////////////
  function handleFileSelect(evt) {
    var files = evt.target.files; // FileList object

    // Loop through the FileList and render image files as thumbnails.
    for (var i = 0, f; f = files[i]; i++) {

      // Only process image files.
      if (!f.type.match('image.*')) {
        continue;
      }

      var reader = new FileReader();

      // Closure to capture the file information.
      reader.onload = (
          function(theFile) {
              return function(e) {
                // Render thumbnail.
                var span = document.createElement('span');
                span.innerHTML = ['<img class="thumb" src="', e.target.result,
                                  '" title="', escape(theFile.name), '"/>'].join('');
                document.getElementById('list').insertBefore(span, null);
              };
          }
      )(f);

      // Read in the image file as a data URL.
      reader.readAsDataURL(f);
    }
  }

  document.getElementById('files').addEventListener('change', handleFileSelect, false);

it is that inner-inner function that uses the 'e.target.result' that is confusing me a good bit. I am not quite grasping what the flow of calls and parameters is, and what the truly represents.

Thanks again,
J
 
  • #6
jjc said:
OK, so here is my updated code, and down below that, the sample code that I am using as my template. There is a curious structure going on there that I don't quite get, and had disassembled slightly in my earlier version of code originally posted here. This new version of my code much more closely mimics the sample, but still doesn't work. Still kind of fumbling around in the dark; any suggestions appreciated.

My current code:

Code:
<script>
    //  From:  http://www.html5rocks.com/en/tutorials/file/dndfiles/
    //  JC update:  changing the handleFileSelect() function to do the base64 Processing
  
  function b64Enc(str) {
      {/*
       * base64.js - Base64 encoding and decoding functions
       * See: http://developer.mozilla.org/en/docs/DOM:window.btoa
       *      http://developer.mozilla.org/en/docs/DOM:window.atob
       * Copyright (c) 2007, David Lindquist <david.lindquist@gmail.com>
       * Released under the MIT license
       *
       * JC, update:  Removed the 'atob' section of code; only need ENcoding, not DEcoding.
       *     Removed 'btoa' naming wrapper-function; just expose the code directly in b64Enc()
       */}
      var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
      var encoded = [];
      var c = 0;
      while (c < str.length) {
          var b0 = str.charCodeAt(c++);
          var b1 = str.charCodeAt(c++);
          var b2 = str.charCodeAt(c++);
          var buf = (b0 << 16) + ((b1 || 0) << 8) + (b2 || 0);
          var i0 = (buf & (63 << 18)) >> 18;
          var i1 = (buf & (63 << 12)) >> 12;
          var i2 = isNaN(b1) ? 64 : (buf & (63 << 6)) >> 6;
          var i3 = isNaN(b2) ? 64 : (buf & 63);
          encoded[encoded.length] = chars.charAt(i0);
          encoded[encoded.length] = chars.charAt(i1);
          encoded[encoded.length] = chars.charAt(i2);
          encoded[encoded.length] = chars.charAt(i3);
      }
      alert ( "Done" ) ;   //OK, it got here...but no output.
      return encoded.join('');
  }
  
  function handleFileSelect(evt) {
    evt.stopPropagation();
    evt.preventDefault();
    var inputFiles = evt.dataTransfer.files; // Gets a FileList object - a list of File objects.
    var fReader = new FileReader () ;  
    var output = [];
    for (var i = 0, f; f = inputFiles[i]; i++) {
      if ( !f.type.match('image.*')) { continue; }    //Breaks out of current loop iteration, i.e. skip non-image files
      alert ( "Image file found!");
      
      fReader.onLoad = (
        function (aFile) {
          return function (e) { output.push('<li><strong>', escape(f.name), '</strong> (', f.type || 'n/a', ') - ',
                  f.size, ' bytes, last modified: ',
                  f.lastModifiedDate ? f.lastModifiedDate.toLocaleDateString() : 'n/a',
                  '<br><br>' , b64Enc(e.target.result) , '<br><br>End File', '</li>');
          };
        }
      ) (f);
      
      fReader.readAsText(f);
    }
    document.getElementById('list').innerHTML = '<ul>' + output.join('') + '</ul>';
  }

  function handleDragOver(evt) {
    evt.stopPropagation();
    evt.preventDefault();
    evt.dataTransfer.dropEffect = 'copy'; // Explicitly show this is a copy.
  }
  
  // Setup the dnd listeners.  [Slightly modified by JC]
  var dropZone = document.getElementById('filedrag');
  dropZone.addEventListener('dragover', handleDragOver, false);
  dropZone.addEventListener('drop', handleFileSelect, false);
</script>

And the original sample that I am using as a template:

Code:
//////////////////////
//  from http://www.html5rocks.com/en/tutorials/file/dndfiles/
//////////////////////
  function handleFileSelect(evt) {
    var files = evt.target.files; // FileList object

    // Loop through the FileList and render image files as thumbnails.
    for (var i = 0, f; f = files[i]; i++) {

      // Only process image files.
      if (!f.type.match('image.*')) {
        continue;
      }

      var reader = new FileReader();

      // Closure to capture the file information.
      reader.onload = (
          function(theFile) {
              return function(e) {
                // Render thumbnail.
                var span = document.createElement('span');
                span.innerHTML = ['<img class="thumb" src="', e.target.result,
                                  '" title="', escape(theFile.name), '"/>'].join('');
                document.getElementById('list').insertBefore(span, null);
              };
          }
      )(f);

      // Read in the image file as a data URL.
      reader.readAsDataURL(f);
    }
  }

  document.getElementById('files').addEventListener('change', handleFileSelect, false);

it is that inner-inner function that uses the 'e.target.result' that is confusing me a good bit. I am not quite grasping what the flow of calls and parameters is, and what the truly represents.

Thanks again,
J

The statement immediately above sets a function that will be called when the 'change' event is raised on the element in your web page whose id is 'files'. The name of the function that will be called is handleFileSelect. Calling addEventListener tells the system that you want to listen for events of a specified type. The variable e represents the events.

When handleFileSelect actually gets called, it executes the statements listed in this function; namely, looping through each file in a list (an array) of files; opening a file reader for each; calling an anonymous function that has an embedded anonymous function.

That's how it looks to me. You can verify for yourself what's happening by using the debugging tools available in your browser by pressing F12. IE, Chrome, and Safari have the debugging tools. You'll probably need to manually set a breakpoint in your code by adding debugger; on the line where you want the debugger to stop.

Hope this helps.
 
  • #7
Mark44 said:
That's how it looks to me. You can verify for yourself what's happening by using the debugging tools available in your browser by pressing F12. IE, Chrome, and Safari have the debugging tools. You'll probably need to manually set a breakpoint in your code by adding debugger; on the line where you want the debugger to stop.

Hope this helps.

Mark,
Thanks for pointing out the debugger tools; I didn't know about those yet. That could help a lot.
I think that much of my confusion was trying to figure WHAT was being passed to all these different layers of functions (the two nested functions of the "onLoad" event function, and the new function that I put in), and what I needed to pass & return myself. The new function I put in ('b64Enc(str)') takes in a string, but I frankly wasn't 100% certain which of the file-reader types (I have tried both the Binary and the AsText ones, same results) I needed to use to pass the proper string-format to my new encoding code.
Or perhaps I just have the reading part in the wrong spot. It is my understanding that it is the reading part (i.e " fReader.readAsText(f); " ) that will trigger the "onLoad" event when the file being processed by the reader reaches the end. Do the HTML5 functions read IN a string of that type, or OUTPUT a string of the named type?

Thanks,
J
 
  • #9
Mark,
Thanks for the reply; I am still looking into this, but have put it on the back burner for a few days. I will re-read things and hope that I can glean what you mention.

-- J
 

1. What is the difference between HTML5 and Javascript file handling?

HTML5 is a markup language used for creating and structuring web pages, while Javascript is a programming language used for adding dynamic behavior to web pages. File handling in HTML5 refers to the ability to include and display files on a webpage, while file handling in Javascript refers to the ability to manipulate and interact with files on a webpage.

2. How do I include a file in an HTML5 webpage?

In HTML5, you can use the <img> tag to include image files, the <audio> and <video> tags to include audio and video files, and the <object> and <embed> tags to include various other file types, such as PDFs or documents.

3. Can I use Javascript to read and manipulate files on a user's computer?

No, Javascript is a client-side language and does not have access to a user's computer for security reasons. However, with the use of libraries or frameworks like Node.js, you can perform file handling operations on a server-side environment.

4. How can I validate user input in a file upload form?

In HTML5, you can use the <input type="file"> tag to create a file upload form. To validate the user's input, you can use the accept attribute to specify the file types that are allowed. In Javascript, you can use the FileReader API to validate and manipulate the selected file before uploading it.

5. Are there any compatibility issues with HTML5 and Javascript file handling?

HTML5 and Javascript are widely supported by modern web browsers, so there should not be any compatibility issues. However, older browsers may not support some of the newer features in HTML5 or the latest versions of Javascript. It is always a good practice to check for browser compatibility before implementing file handling in your web application.

Back
Top