📜 ⬆️ ⬇️

Cross-browser submission of a form with a file or how to rewrite the whole sender several times after testing in IE

Task: sending and processing files using FormData and FileReader in a form with all possible fields and sending additional parameters for each field and combining all form data (except files and system fields) into a common array.

Support: all modern browsers, IE 10+.

Plugins: jquery-2.1.4
')
image

First, let's look at what FormData is.

Formdata is a data type within the framework of XHR2 technology, the data in it is stored as key / value pairs.
new Formdata () is a constructor for creating a FormData object.

Read more about FormData

image

FormData has many methods for working with it, such as:


FormData can also be iterated using a for ... of loop (available in ECMAScript 6, with native support for which there are also problems).

The main problem of FormData lies in Internet explorer (as always), or rather, in its support. Of all the methods available in FormData, Internet explorer only supports append (), which eliminates all ease of use. Therefore, we cannot assemble a form using a simple call to the constructor and then changing the data in it, and we will have to do it manually:


We will collect the files using the list that the user creates when downloading and further manipulating the list on the client, that is, we will compare those files that we have left in the list with those stored in input type = "file" and using a partition add only those that left the user.

Now we will get acquainted with FileReader

FileReader is an object that allows web applications to read the contents of files (or data buffers) asynchronously stored on a user's computer using File or Blob objects, which specify the file or data to be read.

Read more about filereader

With it, we will track the download of files on the client, create a list of downloaded files and display a progress bar for them.

Now to the task itself
The form we will forward:

<form enctype="multipart/form-data" id="form"> <!--   ( )--> <input type="hidden" name="thm" data-title="" value=" "> <div class="radio-list"> <div class="radio"> <input type="radio" name="radiobtn" value="" data-title=" " id="radio1" class="radio__input" checked> <label for="radio1" class="radio__label"> </label> </div> <div class="radio"> <input type="radio" name="radiobtn" value="" data-title=" " id="radio2" class="radio__input"> <label for="radio2" class="radio__label"> </label> </div> </div> <div class="checkbox-list"> <div class="checkbox"> <input type="checkbox" name="checkboxbtn" value="" data-title=" 2" id="checkbox1" class="checkbox__input" checked> <label for="checkbox1" class="checkbox__label"> </label> </div> <div class="checkbox"> <input type="checkbox" name="checkboxbtn" value="" data-title=" 2" id="checkbox2" class="checkbox__input"> <label for="checkbox2" class="checkbox__label"> </label> </div> </div> <input type="text" name="name" data-title=" " class="input-text"> <textarea name="textarea" data-title="" class="textarea"></textarea> <!-- input   --> <input class="input-file js_file_check" type="file" name="file[]" data-title="" multiple="" accept="image"> <!--   --> <ul class="js_file_list file-list"> </ul> <!--    --> <button class="js_btn_submit"> </button> </form> 

For the convenience of users, we will provide them with the ability to immediately add a large number of files. For this purpose, we indicate in the name field the value of file [] and the attribute multiple, with the restriction of only the picture accept = "image".

For users, we will also display a list of files that they downloaded with a separate progress bar for each file and the ability to delete before sending. And here we are faced with a problem. The fact is that the fileList (array of downloaded files) for our input is read-only, and we cannot delete only the file selected by the user. So it was decided before sending to the server to verify the list that the user has already formed, so that it is already loaded. And if it matches the list, the file will be added to FormData.

1) Create the very function to send via ajax:

 var form = form; //  function formSend(formObject, form) { $.ajax({ type: "POST", url: 'form-handler.php', dataType: 'json', contentType: false, processData: false, data: formObject, success: function() { $(form).trigger('reset'); //        alert('Success'); } }); }; 

2) Create a form assembly function:

 function formData_assembly(form) { var formSendAll = new FormData(), //  FormData form_arr = $(form).find(':input,select,textarea').serializeArray(), //       formdata = {}; //       for (var i = 0; i < form_arr.length; i++) { if (form_arr[i].value.length > 0) { //         var current_input = $(form).find('input[name=' + form_arr[i].name + '],select[name=' + form_arr[i].name + '],textarea[name=' + form_arr[i].name + ']'), value_arr = {}; //       +  var title = $(current_input).attr('data-title'); //  if ($(current_input).attr('type') != 'hidden') { //      value_arr['value'] = form_arr[i].value; value_arr['title'] = title; formdata[form_arr[i].name] = value_arr; } else { formSendAll.append(form_arr[i].name, form_arr[i].value); //       } } } formdata = JSON.stringify(formdata); formSendAll.append('formData', formdata); //     formdata // file if ($(form).find('input[type=file]').hasClass('js_file_check')) { //   input type file   var current_input = $(form).find('input[type=file]'); if ($(current_input).val().length > 0) { //   $('.js_file_list li').each(function() { var list_file_name = $(this).find('span').text(); for (var k = 0; k < $(current_input)[0].files.length; k++) { if (list_file_name == $(current_input)[0].files[k].name) { //      formSendAll.append($(current_input).attr('name'), $(current_input)[0].files[k]); //        } } }) } } formSend(formSendAll, form); } formData_assembly(form); 

3) We wrap all this in a function for convenient event call:

 function submit_function(form){...} 

4) We hang the function on the event of a click on the submit button:

 $('.js_btn_submit').click(function (e) { e.preventDefault(); var current_form = $(this).closest('form');//  submit_function(current_form); }) 

Now we have a full form sender, it remains only to write a handler for the files.

1) Create a state tracking function input type = file:

 function checkFile(){ var inputs = document.getElementsByClassName('js_file_check'); for (var i = 0; i < inputs.length; i++) { inputs[i].addEventListener('change', handleFileSelect, false); } } checkFile(); 

2) Write an error handler:

 var reader; function abortRead() { reader.abort(); } function errorHandler(evt) { switch (evt.target.error.code) { case evt.target.error.NOT_FOUND_ERR: alert('File Not Found!'); break; case evt.target.error.NOT_READABLE_ERR: alert('File is not readable'); break; case evt.target.error.ABORT_ERR: break; // noop default: alert('An error occurred reading this file.'); }; } 

3) Let's write a function for rebuilding files in our input type = file fileList:

 function handleFileSelect(evt) { var thisInput = $(this); //input type file    for (var i = 0; i < thisInput[0].files.length; i++) { //         reader_file(thisInput[0].files[i]); //     } } 

4) Now directly handler itself:

 function reader_file(file) { var reader = new FileReader(), fileName = file.name; reader.onerror = errorHandler; //    $('.js_file_list').append('<li><span>' + fileName + '</span><div class="js_file_remove file_remove"></div><div class="progress-bar js_progress_bar"></div></li>'); //        reader.onabort = function(e) { alert('File read cancelled'); }; reader.onload = function(e) { //    //-  } reader.onprogress = function(event) { //     if (event.lengthComputable) { var percent = parseInt(((event.loaded / event.total) * 100), 10); $('.js_progress_bar').css('width', percent + '%'); } } if (reader.readAsBinaryString === undefined) { //     readAsBinaryString reader.readAsBinaryString = function(fileData) { var binary = "", pt = this, reader = new FileReader(); reader.onload = function(e) { var bytes = new Uint8Array(reader.result); var length = bytes.byteLength; for (var i = 0; i < length; i++) { binary += String.fromCharCode(bytes[i]); } pt.content = binary; $(pt).trigger('onload'); } } reader.readAsArrayBuffer(file); } else { reader.readAsBinaryString(file); } } 

5) Add the ability to delete files from the list:

 $(document).on('click', '.js_file_remove', function() { var list_item = $(this).closest('li'); $(list_item).remove(); }); 

6) We can use our sender, not forgetting to raise the local server:

demo link

Source: https://habr.com/ru/post/325340/


All Articles