jQuery.when() – executar função depois de vários carregamentos ajax

Estou desenvolvendo um projeto para um cliente da GS Solutions onde um template será montado dinâmicamente no front-end através do consumo de vários JSONS.

Em um caso específico eu preciso gerar uma lista (array) de jogos, que estão divididos em 3 arquivos JSON diferentes e uma rotina deve ser executada depois desta lista populada. 

Como fazer isso de forma assíncrona após o carregamento dos 3 arquivos por ajax?

jQuery.when()

A função when do jquery permite que você execute um callback após uma série de ações terem sidos realizadas, como o carregamento assíncrono de quantas URLs você precisar.

Exemplo

Quero carregar jogos-1.json e jogos-2.json, percorrer os elementos de cada uma delas e adicionar em um array. Feito isso, quero percorrer este array e imprimir os resultados.

var jogos = [];
$.when(
	$.getJSON('jogos-1.json'),
	$.getJSON('jogos-2.json')
).done(function(jogos1,jogos2){
	/* Será executado somente após as 2 requisições ajax serem carregadas
	Aqui você poderia manipular os dados (jogos1 e jogos2),
	fazer merge dos objetos, ordenar, etc. */
	for (var jogo in jogos1){ jogos.push(jogo) }
	for (var jogo in jogos2){ jogos.push(jogo) }
});

Um exemplo bem simples mas que exemplifica uma forma eficiente de fazer o carregamento assíncrono mesmo quando nós precisamos de todos os request completos antes de executar o callback.

Você pode adicionar quantos eventos forem necessários como parâmetros na função when e deve adicionar o mesmo número de parâmetros na função dentro do done, cada parâmetro representa um resultado do when.

Tem mais dicas? Escreva nos comentários!

Criando sua própria classe para requisições assíncronas – ajax

Sabe esses frameworks javascript que facilitam a nossa vida? Que tem funções, crossbrowsers, prontas para consultas ajax? Então, que tal ver como implementar isso?

E, não, não quero reinventar a roda! Serve mesmo para estudos ou no caso em que não é possível adotar um framework – sim, de vez em quando precisamos implementar algo só com funções nativas do javascript por requisitos do sistema…

Primeiro, criei uma função javascript chamada MyAjaxLoader(). Nela, instancio um objeto que fará as nossas consultas, de uma forma crossbrowser. Esse código, que verifica se existe o ActiveX (browsers IE) ou não, já foi muito explorado em diversos blogs. Indico essa leitura.
O próximo passo é criar uma função load() e adicioná-la a função MyAjadLoader() com a propriedade prototype (não confundir isso com a biblioteca homônima). Essa técnica permite criarmos funções com funções dentro, o que de uma certa forma pode ser abstraído para uma classe.
Essa função recebe dois parâmetros: a url que será requisitada e uma lista de chaves/valor de opções. Nessa lista de chaves/valor estou tratando apenas duas chaves: params, que podem ser os parâmetros a serem passados para a url e onSuccess, a função que será chamada em caso de sucesso na requisição (veja aqui texto sobre como passar funções de callback)

Abaixo, segue um exemplo funcional da classe!

<html>
<head>
<title>Criando sua própria classe para requisição assíncrona (ajax)</title>
<script type="text/javascript">

/**
* classe para requisições assíncronas – código nativo
*/
function MyAjaxLoader() {

    try {
        xmlhttp = new ActiveXObject("Msxml2.XMLHTTP");
    } catch (e) {
        try {
        xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
        } catch (E) {
        xmlhttp = false;
        }
    }

    if  (!xmlhttp && typeof  XMLHttpRequest != "undefined" ) {
        try  {
        xmlhttp = new  XMLHttpRequest();
        } catch  (e) {
        xmlhttp = false ;
        }
    }
    this.xmlhttp = xmlhttp;

}

/**
* método que recebe a url a ser requisitada e pode receber parâmetros adicionais:
* params: parâmetros da url
* onSuccess: função de callback
*/
MyAjaxLoader.prototype.load = function(url, p) {
    //verifica se tem no parâmetro uma chave igual a params; se tiver, concatena com a url
    if(p.params)
        var myUrl = url + "?" + p.params;
    else
        var myUrl = url;
    //abre a conexão
    xmlhttp.open("get", myUrl);
    //faz a requisição
    xmlhttp.send(null);
    var thisMMP = this;
    //verifica os estados de transição
    xmlhttp.onreadystatechange = function() {
            //4 é o final da requisição
            if(xmlhttp.readyState==4) {
                //erro 404: não existe a url ou houve erro ao requisitar
                if(xmlhttp.status==404) {
                    alert("Erro: url não encontrada");
                    return;
                }
                //verifica se tem no parâmetro uma chave igual a onSuccess; se tiver, chama a
                //função de callback passando o resultado da requisição
                if(p.onSuccess)
                    p.onSuccess.call(this, xmlhttp.responseText);   
        }
    }
}

var myAjax = new MyAjaxLoader();
myAjax.load("tmp.htm", {params: "id=1&method=x", onSuccess: functionOk});

/* função de callback */
function functionOk(data) {
    alert(data);
}

</script>
</head>
<body>
</body>
</html>

Usando a Prototype – passo 2 – submit de form por ajax

Uma das coisas básicas em termos de programação para web é fazer um formulário em que você envia os dados para uma página que recebe os mesmos e insere (ou atualiza) uma tabela no banco de dados.
Vou mostrar abaixo como fazer isso de uma forma mais bacana, por ajax (sem precisar dar submit mesmo no form) com a ajuda da prototype (seguindo a série de posts), e totalmente acessíve. (se o usuário estiver num browser com o javascript desabilitado, funcionará corretamente, da forma tradicional).

Como falei, usaremos a prototype. Mais especificamente, o método request() da classe Form. Esse método serializa os dados do formulário e faz o request usando o método request() da classe Ajax da própria prototype, usando a action do form como url.
Obs.: O que é serializar os dados do form? O método percorre todos os elementos de dados de um formulário (inputs, selects, textareas…) e monta uma string no formato [nome do primeiro elemento]=[valor do primeiro elemento]&[nome do segundo elemento]=[valor do segundo elemento]&..&[nome do último elemento]=[valor do último elemento]
Mais sobre a serialização: http://www.prototypejs.org/api/form/serialize

No exemplo abaixo, ao clicar no botão submit do form, a função javascript é chamada. Essa função recebe o próprio form como parâmetro, e já chamo diretamente o método request() que faz o request para edit.php
No retorno da função (chamado de callback) eu verifico o que é retornado por esse edit.php
Nele, eu defini que se der algum erro será jogado o valor -1, e em caso de sucesso, 1 (e, nesse caso, chamo o método reset() que apaga todos os dados preenchidos no formulário, deixando-o pronto para uma nova inserção)

Código funciona html + javascript: 

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<title>Exemplo Prototype - form</title>
<script src="http://www.prototypejs.org/assets/2008/1/25/prototype-1.6.0.2.js"></script>
<script type="text/javascript">
    function submitForm(form) {
        /*
        usa método request() da classe Form da prototype, que serializa os campos
        do formulário e submete (por POST como default) para a action especificada no form
        */
        form.request({
          onComplete: function(transport){
              /*
            se o retorno for diferente de -1, entende-se que não houve problemas, então apaga-se
            os campos do formulário usando o método reset() da classe Form
            */
            if(transport.responseText!=-1)
                form.reset();
            }
        });
        return false;
    }
</script>
</head>
<body>
<h1>Submeter form com prototype</h1>
<form id="edit" name="edit" action="edit.php" onsubmit="submitForm(this); return false;">
    <label for="name">nome:</label><input type="text" name="name" id="name" />
    <label for="desc">descrição:</label><input type="text" name="desc" id="desc" />
    <label for="quant">quantidade:</label><input type="text" name="quant" id="quant" />
    <input type="submit" value="salvar" />
</form>
</body>
</html>

Segue abaixo também o código da página edit.php; vale lembrar que independe se é um php ou asp ou qualquer outra coisa. Conforme já comentei nesse post aqui  a linguagem usada no server-side não tem impacto algum na implementação javascript – devem ser implementações totalmente desacopladas (até porque uma é server-side, e outra client-side).

<?php
$bdConnection = mysql_connect("localhost", "root", "");
mysql_select_db("exemplos_andafter");
$sql = "INSERT INTO `produtos` (`name`, `desc`, `quant`) VALUES(´" . $_POST["name"] . "´, ´" . $_POST["desc"] . "´, ´" . $_POST["quant"] . "´)";
$result = mysql_query($sql) or die("-1");
echo("1");
?>

Qualquer dúvida, comentem 🙂

 

Ajaxload – imagens para preload

Quer colocar uma imagem no seu site enquanto você faz algo – espera uma imagem carregar, ou aguarda o retorno de uma função ajax?
Entre em http://www.ajaxload.info/ e gere a imagem que você quiser, no formato gif.

Dá para customizar o tipo (quase 40 formatos), cor e cor de fundo (pode ser transparente).

Site já antigo, mas vale a referência 🙂

A variável "Mochileiro das Galáxias" no javascript

O termo é novo, mas o conceito é antigo. Sabe aquela variável que você cria no javascript, global, que você acessa em qualquer função, em qualquer js? Eis o seu mochileiro da galáxia!

O conceito é simples, e foi batizado por autores de livros sobre Java usando o famoso livro Guia do Mochileiro da Galáxia; no livro, o protagonista descobre que seu melhor amigo é um extraterrestre e logo após, a Terra é destruída; a partir daí, começa uma grande viagem por várias galáxias. Na programação Java, é comum ver desenvolvedores criarem objetos (esqueça o conceito de variável por enquanto) que viajam entre diversas camadas da aplicação, funcionando como faz-tudo, passando por cima de qualquer escopo e, muitas vezes, tornando impossível descobrir quem está realmente alterando o quê no seu programa.
* a primeira vez que vi esse termo foi num artigo da revista Mundo Java, exposto em partes aqui.

 Agora, imagine: se em Java que a orientação a objetos é algo inerente ao desenvolvimento, imagine a dor-de-cabeça que tais variáveis (podem ser objetos também) podem causar no javascript, onde o desenvolvedor pode simplesmente implementar da forma que quiser – e, tenham certeza, a forma mais rápida é sempre aquela que dá mais brecha para problemas bizarros.
E digo isso por experiência própria: quantas vezes eu não criei uma variável global no javascript, e meses depois ia lá implementar (ou alguém da equipe ia pegar meu código) algo e do nada coisas começavam a quebrar… até descobrir que eu havia criado 2 variáveis globais!!!

Certamente a orientação a objetos ajuda -e muito – a diminuir essa questão. Para quem desenvolve e acha pataquada, dê um passo atrás e procure pelos benefícios da mesma. Existe claro a curva de aprendizado, há novos paradigmas para quem vêm da programação estruturada, é mais complexa, mas vale a pena a longo prazo.
Ao mesmo tempo, orientação a objetos não é tudo. Se fosse, implementações em Java não teriam tal problema.

Aqui vão algumas sugestões (e que podem valer para outras linguagens):

  • usar o mínimo possível de variáveis globais
  • dar nomes que façam sentido às suas variáveis, isso diminui o problema de você criar uma variável global com nomes iguais (como javascript é interpretado e não compilado, não existe uma verificação de variáveis sendo instanciadas com o mesmo nome, o que não ocorre no primeiro caso)
  • sempre que for criar uma variável, usar a diretiva var. Por exemplo, quando for num laço for usar um contador, fazer:
        for(var i=0; i<array.length; i++)
    

    Porque? Assim você garante que sua variável i comece sempre com o valor igual a 0 (vai por mim, já passei por muitos perrengues por ter variáveis em contadores não inicializadas)

  • documentar as funções/classes – o que elas fazem, quais e de onde vêm os parâmetros utilizados, etc…; isso ajuda muito você a, no futuro, entender porque aquela variável global está fazendo ali e de onde ela veio.
  • nunca, jamais nessa vida, utilizar variáveis globais em retornos de métodos ajax. Porque? Eles são assíncronos. Logo, você pode acessar uma variável que está com um valor qualquer num momento, assumir que está correta, e o valor mudar no instante seguinte – levando você a loucura sem entender porque o fluxo esperado não é seguido. E, denovo, vão por mim: já tive dores de cabeça com isso :-/

 

*se alguém tiver outras sugestões, por favor, enviem 🙂

E eu não estou aqui querendo dizer "não usem variáveis globais"; até porque eu sei o quão difícil é em muitos momentos desenvolver em javascript, devido às suas limitações. A idéia é apenas usar com cuidado 🙂