Ajax


Ajax y su ayudante Sarissa

En mi artículo anterior os hablaba de las prendas y virtudes de Ajax, y me quedé con la sensación de haberme quedado muy corto en mis explicaciones y en las implicaciones que conlleva este método (no lo llamaré tecnología tal y como explica Jesse James Garrett en http://www.adaptivepath.com/publications/essays/archives/000385.php.

No obstante, ahora que ya tenemos más o menos claro qué es Ajax, cómo se hace con javascript etc... Llega el "momento API"!!!!!

El momento API es el momento de perfeccionamiento de una técnica o de cualquier tipo de programación. Es el momento en el que todo ese código diseminado, "scattered", forma un conjunto y se vuelve algo profesional.

Yo he optado por tomar prestado una API que hasta el momento ha atendido todas mis necesidades. Se trata de la API de Sarissa:

https://sourceforge.net/projects/sarissa/

Es un proyecto "open source" con el que unificamos el uso de Ajax para diferentes navegadores, no sé si Opera y otros, pero sí para Mozilla e Internet Explorer, y KHTML(Konqueror y Safari).

APIs javascript

Quien nunca haya utilizado una API de javascript, pensará una API de javascript? Ese lenguaje horrible que no puede depurarse como los verdaderos lenguajes de programación?
Eso no puede traer nada bueno!!

Pues bien, se equivocan, por supuesto, javascript no es perfecto, pero conocer javascript para un programador web es como el volante para un conductor, y sin javascript, no hay programador web. Suena obvio pero no es tanto así, porque el programador web tiende a aferrarse al lenguaje de servidor y aunque en una gran empresa todo el trabajo está dividido y seguramente no le toque escribir ni una línea de javascript, para optimizar la navegación del usuario, resulta necesario al 100%.

Al igual que antes los locos del javascript debieron adaptarse a los descubrimientos más óptimos de las CSS, ahora que xmlhttpRequest está dando fuerte, a los programadores ya no les queda otra que aprender javascript o se quedarán obsoletos. De hecho, todos los programadores tenemos que reintepretar la web y la forma de programar, y se debe invertir un período en reflexiónar al respecto.


Beneficios de una API:
-Una API de javascript hace como cualquier otra API, centraliza el código, ahorra código y se edita con facilidad

porque sólo hay que modificarlo una vez.

Negativos de una API:
-El código se vuelve más difícil, el programador tiene que aprender nuevos códigos y sin una buena documentación es de difícil modificación.

Tomando todo esto en cuenta, yo principalmente opto por las APIs de javascript pero no las totalizadoras, que hacen todo lo que quieras en tu web, sólo en casos concretos, donde las diferencias de códigos entre navegadores se vuelve más dispar.

El caso más claro de una buen API es la maravillosa y famosa API de dhtmlcentral.com. Es una API que me ha acompañado siempre y que he mejorado con el tiempo.

Qué conseguimos con una API si javascript hace unas tareas muy sencillas? Pues simplemente, unificamos el javascript que utilizan todos los navegadores. Por ejemplo, para ocultar una capa se hace diferente en IE4 que en IE6, Mozilla o Netscape 4.5. Con la API de dhtmlcentral escribimos una línea y lo hace compatible para todos.

Esto mismo conseguimos con Sarissa, más otras tareas relacionadas con el DOM.

Sarissa, la API para Ajax

En este tutorial, voy a tratar 2 métodos de cargar XML con Ajax.
Uno utilizando XmlhttpRequest y otro cargando XML con el objeto load, que en IE pertenece al ActiveX Msxml2.DOMDocument, y que en Mozilla se implementa de otra manera.
No obstante, como decía más arriba, no nos vamos a preocupar de las diferencias entre navegadores porque Sarissa hará los deberes por nosotros.

El resultado con ambos métodos es el mismo. No obstante, xmlhttpRequest, nos ofrece un mejor control sobre el estado de la carga del xml, porque podemos utilizar la propiedad ReadyState.

Como último apunte, Sarissa no se implementa creando una clase Sarissa, sino que se llama de manera estática.

Empecemos de lleno:
1.-Bajamos la API de su web.

2.-Incluimos los js:
<script language="javascript" src="sarissa.js"></script>
<script language="javascript" src="sarissa_ieemu_xpath.js"></script>

3.-Creamos el formulario que describía en mi último tutorial sobre Ajax, pero incluyendo un nuevo enlace que llama a getDatosXML para cargar XML utilizando load:

<div id="lblError">
</div>
<form id="detalles" name="detalles" method="post">
    <table cellpadding="0" cellspacing="0" border="0">
        <tr>
        <td nowrap="nowrap" class="items" style="visibility:hidden">Metatags:</td><td>
        <textarea cols="40" rows="1" name="metatags" id="metatags"></textarea>
        </td>
        </tr>
        <tr>
        <td nowrap="nowrap" class="items" style="visibility:hidden">Descripción:</td><td>
        <textarea cols="40" rows="1" name="descripcion" id="descripcion"></textarea>
        </td>
        </tr>
        <tr>
        <td nowrap="nowrap" class="items" style="visibility:hidden">Descripción larga:</td><td>
        <textarea cols="40" rows="1" name="descripcionlarga" id="descripcionlarga"></textarea>
        </td>
        </tr>
        </table>
        <div class="buttons1"><a href="javascript:getDatos()">Llenar</a></div>
                <div class="buttons1"><a href="javascript:getDatosXML()">LlenarXML</a></div>
    
</form>

4.-Ahora incluyamos las funciones getDatos y getDatosXML:
var xmlhttp=null;

function getDatos(){
  if(xmlhttp && xmlhttp.readyState!=0 && xmlhttp.readyState!=4){
    alert("espere mientras la conexión anterior termina")
    return;
  }
strUrl="xmlhttp_select.cs.asp?docid=4"

xmlhttp = new XMLHttpRequest();

xmlhttp.open("GET", strUrl, true);
xmlhttp.onreadystatechange=handlergetDatos
xmlhttp.send(null);

}

function getDatosXML(){
strUrl="xmlhttp_select.cs.asp?docid=4"

respuestaXML= Sarissa.getDomDocument();
respuestaXML.async = false;
respuestaXML.load(strUrl);

        mostrarDatos(1)

}

Como vemos más arriba, hemos reducido mucho el código, ya no tenemos ninguna función que se encarga de crear la compatibilidad del objeto xmlhttpRequest. De esto se encarga Sarissa, que ha redefinido el objeto, un poco como se hace con los métodos en .Net con la propiedad "override".

De momento, ya vemos un ahorro de código, y además el sistema está mejorado porque Sarissa para IE, va a hacer un loop a través de los diferentes Activex que utiliza IE para crear el xmlHttpRequest hasta que encuentre el apropiado. Es decir, los activeX:

"Msxml2.XMLHTTP.5.0", "Msxml2.XMLHTTP.4.0", "MSXML2.XMLHTTP.3.0", "MSXML2.XMLHTTP", "Microsoft.XMLHTTP.

Para la función getDatosXml. También ahorramos porque la forma de crear el objeto, lo hará de modo conveniente dependiendo del navegador, y además para IE mejorará el método haciendo un loop buscando la clase apropiada entre:

"Msxml2.DOMDocument.5.0", "Msxml2.DOMDocument.4.0", "Msxml2.DOMDocument.3.0", "MSXML2.DOMDocument", "MSXML.DOMDocument", "Microsoft.XMLDOM"

En mi código, he incluido una mejora:

if(xmlhttp && xmlhttp.readyState!=0 && xmlhttp.readyState!=4){
    alert("espere mientras la conexión anterior termina")
return;
}

Con lo cual nos aseguramos que las diferentes conexiones no se pisan.


5.-Incluyamos la función que controla la carga de datos del xmlhttprequest:

function handlergetDatos(){

    if(xmlhttp.readyState==2)
    {
   
    document.getElementById("lblError").innerHTML="Cargando datos";
   
    }
   
    if(xmlhttp.readyState==3)
    {
   
    document.getElementById("lblError").innerHTML="Datos cargados";
   
    }
   

    if(xmlhttp.readyState==4)
    {
   
    document.getElementById("lblError").innerHTML="Carga de datos completada";
            if(xmlhttp.status==200)
            {

respuestaXML=xmlhttp.responseXML

mostrarDatos();

 
              }else{

                alert("no se encuentra el documento xml")
               
            }

    }


}


Aquí hemos mejorado la forma de hacer un seguimiento del estado de la conexión.

Un detalle a tener en cuenta es que al final la función mostrarDatos, va a utilizar el objeto respuestaXML. En el caso de cargarlo como documento XML usando load. La respuesta xml ya estaá contenida en el objeto mismo,pero en el caso de xmlhttpRequest, respuestaXML equivale a xmlhttp.responseXML.

Desde ese momento ambos XML son iguales y se tratan igual.

6.-Incluimos la función mostrarDatos:

function mostrarDatos(){
numNode=0;
document.getElementById("lblError").innerHTML="";
try{
if(respuestaXML.parseError!=0){
throw "Posible formato erróneo:" + respuestaXML.parseError;
}
        xmlmetatags=Sarissa.getText(respuestaXML.selectNodes("root/message/metatags")[numNode])
        xmldescripcion=Sarissa.getText(respuestaXML.selectNodes("root/message/descripcion")[numNode])
       

xmldescripcionlarga=Sarissa.getText(respuestaXML.selectNodes("root/message/descripcionlarga")[numNode])
   
        Sarissa.clearChildNodes(document.getElementById("metatags"))
        Sarissa.clearChildNodes(document.getElementById("descripcion"))
        Sarissa.clearChildNodes(document.getElementById("descripcionlarga"))
        document.getElementById("metatags").appendChild(document.createTextNode(xmlmetatags))
        document.getElementById("descripcion").appendChild(document.createTextNode(xmldescripcion))
        document.getElementById("descripcionlarga").appendChild(document.createTextNode(xmldescripcionlarga))
           
}
catch(e)
{
    document.getElementById("lblError").innerHTML="error cargando xml."+e+"<br>Lea la descripcíon del error y

póngase en contacto con su departamento técnico:<br>"+Sarissa.getParseErrorText(respuestaXML);
   
}

}

Esta es la función que crea la magia.

Después de haber comprobado que se ha cargado sin problemas, seguimos haciendo comprobaciones:

    if(respuestaXML.parseError!=0){

Con esto vamos a saber si el xml está mal formado.
Sarissa en este sentido nos ofrece 1 propiedad:
respuestaXML.parseError
que debería ser 0 si no hay error.

Y un método para recuperar el error:
Sarissa.getParseErrorText(respuestaXML)

Ahora es cuando utilizamos el segundo .js que incluimos sarissa_ieemu_xpath.js:
xmlmetatags=Sarissa.getText(respuestaXML.selectNodes("root/message/metatags")[numNode])

Con el método getText vamos a recuperar el valor del nodo que le indiquemos. Para eso utilizamos el selectNodes si hay

más de una misma etiqueta (en este caso metatags) con un índice (numNode), o si fuera sólo uno, selectSingleNode.

Fin
Hasta aquí llega mi tutorial, un método muy bueno que utiliza google, ver:
http://serversideguy.blogspot.com/2004/12/google-suggest-dissected.html

Es utilizar en vez de una respuesta xml, una respuesta de texto, y ejecutarla como javascript, así:
eval(_xmlHttp.responseText)

Pero el lado de la fuerza incluye muchos otros métodos relacionados con hojas de estilo XML (
http://www-128.ibm.com/developerworks/xml/library/x-xslt/?article=xr ) que exploraremos otro día.Sólo mostraros un ejemplo de diccionario basado en Google Suggests:
http://www.objectgraph.com/dictionary/



Miguel Ángel.
Programador.

 

Ajax o el milagro de Xmlhttprequest

Todo programador sabe apreciar la originalidad y buen hacer con el que Gmail, Google Groups, Google Suggest, Google Maps... se han programado, y todos los que saben un poquito hablan del maravilloso mundo del xmlhttprequest. Es como el amigo que uno tiene que hizo una cosa sorprendente pero que nadie conoce.
En este artículo voy a intentar explicar un poco los entresijos de algo que puede ser verdaderamente beneficioso para cualquier cerebrito que quiera mejorar la navegación de sus páginas.

En primer lugar resaltemos algunas de las prestaciones que luce este nuevo planteamiento:

-xmlhttprequest es un objeto que nació en el Internet Explorer 5 como un ActiveX, y que después pasó a formar parte de los principales navegadores: Mozilla, Safari etc...
Es un objeto que se crea desde javascript, y que apunta a una página en el servidor.

-
no hace falta moverse de página para actuar en el servidor y responder al cliente. Para poner un símil, hace lo que las llamadas en Actionscript dentro de un flash hacen a otras páginas sin que el cliente lo perciba.

-xmlhttprequest además de poder actuar en el servidor, puede devolver datos, por ejemplo, XML, pero también texto que podemos utilizar a nuestro antojo. En el primer caso, por ejemplo, podríamos utilizar el DOM para reestructurar la web a partir de un documento xml, y en el segundo caso, si utilizamos texto, podría mandar de vuelta la confirmación de que todo fue bien en el servidor.

-Por último y no menos importante, hay una limitación de seguridad, y es que no se puede hacer una llamada a una página que no tenga el mismo dominio que la página que contiene el javascript. Es decir, que no podemos llamar a un web service directamente.

Todo esto llevado al séptimo arte es el motor principal de Gmail con un javascript supersofisticado,  y creo que cualquiera puede apreciar la rapidez y amenidad con que se navega en el popular proveedor de correo.

Pero bueno, dejémonos de prolegómenos y hinquémosle el diente al asado!!!

    1.- Creemos este formulario:

<form id="detalles" name="detalles" method="post">
    <table cellpadding="0" cellspacing="0" border="0">
        <tr>
        <td nowrap="nowrap" class="items">Metatags:</td><td>
        <textarea cols="40" rows="2" name="metatags" id="metatags"></textarea>
        </td>
        </tr>
        <tr>
        <td nowrap="nowrap" class="items">Descripción:</td><td>
        <textarea cols="40" rows="2" name="descripcion" id="descripcion"></textarea>
        </td>
        </tr>
        <tr>
        <td nowrap="nowrap" class="items">Descripción larga:</td><td>
        <textarea cols="40" rows="2" name="descripcionlarga" id="descripcionlarga"></textarea>
        </td>
        </tr>
        </table>
        <div id="grabar" class="buttons1"><a href="#">Grabar</a></div>
</form>

    2.-Como habremos visto, el hyperlink de "Grabar" es una llamada a una función updateDatos. Esta función pone en marcha todo el tinglado!!.

Antes de ver dicha función debemos crear el javascript siguiente:

        function getHTTPObject()
        {
          var xmlhttp;
   
          if (!xmlhttp)
          {
                               
            if(window.XMLHttpRequest)
            {
                   
                    try
                    {
                       
                        xmlhttp = new XMLHttpRequest();
       
                    }
                   
                    catch(e)
                    {
       
                            alert("mensaje error 1.1")
                            xmlhttp = false;
                    }
    // branch for IE/Windows ActiveX version
            }
            else if(window.ActiveXObject)
            {
               
                       try
                       {
                        xmlhttp = new ActiveXObject("Msxml2.XMLHTTP");
                      }
                      catch(e)
                      {
                        try
                        {
                         
                          xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
                         
                        }
                        catch(e)
                        {

                        alert("mensaje de error 1.2")
                          xmlhttp = false;
                        }
                    }
            }
               
         }
        return xmlhttp;
}
var http = getHTTPObject(); // We create the HTTP Object



Lo que hemos hecho ha sido crear una instancia del objeto Xmlhttprequest, en el caso de Mozilla y Safari es el objeto
XMLHttpRequest(), y en el caso de Internet Explorer, o ActiveXObject("Msxml2.XMLHTTP"); o ActiveXObject("Microsoft.XMLHTTP").

Todo lo demás son comprobaciones que son útiles para ofrecer una mayor capacidad de respuesta al cliente. Un buen sistema de aviso de errores, y esto lo digo para cualquier tipo de programación, debe contener el error y asignarle un número, que a su vez tendríamos referenciado, al estilo de programas de consola, para que el soporte técnico supiera enseguida de qué se trata.

Sigamos, creemos la función de updateDatos():

function updateDatos(){

 var urldatos = "xmlhttp_insert.cs.asp?"; // The server-side script
 var metaValue = document.getElementById("metatags").value;
 var descripValue=document.getElementById("descripcion").value;
 var descriplargaValue=document.getElementById("descripcionlarga").value;

    strUrl=urldatos + "metas=" +escape(metaValue) + "&descrip="+ escape(descripValue)+"&descriplarga="+escape(descriplargaValue);
   
    if(http)
    {   

            http.open("GET",strUrl, true);
            http.setRequestHeader('Accept','message/x-jl-formresult')
            http.send(null);

    }
    else
    {
   
            alert("Hubo un problema al conectar. Consulte con soporte técnico");

    }  
 
}


Aquí utilizamos la instancia del objeto xmlhttprequest y le asignamos la url de destino:
http.open("GET",strUrl, true);

El string con todos los parámetros están añadidos a "urldatos". Por cierto, siempre hacer escape!! ya que se pasa por GET y así le damos el formato adecuado a la cadena.

http.onreadystatechange = handleHttpResponse;
http.send(null);

Controlamos el estado de nuestra conexión con la url de destino de esta manera. La función handleHttpResponse actuara según si la conexión prospera o no.

function handleHttpResponse() {

  if (http.readyState == 4) {
   
    // Split the comma delimited response into an array
   
   
    results = http.responseText;
     if (http.status == 200) {
           
            if(results=="OK" ){
               
              alert("OK:<br>Base de datos actualizada.");
           
            }
           
            else{
                   
             alert("Hubo un problema actualizando, inténtelo de nuevo, y si devuelve error, por favor, consulte soporte técnico");
           
            }
    }else{
       
       alert ("Hubo un problema actualizando, inténtelo de nuevo, y si devuelve error, por favor, consulte soporte técnico");
   
    }   
 
   
  }
}

"http.readyState == 4" nos dice que la conexión se ha realizado con éxito, y "http.status == 200" que la página existe. results=="OK", simplemente nos informa de cómo se realizó todo en el servidor.

Bueno, nada más me queda recomendaros el siguiente link donde se amplian bastantes conocimientos:

http://jibbering.com/2002/4/httprequest.html


y para los que queréis el código directamente, un ejemplo sencillito y que aproveche!!:


<script language="javascript" type="text/javascript">

        function getHTTPObject()
        {
          var xmlhttp;
   
          if (!xmlhttp)
          {
                               
            if(window.XMLHttpRequest)
            {
                   
                    try
                    {
                       
                        xmlhttp = new XMLHttpRequest();
       
                    }
                   
                    catch(e)
                    {
       
                            alert("No se puede realizar la actualización de datos, consulte con soporte técnico")
                            xmlhttp = false;
                    }
    // branch for IE/Windows ActiveX version
            }
            else if(window.ActiveXObject)
            {
               
                       try
                       {
                        xmlhttp = new ActiveXObject("Msxml2.XMLHTTP");
                      }
                      catch(e)
                      {
                        try
                        {
                         
                          xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
                         
                        }
                        catch(e)
                        {

                        alert("No se puede realizar la actualización de datos, consulte con soporte técnico")
                          xmlhttp = false;
                        }
                    }
            }
               
         }
        return xmlhttp;
}

   
   
    function supportsDynamicLabels()
        {
   
         return     document.getElementById &&   
        (window.attachEvent ||window.addEventListener) && 
         null == navigator.appVersion.match(/Safari/d+$/);    
       
         }

function handleHttpResponse() {

  if (http.readyState == 4) {
   
    // Split the comma delimited response into an array
   
   
    results = http.responseText;
     if (http.status == 200) {
           
            if(results=="OK" ){
               
                document.getElementById("loading").innerHTML="OK:<br>Base de datos actualizada.";
           
            }
           
            else{
                   
                    document.getElementById("loading").innerHTML="Hubo un problema actualizando, inténtelo de nuevo, y si devuelve error, por favor, consulte soporte técnico";
           
            }
    }else{
       
        document.getElementById("loading").innerHTML="Hubo un problema actualizando, inténtelo de nuevo, y si devuelve error, por favor, consulte soporte técnico";
   
    }   
   
    setTimeout('document.getElementById("loading").style.display="none"',1500);   
   
  }
}
        function getHTTPObject()
        {
          var xmlhttp;
   
          if (!xmlhttp)
          {
                               
            if(window.XMLHttpRequest)
            {
                   
                    try
                    {
                       
                        xmlhttp = new XMLHttpRequest();
       
                    }
                   
                    catch(e)
                    {
       
                            alert("No se puede realizar la actualización de datos, consulte con soporte técnico")
                            xmlhttp = false;
                    }
    // branch for IE/Windows ActiveX version
            }
            else if(window.ActiveXObject)
            {
               
                       try
                       {
                        xmlhttp = new ActiveXObject("Msxml2.XMLHTTP");
                      }
                      catch(e)
                      {
                        try
                        {
                         
                          xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
                         
                        }
                        catch(e)
                        {

                        alert("No se puede realizar la actualización de datos, consulte con soporte técnico")
                          xmlhttp = false;
                        }
                    }
            }
               
         }
        return xmlhttp;
}
var http = getHTTPObject(); // We create the HTTP Object

function updateDatos(){

document.getElementById("loading").style.display="block";

 var urldatos = "xmlhttp_insert.cs.asp?"; // The server-side script
 var metaValue = document.getElementById("metatags").value;
 var descripValue=document.getElementById("descripcion").value;
 var descriplargaValue=document.getElementById("descripcionlarga").value;

    strUrl=urldatos + "metas=" +escape(metaValue) + "&descrip="+ escape(descripValue)+"&descriplarga="+escape(descriplargaValue);
   
    if(http){   
   
//http.open("HEAD", "/faq/index.html",true);
//     http.onreadystatechange=function() {
  //if (http.readyState==4) {
            //alert("Status is "+http.status)
    //}
 //}
     
    http.open("GET",strUrl, true);
    http.onreadystatechange = handleHttpResponse;
    http.setRequestHeader('Accept','message/x-jl-formresult')
    http.send(null);
    }
    else{
   
    alert("Hubo un problema al conectar. Consulte con soporte técnico");
    }
   
 
}

     
     
    function addEvent(objObject, strEventName, fnHandler) {
      if (objObject.addEventListener)
        objObject.addEventListener(strEventName, fnHandler, false);
      else if (objObject.attachEvent)
        objObject.attachEvent("on" + strEventName, fnHandler);
    }


  function setupLabels() {

          
     addEvent(document.getElementById("grabar"),"click",updateDatos);
     
     }
     
     

         if (supportsDynamicLabels()) {
      addEvent(window, "load", setupLabels);
    }

       


</script>

<BODY>
<div id="loading" style="display:none;BORDER-RIGHT: black 1px solid; BORDER-TOP: black 1px solid; FONT-WEIGHT: bold; FONT-SIZE: 12px; LEFT: 50px; BORDER-LEFT: black 1px solid; WIDTH: 200px; COLOR: crimson; PADDING-TOP: 20%; BORDER-BOTTOM: black 1px solid; FONT-FAMILY: verdana; POSITION: absolute; TOP: 50px; HEIGHT: 100px; BACKGROUND-COLOR: lightgrey; TEXT-ALIGN: center">
Actualizando
</div>
<div id="lblError">
</div>
<form id="detalles" name="detalles" method="post">
    <table cellpadding="0" cellspacing="0" border="0">
        <tr>
        <td nowrap="nowrap" class="items">Metatags:</td><td>
        <textarea cols="40" rows="2" name="metatags" id="metatags"></textarea>
        </td>
        </tr>
        <tr>
        <td nowrap="nowrap" class="items">Descripción:</td><td>
        <textarea cols="40" rows="2" name="descripcion" id="descripcion"></textarea>
        </td>
        </tr>
        <tr>
        <td nowrap="nowrap" class="items">Descripción larga:</td><td>
        <textarea cols="40" rows="2" name="descripcionlarga" id="descripcionlarga"></textarea>
        </td>
        </tr>
        </table>
        <div id="grabar" class="buttons1"><a href="#">Grabar</a></div>
</form>