Usando uploads de arquivos HTML5 com AJAX e jQuery

Evidentemente, existem perguntas semelhantes espalhadas no Stack Overflow, mas parece que nenhuma delas atende aos meus requisitos.

Aqui está o que eu estou procurando fazer:

  • Carregar um formulário inteiro de dados, um dos quais é um único arquivo
  • Trabalhar com a biblioteca de upload de arquivos do Codeigniter

Até aqui tudo está bem. Os dados chegam ao meu database conforme necessário. Mas também gostaria de enviar meu formulário por meio de uma postagem AJAX:

  • Usando a API de arquivos HTML5 nativa, sem flash ou uma solução de iframe
  • De preferência, fazendo interface com o método jQuery .ajax() baixo nível

Acho que posso imaginar como fazer isso fazendo o upload automático do arquivo quando o valor do campo mudar usando JavaScript puro, mas prefiro fazer tudo de uma só vez para enviar no jQuery. Eu estou pensando que não é possível fazer através de strings de consulta, pois eu preciso passar todo o object de arquivo, mas estou um pouco perdido sobre o que fazer neste momento.

Isso pode ser conseguido?

Não é tão difícil. Em primeiro lugar, dê uma olhada na Interface FileReader .

Então, quando o formulário é submetido, pegue o processo de submissão e

 var file = document.getElementById('fileBox').files[0]; //Files[0] = 1st file var reader = new FileReader(); reader.readAsText(file, 'UTF-8'); reader.onload = shipOff; //reader.onloadstart = ... //reader.onprogress = ... < -- Allows you to update a progress bar. //reader.onabort = ... //reader.onerror = ... //reader.onloadend = ... function shipOff(event) { var result = event.target.result; var fileName = document.getElementById('fileBox').files[0].name; //Should be 'picture.jpg' $.post('/myscript.php', { data: result, name: fileName }, continueSubmission); } 

Então, no lado do servidor (ie myscript.php):

 $data = $_POST['data']; $fileName = $_POST['name']; $serverFile = time().$fileName; $fp = fopen('/uploads/'.$serverFile,'w'); //Prepends timestamp to prevent overwriting fwrite($fp, $data); fclose($fp); $returnData = array( "serverFile" => $serverFile ); echo json_encode($returnData); 

Ou algo parecido. Posso estar enganado (e, se estiver, por favor, corrija-me), mas isso deve armazenar o arquivo como algo como 1287916771myPicture.jpg em /uploads/ em seu servidor e responder com uma variável JSON (para uma function continueSubmission() ) contendo o nome do arquivo no servidor.

Confira fwrite() e jQuery.post() .

Na página acima, detalha como usar readAsBinaryString() , readAsDataUrl() e readAsArrayBuffer() para suas outras necessidades (por exemplo, imagens, vídeos, etc).

Com jQuery (e sem FormData API) você pode usar algo assim:

 function readFile(file){ var loader = new FileReader(); var def = $.Deferred(), promise = def.promise(); //--- provide classic deferred interface loader.onload = function (e) { def.resolve(e.target.result); }; loader.onprogress = loader.onloadstart = function (e) { def.notify(e); }; loader.onerror = loader.onabort = function (e) { def.reject(e); }; promise.abort = function () { return loader.abort.apply(loader, arguments); }; loader.readAsBinaryString(file); return promise; } function upload(url, data){ var def = $.Deferred(), promise = def.promise(); var mul = buildMultipart(data); var req = $.ajax({ url: url, data: mul.data, processData: false, type: "post", async: true, contentType: "multipart/form-data; boundary="+mul.bound, xhr: function() { var xhr = jQuery.ajaxSettings.xhr(); if (xhr.upload) { xhr.upload.addEventListener('progress', function(event) { var percent = 0; var position = event.loaded || event.position; /*event.position is deprecated*/ var total = event.total; if (event.lengthComputable) { percent = Math.ceil(position / total * 100); def.notify(percent); } }, false); } return xhr; } }); req.done(function(){ def.resolve.apply(def, arguments); }) .fail(function(){ def.reject.apply(def, arguments); }); promise.abort = function(){ return req.abort.apply(req, arguments); } return promise; } var buildMultipart = function(data){ var key, crunks = [], bound = false; while (!bound) { bound = $.md5 ? $.md5(new Date().valueOf()) : (new Date().valueOf()); for (key in data) if (~data[key].indexOf(bound)) { bound = false; continue; } } for (var key = 0, l = data.length; key < l; key++){ if (typeof(data[key].value) !== "string") { crunks.push("--"+bound+"\r\n"+ "Content-Disposition: form-data; name=\""+data[key].name+"\"; filename=\""+data[key].value[1]+"\"\r\n"+ "Content-Type: application/octet-stream\r\n"+ "Content-Transfer-Encoding: binary\r\n\r\n"+ data[key].value[0]); }else{ crunks.push("--"+bound+"\r\n"+ "Content-Disposition: form-data; name=\""+data[key].name+"\"\r\n\r\n"+ data[key].value); } } return { bound: bound, data: crunks.join("\r\n")+"\r\n--"+bound+"--" }; }; //---------- //---------- On submit form: var form = $("form"); var $file = form.find("#file"); readFile($file[0].files[0]).done(function(fileData){ var formData = form.find(":input:not('#file')").serializeArray(); formData.file = [fileData, $file[0].files[0].name]; upload(form.attr("action"), formData).done(function(){ alert("successfully uploaded!"); }); }); 

Com a API FormData, basta adicionar todos os campos do formulário ao object FormData e enviá-lo via $ .ajax ({url: url, data: formData, processData: false, contentType: false, tipo: "POST"})