Search code examples
javascriptajaxfile-uploadmultiple-instancesinstances

Multiple Instances of Ajax Uploader on the Same Page


I developed an ajax file uploader and it works great if the one instance is on the page. I'm trying to convert it to allow multiple instances, and it almost works, EXCEPT, when I'm uploading multiple files in one form, it uploads the first file fine, but then for all subsequent files, for some reason it points forward to the next file reader from the second form instance. If I upload using the last (second) instance of the upload form, multiple files upload fine. But if I try uploading with the first instance, it will upload the first file, but all subsequent files get sent to the empty file input from the second instance. I can't understand why. I'm using unique id names throughout the upload form and referencing that unique id throughout the javascript functions. I'll try to include all the necessary bits of code below.

The forms are generated by PHP. The php var $uid is a randomly generated unique id that I use throughout. First, the initialization of the upload function is output to the page in a <script> tag:

'<script> '.
    'jQuery(document).ready(function($){ '.
        'var myup_'.$uid.' = new MyUp({ '.
            'form_id: "myup_form_'.$uid.'", '.
            'uid: '.$uid.', '.
            'container: "'.$name.'", '.
            'table: "'.$style.'", '.
            'iconcolor: "'.$iconcolor.'", '.
            'maxsize: '.$maxsize.', '.
            'permitted: '.$permitted.', '.
            'prohibited: '.$prohibited.', '.
            'fixed: '.$fixedsetting.', '.
            'pathcheck: "'.$pathcheck.'", '.
            'uploader: '.$uploader.', '.
            'loading: "'.my_url.'/lib/img/ajax.gif" '.
        }); '.
    '}); '.
'</script>';

Obviously all those variables for my settings are defined earlier. With multiple instances on the page, it reads the settings correctly for each different instance. They are successfully showing up with different styles, icon colors, file type permissions, max file size settings, etc. All that works with multiple instances.

Now the form:

'<div class="myup_container" style="'.$inlinestyle.'">'.
    '<form name="myup_form_'.$uid.'" id="myup_form_'.$uid.'" action="javascript:void(0);" enctype="multipart/form-data">'.
        '<input type="hidden" id="upload-actionpath-'.$uid.'" value="'.$fixed.'" data-basename="'.$basename.'" data-start="'.$start.'" />'.
        '<div class="myup_buttons_container" style="text-align:right;">'.
            '<span class="myup_wrapper" style="text-align:left;">'.
                '<input type="file" name="myup_files_'.$uid.'[]" id="myup_files_'.$uid.'" class="hidden_browse"'.$multiple.' />'.
                '<span class="add_files">'.$addfiles.'</span>'.
                '<span id="submit_upload_'.$uid.'">'.$uploadlabel.'</span>'.
            '</span>'.
        '</div>'.
    '</form>'.
    '<div id="myup_files_container_'.$uid.'" class="myup_files_container"></div>'.
    '<span id="my_removedfiles_'.$uid.'" style="display:none;"></span>'.
'</div>';

So that's a pared down version of it. The instance in the script tag, containing the settings, and the html form, are output by a shortcode. So multiple shortcodes on the page will output multiple instances of the MyUp function.

Here's what I hope are all the pertinent bits of the javascript functions (it's long, sorry, but I removed a ton of stuff):

jQuery(document).ready(function($)
{
// throughout, the var fuid will refer 
// to the php var $uid from the html form and instance settings

function myupRemove(id, filename, fuid)
{
    // handle files the user removes from the input field 
    // before submitting the upload
}
function MyUp(config)
{
    this.settings = config; 
    this.fuid = this.settings.uid;
    this.file = ""; 
    this.browsed_files = []; 
    var self = this;
    MyUp.prototype.myupDisplay = function(value)
    {
        this.file = value;
        if(this.file.length > 0)
        {
            /* this is a really long bit of code
             * that i'll spare you, but basically I iterate 
             * through all the files in the input field
             * and put them dynamically into a table 
             * and drop the table onto the page
             * so the user can rename them, remove them before upload,
             * and then watch the status bar for each file as it uploads.
             * This portion of the code works fine with multiple instances.
            */
        }
    }
    // Create Unique ID
    MyUp.prototype.uid = function(name)
    {
        // Here I generate a unique ID for each file, 
        // and prepend it with the Instance's unique id (i.e., this.fuid)
        return this.fuid+'_'+name.replace(/[^a-z0-9\s]/gi, '_').replace(/[_\s]/g, '_');
    }           
    // Get File Extension
    MyUp.prototype.ext = function(file, lowercase)
    {
        return (/[.]/.exec(file)) ? (lowercase ? /[^.]+$/.exec(file.toLowerCase()) : /[^.]+$/.exec(file)) : '';
    }
    // Format File Size
    MyUp.prototype.nicesize = function(fileSize)
    {
        // a bunch of code to format the file size then...
        return niceSize;
    }
    // Attribute FileType Icons
    MyUp.prototype.icon = function(icon_ext, color)
    {
        // a bunch of arrays to determine 
        // which file type icon to display in the table
    }
    //File Reader
    MyUp.prototype.read = function(e) 
    {
        if(e.target.files) 
        {
            // references the myupDisplay function 
            // where I add the files to a table
            self.myupDisplay(e.target.files);
            self.browsed_files.push(e.target.files);
        } 
    }
    function addEvent(type, el, fn)
    {
        if(window.addEventListener)
        {
            el.addEventListener(type, fn, false);
        } 
        else if(window.attachEvent)
        {
            var f = function()
            {
              fn.call(el, window.event);
            };          
            el.attachEvent('on' + type, f)
        }
    }
    // Collect File IDs and Initiate Upload for Submit
    MyUp.prototype.starter = function() 
    {
        if(window.File && window.FileReader && window.FileList && window.Blob) 
        {

            var browsed_file_id = $("#"+this.settings.form_id).find("input[type='file']").eq(0).attr("id");
            document.getElementById(browsed_file_id).addEventListener('change', this.read, false);
            document.getElementById('submit_upload_'+this.fuid).addEventListener('click', this.submit, true);
        } 
        else alert(browser_cant_read_message);
    }
    // Begin Upload on Click
    MyUp.prototype.submit = function()
    { 
        self.begin(); 
    }
    // Initiate Upload Iterator
    MyUp.prototype.begin = function() 
    {
        if(this.browsed_files.length > 0)
        {
            for(var k=0; k<this.browsed_files.length; k++)
            {
                var file = this.browsed_files[k];
                this.myupAjax(file,k);
            }
            this.browsed_files = [];
        }
        else alert(no_files_chosen_message);
    }
    // Ajax Upload
    MyUp.prototype.myupAjax = function(file,i)
    {
        if(file[i]!== undefined)
        {
            var id = file_id = self.uid(file[i].name),
                rawname = file[i].name.substr(0, file[i].name.lastIndexOf('.')) || file[i].name,
                extension = self.ext(file[i].name, false),
                browsed_file_id = $("#"+this.settings.form_id).find("input[type='file']").eq(0).attr("id");
                path = this.settings.fixed ? this.settings.fixed : String($('input#upload-actionpath-'+this.fuid).val()),
                pathcheck = String(this.settings.pathcheck),
                removed_file = $("#"+id).val(),
                newname = String($('input#rename_upfile_id_'+id).val()),
                new_name = newname === '' || newname === 'undefined' || newname === undefined ? file[i].name : newname+'.'+extension,
                removed = this.settings.removed,
                loading = this.settings.loading,
                fixedchars = this.settings.fixed;
            // if the file is removes, skip to the next file
            if(removed_file !== '' && removed_file !== undefined && removed_file == id) self.myupAjax(file,i+1); 
            else
            {
                var myupData = new FormData();
                myupData.append('upload_file',file[i]);
                myupData.append('upload_file_id',id);
                myupData.append('max_file_size',this.settings.maxsize);
                myupData.append('upload_path',path);    
                myupData.append('new_name',new_name);
                myupData.append('extension',extension);
                myupData.append('uploader',this.settings.uploader);
                myupData.append('act','upload');
                myupData.append('nonce',myup.nonce);                
                $.ajax(
                {
                    type        : 'POST',
                    url         : myup.ajaxurl+'?action=myup-uploads',
                    data        : myupData,
                    id          : id,
                    fuid        : this.fuid,
                    new_name    : new_name,
                    rawname     : rawname,
                    extension   : extension,
                    path        : path,
                    pathcheck   : pathcheck,
                    removed     : removed,
                    loading     : loading,
                    fixedchars  : fixedchars,
                    cache       : false,
                    contentType : false,
                    processData : false,
                    beforeSend  : function(xhr, settings)
                    {
                        // I added this alert because it shows me that when I'm using the first instance
                        // after the first file uploads, it starts looking to the file input field 
                        // from the second instance
                        alert('path: '+settings.path+' pathcheck: '+settings.pathcheck);

                        // in here I do a bunch of security stuff before uploading
                    },
                    xhr: function()
                    {
                        // my progress bar function here
                    },
                    success : function(response)
                    {
                        setTimeout(function()
                        {
                            if(response.indexOf(id) != -1)
                            {
                                // do success stuff, like green checkmark, remove table row, etc.
                            }
                            else
                            {
                                // do failure stuff, like red x and error message
                            }

                            // AND HERE IS THE IMPORTANT PART
                            // THIS SAYS TO GO ON TO THE NEXT FILE IN THE ARRAY
                            // BUT WHEN USING THE FIRST INSTANCE
                            // IT SENDS US TO THE SECOND INSTANCE'S FILE READER
                            // if I'm uploading with the second form it's fine
                            if(i+1 < file.length) self.myupAjax(file,i+1); 
                        },500);
                    }
                });
             }
        }
    }   
    this.starter();
}
window.MyUp = MyUp;
window.myupRemove = myupRemove;
});

Note the comment block toward the end of the above code snippet, where the comment is in all caps. That's where it completes the ajax upload for a file, then sends us back to do the next one.

So, basically, to reiterate, if I have two forms on the page, when I use the second form, everything works fine. When I use the first form, the first file will upload fine, but then it starts looking for the next file in the input field from the second instance.

Is there a relatively simple solution for this?


Solution

  • Well I couldn't get it to work the way Alex Gyoshev suggested (deficiency on my part no doubt), so I decided to get rid of the object/prototype route altogether. Instead of creating instances in the form, I declare a MyUpConfig variable in the global space, as an empty array. Then in each html form, I add MyUpConfig['.$uid.'] = and then my array of settings.

    Then in the js file I bind each form's file input and submit events to the unique id, and store each input field's file data in an array like this: TheFiles[uid] = files. So when the upload submit occurs, it gets the uid from the upload button element, and only attempts to upload the files stored in the array matching that unique id.

    I now have it working with multiple upload forms on the same page. They can each be running simultaneously without interference.