Drag and drop de múltiplos arquivos para upload via Ajax com HTML 5

{lang: 'pt-BR'}

Uma funcionalidade bem esperada do HTML5 é a capacidade de arrastar e soltar arquivos para dentro do browser. Desta forma, o usuário é capaz de fazer upload ou manipular arquivos, sem ter navegar por toda árvore de diretórios do sistema através da caixa de diálogo padrão do sistema.  É possível também fazer upload de múltiplos arquivos simultaneamente usando AJAX, e é isso que eu explicarei neste artigo.

Infelizmente, até o momento, a API de manipulação de arquivos ainda não está plenamente definida. O exemplo construído funciona apenas no Firefox e no Chrome.

O passo principal da especificação HTML 5 para oferecer o mecanismo de Drag and Drop, foi definir um conjunto de eventos para os diversos passos que executamos ao arrastarmos e soltarmos um objeto. E a API de Drag and Drop não está restrita à manipulação de arquivos. É possível manipular qualquer elemento visual da página, usando a nova API nativa de Drag and Drop.

Em nosso caso não precisamos nos preocupar com o objeto que será arrastado, pois o sistema operacional já irá tratá-lo. Nossos esforços estarão focados em tratar a zona alvo do arquivo. A idéia principal é adicionar event listeners aos eventos de drag and drop e tratá-los da maneira correta.

Para que elemento html aceite a operação de ‘drop’, é preciso que ele escute pelo menos um dos três eventos:

  1. dragenter: Usado para definir se o elemento aceitará ou não o drop de um objeto. Para aceitar, deve-se cancelar o compartamento padrão do evento.
  2. dragover: Determina que tipo de feedback será exibido ao usuário (mudança de cor, alpha etc).
  3. drop: Evento que permite realizar uma operação quando o usuário solta o objeto na zona alvo.

Existe ainda mais um evento dragleave que é executado quando o usuário deixa a zona alvo sem realizar o ‘drop’ do objeto.

Uma parte importante da manipulação de eventos de Drag and Drop é evitar o comportamento padrão do browser, e isso é feito com o método preventDefault() do objeto de evento

dropArea.addEventListener("dragover", function(event) {
	  event.preventDefault();
}, true);

O objeto de evento, obtido quando se escuta um determinado evento, transporta um outro objeto de tipo DataTransfer. Este objeto, entre outras coisas, contém a lista de arquivos que foram selecionados e arrastados para a zona alvo do Drag and Drop.

dropArea.addEventListener("drop", function(event) {
    for (var i = 0; i<allFiles.length; i++){
	var file = event.dataTransfer.files[i];
        // ...
    }
}

Uma outra novidade da API html5 é interface FileReader, que provê métodos para leitura de arquivos e eventos para manipular o resultado. Podemos juntar a interface FileReader com os arquivos contidos no objeto DataTransfer e fazer uma manipulação para criarmos um elemento do tipo <img> cujo atributo src é uma url fictícia, com o conteúdo do arquivo. Desta forma, criamos um preview do arquivo que será feito upload (assumindo que o arquivo será uma imagem).

var file = event.dataTransfer.files[0]
var img = document.createElement("img");
var reader = new FileReader();
reader.onloadend = function() {
	img.src = this.result;
}
reader.readAsDataURL(file);
img.classList.add("obj");
var content  = document.getElementById("content");
content.appendChild(img);

Por fim, precisamos realizar a operação de upload usando Ajax. Queremos fazer upload de todos os arquivos simultaneamente. Ainda não existe um consenso a respeito da maneira como os dados são enviados para o servidor. O objeto File, implementado pelo Firefox, possui o método getAsBinary(), que não é definida pela especificação. Já a versão 4, deste mesmo browser, trás o objeto FormData, que permite enviar os dados de forma mais simples e eficiente. O objeto FormData também foi implementado pelo Chrome e me parece que se firmará como futuro padrão.

A interface FormData é bem simples de ser usada e não exige manipulação direta do protocolo de comunicação. Através dela é possível associar identificadores/valores de um formulário e enviar todo o objeto via Ajax.

var f = new FormData();
var file = event.dataTransfer.files[0]
f.append("images", file);
var request = new XMLHttpRequest();
request.open("POST", "upload.php", true);
request.send(f);

O envio é feito de forma eficiente, e o arquivo não precisa ser todo armazenado em memória, como é o caso do segundo método de envio que veremos a seguir.

A segunda forma de envio, compatível com o Firefox, envia os dados via POST usando o header de transmissão de arquivos (multipart/form-data). Os dados do arquivo precisam estar em um formato específico, ditado pela RFC 2388. Criamos, portanto, uma grande string contendo todo o conteúdo dos arquivos:

var boundary = '------multipartformboundary' + (new   Date).getTime();
var dashdash = '--';
var crlf     = '\r\n';

/* String seguindo a RFC2388. */
var builder = '';

builder += dashdash;
builder += boundary;
builder += crlf;

/* Para cada arquivo que foi arrastado. */
for (var i=0; i<filesToUpload.length; i++){
       var file = event.dataTransfer.files[i];

       /* Gerar os headers. */
       builder += 'Content-Disposition: form-data; name="images[]"';
       if (file.fileName) {
           builder += '; filename="' + file.fileName + '"';
       }
       builder += crlf;

       builder += 'Content-Type: application/octet-stream';
       builder += crlf;
       builder += crlf; 

       /* Anexar os dados binários. */
       builder += file.getAsBinary();
       builder += crlf;

       builder += dashdash;
       builder += boundary;
       builder += crlf;
}

/* Marcar o final da requisição. */
builder += dashdash;
builder += boundary;
builder += dashdash;
builder += crlf;

Finalmente criamos a rquisição Ajax através de um objeto do tipo XMLHttpRequest e enviamos a string criada que contém o conteúdo dos arquivos já no formato adequado.

var request = new XMLHttpRequest();

request.open("POST", "upload.php", true);
request.setRequestHeader('content-type', 'multipart/form-data; boundary='+ boundary);
request.sendAsBinary(builder);        

request.onload = function(event) {
     alert("Arquivo enviado");
};

Voila! Múltiplos arquivos sendo enviados usando Drag and Drop por meio de requisições Ajax.

Você pode olhar o código fonte de meu exemplo: upload.zip
Adicionei também um arquivo PHP para manipular os arquivos que chegam no servidor. Não se esqueça que, para executar o exemplo, é preciso dar permissão de escrita na pasta upload/.

Bons estudos e até o próximo artigo.

{lang: 'pt-BR'}
  • Salvador Torres

    Tutorial perfeito! bem explicado e simples!
    Como disse Vinicius, difícil achar um blog com um conteúdo tão bom ultimamente!
    Vou acompanhar sempre também.

  • Ricardo Paiva

    Obrigado pessoal. O objetivo é criar um conteúdo que seja útil e em português. Eu gostaria de escrever mais, mas o tempo está escasso, então tento fazer o meu melhor quanda sobra um tempinho. Espero que o artigo tenha ajudado. Abraços.

  • Anselmo Gomes

    Post espetacular! Parabéns!

  • Jayr

    COMO inserir no Banco de dados

  • Jayr

    Muito bom o gódigo. Como colocar uma progressbar?