Safari 11.1: el envío del formulario ajax / XHR falla cuando la entrada está vacía

ACTUALIZACIÓN : A partir de la comstackción r230963 de Webkit , este problema se ha resuelto en Webkit.

===========

Desde la reciente actualización de Safari 11.1 en macOS e iOS, así como en Safari Technology Preview 11.2, las llamadas $.ajax en mi aplicación web fallan cuando un campo de input[type=file] no tiene archivo seleccionado (no es necesario) en mi forma). No falla cuando el campo tiene un archivo elegido.

La callback de error de ajax ejecuta y la consola de Safari contiene el siguiente mensaje: Failed to load resource: The operation couldn't be completed. Protocol error Failed to load resource: The operation couldn't be completed. Protocol error . Soy HTTPS y estoy enviando a una ubicación en el mismo dominio (y servidor) también a través de HTTPS.

Antes de la actualización 11.1, la llamada $.ajax envió $.ajax cuando no se eligió ningún archivo. Las últimas versiones de Chrome y Firefox no tienen problemas.

Partes relevantes de mi código:

La entrada:

 Browse...  

El JS:

 var formData = new FormData($(this)[0]); $.ajax({ type: 'POST', enctype: 'multipart/form-data', url: '../process.php', data: formData, contentType: false, processData: false, cache: false, success: function(response) { ... }, error: function() { //my code reaches here } }); 

Como una solución temporal (con suerte), estoy detectando un campo de archivo vacío y eliminándolo de formData antes de la llamada ajax y todo funciona como se esperaba / antes:

 $("input[type=file]").each(function() { if($(this).val() === "") { formData.delete($(this).attr("name")); } }); 

¿Estoy haciendo algo mal, hay un problema con Safari o hay un cambio en Safari que debe tenerse en cuenta ahora en las llamadas ajax?

ACTUALIZACIÓN: la respuesta anterior NO funciona en Firefox.

Firefox solo devuelve una cadena vacía para FormData.get() en el campo de archivo vacío (en lugar del objeto File en otros navegadores). Por lo tanto, cuando se usa una solución anterior, el vacío se enviará como vacío . Desafortunadamente, no hay manera de distinguir un archivo vacío de un texto vacío después de crear el objeto FormData.

Utilice esta solución en su lugar:

 var $form = $('form') var $inputs = $('input[type="file"]:not([disabled])', $form) $inputs.each(function(_, input) { if (input.files.length > 0) return $(input).prop('disabled', true) }) var formData = new FormData($form[0]) $inputs.prop('disabled', false) 

Demostración en vivo: https://jsfiddle.net/yprest/05Lc45eL/

Para entornos no jQuery:

 var form = document.querySelector('form') var inputs = form.querySelectorAll('input[type="file"]:not([disabled])') inputs.forEach(function(input) { if (input.files.length > 0) return input.setAttribute('disabled', '') }) var formData = new FormData(form) inputs.forEach(function(input) { input.removeAttribute('disabled') }) 

Para Rails (rails-ujs / jQuery-ujs): https://gist.github.com/yprest/cabce63b1f4ab57247e1f836668a00a5


Respuesta anterior:

Filtrar FormData (en la respuesta de Ravichandra Adiga) es mejor porque no manipula ningún DOM.

Pero se garantiza que el orden de las partes en FormData es el mismo orden para ingresar elementos en la forma , de acuerdo con la especificación

. Podría causar otro error si alguien confía en esta especificación.

Debajo del fragmento de código mantendrá el orden de FormData y la parte vacía.

 var formDataFilter = function(formData) { // Replace empty File with empty Blob. if (!(formData instanceof window.FormData)) return if (!formData.keys) return // unsupported browser var newFormData = new window.FormData() Array.from(formData.entries()).forEach(function(entry) { var value = entry[1] if (value instanceof window.File && value.name === '' && value.size === 0) { newFormData.append(entry[0], new window.Blob(), '') } else { newFormData.append(entry[0], value) } }) return newFormData } 

El ejemplo en vivo está aquí: https://jsfiddle.net/yprest/y6v333bq/

Para los Rails, consulte aquí: https://github.com/rails/rails/issues/32440#issuecomment-381185380

(NOTA: Safari de iOS 11.3 tiene este problema, pero 11.2 no lo es).

Para solucionar el problema, elimino el archivo de tipo de entrada completamente de DOM usando el método jQuery remove ().

 $("input[type=file]").each(function() { if($(this).val() === "") { $(this).remove(); } }); 

A partir de la comstackción r230963 de Webkit , este problema se ha resuelto en Webkit. Descargué y ejecuté esa comstackción y confirmé que el problema se resolvió. No sé cuándo estará disponible una versión pública para Safari que contenga esta solución.

Trabajé en lo que parece ser el mismo problema en un progtwig Perl

Procesar multipart / form-data en Perl invoca el error Apache con dispositivos Apple, cuando un elemento de archivo de formulario está vacío

La solución es eliminar los elementos de formulario antes de que se asigne el formdata:

 $('#myForm').find("input[type='file']").each(function(){ if ($(this).get(0).files.length === 0) {$(this).remove();} }); var fData = new FormData($('#myForm')[0]); ... 
  var fileNames = formData.getAll("filename[]"); formData.delete("filename[]"); jQuery.each(fileNames, function (key, fileNameObject) { if (fileNameObject.name) { formData.append("filename[]", fileNameObject); } }); 

Prueba esto !!

Esto me funciona para verificar si el campo de entrada está vacío. Si está vacío, deshabilite el campo de entrada antes de crear el FormData. Después de crear el FormData, elimine el atributo “deshabilitado”. La diferencia con otras respuestas es que busco “input [0] .files.length == 0”.

 // get the input field with type="file" var input = $('#myForm').find("input[type='file']") // add the "disabled" attribute to the input if (input[0].files.length == 0) { input.prop('disabled', true); } // create the formdata var formData = new FormData($(this)[0]); // remove the "disabled" attribute input.prop('disabled', false);