Upload Assíncrono de Arquivos com Mootools
Por questões de segurança, os navegadores não permitem que o JavaScript acesse o sistema de arquivos do cliente, inviabilizando o upload via ajax no ambiente web. Assim, temos que criar alternativas para o upload assíncrono. Vários já criaram soluções para o problema, mas a maioria como funções autônomas. Como sou fã da Mootools, decidi criar a minha solução, e vou aproveitar para explicá-la aqui.
A Lógica
A lógica para essa “gambiarra” não é nova, já existe desde que o HTML é HTML. Existe o atributo target da tag form. Através dele, podemos escolher em que janela queremos que seja feita a submissão. Então a página não recarregará, e o resultado do formulário será carregado em outra janela.
Aí surge o primeiro porém: não queremos abrir uma nova janela (pop-ups são chatos, não concorda?)! Lembro que, nas minhas primeiras experiências com HTML e frames, podíamos fazer com que links abrissem em determinado frame através do mesmo atributo. Mas como também não queremos frames, vamos utilizar um irmão mais novo: o iframe.
Simplificando, a idéia é enviar o formulário para um iframe invisível, e recuperar a resposta quando este tiver terminado de carregar.
Preparando o Script
Desde o começo, pensei em fazer o script bem integrado à Mootools, como se fosse um plugin. Para isso, usei o mesmo processo utilizado para todas as funcionalidades relacionadas ao HTML: estendi a classe Element com um método upload:
1 2 3 4 5 | Element.extend({ "upload": function(options) { if(this.getTag() != "form") return false } }) |
Também incluí uma pequena verificação na linha 3: se não for um formulário, não devemos submeter!
Criando o IFrame
Como não devemos deixar um iframe perdido pelo documento, vamos criá-lo pelo JavaScript também. Seguindo o código:
4 5 6 7 8 9 10 | var iFrame = new Element("iframe", { "id": "fileUpload", "name": "fileUpload", "styles": { "display": "none" } }) this.adopt(iFrame) window.frames["fileUpload"].name = "fileUpload" // IE... Sempre ele... |
Para criá-lo, utilizei a classe Element, nativa da Mootools. Em uma chamada, já defini todos os atributos necessários. Primeiro, precisamos de um ID para localizarmos o iframe quando for necessário. O nome serve para o iframe ser identificado no target. O estilo só serve pra esconder o iframe.
Logo abaixo, fiz com que o formulário “adotasse” o iframe. Ele precisa estar em algum lugar para funcionar… Ah, e a correção made-for-IE, não esqueça dela!
Agora, precisamos saber quando o iframe carregou, para podermos disparar um callback com o retorno da requisição. Para isso, usamos o método addEvent, também da Mootools. No “load” do iframe, disparamos uma função onComplete, definida no parâmetro lá na definição da função.
11 12 13 14 15 16 | iFrame.addEvent("load", function() { iFrame.removeEvent("load", arguments.callee) // removemos o evento, para que ele não dispare novamente if($type(options.onComplete) == "function") options.onComplete(iFrame.contentDocument.body.innerHTML) iFrame.remove.delay(20, iFrame) }) |
Note que usei a propriedade contentDocument para recuperar a resposta da requisição. Com a “Same Origin Policy” (Política de Mesma Origem), só conseguimos recuperar a resposta de requisições feitas dentro do mesmo domínio. Existem maneiras de se driblar isso, mas o script se tornaria muito menos versátil, pois não consegui uma forma de executar funções com a resposta.
Executado o papel do nosso iframe, não precisamos mais dele populando nosso HTML, mas não podemos removê-lo imediatamente, pois assim causaríamos um “loading” que não terminaria. Então “atrasei” a função em alguns milissegundos, evitando esse probleminha.
Adaptando o Formulário
Se estamos criando um script para isso, você não quer ir lá e definir todos os atributos manualmente, quer? Então fiz com que o próprio script cuidasse disso. Apenas defini o target, o encoding e o método do formulário (só por segurança, caso ele não tenha sidio definido):
17 18 19 20 21 22 23 | this.setProperties({ "target": "fileUpload", "enctype": "multipart/form-data", "encoding": "multipart/form-data", "method": "post" }) return this |
Código Completo
Juntando tudo isso, temos o código abaixo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | Element.extend({ "upload": function(options) { if(this.getTag() != "form") return false var iFrame = new Element("iframe", { "id": "fileUpload", "name": "fileUpload", "styles": { "display": "none" } }) this.adopt(iFrame) window.frames["fileUpload"].name = "fileUpload" iFrame.addEvent("load", function() { iFrame.removeEvent("load", arguments.callee) if($type(options.onComplete) == "function") options.onComplete(iFrame.contentDocument.body.innerHTML) iFrame.remove.delay(20, iFrame) }) this.setProperties({ "target": "fileUpload", "enctype": "multipart/form-data", "encoding": "multipart/form-data", "method": "post" }) return this } }) |
Utilização
Para utilizar o código, é ainda mais simples. Basta definir, no onSubmit do formulário, nossa função:
$(window).addEvent("load", function() { // primeiro esperamos a janela carregar $("form").addEvent("submit", function() { this.upload({"onComplete": function(response) { alert(response) } }) }) })
Se quiser ver um exemplo funcionando ou fazer download do script, veja Async Upload - Upload Assíncrono de Arquivos. E espero que eu tenha conseguido mostrar que há vida além de jQuery… Até mais!
Entry Details
Publicado em 25/05/2008 às 07:05 em Ajax, Frameworks

Acho muito gambiarra utilizar IFRAME para fingir um upload dinâmico de arquivos, o incrível que isto existe a eras e atualmente o pessoal utiliza (advanto “Web 2.0″). Seria uma gambiarra 2.0?
Prefico utilizar um approach em Flash mesmo (nem vamos considerar o fato “a nem todo mundo tem Flash”), já tem até um próprio plugin para o Mootols, chamado FancyUpload. Não deixa de ser uma gambi tambem, mas lhe oferece um GRANDE leque de opções (como ver o progresso do envio do arquivo) ;). Gambiarra “xique”
Comentei sobre ele no meu blog: FancyUpload - Swiff meets Ajax
Vale a pena vocês escreverem sobre ele!
[]’s