21 octubre, 2007

DWR - AJAX

.
AJAX es un acrónimo que significa "Asynchonous JavaScript XML".

En si AJAX es una técnica de programación web mediante la cual una página web puede intercambiar información con el servidor en forma asincrónica, de manera tal que la página puede actualizarse sin necesidad de ser recargada. El cliente (browser con la página web) y el servidor mantienen una comunicación asincrónica y en background.

En sí, AJAX es netamente JavaScript pero en este capítulo vamos a explicar un framework que permite invocar desde JavaScript (browser, cliente) métodos remotos, en el servidor implementados en Java.


DWR - Direct Web Remoting

DWR es un framework open soucre que ofrece una alternativa estilo RPC (invocación remota de procedimientos) entre el cliente (browser) y el servidor. Utilizando DWR podemos invocar métodos vía JavaScript pero que se procesarán en el server. DWR se encarga de todo el marshalling de la comunicación.

DWR cconsiste en dos partes principales:
  • Un servlet corriendo en el server que procesa los requests y envia los responses al browser
  • JavaScript corriendo en el browser que envia los requests y puede dinámicamente actualizar la página HTML.
El JavaScript necesario para establecer la invocación remota de métodos se genera “on the fly” por lo que se minimiza bastante el código cliente que debemos escribir.


Hola Mundo DWR !

Para comenzar veremos como configurar DWR en nuestro proyecto web y luego desarrollaremos un HolaMundo. El siguiente tutorial (paso a paso) está sacado del sitio oficial de DWR: http://getahead.org/dwr/getstarted

1 - Descargar el archivo dwr.jar y copiarlo en el directorio WEB-INF/lib de la aplicación web.

2 - Agregar las siguientes líneas en el web.xml de la aplicación web.
   1:
2: <servlet>
3: <servlet-name>dwr-invoker</servlet-name>
4: <servlet-class>
5: org.directwebremoting.servlet.DwrServlet
6: </servlet-class>
7: <init-param>
8: <param-name>debug</param-name>
9: <param-value>true</param-value>
10: </init-param>
11: </servlet>
12:
13: <servlet-mapping>
14: <servlet-name>dwr-invoker</servlet-name>
15: <url-pattern>/dwr/*</url-pattern>
16: </servlet-mapping>
17:

3 - Crear un archivo llamado dwr.xml en el directorio WEB-INF de la aplicación web, con el siguiente código.

dwr.xml
   1:
2:<!DOCTYPE dwr PUBLIC
3:"-//GetAhead Limited//DTD Direct Web Remoting 1.0//EN"
4:"http://www.getahead.ltd.uk/dwr/dwr10.dtd">
5:
6:<dwr>
7: <allow>
8: <create creator="new" javascript="JDate">
9: <param name="class" value="java.util.Date"/>
10: </create>
11: </allow>
12:</dwr>
13:

4 - Ahora probemos ingresar en nuestra aplicación web, en el directorio dwr:

http://localhost:8080/[YOUR-WEBAPP]/dwr/

DWR mostrará una página en la que podremos ver todas las clases publicadas, cuyos métodos podrán ser invocados vía JavaScript.



En este caso (a modo de ejemplo) solo está publicada la clase JDate (la de java.util). Recordemos que cuando creamos el archivo dwr.xml, definimos que íbamos a utilizar esta clase.

5 - Si hacemos "click" en JDate entonces veremos una página en la que podremos probar todos los métodos de (en este caso) JDate. Algo así:



Además (en la parte superior) veremos el código que tenemos que incluir en nuestro JSP o HTML para poder invocar al objeto remoto.


Ahora si, Hola Mundo !

Con el proyecto web correctamente configurado para poder utilizar DWR estamos en condiciones de hacer el primer ejemplo.

6 - Creamos la clase HolaMundoDWR.

HolaMundoDWR.java
   1:
2:package test;
3:
4:public class HolaMundoDWR
5:{
6: public String saludo(String n)
7: {
8: return "Hola Mundo, "+n;
9: }
10:}
11:

7 - Tenemos que publicar la clase en DWR. Para esto agregamos el siguiente código en el archivo dwr.xml.
   1:
2: <create creator="new" javascript="HolaMundoDWR">
3: <param name="class" value="test.HolaMundoDWR"/>
4: </create>
5:

8 - Volvemos a ingresar en el directorio dwr de la aplicación web para asegurarnos que la clase HolaMundoDWR fue correctamente deployada.

http://localhost:8080/[YOUR-WEBAPP]/dwr/test/HolaMundoDWR

Si todo está bien veremos la siguiente página:



En esta página podemos probar el método saludo, pero también DWR nos muestra los scripts que tenemos que incluir en nuestra página JSP si queremos invocar métodos de la clase HolaMundoDWR. También nos sugiere incluir el script util.js que será muy util para actualizar dinámicamente contenido HTML.

9 - Ahora si, podemos programar la página JSP.

testDWR.jsp
   1:
2:<html>
3: <head>
4: <!-- incluimos los scripts que nos indico -->
5: <!-- la pagina de prueba de DWR -->
6: <script
7: type='text/javascript'
8: src='/ZDWR/dwr/interface/HolaMundoDWR.js'>
9: </script>
10: <script type='text/javascript'
11: src='/ZDWR/dwr/engine.js'>
12: </script>
13: <script type='text/javascript'
14: src='/ZDWR/dwr/util.js'></script>
15: </head>
16:
17: <body>
18: <!-- es muy importante el id (tfNom) -->
19: Nombre <input type="text" id="tfNom">
20:
21: <!-- en el onclick del boton llamamos a la -->
22: <!-- funcion saludo() que esta mas abajo -->
23:
24: <input value="Enviar"
25: type="button"
26: onclick="saludo()" />
27: <br>
28: <!-- notemos que el spam tiene un id -->
29: Respuesta: <b><span id="lblNom" /></b>
30:
31: </body>
32:</html>
33:
34:<!-- ahora desarrollamos la funcion saludo que -->
35:<!-- obtiene el nombre que ingresamos en tfNom, -->
36:<!-- invoca a la funcion remota y setea en el -->
37:<!-- span (lblNom) el resultado recibido -->
38:<script>
39: function saludo()
40: {
41: // obtenemos el nombre ingresado por el usuario
42: var nom = dwr.util.getValue("tfNom");
43:
44: // invocamos la funcion remota pasandole nom
45: // y una funcion de callback que DWR invocara
46: // cuando la informacion enviada por el server
47: // este disponible en para ser utilizada
48: HolaMundoDWR.saludo(nom, function(data)
49: {
50: // seteamos el resultado en el span
51: dwr.util.setValue("lblNom", data);
52: });
53: }
54:</script>
55:

Podemos ver que con la librería util.js podemos acceder muy facilmente a los objetos de la página para obtener los datos que tienen cargados y para setearles resultados.

Para invocar al método remoto saludo necesitamos utilizar una función de callback. Esto se debe a que Java es sincrónico pero AJAX es asincrónico por lo tanto le pasamos esta función para que DWR la invoque cuando la información enviada por el servidor esté disponible y pueda ser utilizada en el browser. Allí seteamos el resultado en el span lblNom.


Manipulación de Objetos HTML

Como vemos, DWR resuelve de manera extremadamente simple la invocación remota de métodos entre la página HTML (o JSP) y la clase Java que los implementa. Por lo tanto la mayor complejidad está dada en poder actualizar dinámicamente el contenido HTML con la información que llega desde el servidor.

Para esto DWR provee una librería de funciones JavaScript llamada: util.js. Veremos algunos ejemplos de como actualizar contenido dinámicamente utilizando esta librería.


Combos Dependientes

En este ejemplo veremos como actualizar el contenido de un combo dependiendo de lo que el usuario selecciona en otro combo.

Para esto programamos la siguiente clase que basicamente proveerá dos métodos:
  • Collection obtenerArtistas();
  • Collection obtenerDiscos(String artista);
Es decir: el primer método retornará una Collection de Strings con los nombres de los artistas "registrados en una base de datos" y el segundo método, dado un nombre de artista retornará todos los discos de ese artista que "tenemos registrados".

Para simplificar, los datos los harcodearemos en una Hashtable.

CatalogoCD.java
   1:
2:package test;
3:import java.util.*;
4:
5:public class CatalogoCD
6:{
7: private Hashtable artistas;
8:
9: public CatalogoCD()
10: {
11: artistas=new Hashtable();
12: _cargarInformacion();
13: }
14:
15: // retorna una Collection de Strins con los
16: // artistas que tenemos registrados
17: public Collection obtenerArtistas()
18: {
19: Vector v=new Vector();
20: for(Enumeration e=artistas.keys()
21: ;e.hasMoreElements();)
22: {
23: v.add(e.nextElement());
24: }
25: return v;
26: }
27:
28: // dado un artista, retorna una Collection de
29: // Strings con los titulos de los discos del
30: // artista especificado
31: public Collection obtenerDiscos(String artista)
32: {
33: return (Collection) artistas.get(artista);
34: }
35:
36: // todo harcodeado...
37: private void _cargarInformacion()
38: {
39: Vector v1=new Vector();
40: v1.add("Please Please Me");
41: v1.add("Abbey Road");
42: v1.add("Magical Mistery Tour");
43: artistas.put("The Beatles",v1);
44:
45: Vector v2=new Vector();
46: v2.add("Demasiado Ego");
47: v2.add("La Hija de la Lagrima");
48: v2.add("Say No More");
49: v2.add("Kill Gil");
50: artistas.put("Charly Garcia",v2);
51:
52: // sexo Ibiza Locomia...!
53: Vector v3=new Vector();
54: v3.add("A Ibiza con Locomia");
55: v3.add("Abanicos Por Doquier!");
56: artistas.put("Locomia",v3);
57: }
58:}
59:

Para registrar la clase como servicio en DWR tenemos que agregar las siguientes líneas en el archivo dwr.xml.
   1:
2: <create creator="new" javascript="CatalogoCD">
3: <param name="class" value="test.CatalogoCD"/>
4: </create>
5:

Ahora vamos a:
http://localhost:8080/[YOUR-WEBAPP]/dwr/test/CatalogoCD

para ver los scripts que debemos incluir en la página JSP.



Y ahora veamos la página JSP:

discos.jsp
   1:
2:<html>
3: <head>
4: <script
5: type='text/javascript'
6: src='/TestAjax/dwr/interface/CatalogoCD.js'>
7: </script>
8: <script
9: type='text/javascript'
10: src='/TestAjax/dwr/engine.js'>
11: </script>
12: <script
13: type='text/javascript'
14: src='/TestAjax/dwr/util.js'>
15: </script>
16:
17: <script>
18: function obtenerArtistas()
19: {
20: CatalogoCD.obtenerArtistas(function(data){
21: dwr.util.removeAllOptions("cbArtista");
22: dwr.util.addOptions("cbArtista", data);
23: obtenerDiscos();
24: });
25: }
26:
27: function obtenerDiscos()
28: {
29: var art = dwr.util.getValue("cbArtista");
30: CatalogoCD.obtenerDiscos(art,function(data)
31: {
32: dwr.util.removeAllOptions("cbDisco");
33: dwr.util.addOptions("cbDisco", data);
34: });
35: }
36: </script>
37: </head>
38:
39: <body>
40: <input type="button"
41: value="Cargar"
42: onclick="obtenerArtistas()" />
43: <select id="cbArtista"
44: onChange="obtenerDiscos()" />
45: <select id="cbDisco" />
46: </body>
47:</html>
48:

Como vemos, la página en si comie
nza a partir de la línea 39. Tenemos dos select (combos), uno con id="cbArtista" y el otro con id="idDisco" y un button.

En el evento onclick del botón invocamos a la función obtenerArtistas, y en el evento onChange del combo de artistas invocamos a la función obtenerDiscos.

Analicemos entonces la función obtenerArtistas (que se encuentra a partir de la línea 18).
17:
18: function obtenerArtistas()
19: {
20: CatalogoCD.obtenerArtistas(function(data){
21: dwr.util.removeAllOptions("cbArtista");
22: dwr.util.addOptions("cbArtista", data);
23: obtenerDiscos();
24: });
25: }
26:

La función invoca a CatalogoCD.obtenerArtistas pasándole una función callback en la que primero removemos todos los items del combo y luego seteamos en el combo de artistas la colección que retorna el método Java. Por último invocamos a la función obtenerDiscos para cargargar los discos del artistas que quedó seleccionado.

El código de la función obtenerDiscos está a partir de la línea 27 y lo podemos ver a continuación.
26: 
27: function obtenerDiscos()
28: {
29: var art = dwr.util.getValue("cbArtista");
30: CatalogoCD.obtenerDiscos(art,function(data)
31: {
32: dwr.util.removeAllOptions("cbDisco");
33: dwr.util.addOptions("cbDisco", data);
34: });
35: }
36:

En esta función tomamos el valor que se encuentra seleccionado en el combo de artistas y lo asignamos a la variable art. Luego invocamos a la función CatalogoCD.obtenerDiscos pasándole art y una función callback dentro de la cual borramos los items del combo de discos y seteamos en dicho combo la colección que retorna el método Java.

El resultado será:







.

7 comentarios:

Un tipo dijo...

Excelente tutorial, hice los dos y andan perfecto

Es la belleza de la simpleza :)

Anónimo dijo...

Cuando lo hago correr en explorer 7 solo aparece el primer combo.
Cual puede ser el problema?

Anónimo dijo...

3-nov-2008

no c si pudieran enviarme un proyecto donde si funcione el combo dependiente, es que ya lo trate de realizar pero no corre me marca error en la variable de CatalogoCD-- dice que no esta defiida..


ruben_mtz23@msn.com



Gracias!

Anónimo dijo...

Casi te puedo apostar que ese error es por que no estas definiendo en el dwr.xml la clase de CatalogoCD...! el tutorial esta sencillo y jala bien sigue los pasos .. y mas alla verifica tengas el .jar de dwr

Anónimo dijo...

Excelente, el ejemplo es muy bueno... Saludos

Anónimo dijo...

No le entiendo bien eso de la funcion(data) que esta en el JS, me lo podrian explicar mejor por fa!! :S

Anónimo dijo...

HOLA,ESTOY DESARROLLANDO UN PROYECTO CON DWR Y HIBERNATE, YA HICE TODA LA CONFIGURACION DE HIBERNATE PARA MANIPULAR UNA TABLA, Y TAMBIEN HICE LA CONFIGURACION DE ESA CLASE PARA PODER MANEJARLA CON DWR, TODO VA BIEN, ME PUBLICA LA CLASE EN DWR Y VEO LOS METODOS, PERO CUANDO TRATO DE EJECUTAR UN METODO MA MARCA ERROR EN UN MENSAJE, NO ME DICE QUE TIPO NI NADA SOLO ME MUESTRA "ERROR" HE LEIDO ALGO DE CONVERTIDORES DE DWR PARA HIBERNATE PERO NO SE COMO UTILIZARLOS OJALA ALGIEN ME PUDIERA AYUDAR.