John
John

Reputation: 13729

AJAX multiple image uploader progress in JavaScript not working in Firefox

Here is the full and almost working version of my XHTML AJAX image uploader. It uses AJAX to upload files and I'm okay with not supporting IE9 and older and Opera 11.6 and older (IE10 and Opera 12 support it). I am however having trouble with Firefox.

The main problem is dynamically attaching the id to target (or even pass along the i which would correlate with the element[i]) to the progress event. It works fine in Chrome and Opera 12 though not Firefox 10. The event listener is on line 100 in the code below.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
<title>Image Preview</title>
<script type="application/javascript">
//<![CDATA[
function images_name(s)
{
 var a = reverse(s);
 var b = a.split('.');
 var c = reverse(b[1]);
 var d = c.toLowerCase();
 var e = d.replace(/^\s+|\s+$/g,'');
 var f = e.replace(/^-+|-+$/g,'');
 var g = f.replace(/ /g,'-');
 var h = g.replace(/[--]+/g,'-');
 var i = h.replace(/[^a-zA-Z 0-9-]+/g,'');
 return i;
}


function images_preview()
{
 if (typeof FileReader=='function')
 {
  var files = document.getElementById('post_files').files;

  for (var i = 0; i < files.length; i++)
  {
   var fr = new FileReader();
   fr.file = files[i];
   fr.onloadend = function(e)
   {
    var ip = document.getElementById('images_preview');
    var file = e.target.file;

    var d1 = document.createElement('div');
    var dn = images_name(file.name);
    d1.setAttribute('id',dn);
    var t = document.createElement('img');
    t.setAttribute('alt','Preview');
    t.setAttribute('class','left');
    t.setAttribute('src',e.target.result);
    d1.appendChild(t);
    ip.appendChild(d1);

    var d2 = document.createElement('div');
    var s2 = document.createElement('span');
    var s2t = document.createTextNode(file.name);
    s2.appendChild(s2t);
    d2.appendChild(s2);
    d1.appendChild(d2);

    var d3 = document.createElement('div');
    var s3 = document.createElement('span');
    var s3t = document.createTextNode(file.type);
    s3.appendChild(s3t);
    d3.appendChild(s3);
    d1.appendChild(d3);

    var d4 = document.createElement('div');
    var s4 = document.createElement('span');
    var s4t = document.createTextNode(Math.round(file.size/1024)+' KB');
    s4.appendChild(s4t);
    d4.appendChild(s4);
    d1.appendChild(d4);

    var d5 = document.createElement('div');
    d5.setAttribute('class','progress');
    var d6 = document.createElement('div');
    d6.setAttribute('class','status');
    d5.appendChild(d6);

    d1.appendChild(d5);
   }
   fr.readAsDataURL(files[i]);
  }
 }
 else {alert('Notice: image preview not supported by your browser.');}
}


function images_upload(e)
{
 e.preventDefault();
 var n = '';

 for (var i = 0; i < document.getElementById('post_files').files.length; i++)
 {
  var file = document.getElementById('post_files').files[i];
  n = images_name(file.name);
  //alert('n1 = '+n);
  var xhr = new XMLHttpRequest();

  if (typeof xhr.upload=='object')
  {
   var upload = xhr.upload;
   upload.addEventListener('progress',function(n)
   {
    return function(e)
    {
     //alert('n = '+n);
     var loader = document.getElementById(n).getElementsByClassName('status')[0];
     var p = Math.round((e.loaded * 100) / e.total);
     loader.style.width = p+'%';
    //alert('n = ' + n);
    };

   }(n), false);
   xhr.open('POST','upload.php');
   xhr.setRequestHeader('Cache-Control','no-cache');
   xhr.setRequestHeader('X-Requested-With','XMLHttpRequest');
   xhr.setRequestHeader('X-File-Name',file.name);
   xhr.send(file);
  }
  else {var ns = true;}
 }

 if (ns) {alert('Error: your browser does not support AJAX file uploads.');}
}


function reverse(s) {return s.split('').reverse().join('');}


window.onload = function()
{
 if (window.addEventListener)
 {
  document.getElementById('form_images').addEventListener('submit',function(e) {images_upload(e);},false);
  document.getElementById('post_files').addEventListener('change',function() {images_preview();},false);
 }
}
//]]>
</script>
<style type="text/css">
.left {float: left;}
#images_preview > div {border: #000 solid 1px; clear: both; float: right; margin: 8px; overflow: auto; width: 400px;}
#images_preview > div > div {margin-top: 8px;}
#images_preview div.progress, #images_preview div.status {background-color: orange; bottom: 0px; clear: both; height: 2px; position: relative;}
#images_preview div.status {background-color: #0f0; width: 0px;}
#images_preview img {background-color: #ccc; border: #666 solid 1px; margin: 8px; max-height: 100px; max-width: 100px; padding: 4px;}
</style>
</head>

<body>

<form action="" id="form_images" method="post" enctype="multipart/form-data">
<fieldset>
<div><input id="post_files" name="post_files" multiple="multiple" size="128" type="file" /></div>
<div><input name="post_files" type="submit" /></div>

<div id="images_preview"></div>
</fieldset>
</form>

</body>
</html>

Some clarifications: first I only care about having this work in Firefox, Chrome, Opera 12+ and IE 10+, I'll have single file non-AJAX fallback in place. Right now I just want to concentrate on getting this to work however.

I've tested this and it works in Opera 12+, IE 10, Chrome (whatever) though not even Firefox 14 (current nightly build version). I would highly prefer a reasonable version number for Firefox to work like Firefox 4 in example.

No frameworks, I try to do quality and figuring problems out like this helps me learn real JavaScript coded correctly.

When I post JavaScript questions people start picking things out that clearly should be left in, example this is an AJAX file upload so why anyone would remove preventDefault is beyond me.

Upvotes: 0

Views: 636

Answers (1)

Stock Overflaw
Stock Overflaw

Reputation: 3321

function images_upload(e)
{
    //...
    var n = '';
    //...
    upload.addEventListener('progress',function(n)
    {
        return function(e)
        {
            //using n here
        };
    }(n), false);

In my - sole, humble and unverified - opinion:

Your variable n is visible in the first function (images_upload(e))

You pass it to the second function (the unnamed function(n)), assuming it is not overridden by the event object that is automatically passed (which I doubt), so it's visible here too (as a "different" n, see comments in the code block below):

function(n){...}(n)
         1       2
//1: the unnamed function's argument, should have been called differently to avoid confusion
//2: the n that is declared inside images_upload() with a limited visibility (using 'var' keyword), and which is now passed to the unnamed function as its argument

However, you don't pass it to your third function (the unnamed function(e)).There are many posts all over the Web about this constraint, but I can think of 2 workarounds (to be tested, here is only the spirit):

    upload.savemyvar=n;
    upload.addEventListener('progress',function(e)
    {
        return function(e , n)
        {
            //using the event e and your variable n here
        }(e , this.savemyvar);
    }, false);

And even simpler:

    upload.savemyvar=n;
    upload.addEventListener('progress',function(e)
    {
        var n=this.savemyvar;
        //using the event e and your variable n here
    }, false);

Or, and I'm pretty sure it should work (slowly though):

    eval("upload.addEventListener('progress',function(e)
    {
        return function(e , n)
        {
            //using the event e and your variable n here
            //don't forget to \"escape\" correctly if needed ;)
        }(e , "+n+");
    }, false);");

And, again, simpler:

    eval("upload.addEventListener('progress',function(e)
    {
        var n="+n+";
        //using the event e and your variable n here
        //don't forget to \"escape\" correctly if needed ;)
    }, false);");

In this last case, you may need to put all the eval code on one single line, depending on whether the JS parser needs a ; to end a codeline or just a \n.

Please tell if the first option works "as is", and most importantly if it solved your problem (as it sounds weird that your code works with other browsers).

Upvotes: 1

Related Questions