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!