Afinal, o que é AJAX?

Para entender o AJAX, é interessante recapitular o funcionamento de requisições / respostas e a exibição de paginas web, e soluções para problemas comuns que ocorrem com essas operações. Numa paginas web, quando o usuário clica num link ou submete um formulário,o servidor processa as informações submetidas e devolve como resposta outra página web completa, que então é renderizada pelo browser em substituição à primeira. Um problema é que o atraso da transferência dos dados, somando ao tempo de renderização pelo browser resulta na apresentação de páginas vazias e lentidão na resposta a ações do usuário.

Para minimizar esse efeito, uma abordagem é fazer a submissão de parâmetros e dados por um “canal separado” (através de uma thread própria dentro do browser) utilizando JavaScript. Em seguida o servidor devolve a resposta através do mesmo canal, evitando a necessidade de uma nova renderização da página. Dessa forma, a resposta não terá o conteúdo de uma página inteira, mas apenas os dados que necessitam ser modificados (ex.: o conteúdo de uma combobox). Um trecho de código JavaScript se encarrega de manipular esses dados e atualizar a página – e é dada a impressão para o usuário de uma aplicação mais interativa e responsiva.

Tal idéia existe desde que se começou a usar scripts para manipular elementos de páginas HTML. Inicialmente, a técnica do “canal separado” era implementada por meio de um frame HTML de tamanho mínimo, que realizava a requisição e obtinha como resposta uma pagina contendo, além dos dados, um script que atualizava o frame principal. O problema é que tal abordagem aumenta demais a complexidade de gerenciamento. Construir uma página desse modo exige uma página contendo o frameset (que define o layout dos frames na janela), um frame principal para mostrar os dados, e uma ou mais páginas para implementar a atualização do frame principal (cada atualização normalmente precisa de uma página de resposta específica).

A situação melhorou com a introdução, no Internet Explorer 5, da classe XMLHttpRequest, que permite a requisição assíncrona de páginas web e a manipulação do conteúdo dessas páginas na forma de dados (desde que a página esteja num formato apropriado). Isso simplificou muito a implementação de requisições através de threads próprias dentro do browser, sem a necessidade de utilização de utilização de artifícios como frames escondidos.

Além disso, num esforço por maior compatibilidade entre browser, o grupo Mozilla implementou uma classe equivalente ao XMLHttpRequest, o que foi seguido por outros browsers como Safari, Konqueror, OmniWeb e Opera. Criou-se então uma ambiente propício para atualização da classe XMLHttpRequest (ou equivalentes) por grandes aplicações como GMail, Google Earth, Google Suggest e Yahoo! Flickr, entre outras.

A técnica de criar uma página atualizada dinamicamente e parcialmente através de requisições ao servidor, utilizando o objeto XMLHttpRequest, ficou conhecida como AJAX.

Implementação AJAX

Após esse posicionamento histórico e tecnológico, está na hora de vermos a técnica em ação. Como exemplo, vamos criar uma página simples, onde temos uma combobox com projetos cadastrados num sistema: ao mudar o projeto selecionado, é mostrada logo abaixo da combo uma tabela com as atividades cadastradas naquele projeto.

A Listagem 1 apresenta a página de exemplo. A chamada a Fabrica.getProjetos() simula a obtenção de todos os projetos cadastrados no sistema; sua implementação não é discutida neste artigo, porém os fontes completos podem ser obtidos no site da Java Magazine.

O fragmento JavaScript abaixo cria a referência para o objeto XMLHttpRequest:


var xmlhttp=false;

try {

   xmlhttp=new ActiveXObject(“Msxml2.XMLHTTP”);

} catch (e) {

   try { xmlhttp=new ActiveXObject(“Microsoft.XMLHTTP”);}

   catch (E) { xmlhttp=false;}

}

if (!xmlhttp && typeof XMLHttpRequest!=’undefined’){

 xmlhttp=new XMLHttpRequest();

}

Este script é encontrado como exemplo em vários sites sobre AJAX e tem sido considerado um dos padrões para a criação da instância de XMLHttpRequest de forma independente de browser. Inicialmente, tenta-se obter a instância de Msxml12.XMLHTTP ou Microsoft.XMLHTTP, para o Internet Explorer (o nome da classe depende da versão da biblioteca de XML da Microsoft instalada na máquina cliente). Caso o browser tenha a sua própria implementação do tipo XMLHttpRequest, o script cria uma instância desse tipo no bloco condicional no final do script.

O evento onchange da combobox invoca a função JavaScript alteraProjeto():


function alteraProjeto() {

  var theUrl=”obtemAtividadesXML.jsp?projeto=”

     +document.getElementByld(“projeto”).value;

  xmlhttp.open(“GET,theUrl,true);

  xmlhttp.onreadystatechange=function() {

    if (xmlhttp.readyState==4) {

       criaTabela(xmlhttp.responseXML);

    }

  }

  xmlhttp.send(null);

};

Essa função requisita a página obtemAtividadesXML.jsp, passando como parâmetros a identificação do projeto selecionado. Note que a execução de uma chamada AJAX segue sempre os mesmos passos:

  1. É invocado o método open() do objeto XMLHttpRequest, passando o método http desejado (normalmente GET ou POST), além da URL a ser requisitada e um flag indicando se a chamada é assíncrona ou não.
  2. Como a chamada é assíncrona, é definido no nosso caso um manipulador de eventos para notificar quando a resposta do servidor estiver completa (readyState igual a 4). Será invocada a função criaTabela(), passando-se o conteúdo XML obtido como resposta do servidor.
  3. Caso o flag passado a open() seja false, a chamada do método será síncrona, ou seja, o método send() irá bloquear a execução do script até que a resposta completa da requisição chegue do servidor. Nesse caso, a resposta do servidor pode ser obtida logo após o send(), sem a necessidade de criação de um manipulador de eventos. Entretanto, isso fará com que a página “congele” até que a resposta chegue, diminuindo a percepção de interatividade pelo usuário.
  4. Finalmente, o método send() realiza o envio da requisição, passando como parâmetro o corpo da mensagem a ser enviado (no caso de uma requisição POST).

A página obtemAtividadesXML.jsp, apresentada na Listagem 2, é responsável por gerar um documento XML com os dados de atividades do projeto passado como parâmetro. A Listagem 3 apresenta o resultado da execução da página JSP: esse XML será utilizado para facilitar a explicação do código JavaScript que faz a criação da tabela.

A página JSP utiliza somente conceitos de JSP e JSTL. O único detalhe que vale a pena mencionar é a inclusão da tag <pag> definindo o tipo de conteúdo da resposta (contentType) como sendo text/xml. Isso é importante para que o objeto XMLHttpRequest no browser consiga montar as estrutura XML a partir desse conteúdo.

Um ponto muito importante é que a manipulação dos dados de resposta como uma estrutura XML dentro do JavaScript requer duas condições:

  • O content-type de página de resposta deve ser definido como text/xml.
  • Os dados de resposta precisam ser obtidos através da propriedade responseXML do objeto XMLHttpRequest.

Finalmente, a função JavaScript criaTabela() monta uma tabela HTML utilizando os dados XML obtidos pelo objeto XMLHttpRequest, e define essa tabela como valor do campo innerHTML do elemento

da página, que tem id igual a tabela, fazendo com que a tabela desejada apareça na tela.

Dentro dessa função, toda a manipulação dos dados do XML é feita através do método getElementByTagName() do objeto XMLDocument. Esse método recebe como parâmetro um nome da tag e retorna uma lista de elementos XML referentes à tag passada. Cada elemento XML será representado pela sua estrutura DOM. Assim, a linha:


var data = AjaxResponse.getElementByTagName(“atividade”);

Atribui à variável data um array com três elementos (considerando como resposta o XML da Listagem 3). Cada elemento representa a estrutura DOM de cada atividade definida dentro da tag raiz <dados>. Com isso, definimos os conceitos básicos para o próximo comando, que é responsável por obter o valor das tags < descricao>, <dataInicial> e <dataFinal>, dentro de cada tag <atividade>:


var desc=data[i];getElementsByTagName(“descricao”).item(0);

Como já visto, getElementsByTagName() retorna um array com as estruturas DOM representando os elementos associados à tag passada. Neste caso, o método retorna uma lista de apenas um elemento, pois só existe uma tag < descricao> dentro de < atividade>. Como o retorno é uma lista e sabemos que haverá um só elemento, usamos diretamente uma chamada item(0). Dessa forma, a variavel desc contém a estrutura DOM da tag < descricao>

Deve-se tomar cuidado com o escopo do objeto sobre o qual é invocado o método getElementByTagName(). Por exemplo, data[i].getElementsByTagName(“descricao”) retorna uma lista com apenas um elemento. Por outro lado, invocar AjaxResponse.getElementsByTagName(“descricao”) retorna um array com três elementos para o documento XML na Listagem 3. Isso porque a variável AjaxResponse representa o documento total, e este contém três tags <descricao> dentro do seu contexto (uma em cada tag < atividade>).

Alguma confusão às vezes acontece quando é necessário obter o texto que está dentro de uma tag, mas não se está acostumado com a estrutura DOM. No exemplo, o texto da tag <descricao> é obtido com o seguinte comando:


desc.firstChild.data

Pode parecer estranha a construção firstChild.data: como firstChild denota a primeira tag filha de uma tag, e <descricao> não possui tags filhas, então porque fazer o acesso dessa forma? A questão é que em DOM, o texto dentro de uma tag na realidade é o valor de uma tag filha especial. Assim, para obter a string “texto” dentro de <descricao>, é necessário primeiro obter essa tag filha (através da propriedade firstChild) para então obter o texto a partir da sua propriedade data.

Listagem 1. listaXML.jsp.

<%@ page import=”jm.ajax.data.*”%>

<%@ taglib uri=http://java.sun.com/jsp/jstl/core” prefix=”c”%>

 

<script type=”text/javascript”>

var xmlhttp=false;

  try {

   xmlhttp = new ActiveXObject(“Msxml2.XMLHTTP”);

  }  catch (e) {

      try { xmlhttp = new ActiveXObject(“Microsoft.XMLHTTP”); }

         catch (E) { xmlhttp = false; }

  }

 

if (!xmlhttp && typeof XMLHttpRequest!=’undefined’) {

   xmlhttp = new XMLHttpRequest();

}

function alteraProjeto()

{

 

    var theUrl = “obtemAtividadesXML.jsp?projeto=”

        + document.getElementById(“projeto”).value;

    xmlhttp.open(“GET”, theUrl,true);

    xmlhttp.onreadystatechange-function() {

         if (xmlhttp.readyState==4) {

           criaTabela(xmlhttp.responseXML);

         }

    }

    xmlhttp.send(null);

};

 

function criaTabela(AjaxResponse)

{

    var data = AjaxResponse.getElementsByTagName(“atividade”);

    var htmlText = “<table border=’1’>”

       +”  <tr>”

       +”     <th>Descrição</th>”

       +”     <th>Data Inicial</th>”

       +”     <th>Data Final</th>”

       +”  </tr>”

 

       for(i=0;i<data.length;i++)

       {

          var desc = data[i].getElementsByTagName(

               “descrição”).item(0);

          var dataInicial = data[i].getElementsByTagName(

               “dataInicial”).item(0);

          var dataFinal = data[i].getElementsByTagName(

               “dataFinal”).item(0);

          htmlText=htmlText+”<tr><td>”

           + desc.firstChild.data+”</td><td>”

           + dataInicial.firstChild.data+”</td><td>”

           + dataFinal.firstChild.data+”</td><td></tr>”;

          }

       htmlText=htmlText+”</table>”;

       document.getElementById(“tabela”).innerHTML=htmlText;

}

</script>

 

<html>

<c:set var=”projetos” value=”<%= Fabrica.getProjetos()%>” />

 

<center>

<select id=”projeto” onchange=”javascript:alteraProjetos()”>

   <c:forEach var=”p” items=”${projetos}”>

      <option value=”${p.id}”>${p.nome}</option>

   </c:forEach>

</select>

  <br><br>

<div id=”tabela”></div>

</center>

</html>
Listagem 2. obtemAtividadesXML.jsp.

<%@ page import=”jm.ajax.data.*” %>

<%@ page contentType=”text/xml” %>

 

<%@ taglib uri=http://java.sun.com/jsp/jstl/core” prefix=”c” %>

<%@ taglib uri=http://java.sun.com/jsp/jstl/fmt” prefix=”fmt” %>

 

<c:set var=”atividades” value=

      “<%= Fabrica.getAtividades(request.getParameter(“projeto”))%>” />

 

 

<dados>

    <c:forEach var=”atividade” items=”${atividades}”

              varStatus=”status”>

      < atividade id=”${atividade.id}”>

          <descricao>${atividade.descricao}</descricao>

          <dataInicial>

             <fmt:formatDate value=”${atividade.dataInicial}”

                   dataStyle=”short”/>

          </dataInicial>

          <dataFinal>

               <fmt:formatDate value=”${atividade.dataFinal}”

                   dateStyle=”short”/>

          </dataFinal>

      < /atividade>

    </c:forEach>

</dados>
Listagem 3. Exemplo de conteúdo XML obtido a partir da execução de obtemAtividadesXML.jsp.

<?xml version=”1.0” encoding=”UTF -8”?>

<dados>

    < atividade id=”1”>

       <descricao>Instalar Laszlo</descricao>

       <dataInicial>01/08/05</dataInicial>

       <dataFinal>01/08/05</dataFinal>

    < /atividade>

    < atividade id=”2”>

       <descricao>Criar o projeto exemplo</descricao>

       <dataInicial>02/08/05</dataInicial>

       <dataFinal>08/08/05</dataFinal>

    < /atividade>

    < atividade id=”3”>

       <descricao>Escrever o artigo</descricao>

       <dataInicial>10/08/05</dataInicial>

       <dataFinal>20/08/05</dataFinal>

    < /atividade>

</dados>

Implementando AJAX com JSON

Pessoalmente, acho a manipulação da resposta do servidor através do modelo DOM pouco prática, além de não ser reutilizável – principalmente quando é necessário manipular estruturas de respostas mais complexas, com vários níveis de tags aninhadas. Melhor seria se pudéssemos manipular diretamente objetos JavaScript, o que facilitaria a criação de rotinas AJAX reutilizáveis, que pudessem ficar definidas num arquivo de inclusão separado, ou mesmo ser encapsuladas em taglibs JSP.

Para isso, podemos utilizar a notação JSON (JavaScript Object Notation) em vez de XML, para a formatação dos dados nas páginas de resposta. A vantagem é que o JavaScript consegue, a partir de um documento escrito em JSON, criar automaticamente a estrutura de objetos correspondente, o que não seria possível para uma estrutura XML.

Você pode ver uma definição mais formal de JSON no links, mas por ora, o que é necessário saber que é essa notação representa uma estrutura de objetos através da definição de pares atributo-valor na seguinte forma:


{atr1:vlr1,atr2: vlr2, ..., atrN, vlrN}

Um array de objetos escritos em JSON é uma seqüência de definições de objetos, como a mostrada acima, separadas por vírgulas e delimitadas por colchetes:


[{atr1.1:vlr1.1,...,atr1.N,vlr1.N}, {atr2.1,vlr2.1,...,atr2.N:vlr2.N},...]

Para aplicar estruturas JSON no nosso exemplo, primeiro alteramos a página de resposta para que as informações sobre as atividades associadas a um projeto sejam escritas nessa notação e não em XML. A alteração pode ser vista na Listagem 4. Note que não é mais necessário definir o conteúdo da resposta como text/xml. Na realidade, não fará diferença, ao usar JSON, qual o tipo de conteúdo de retorno, desde que seja do grupo text/xxxx.

A Listagem 5 apresenta a página de lista de atividades, alterada para o suporte a JSON. As diferenças mais significativas são:

  • Utilização da propriedade responseText em vez de responseXML: no comando criaTabela(xmlhttp.responseText) é passada como parâmetro a propriedade responseText do objeto XMLHttpRequest, uma vez que a resposta obtida do servidor não está mais no formato XML.
  • Criação do array de elementos < atividade> através da função javascripEval(): o comando var data=eval(AjaxResponse) cria o array de atividades, em vez do método getElementsByTagName(“atividade”) utilizado na versão anterior. Isso porque, como a resposta do servidor já está na notação JSON, o resultado da avaliação já será um array JavaScript válido.
  • Acesso aos dados da atividade através das propriedades do objeto: o comando data[i].descricao imprime o valor da propriedade descricao da atividade que está sendo analisada, de forma mais legível do que o seu equivalente na versão anterior:

 data[i].getElementsByTagName(

    “dataInicial”).item(0).firstChild.data

Note como se tem maior simplicidade e legibilidade com o modo de acesso em JSON. Cada elemento do array data é um objeto JavaScript com as propriedades descricao, dataInicial e dataFinal, as quais podem ser acessadas diretamente através do operador “ponto”. Enquanto que em uma estrutura XML, sempre seria necessário navegar dentro da hierarquia DOM para obter os dados desejados.

Listagem 4. obtemAtividades.jsp:página JSP de obtenção de dados em JSON.

<%@ page import=”jm.ajax.data.*” %>

<%@ page contentType=”text/xml” %>

<%@ taglib uri=http://java.sun.com/jsp/jstl/core” prefix=”c” %>

<%@ taglib uri=http://java.sun.com/jsp/jstl/fmt” prefix=”fmt” %>

 

<c:set var=”atividades” value=

      “<%= Fabrica.getAtividades(request.getParameter(“projeto”))%>” />

[

<c:forEach var=”atividade” items=”${atividades}” varStatus=”status”>

{

    id:’${atividade.id}’,

    descricao:’${atividade.descricao}’,

    dataInicial: ‘<fmt:formatDate value=

        “${atividade.dataInicial}” dateStyle=”short”/>’,

    dataFinal: ’<fmt:formatDate value=

        “${atividade.dataFinal}” dateStyle=”short”/>’

    }

    <c:if test=”${not status.last}”>

 

    </c:if>

</c:forEach>

]
Listagem 5. Lista.jsp:página JSP de lista de projetos, adaptada para o uso de JSON.

<%@ page import=”jm.ajax.data.*” %>

<%@ taglib uri=http://java.sun.com/jsp/jstl/core” prefix=”c” %>

<script>

var xmlhttp=false;

try {

  xmlhttp = new ActiveXObject(“Msxml.XMLHTTP”);

}catch (e) {

  try { xmlhttp = new ActiveXObject(“Microsoft.XMLHTTP”);}

  catch(E) { xmlhttp = false; }

}

 

If (!xmlhttp && typeof XMLHttpRequest!=’undefined’) {

  Xmlhttp = new XMLHttpRequest();

}

 

function alteraProjeto()

{

   var theUrl = “obtemAtividades.jsp?projeto=”

        + document.getElementById(“projeto”).value;

   xmlhttp.open(“GET”, theUrl.true);

   xmlhttp.onreadystatechange=function() {

       If (xmlhttp.readyState==4) {

         criaTabela(xmlhttp.responseText);

       }

   }

   xmlhttp.send(null);

};

 

function criaTabela(AjaxResponse)

{

var data = eval(AjaxResponse);

var htmlText = “<table border=’1’>”+

     “ <tr>”+

     “     <th> Descrição</th>”+

     “     <th>Data Inicial</th>”+

     “     <th>Data Final</th>”+

     “ </tr>”

   for(i=0;i<data.length;i++)

   {

      htmlText=htmlText+”<tr><td>”

         + desc.firstChild.data+”</td><td>”

         + dataInicial.firstChild.data+”</td><td>”

         + dataFinal.firstChild.data+”</td><td></tr>”

   }

   htmlText=htmlText+”</table>”;

   document.getElementById(“tabela”).innerHTML=htmlText;

}

</script>

 

<html>

    <c:set var=”projetos” value=”<%= Fabrica.getProjetos()%>” />

 

    <center>

    <select id=”projeto” onchange=”javascript:alteraProjetos()”>

        <c:forEach var=”p” items=”${projetos}”>

           <option value=”${p.id}”>${p.nome}</option>

        </c:forEach>

    </select>

    <br><br>

    <div id=”tabela”></div>

    </center>

</html>

Conclusões

AJAX é uma técnica que, de uma forma ou de outra, vem sendo aplicada em aplicações web há algum tempo. Recordo-me de um projeto em 2001 que utilizava um conceito semelhante, mas lançando mão de frames escondidos para realizar a tarefa de requisição de dados e atualização da página principal. Também tenho noticia de vários projetos que já faziam uso da atualização dinâmica de páginas através do objeto XMLHttpRequest, nessa mesma época.

Então podemos perguntar o porquê de tanto alarde e de um novo nome para uma técnica antiga. Existem várias discussões na internet sobre o tema, algumas atacando a empresa Adaptive Path por tentar assumir o crédito (veja links), outras argumentando que o AJAX não passa de oportunismo de alguns aproveitando a onda de interfaces internet “ricas” (RIAs ou Rich Internet Applications) e outros defendendo uma miríade de opiniões (contra e a favor) sobre o AJAX.

De uma perspectiva pragmática, minha opinião sobre AJAX é que se trata de uma técnica antiga que está sendo resgatada e destacada, principalmente porque o problema da incompatibilidade entre browser que existia no começo da década foi hoje em grande parte resolvido. O fato é que hoje em grande parte resolvido. O fato é que AJAX traz um benefício muito grande para as interfaces de aplicações web, com custo relativamente baixo. E levando em consideração o número de bibliotecas e tag libraries que estão surgindo com o objetivo de facilitar ainda mais a criação de interfaces AJAX, esta técnica deveria ser carta sempre disponível na manga do desenvolvedor web.

Finalmente, sempre que surge uma “grande nova onda” é importante observar o movimento dos grandes da indústria, em vez de ouvir os neo-xiitas tecnológicos de plantão:

  • TIBCO anunciou no JavaOne seu “AJAX Accelerator Program” e suporta o AJAX já na versão 2.4 do seu produto chamado “General Interface”.
  • A Sun divulgou que pretende incluir um framework AJAX no Java Studio Creator 2.
  • A Microsoft irá lançar a versão beta de um framework chamado “Atlas”, que visa incorporar um estilo de desenvolvimento “AJAX-like” de interfaces web.
  • A IBM incluiu AJAX em seu programa jStart, uma espécie de programa de consultoria para empresas, para suportar o uso das melhores tecnologias no desenvolvimento de suas soluções de TI. Particularmente, a IBM está apostando na integração de AJAX com web services.

Em resumo, nova ou não, a técnica AJAX apresenta uma visão diferente para o desenvolvimento de interfaces mais intuitivas e usáveis para o ambiente web, sem a necessidade de applets ou plug-ins. Desenvolvedor com AJAX “no braço” já não é mais uma tarefa complicada, e com os novos frameworks e taglibs que estão surgindo, a tarefa irá ficar mais simples ainda.

Então, para aqueles que (como eu) sempre consideraram JavaScript um “mal necessário”, fica a sugestão: talvez seja a hora de tirar a poeira daquele manual de JavaScript e começar a olhar a arquitetura de sistemas web de uma perspectiva mais interativa!

Confira também