Una petición AJAX es una petición asincrona a un servidor, es decir, en segundo plano. De esta forma es posible actualizar los datos de una página sin necesidad de recargarla por completo. Con jQuery este tipo de peticiones se realiza básicamente con la función jQuery.ajax() o lo que es lo mismo $.ajax().

Cuando se realiza una petición Ajax el usuario no tiene un feedback claro como el que se recibe cuando se hace click en un enlace normal. No sabe por tanto que algo está ocurriendo hasta que ocurre. El problema es que el usuario puede pensar que no ha pinchado bien el enlace y reintentarlo mientras aún se está procesando la primera petición.

Es necesario, por tanto, que proporcionemos al usuario un indicador de que se está llevando a cabo algun tipo de acción.

Esto se puede hacer de diferentes maneras, ya sea cambiando el texto del enlace o desactivandolo, insertar algun tipo de spinner o mostrando un mensaje indicativo, etc., y esto deberemos hacerlo mismo en cada petición Ajax. La función mostrada previamente ($.ajax()) nos ofrece una forma de crear callbacks que nos permitirán realizar las acciones que nosotros queramos cuando se produzca algun tipo de evento relacionado con la petición AJAX. Estos eventos son tales como: inicio de la petición, petición satisfactoria, error en la petición, petición completada, etc..

Para mostrar el ejemplo vamos a hacer una simple petición Ajax para obtener los últimos 15 mensajes de estado de la cuenta de twitter de Theproc.es. Estos mensajes se obtendrán de forma asincrona al hacer click en un enlace y se mostrarán en la misma página sin necesidad de recargarla.

Aquí vamos a crear un notificador que se encargará de mostrar bajo el cursor del ratón un spinner para que el usuario sepa que algo está ocurriendo:

Notificador Ajax

Paso 1 - Petición AJAX

La URL para obtener los mensajes de estado en formato JSON es: http://api.twitter.com/status/user_timeline/theproc_es.json

La petición tendrá la siguiente forma:

$.ajax({
  url: "http://api.twitter.com/status/user_timeline/theproc_es.json?count=15&callback=?"
  dataType: 'json',
  success: function(data){
    // procesado de la respuesta
  }
});

El contenido de la función encargada de procesar la respuesta es el siguiente:

function(data){
  var list = $('<ul>')
  $(data).each(function(){
    $('#result').html("");
    $(data).each(function(){
      $('<div>').append("<div><strong>"+this.user.screen_name+":</strong> "+this.text+"</div>");
    });
  });
}

Paso 2 - Notificando la petición

La función $.ajax() permite el uso de callbacks, es decir, la funciones que se llamaran en determinados momentos de la petición. De hecho, en el ejemplo anterior ya hemos usado uno, el callback success que es el que se invoca cuando se reciben los datos correctamente.

Ahora los que nos interesan son ajaxStart y ajaxStop. Estos se producen, justo antes de enviar los datos al servidor y al final de la petición respectivamente.

El código anterior con los nuevos callbacks es el siguiente:

$('body').append(
  $('<div>').attr('id', 'loading').append(
      $('<img>').attr('src', 'ajax-loader.gif').attr('alt', 'Loading...')
    ).css({
      position: 'absolute',
      display: 'none'
    })
  );

$(document).mousemove(function(e){
  $('#loading').css({left: e.pageX + 10, top: e.pageY + 15});
}

$('#link').click(function(e){
  e.preventDefault();
  $.ajax({
    url: "http://api.twitter.com/status/user_timeline/theproc_es.json?count=15&callback=?"
    dataType: 'json',
    success: function(data){
      // procesado de la respuesta
    },
    beforeSend: function(){
      $('#loading').show();
    },
    complete: function(){
      $('#loading').hide();
    }
  });
});

Primero he añadido una imágen al final del body. Esta imagen tiene posición absoluta y está oculta.

A continuación he creado una función sobre el evento de movimiento del ratón. Cuando el ratón se mueva se modificará la posición de la imagen según la posición actual del ratón.

El siguiente paso ha sido crear las funciones sobre los callback beforeSend y complete encargados de mostrar la imagen y ocultarla respectivamente.

Paso 3 - Notificador global

El problema es que si quisieramos hacer otra petición Ajax tendríamos que volver a crear las funciones de callback.

Por ello puede ser conveniente aplicar las funciones de callback automáticamente a todas las peticiónes AJAX.

Las peticiones AJAX disponen de una serie de eventos, unos locales (como los que hemos usado hasta ahora) que se son propios a la petición AJAX y otros globales que son retrasmitidos a todos los elementos del DOM.

El evento ajaxStart se transmite si se inicia una petición ajax y no hay ninguna otra petición ajax ejecutandose. Por otro lado, ajaxStop se transmite cuando ya no hay más peticiones Ajax ejecutandose.

Al ser retransmitidos por todos los elementos del DOM podemos escucharlos en el elemento #loading directamente:

$('#loading').bind('ajaxStart', function(){
    $(this).show();
  }).bind('ajaxStop', function(){
    $(this).hide();
  });

Código final:

$(document).ready(function(){
  // Insertamos la imagen
  $('body').append(
    $('<div>').attr('id', 'loading').append(
        $('<img>').attr('src', 'ajax-loader.gif').attr('alt', 'Loading...')
      ).css({
        position: 'absolute',
        display: 'none'
      })
    );

  // Reposicionado de la imagen
  $(document).mousemove(function(e){
    $('#loading').css({left: e.pageX + 10, top: e.pageY + 15});
  }
 
  // Eventos globales
  $('#loading').bind('ajaxStart', function(){
      $(this).show();
    }).bind('ajaxStop', function(){
      $(this).hide();
    });


  // La petición Ajax
  $('#link').click(function(e){
    e.preventDefault();
    $.ajax({
      url: "http://api.twitter.com/status/user_timeline/theproc_es.json?count=15&callback=?"
      dataType: 'json',
      success: function(data){
        $(data).each(function(){
          $('#result').html("");
          $(data).each(function(){
            $('#result').append("<div><strong>"+this.user.screen_name+":</strong> "+this.text+"</div>");
          });
        });
      }
    });
  });

});

Demostración