Asynchronous JavaScript Technology and XML (AJAX)

O problema

Introdução







Funcionamento

Para mostrar o funcionamento, vamos considerar uma página web (estática ou desenvolvidida utilizando JSP) contendo uma caixa de texto, quando o usuário digita algo, uma mensagem ao lado informa se o conteúdo digitado é válido ou não (sem fazer refresh na tela).

A figura a seguir descreve em detalhes o funcionamento dessa validação.
Funcionamento:
  1. Ocorre um evento do cliente
  2. Um objeto XMLHttpRequest é criado e configurado
  3. O objeto XMLHttpRequest criado realiza uma chamada
  4. A requisição é processada pelo servlet
  5. O servlet retorna um documento XML contendo o resultado
  6. O objeto XMLHttpRequest chama a função callback que processa o resultado
  7. O DOM da página é atualizado

1. Ocorre um evento do cliente

Utilizando JavaScript é possível chamar uma função toda vez que ocorre um evento.

Nesse caso a função validate será chamada toda vez que o usuário pressionar uma tecla dentro da caixa de texto.
<input type="text" size="20" id="userid" name="id" onkeyup="validate();">

2. Um objeto XMLHttpRequest é criado e configurado

A função validate cria um objeto XMLHttpRequest (chamando a função createXMLHttpRequestObject na linha 14).

Em sequida, é chamada a função open do objeto. Três parâmetros são necessários:
Se a chamada for feita de forma assíncrona, a função de callback precisa ser especificada. Essa função deve ser especificada da seguinte forma:
01 var req;
02
03 function createXMLHttpRequestObject() {
04 if (window.XMLHttpRequest) {
05 req = new XMLHttpRequest();
06 } else if (window.ActiveXObject) { //IE
07 req = new ActiveXObject("Microsoft.XMLHTTP");
08 }
09 }
10
11 function validate() {
12 var idField = document.getElementById("userid");
13 var url = "validate?id=" + escape(idField.value);
14 createXMLHttpRequestObject();
15 req.open("GET", url, true);
16 req.onreadystatechange = callback;
17 req.send(null);
18 }

3. O objeto XMLHttpRequest criado realiza uma chamada

Observe que no exemplo anterior o objeto foi apenas criado e configurado. Ainda não falamos da linha 17, onde finalmente o objeto realiza a chamada.

É possível fazer dois tipos de requisições utilizando o objeto XMLHttpRequest
req.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
req.send("id=" + escape(idTextField.value));

4. A requisição é processada pelo servlet

O servlet é responsável por processar a requisição e retornar um documento XML.

Note que o tratamento de uma requisição XMLHttpRequest é semelhante a de qualquer outra requisição.
public class ValidateServlet extends HttpServlet {

private ServletContext context;
private List <String> listOfExpectedIds = new ArrayList<String>();

public void init(ServletConfig config) throws ServletException {
this.context = config.getServletContext();
listOfExpectedIds.add("daca");
listOfExpectedIds.add("turma");
listOfExpectedIds.add("ajax");
}

public void doGet(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException {
if (request.getRequestURI().endsWith("/validate")) {
processaValidate(request, response);
}
}

private void processaValidate(HttpServletRequest request, HttpServletResponse response) throws IOException {
String id = request.getParameter("id");
response.setContentType("text/xml");
response.setHeader("Cache-Control", "no-cache");
if ( listOfExpectedIds.contains(id) ) {
response.getWriter().write("<message>valid</message>");
} else {
response.getWriter().write("<message>invalid</message>");
}
}
}

5. O servlet retorna um documento XML contendo o resultado

Durante o tratamento da requisição, o servlet deve verificar se o texto digitado pelo usuário existe ou não em nossa lista de nome pré-definidos.
Caso exista, o servlet deve retornar um documento XML no seguinte formato:
<message>valid</message>
Caso contrário,
<message>invalid</message>
Além disso, o servlet precisa setar mais dois parâmetros na requisição:
response.setContentType("text/xml");
response.setHeader("Cache-Control", "no-cache");

6. O objeto XMLHttpRequest chama a função callback que processa o resultado

Para entendermos melhor como funciona o método callback, precisamos entender um pouco mais da API da classe XMLHttpRequest.

Sejam alguns atributos importantes dessa API:
Dessa forma, para que possamos tratar o resultado de uma requisição XMLHttpRequest, é necessário que o valor do atributo readyState seja 4 e do atributo status seja 200.

Veja um exempo:
function callback() {
if (req.readyState == 4) {
if (req.status == 200) {
//
}
}
}
Agora precisamos manipular o documento XML recuperado da requisição.

Browsers possuem uma representação na forma de objeto dos documentos apresentados. Essa representação segue uma especificação da W3C conhecida como DOM (Document Object Model).

Utilizando JavaScript é possível acessar o DOM de uma página HTML e atualizar o conteúdo, estrutura e estilo das páginas HTML.

Da mesma forma, tembém é possível acessar o conteúdo do documento XML resultado da requisição do objeto XMLHttpRequest.

Existem dois atributos no objeto XMLHttpRequest que podem ser utilizados para recuperar o conteúdo XML; são eles:
Seja o seguinte exemplo:
<message>valid</message>
Para acessar o conteúdo da tag message,
function parseMessage() {
var message = req.responseXML.getElementsByTagName("message")[0];
setMessage(message.childNodes[0].nodeValue);
}
Caso o documento XML fosse:
<message>
<curso>Computação</curso>
<disciplina>DACA</disciplina>
<disciplina>ATAL</disciplina>
</message>
Veja como seria o código para acessar o conteúdo das tags XML do exemplo acima.
var message = req.responseXML.getElementsByTagName("message")[0]
var curso = message.getElementsByTagName("curso")[0].childNodes[0].nodeValue;
for (var cont=0; cont < message.getElementsByTagName("disciplina").length; cont++) {
var disciplina = message.getElementsByTagName("disciplina")[cont].childNodes[0].nodeValue;
}
Agora só falta atualizar o DOM de nossa página e ver, finalmente, ela sendo atualizada sem fazer refresh!!!!

7. O DOM da página é atualizado

Antes de mostrar como iremos atualizar o DOM de nossa página, vamos analisá-la mais atentamente.
<html>
<head>
<script type="text/javascript">
....
</head>
<body>
<input type="text" size="20" id="userid" name="id" onkeyup="validate();">
<div id="userIdMessage"></div>
</body>
</html>
Observe que o DOM de nossa página possui dois elementos:

Resultado esperado de nossa página quando digitamos algo que não existe!


Invalid User Id

Veja como implementamos o método que atualiza o DOM da página. 
function setMessageUsingDOM(message) {
var userMessageElement = document.getElementById("userIdMessage");
if (message == "invalid") {
messageText = "<div style=\"color:red\">Invalid User Id</ div>";
} else {
messageText = "<div style=\"color:green\">Valid User Id</ div>";
}
userMessageElement.innerHTML = messageText;
}
Note que o HTML está muito parafusado no código. Uma forma de evitarmos isso seria fazer o seguinte:
<script type="text/javascript">
function setMessage(message) {
var userMessageElement = document.getElementById("userIdMessage"); var messageText;
if (message == "invalid") {
userMessageElement.style.color = "red";
messageText = "Invalid User Id";
} else {
userMessageElement.style.color = "green";
messageText = "Valid User Id";
}
var messageBody = document.createTextNode(messageText);
if (userMessageElement.childNodes[0]) {
userMessageElement.replaceChild(messageBody, userMessageElement.childNodes[0]);
} else {
userMessageElement.appendChild(messageBody);
}
}
</script>

Download

    Download do exemplo utilizado (clique aqui).

Conclusões

Bibliografia