1

I tried to create my own plugin for upload files via ajax.
If the page where there is the input file is reloaded after upload It seems to work good.
If the page where there is the input file is NOT reloaded after upload (because was reloaded only ajax content) There are problems with IE and Chrome because the files to upload are appended to previous just uploaded (with firefox is ok).
I tried to fix it by cleaning the input file after the first upload but in this way then with IE and Chrome I can no longer upload other files.

MY FIX

complete: function () {
       defaults.onFinish.call(this);
       // If page where is the input file not reloaded
       // after upload files IE and Chrome not working
       $this.replaceWith($this.val('').clone(true));
       $this.val('');
}

In truth I would clean the formData object after every upload but I haven't been able to do it

MY PLUGIN

;(function ($, window, document, undefined) {

    // Function-level strict mode syntax
    'use strict';

    $.fn.ajaxUpload = function(options) {

        var defaults = {
            num_files       : 0,
            max_files       : 2,
            max_concurrent  : 10,
            max_filesize    : 1024 * 4096,
            php_max_size    : 1024 * 8192,
            allowed_types   : ['jpeg','jpg'],
            ajax_url        : 'action.php',
            var_name        : 'file',
            extra_fields    : {},
            onFinish        : function() {}
        };

        var options = $.extend(defaults, options);

        return this.each(function() {

            var $this = $(this);

            $this.on('change', function() {

                var files = $this[0].files;
                var len = files.length;
                var items = 0;
                var diff_files = parseInt(defaults.max_files - defaults.num_files - len);

                if(diff_files < 0) {
                    return false;
                }
                if(!maxUploadFiles(len, defaults.max_concurrent)) {
                    return false;
                }
                var formdata = new FormData();

                jQuery.each(files, function(i, file) {
                    if(!isOverSized(file, defaults.max_filesize)) {
                        return false;
                    }
                    if(!isAllowedTypes(file, defaults.allowed_types)) {
                        return false;
                    }
                    if(!totalFilesSize(file, defaults.php_max_size)) {
                        return false;
                    }

                    formdata.append(defaults.var_name + '['+i+']', file);
                    items++;
                });

                // Append extra data to formdata
                $.each(defaults.extra_fields, function(name, value) {
                    formdata.append(name, value);
                });

                // Check that files have passed all test
                if (len != items) { return false; }

                $.ajax({
                    url: defaults.ajax_url,
                    data: formdata,
                    cache: false,
                    contentType: false,
                    processData: false,
                    type: 'POST',
                    beforeSend: function () {
                    },
                    success: function(data) {
                        totalSize = 0;
                    },
                    complete: function () {
                        defaults.onFinish.call(this);

                        // If page where is the input file not reloaded
                        // after upload files IE and Chrome not working
                        //$this.replaceWith($this.val('').clone(true));
                        //$this.val('');
                    }
                });

            });
       });
    };

    var totalSize = 0;

    function totalFilesSize(file, php_max_size) {
        totalSize += file.size;
        if(totalSize > php_max_size) {
            totalSize = 0;
            return false;
        }
        return true;
    }
    function maxUploadFiles(len, max_concurrent) {
        if(len > max_concurrent) {
            return false;
        }
        return true;
    }
    function isAllowedTypes(file, allowed_types) {
        var ext = file.name.split('.').pop().toLowerCase();
        if(jQuery.inArray(ext, allowed_types) < 0) {
            return false;
        }
        return true;
    }
    function isOverSized(file, max_filesize) {
        if(file.size > max_filesize) {
            return false;
        }
        return true;
    }

})(jQuery, window, document);

According to you that changes should I do to solve my problem? Thank you

EDIT

I add this line on complete, and It seems to work

$this.val('');
$this.wrap('<form>').parent('form').trigger('reset');
$this.unwrap();
$this.replaceWith($this.clone());
Paolo Rossi
  • 2,490
  • 9
  • 43
  • 70

1 Answers1

1

The problem with your plugin is that you keep a reference to the original input with $this and then tried to replace it with a clone. Because you are cloning is better to get a new reference each time so you should unbind and bind .

(function ($, window, document, undefined) {

    // Function-level strict mode syntax
    'use strict';

    $.fn.ajaxUpload = function (options) {

        var defaults = {
            num_files: 0,
            max_files: 2,
            max_concurrent: 10,
            max_filesize: 1024 * 4096,
            php_max_size: 1024 * 8192,
            allowed_types: ['jpeg', 'jpg'],
            ajax_url: 'action.php',
            var_name: 'file',
            extra_fields: {},
            onFinish: function () {}
        };

        var options = $.extend(defaults, options);

        var bindInput = function (elem) {
            var element = $(elem),
            bindFunc = function (evt) {

                var files = evt.currentTarget.files;
                var len = files.length;
                var items = 0;
                var diff_files = parseInt(defaults.max_files - defaults.num_files - len);

                 if (diff_files < 0) {
                     return false;
                 }
                 if (!maxUploadFiles(len, defaults.max_concurrent)) {
                     return false;
                 }
                 var formdata = new FormData();

                 jQuery.each(files, function (i, file) {
                     if (!isOverSized(file, defaults.max_filesize)) {
                         return false;
                     }
                     if (!isAllowedTypes(file, defaults.allowed_types)) {
                         return false;
                     }
                     if (!totalFilesSize(file, defaults.php_max_size)) {
                         return false;
                     }

                     formdata.append(defaults.var_name + '[' + i + ']', file);
                     items++;
                 });

                 // Append extra data to formdata
                 $.each(defaults.extra_fields, function (name, value) {
                     formdata.append(name, value);
                 });

                 // Check that files have passed all test
                 if (len != items) {
                     return false;
                 }

                 $.ajax({
                     url: defaults.ajax_url,
                     data: formdata,
                     cache: false,
                     contentType: false,
                     processData: false,
                     type: 'POST',
                     beforeSend: function () {},
                     success: function (data) {
                         totalSize = 0;
                     },
                     complete: function () {
                         defaults.onFinish.call(this);
                         var previous = $(evt.currentTarget);
                         previous.off('change', bindFunc);
                         var newElem = previous.val('').clone(true)
                         previous.replaceWith(newElem);
                         bindInput(newElem);

                     }
                 });

             };
             element.on('change', bindFunc);
         };

         return this.each(function () {
             bindInput(this)
         });
    };

    var totalSize = 0;

    function totalFilesSize(file, php_max_size) {
        totalSize += file.size;
        if (totalSize > php_max_size) {
            totalSize = 0;
            return false;
        }
        return true;
    }

    function maxUploadFiles(len, max_concurrent) {
        if (len > max_concurrent) {
            return false;
        }
        return true;
    }

    function isAllowedTypes(file, allowed_types) {
        var ext = file.name.split('.').pop().toLowerCase();
        if (jQuery.inArray(ext, allowed_types) < 0) {
            return false;
        }
        return true;
    }

    function isOverSized(file, max_filesize) {
        if (file.size > max_filesize) {
            return false;
        }
        return true;
    }

})(jQuery, window, document);

{Edit}

The problem that originate your question is the nightmare of every file upload plugin developer. As you are developing a plugin you should be aware that the input tag may contain other styles and event handlers set by the consumer of the plugin that you must preserve or you will break existing functionality.

For security reasons the value of the input type file cannot be changed with javascript. There are a lot of answers in SO about that. Search for clear+input+file and see for yourself, the most remarkable is this Clearing <input type='file' /> using jQuery

As you can see there are basically two choices:

  1. Clone the input and call val('') before cloning (calling jQuery $(input).val('') is not the same that calling input.value = '').

    The problems of this approach is for example that in IE this event is called twice when clearing the file input and you must be carefull about releasing memory and references to the input being replaced while preserving current styles and event handlers that were not set by your plugin

  2. The second is better but has issues as well. Wrap your input in a form tag and call the form's reset method.

    input.wrap('<form>').parent('form').trigger('reset');
    input.unwrap();
    

    Check the docs about the sintax of the form tag and you will see the following quote

    Note: It's strictly forbidden to nest a form inside another form. Doing so can behave in an unpredictable way that will depend on which browser the user is using.

    The main reasoning behind that is that your plugin can be applied to an input tag that is already inside a form leaving you with invalid html so you must wrap the form call the reset method and remove this form right away. Also remember that forms may have visual styles applied to them breaking the user interface if you leave them around.

In the second alternative is easier to fix your code. Just change the complete callback like this. No cloning is needed in this case.

complete: function () {
    defaults.onFinish.call(this);
    $this.wrap('<form>').parent('form').trigger('reset');
    $this.unwrap();
}

This changes should happen so fast that the users will not notice them. I tested with 1000 elements around and no visual glitches were visible.

Community
  • 1
  • 1
devconcept
  • 3,665
  • 1
  • 26
  • 40
  • Thanks for your solution. But I notice that with chrome after the uploaded file, the file's name remain in input file. Please see my edit and tell me what you think about. With my solution though I must delegate the input click event. Thank you – Paolo Rossi Jun 05 '15 at 20:51
  • Thank you very much for your help! – Paolo Rossi Jun 06 '15 at 16:02