Pues el objeto de JavaScript XMLHttpRequest es la base, junto con DOM del AJAX y la famosa Web 2.0. Ahí donde lo veis.
¿Para qué sirve? ¿Cómo se utiliza? ¿Qué has fumado?
Fumar, sólo fumo tabaco, cosa que no debiera hacer (fumar tabaco, no fumar sólo tabaco). La idea detrás de todo esto es “en lugar de recargar toda la página cuando el usuario hace no-sé-qué, cargo sólo el trozo que me interesa”.
El ejemplo que me viene a la cabeza es Gmail.
Cuando hago click en la solapa del penúltimo correo de una conversación, la página no se recarga entera: lo que ocurre es que el navegador carga el cuerpo del correo y la pestaña se “expande” para mostrarlo:
La primera parte de esta operación se hace mediante XMLHttpRequest, la segunda mediante DOM.
El XMLHttpRequest es relativamente sencillo. Su objetivo original es hacer una consulta HTTP (o HTTPS) a un servidor y guardar el cuerpo del resultado en una variable. Nótese que esto es independiente del lenguaje en el que se desarrolle la aplicación en la parte servidor, ya sea perlCGI, Ruby on Rails, J2EE o un CGI en C. Por poder, podríamos hacerlo hasta con ficheros estáticos.
La segunda parte se hace mediante DOM. El Document Object Model es una interfaz de programación estándar que permite leer y modificar una estructura XML (y HTML es un caso concreto de XML, más o menos), tanto en su contenido como en el estilo de resentación asociado. Por ejemplo, pongamos que tengo una página HTML que contiene lo siguiente:
<p>Mi color favorito es el <span id="color" style="color: blue">azúl</span>.</p>
Si, sobre esta página, ejecutamos el siguiente fragmento de código JavaScript:
document.getElementById('color').style.color='red';
document.getElementById('color').textContent='rojo';
lo que muestra el navegador cambiará de
Mi color favorito es el azúl.
a
Mi color favorito es el rojo
Como se ve, es ligeramente engorroso de escribir, pero bastante simple.
XMLHttpRequest es menos intuitivo, pero, una vez “le pillas el truco”, resulta bastante sencillo de utilizar.
La primera complicación viene derivada del hecho de que el código JavaScript se ejecuta en el navegador, sobre el que no tenemos control: puede ser Safari, Firefox o Internet Explorer, entre otros. El problema es que, muy al estilo de Microsoft, la forma de crear un objeto XMLHttpRequest es distinta en Internet Explorer. De hecho, ni siquiera es igual en todas las versiones de IE.
De manera que, para crear nuestro objeto XMLHttpRequest, tenemos que hacer lo siguiente o algo parecido:
function getXmlHttp() {
var result;
if (window.XMLHttpRequest) {
// Estamos en un navegador que intenta cumplir estándares
result=new XMLHttpRequest();
} else if (window.ActiveXObject) {
// Estamos en IE. La única manera de saber cómo debemos
// hacerlo, es probar una manera y, si falla, probar la otra...
try {
result=
new ActiveXObject("Msxml2.XMLHTTP");
} catch(e) {
try {
result=
new ActiveXObject("Microsoft.XMLHTTP");
} catch (e) {
// ¿Un IE anticuado?
return false;
}
}
} else {
// Navegador desconocido que no soporta eso
return false;
}
return result;
}
function trabajar() {
var xhr=getXMLHttpRequest();
// hacemos cosas con xhr
}
Una vez creado el objeto, las complicaciones no han terminado aún. Las consultas se hacen de manera asíncrona, así que no es tan sencillo como
// Este código es de mentira
document.getElementById('contenido').innerHTML=
xhr.get("http://servidor/contenido.pl");
No. Lo que tenemos que hacer es crear una función que se ejecute cuando el navegador termine de descargar los datos y “haga algo con ellos”.
Ese código “ideal” de antes es, en la vida real, como sigue:
function cargacontenido() {
// A esta función la llamamos, por ejemplo, cuando
// el usuario pulsa un botón
var xhr=getXMLHttpRequest();
xhr.onreadystatechange=function () {
// El objeto pasa por cuatro estados, siendo el
// cuarto que ha terminado de descargar los datos
// Se llamará a esta función cuatro veces
if (xhr.readyState == 4) {
document.getElementById('contenido' ).innerHTML =
xhr.responseText;
}
}
xhr.open('GET', 'http://servidor/contenido.pl', true);
xhr.send(null);
}
Vale, he mentido: el true en la función open indica que la llamada es asíncrona. Pero si la hacemos síncrona, la gente nos mirará mal y, además, no podremos cargar varios fragmentos de página simultaneamente.
Y eso es todo. AJAX no tiene más. Por supuesto, en la práctica, no llamaremos a “http://servidor/contenido.pl” sino a algo como “http://servidor/contenido.pl?id=227“. O bien utilizaremos HTTP POST en lugar de HTTP GET y pasaremos entonces los parámetros a través de la función send.
Vayamos a algo parecido a un ejemplo práctico. Supongamos que tenemos nuestro sistema de gestión de documentos con función de búsqueda incorporada y queremos que el usuario tenga sugerencias de búsqueda según teclea, basadas en antiguas búsquedas.
Que conste que lo voy a hacer muy cutre. En particular, no manejamos errores para nada.
Empezamos por tener nuestro formulario HTML:
<html> <head> <title>Formulario</title> <script language="javascript" src="buscar.js"></script> <script language="javascript" src="getxmlhttprequest.js"></script> </head> <body> <form action="buscar.pl"> Términos: <input type="text" name="busqueda" id="busqueda" onchange="javascript:sugerencia()"/> <input type="button" name="Buscar" value="buscar"/></form> <div style="display: none;" id="sugerencias"></div> </body> </html>
Vale. Supongamos, por cierto, que gethtmlhttprequest.js contiene la función que definí antes. En buscar.js pondríamos esto:
function sugerencia() {
var busqueda=document.getElementById("busqueda");
var sugerencias=document.getElementById("sugerencias");
// comprobamos que ha tecleado, al menos, dos carácteres
if (busqueda.value.length < 2) {
sugerencias.style.display='none';
return true;
}
my xhr=getXMLHttpRequest();
xhr.onreadystatechange=function() {
if (xhr.readyState == 4) {
sugerencias.innerHTML=xhr.responseText;
sugerencias.style.display='block';
}
}
xhr.open('GET',
'sugerencias.pl?busca=' + busqueda.value,
true);
xhr.send(null);
}
// A esta función se la llamará desde el contenido
// (futuro) del DIV "sugerencias"
function selecciona(sugerencia) {
document.getElementById('busqueda').value = sugerencia;
}
Ahora, en el servidor, tendremos dos scripts CGI en Perl, buscar.pl y sugerencia.pl. Los fragmentos de código relevantes de ambos serían los siguientes (sería más feliz si sintaxhighlighter soportase Perl…):
# sugerencia.pl
# Tenemos una conexión a base de datos mediate DBI en $dbh
# También tenemos un "use CGI;" por algún sitio...
my $busca=param('busca' );
print header(-type=>'text/html', expires='+15m' );
# Nótese que sólo mandamos una tabla, sin
# <html>, <body>, etcétera
print '<table>';
my $sth=$dbh->prepare('SELECT SUGERENCIA FROM SUGERENCIAS ' .
'WHERE SUGERENCIA LIKE ?' .
'TOP 10 ORDER BY HITS' );
$sth->bind_param(1, $busca . '%' );
$sth->execute;
while (my $record=$sth->fetchrow_hashref()) {
printf '<tr><td onclick="javascript:selecciona(\'%s\')">%s</td></tr>',
$record->{'SUGERENCIA'}, $record->{'SUGERENCIA'};
}
$sth->finish();
print '</table>';
y
# buscar.pl
# Como antes, $dbh es un manejador DBI y está cargado CGI
my $busqueda=param('busqueda' );
# Antes o después del siguiente código, hacemos la búsqueda
# y se la mostramos al usuario
# Miramos si el término que han mandado está en la base de datos
my $sth=$dbh->prepare('SELECT HITS FROM SUGERENCIAS ' .
'WHERE SUGERENCIA=?' );
$sth->bind_param(1, $busqueda);
$sth->execute();
if (my $record= $sth->fetchrow_hashref()) {
# El término ya está
my $sth=$dbh->prepare('UPDATE SUGERENCIAS SET ' .
'HITS=? WHERE SUGERENCIA=?' );
$sth->bind_param(1, $record->{'HITS'} + 1);
$sth->bind_param(2, $busqueda);
$sth->execute();
} else {
# No estaba en la lista de búsquedas anteriores
my $sth=$dbh->prepare('INSERT INTO SUGERENCIAS ' .
'(SUGERENCIA, HITS) ' .
'VALUES (?, 1)' );
$sth->bind_param(1, $busqueda);
$sth->execute;
}
Y ya está. Le añades el código de la búsqueda de verdad y metes (mucho) CSS para que quede bonito y ya tienes tu formulario de búsqueda con sugerencias Web 2.0-mola-mogollón.
Referencias
Archivado bajo:Javascript , AJAX, Perl, Web 2.0
