La Web 2.0 i les tecnologies que la fan possible/AJaX
Ja hem vist que amb el JavaScript la pàgina pot interaccionar amb l'usuari canviant qualsevol cosa del document que està veien, accedint a aquesta a través del DOM. Tot i així, el JavaScript presenta limitacions per sí sol ja que actua al costat del client i no té les funcions dels llenguatges de servidor de gestionar bases de dades o accedir al sistema de fitxers del servidor web.
És per això que per assabentar-se de qualsevol canvi que hagi ocorregut al costat del servidor, com rebre un nou correu, s'havia de recarregar la pàgina, amb l'espera que això comporta. També la interacció amb aquest, com emplenar un formulari per tal de que sigui guardat a una base de dades, tenia el seu cost en temps, durant el qual el navegador presentava un fons blanc, fins que el servidor responia.
Els usuaris esperaven pacient o impacientment a que la pàgina es tornés a carregar, o almenys fins ara, amb l'aparició massiva de pàgines que fan servir AJaX per tal d'evitar tan com es pugui aquest trencament que es produeix al interaccionar amb el servidor. Però, què és AJaX?
AJaX no és un producte de neteja
modificaBruce W. Perry comença d'una manera pareguda a aquesta a definir el concepte que es té d'AJaX i perquè realment "ajax" té moltes accepcions, entre les quals és un equip de futbol, una banda de música de Nova York i un heroi de la guerra de Troia.
L'AJaX en programació és l'acrònim de Asynchronous JavaScript and XML, una tècnica que permet a través de JavaScript enviar i rebre dades (normalment XML) del servidor sense tenir de recarregar la pàgina i amb independència d'aquesta, és a dir, d'una manera asíncrona.
Per tal d'aconseguir-ho fa ús de tecnologies ja existents i que ja he explicat en altres capítols:
- (X)HTML i CSS, que conformen la presentació de la pàgina
- El Document Object Model (DOM) per tal d'interaccionar amb l'usuari modificar dinàmicament l'(X)HTML i l'estil CSS
- XML i XSLT que s'utilitzen per transportar les dades i manipular-les
- i la classe JavaScript XMLHttpRequest per enviar i rebre les dades de manera asíncrona
Una pàgina que utilitza tècniques AJaX és descarregada des del servidor com qualsevol altra, però, un cop al navegador, entren en joc diversos mecanismes programats amb JavaScript de tal manera que pot interaccionar amb el servidor en segon pla. Normalment aquesta interacció la desencadena el mateix usuari que fa clic sobre algun element o canvia el valor d'algun camp de formulari de la pàgina, de manera que provoca un esdeveniment. Si l'esdeveniment té una funció JavaScript assignada, aquesta s'executa i si necessita d'alguna dada que és al servidor fa una petició mitjançant una instància de la classe XMLHttpRequest. També pot ser que no sigui l'usuari qui provoqui la crida de la funció i que aquesta estigui programada amb un mètode temporitzador com el window.setInterval() o el window.setTimeout().
La l'objecte XMLHttpRequest fa una petició al servidor, que aquest processa i li retorna l'arxiu XML creat dinàmicament com a resposta. Un cop l'objecte XMLHttpRequest ha rebut aquest document XML, crida a la funció JavaScript que l'analitza i fa les accions que ha de realitzar, inclosa la de modificar el document mitjançant el DOM per tal d'avisar a l'usuari, en cas de que escaigui. Aquest objecte és, doncs, l'eix vertebrador de tota l'arquitectura AJaX.
XMLHttpRequest
modificaPer fer ús d'un objecte XMLHttpRequest s'ha de fer una instància d'aquesta classe, de manera que s'utilitza un objecte per a cada petició. Un cop construït l'objecte, aquest disposa dels següents mètodes i propietats:
Mètode | Descripció |
---|---|
abort() | Cancel·la la petició. |
getAllResponseHeaders() | Retorna tots els encapçalats HTTP en forma de cadena de text. |
getResponseHeader(nomEncapçalat) | Retorna el valor de l'encapçalat especificat com a paràmetre. |
open(mètodeHTTP, URL) | Especifica el mètode HTTP amb el qual s'enviarà la petició (normalment GET o POST) i la URL de la pàgina de servidor que processarà la petició i retornarà la resposta. És possible definir també, amb paràmetres addicionals, si la petició es farà d'una forma asíncrona o no, i l'usuari i la contrasenya si escau. |
send(contingut) | Envia la petició al servidor. Si el mètode HTTP especificat abans amb open era POST, el text del paràmetre contingut serà enviat com a part de la petició. Si era GET, com que el contingut ja està definit en la mateixa URL (ja especificada al mètode open) no fa falta especificar aquí res. |
setRequestHeader(nomEncapçalat, valor) | Afegeix un encapçalat HTTP a la petició. |
Propietat | Descripció |
---|---|
onreadystatechange | Amb el que es pot definir la funció que serà cridada quan la propietat readyState canviï. |
readyState | Returns the state of the object as follows:
|
responseText | Conté la resposta rebuda del servidor i la dona en forma de cadena de text. Tan sols és accessible quan la propietat readyState té el valor 3 o 4. |
responseXML | També conté la resposta rebuda, però la dóna en forma d'XML, i és per això que tan sols és accessible quan el readyState està en 4, ja que fins que no està tot baixat no es pot fer l'arbre de nodes de l'XML. |
status | Conté l'estat HTTP en codi numèric per saber si s'ha carregat bé (200), si no s'ha trobat (404), etc. |
statusText | També conté l'estat HTTP, però enlloc de donar-lo com un nombre el dona com una cadena ("OK", "Not Found", etc.). |
Així doncs, rebre el document preus.xml un cop la pàgina s'ha descarregat és tan fàcil com:
oXMLHttp = new XMLHttpRequest(); oXML.onreadystatechange = alCanviar; oXMLHttp.open('get', 'preus.xml', false); oXMLHttp.send();
Amb la primera línia es crea una instància de l'objecte XMLHttpRequest, i se li assigna a la variable oXMLHttp. Amb la segona, se li assigna a la propietat onreadystatchange la funció alCanviar(), que no apareix en el codi. Aquesta funció serà cridada cada cop que el valor de readyState canviï. En aquest moment el seu valor encara és 0 i ho serà fins que no es cridi al mètode open(), a la tercera línia. Quan es crida, s'estableix el mètode HTTP utilitzat, que serà GET, la URL que es demanarà al servidor, preus.xml, i el tipus de petició es realitzarà, síncrona (el tercer paràmetre marca si la petició és realitzarà d'una manera síncrona o asíncrona, amb els valors false i true respectivament).
Al cridar-se al mètode open(), el readyState canvia de 0 a 1, i aquest fet crida a la funció definida abans amb onreadystatechange, alCanviar() el que permet definir aquí els encapçalaments HTTP que s'enviaran al servidor, i que per tant s'han de definir abans que la petició s'enviï.
Per últim, es crida al mètode send(), que envia la petició. La propietat readyState canvia de 1 a 2, el que també crida a la funció alCanviar(). Un cop enviada la petició, el servidor la processa i retorna una resposta, la qual cosa torna a canviar el valor de readyState, de 2 a 3 i torna a cridar a la funció alCanviar().
En aquesta fase ja es pot accedir als encapçalats que ha enviat el servidor, com el Accept-Encoding que defineix la codificació utilitaza o Content-Length informa de la mida total de les dades enviades. També és accessible la part de la resposta que es va descarregant emmagatzemada a la propietat responseText.
Al finalitzar la descàrrega de la resposta, aquesta ja està disponible en la seua totalitat a les propietats responseText i responseXML. Ara ja es feina del DOM de processar la resposta i fer els canvis en la pàgina per fer-li percebre a usuari els canvis ocorreguts al servidor. Aquestes accions també s'acostumen a fer des de la funció definida per onreadystatechange, en aquest cas alCanviar(), perquè al rebre les dades és cridada, ja que la propietat readyState ha canviat de 3 a 4.
Tots els navegadors actuals incorporen aquesta classe. Mozilla Firefox ja ho incorporava des de la primera versió (2002), Safari des de la versió 1.2 (2004) i Opera des de la seua versió 8 (2005). Internet Explorer no ha incorporat aquesta classe fins a la seua versió 7 (2006), tot i que ja estava disponible amb un altre nom; s'havia de declarar com a un objecte ActiveX, de la següent manera:
oXMLHttp = new ActiveXObject("Msxml2.XMLHTTP");
Com que l'objecte XMLHttpRequest també pot rebre respostes que no siguin XML i processar-les igualment (amb la propietat responseText), també és molt comú utilitzar JSON enlloc de XML per estructurar la resposta del servidor. Rebre un objecte JavaScript pot presentar molts avantatges ja aquest pot incorporar mètodes amb codi JavaScript que XML, encara que fos parcejat com un objecte JavaScript, no incorporaria. Tot i així, moltes vegades el que es descarrega del servidor és una porció de codi XHTML que s'incorpora, sense modificar i mitjançant DOM, a la pàgina.
Una alternativa al XMLHttpRequest és la de inserir aquest codi JSON directament a la pàgina amb etiquetes <script> dinàmiques. Aquestes etiquetes són creades dinàmicament mitjançant DOM i demanen al navegador la descàrrega d'un arxiu JavaScript extern que es troba al servidor, el que li retorna una resposta JSON.
Tot i que és relativament fàcil fer una aplicació AJaX amb els mètodes i propietats que ofereix l'objecte XMLHttpRequest o mitjançant <script> dinàmiques, han aparegut implementacions d'AJaX que faciliten molt més el treball, com en el cas de les llibreries xaJax i Prototype.
Prototype
modificaPrototype, la llibreria JavaScript de Sam Stephenson de la que parlava abans, proporciona un marc de treball (un framework) per al webmestre que vol fer pàgines dinàmiques al costat del client. La major part del codi són extensions del DOM per fer-lo més fàcil i més ràpid de fer-lo servir, com la funció $(), que substitueix tot el document.getElementById(). També permet afegir funcions pròpies als elements del DOM, als nodes del document, cosa que han aprofitat altres llibreries que es construeixen sobre Prototype com script.aculo.us.
Prototype també fa molt fàcil l'ús de tècniques AJaX amb el seu objecte Ajax. Aquest objecte incorpora altres objectes i classes que simplifiquen molt els processos d'enviar peticions i rebre respostes del servidor incorporat nous mètodes que l'XMLHttpRequest no té. Aquestes classes i objectes són:
Classe o Objecte | Descripció |
Ajax.Request | És la classe que s'utilitza per a qualsevol àmbit AJaX. S'instancïa un objecte d'aquesta classe passant-li com a paràmetres la URL a la que es farà la petició i les opcions que ofereix la llibreria Prototype. |
Ajax.Responders | És un objecte amb dos mètodes, un per especificar les funcions que es cridaran quan ocorrin determinats esdeveniments a qualsevol objecte d'aquestes classes i un altre per esborrar-les. |
Ajax.Response | És la classe que substitueix la classe nativa XMLHttpRequest a la que li afegeix noves propietats i mètodes que milloren la interacció amb JSON, i en treu d'altres que ja estan recollits en altres classes. També millora la compatibilitat amb versions anteriors d'Internet Explorer, en que l'objecte XMLHttpRequest no hi era disponible i s'havia de declarar un objecte ActiveX. |
Ajax.Updater | Classe que permet especificar un element del document per tal d'actualitzar el seu contingut amb la resposta del servidor. Al instanciar un objecte, s'especifiquen com a paràmetres l'element que canviarà, la URL on es farà la petició i altres opcions. |
Ajax.PeriodicalUpdater | Classe que també actualitza un element del document però de manera repetida en el temps. Així doncs, s'ha d'especificar com a paràmetre també el temps entre una petició i l'altra. |
El seu potencial, a més, s'aprofita al màxim quan es desenvolupa utilitzant Ruby on Rails al costat del servidor. Ruby on Rails té automatitzada la inserció de l'etiqueta <script> de Prototype, i algunes funcions d'aquest últim estan inspirades en el llenguatge Ruby. Tot i així, es pot utilitzar qualsevol altre llenguatge per fer ús de Prototype, al ser aquesta una llibreria escrita en JavaScript i executada al costat del client.
Aquesta és una adaptació de l'exemple que donen a la documentació de Prototype per a Ajax.Request:
var url = '/proxy?url=' + encodeURIComponent('http://www.google.com/search?q=Prototype'); new Ajax.Request(url, { method: 'get', onSuccess: function(transport) { var anunci = $('anunci'); if (transport.responseText.match(/href="http:\/\/prototypejs.org/)) anunci.update('Sí! Estem entre els 10 primers!').setStyle({ background: '#dfd' }); else anunci.update('Ohh! Estem per baix del número 10...').setStyle({ background: '#fdd' }); } });
El que fa el codi és actualitzar l'element (X)HTML amb ID anunci fent una petició al servidor un cop descarregada la pàgina. Si a la pàgina rebuda, en concret la pàgina de Google quan s'ha buscat la paraula Prototype, hi apareix el text «href="http://prototypejs.org/» el contingut de l'element s'actualitza amb «Sí! Estem entre els 10 primers!», amb el fons de color verd, i en cas contrari, s'actualitzarà amb «Ohh! Estem per baix del número 10...» i el fons de color roig.
Si analitzem amb deteniment el codi, podem veure que a la primera línia es declara la variable url amb una cadena que conté la direcció URL de la pàgina de servidor a la que es farà la petició. Li passa mitjançant el mètode GET la variable url amb el valor de la pàgina de Google a la que s'ha buscat la paraula "Prototype". Això ho fa perquè mitjançant l'objecte XMLHttpRequest tan sols pots fer peticions al servidor del que prové la pàgina, i no a servidors externs. El que fa la pàgina anomenada /proxy al codi és descarregar al servidor la pàgina del Google i després enviar-la al navegador de l'usuari.
Amb la segona línia de codi es crea un objecte Ajax.Request al qual se li passa com a paràmetres la variable url abans definida i un objecte creat mitjançant JSON que comprèn fins a l'última línia. Aquest objecte té una propietat anomenada method i que especifica que les variables de la petició se li passaran al servidor mitjançant GET, i un mètode que s'executarà quan un cop s'hagi rebut la resposta del servidor i aquesta hagi tingut èxit.
Aquest mètode declara una nova variable, anunci, a la que li atribueix un node de l'arbre del DOM, el que té a l'atribut id el valor «anunci». Tot seguit comprova que a la resposta hi aparegui el text «href="http://prototypejs.org/» per tal de modificar el DOM amb les extensions que proporciona Prototype i d'informar a l'usuari.
Com es pot notar a l'exemple, la programació amb Prototype varia molt de la programació tradicional amb JavaScript. Prototype incentiva a utilitzar objectes declarats utilitzant JSON i a passar funcions anònimes (sense ser definides per un nom) com a paràmetre.
script.aculo.us
modificascript.aculo.us és una llibreria desenvolupada per Thomas Fuchs que amplia les capacitats de Prototype. Incorpora una impressionant varietat d'efectes visuals, funcionalitat Drag and Drop (arrossega i deixa), controls AJaX i més extensions per al DOM de les que aporta Prototype.
El principal atractiu de la llibreria són els efectes visuals que inclou. Entre ells es pot trobar aparicions, desaparicions, engrandiments, plegaments i desplegaments, tremolors, pulsacions, flaixos de color, etc. Tots aquests efectes són mètodes de l'objecte Effect i tots requereixen especificar com a primer paràmetre l'ID de l'element, a més de les múltiples opcions que pugin tenir de configuració i personalització. A més, incorporant l'objecte Reflector es poden fer els efectes mirall a imatges que explicava al capítol de Disseny web, tractament d'imatges i CSS sense l'ús d'un programa de retoc fotogràfic, directament des de JavaScript. L'objecte Reflector no està a la mateixa llibreria, però publiquen un exemple amb el codi a la mateixa pàgina. Als annexos incorporo el codi i un parell d'exemples.
Una altra de les funcionalitats que aporta la llibreria és la de poder arrossegar elements i situar-los a zones de la pàgina habilitades per poder ser deixats. Així doncs, es diferencia entre elements «draggables» (que es poden arrossegar) i elements «droppables» (que a dins es pot dipositar elements arrossegats). Hi ha dos tipus més de comportaments drag and drop que no fan aquesta distinció: Els «sortables», que són elements situats dins una llista que l'usuari pot ordenar arrossegant els seus elements, i els «sliders », barres on es poden fer córrer dos indicadors per determinar un màxim i un mínim.
script.aculo.us no incorpora nous objectes ni noves propietats i mètodes als objectes ja existents de Prototype, tot i així té un parell de controls AJaX que poden ser útils. La llibreria aporta un suggeridor per als camps de formulari de la pàgina que mostra a l'usuari, mentre està escrivint, els suggeriments que li proposa el servidor o el mateix programa JavaScript. L'altre control és la classe Ajax.InPlaceEditor, que crea dinàmicament camps de formulari allí on l'usuari li diu que vol canviar el text de la pàgina i després envia mitjançant AJaX la petició al servidor amb els canvis que ha fet l'usuari.
Per últim, script.aculo.us aporta l'objecte Builder que té un únic mètode, node, i que ajuda a crear elements mitjançant el DOM molt fàcilment. El primer paràmetre del mètode és el nom de l'etiqueta i és obligatori. Els altres dos són els atributs d'aquesta dins un objecte JSON i el contingut de l'etiqueta, i són opcionals.
Un cop creats els elements s'han d'annexar a la pàgina amb el mètode appendChild(), tal com explicava a l'apartat del Document Object Model al capítol corresponent. Tot i així, tan sols cal annexar-ne un, ja que els altres, que aniran dins d'aquest, ja els annexa el mateix objecte. Així doncs, aquest objecte estalvia molta feina quan s'han d'inserir etiquetes dinàmicament a la pàgina.
Vegem-ho en un exemple. Imagina que hagem de crear aquest codi dinàmicament.
<table id="" style="width:90%;border:solid #333 medium"> <tbody> <tr> <td style="border:solid #633 medium">fila 1, cel·la 1</td> <td>fila 1, cel·la 2</td> </tr> <tr> <td>fila 2, cel·la 1</td> <td style="border:solid #363 medium">fila 2, cel·la 2</td> </tr> </tbody> </table>
Si fem servir el DOM sense cap framework que ens ajudi, hem de crear cada element i després unir-los els uns dins els altres:
table=document.createElement('table'); table.setAttribute("id", "taula"); table.setAttribute("style","width:90%;border:solid #333 medium"); tbody=document.createElement('tbody'); tr1=document.createElement('tr'); td1=document.createElement('td'); td1.innerHTML="fila 1, cel·la 1"; td1.setAttribute("style","border:solid #633 medium") td2=document.createElement('td'); td2.innerHTML="fila 1, cel·la 2"; tr2=document.createElement('tr'); td3=document.createElement('td'); td3.innerHTML="fila 2, cel·la 1"; td4=document.createElement('td'); td4.innerHTML="fila 2, cel·la 2"; td4.setAttribute("style","border:solid #363 medium") tr1.appendChild(td1); tr1.appendChild(td2); tr2.appendChild(td3); tr2.appendChild(td4); tbody.appendChild(tr1); tbody.appendChild(tr2); table.appendChild(tbody);
I aquest és l'equivalen amb el builder de script.aculo.us. La diferència de línies és considerable, i la de temps també.
table = Builder.node('table',{id:'taula', style:'width:90%;border:solid #333 medium'},[ Builder.node('tbody',[ Builder.node('tr',[ Builder.node('td',{style:border:solid #633 medium}, 'fila 1, cel·la 1'), Builder.node('td', 'fila 1, cel·la 2') ]), Builder.node('tr',[ Builder.node('td','fila 2, cel·la 1'), Builder.node('td',{style:border:solid #633 medium},'fila 2, cel·la 2') ]) ]) ]);
A més de Prototype i script.aculo.us existeixen moltes més llibreries escrites en JavaScript amb funcionalitats similars, com Rico, Ext, MoochiKit, Dojo Toolkit, jQuery o Yahoo! UI. També cal destacar l'existència d'altres llibreries que implementen AJaX però que presenten un marc de treball per a llenguatges de servidor, com en el cas de xaJax amb PHP i ZK amb Java. Ambdós llibreries permeten configurar els canvis en els elements del document des del servidor, com a resposta a una petició AJaX, i no és necessari tenir coneixements molt profunds de JavaScript per usar-les, al contrari del que passava amb els anteriors frameworks.