En una de mis más recientes incursiones al mundo RichFaces, me encontré con el problema de tener que actualizar una serie de objetos de tipo dropdown en cascada.

Me explico mejor: El aplicativo a desarrollar debía exponer una caja desplegable (dropdown) con los nombres de los continentes del mundo. Al momento de seleccionar algún continente, la caja desplegable de “Países” debía ser cargada con los países que posee el continente seleccionado y adicionalmente, el dropdown de “Países” deberá quedar mostrando el primero de ellos.

El proceso anterior se debe repetir para los estados de cada país y las ciudades de cada estado para cada país.

En resumen, se deberán implementar 4 niveles de actualización:

1) Continentes actualiza Países
2) Países actualiza Estados
3) Estados actualiza Ciudades
4) Ciudades actualiza el resultado final mostrando la selección completa.

Originalmente, pensé que esta tarea sería trivial, ya que debe de ser muy común (en el mundo del desarrollo de aplicativos para WEB) este tipo de funcionalidades. De hecho, en tecnologías anteriormente usadas por un servidor, esto ya estaba más que resuelto usando uno u otro medio. (Hablo de jScript o JSP’s básicos, .NET, PHP, etc.)

El problema (para mi) era que en esta ocasión, tenía que usar RichFaces (además de Hibernate, mySQL y el excelente entorno integrado de programación MyEclipse que me precio de medio conocer…)

Sin embargo, era nuevo en RichFaces y pensé que con un par de “googleasos” tendría la solución a este problemilla…

La sorpresa que me llevé fue que no encontré nada que me ayudara tanto como lo deseaba y pues tuve que hacerlo yo solo desde cero. Por ello, pensé que si a alguien alguna vez se le presentara este problema sería un buen gesto darle la mano… y ése es el motivo de esta entrada!!!

Primero:

Asumo que tienen el siguiente ambiente de trabajo (o uno similar que dominan bien):

1) MyEclipse 7.5
2) Hibernate
3) RichFaces (v 3.x)
4) MySQL (v 5.x)

Segundo:

La estructura de la base de datos debe contener al menos esta 4 tablas:

1) Regiones
2) Países
3) Estados
4) Ciudades

Y Yo esperaría que las carguen con datos que, aunque ficticios, no dejen padres sin hijos… (Sería terrible ver que Europa no tiene países o que Coahuila no tiene municipios)

Tercero:

Deben de crear una clase (yo la llamé DropDownInfo) que contenga los siguientes 4 métodos:

List<SelectItem> getRegiones();
List<SelectItem> getPaises(int idRegion);
List<SelectItem> getEstados(int idPais);
List<SelectItem> getCiudades(int idEstado);

La implementación de esta clase y sus métodos la dejo a la imaginación del lector, ya que eso si que fue fácil con el nunca bien ponderado Hibernate y el siempre amable MySQL.

Sobra decir que cada SelectItem estará compuesto por un número (el id del elemento en cuestión) y una cadena de texto (que será el nombre concreto de cada elemento, como Yucatán, Cortland, Asia, etc…)

Cuarto:

Crear una clase (yo la llamé MultiCombo) que tenga las siguientes propiedades:

Integer idRegion=1; // OJO: Sólo éste lleva un 1… los demás no!
Integer idPais;
Integer idEstado;
Integer idCiudad;
List<SelectItem> listaRegiones;
List<SelectItem> listaPaises;
List<SelectItem> listaEstados;
List<SelectItem> listaCiudades;

Recuerda: una propiedad se define como un atributo privado de cierto tipo y puede ser de (lectura) o (de lectura Y escritura). En este caso, todas deben ser tanto de lectura como de escritura. (O sea que hay que ponerles su set y su get a cada una de ellas)

Declarar un atributo privado de tipo DropDownInfo:

private DropDownInfo pmr;

Y un constructor como el siguiente:

public MultiCombo() {
	pmr = new DropDownInfo();
	this.regionCh();
}

Un par de métodos utilitarios:

private Integer obten(ActionEvent event) {
	ValueHolder input = (ValueHolder)event.getComponent().getParent();
	return (Integer)input.getValue();
}

private Integer crea(List<SelectItem> lista) {
	if(lista!=null && lista.size()>0)
	return (Integer)lista.get(0).getValue();
	return 1;
}

Y finalmente dos grupos….

private void regionCh(){
	listaPaises = pmr.cargaPaises(this.idRegion);
	this.idPais = crea(listaPaises);
	paisCh();
}

private void paisCh() {
	listaEstados = pmr.cargaEstados(this.idPais);
	this.idEstado = crea(listaEstados);
	estadoCh();
}

private void estadoCh() {
	listaCiudades = pmr.cargaCiudades(this.idEstado);
	this.idCiudad = crea(listaCiudades);
	ciudadCh();
}

private void ciudadCh() {
	//avisa();
}

Que será de utilidad para asignar el estado a los atributos de tipo List que contienen la información referente a las regiones, paises, estados y ciudades y…

public void regionChanged(ActionEvent event) {
	this.idRegion = obten(event);
	regionCh();
}

public void paisChanged(ActionEvent event) {
	this.idPais = obten(event);
	paisCh();
}

public void estadoChanged(ActionEvent event) {
	this.idEstado = obten(event);
	estadoCh();
}

public void ciudadChanged(ActionEvent event) {
	this.idCiudad = obten(event);
	ciudadCh();
}

Que serán invocados al momento de que se lleve a cabo la selección en una caja desplegable.

Obviamente, el método comentado “avisa()” contendrá lo que ustedes gusten… Yo puse unos mensajes que avisan la selección final.

Ahora la parte interesante….

Para esto voy a requerir que se seguren de que su proyecto a la fecha compile y pueda ser deplorado sin problema alguno. Lo anterior suena fácil, pero requiere de su tiempo y atención al respecto. Por ejemplo:

- Cargar las bibliotecas de RichFaces

- Configurar Hibernate

- Crear los DAOs

Y como cien cosas mas que no competen a este blog…

Bueno… dicho lo anterior, vamos a crear un jsf cuyo núcleo se vea así:





<h:outputLabel for="regionCombo" value="Region:"/>
<h:selectOneMenu
	id="regionCombo"
	value="#{multiCombo.idRegion}">
	<f:selectItems value="#{multiCombo.regiones}" />
	<a4j:support
		event="onchange"
		reRender="paisCombo, estadoCombo, ciudadCombo"
		actionListener="#{multiCombo.regionChanged}"/>
</h:selectOneMenu>
<rich:message for="regionCombo" styleClass="msg" />



<h:outputLabel for="paisCombo" value="Pais:"/>
<h:selectOneMenu
	id="paisCombo"
	value="#{multiCombo.idPais}">
	<f:selectItems value="#{multiCombo.paises}" />
	<a4j:support
		event="onchange"
		reRender="estadoCombo, ciudadCombo"
		actionListener="#{multiCombo.paisChanged}"/>
</h:selectOneMenu>
<rich:message for="paisCombo" styleClass="msg" />



<h:outputLabel for="estadoCombo" value="Estado:"/>
<h:selectOneMenu
	id="estadoCombo"
	value="#{multiCombo.idEstado}">
	<f:selectItems value="#{multiCombo.estados}" />
	<a4j:support
		event="onchange"
		reRender="ciudadCombo"
		actionListener="#{multiCombo.estadoChanged}"/>
</h:selectOneMenu>
<rich:message for="estadoCombo" styleClass="msg" />



<h:outputLabel for="ciudadCombo" value="Ciudad:"/>
<h:selectOneMenu
	id="ciudadCombo"
	value="#{multiCombo.idCiudad}">
	<f:selectItems value="#{multiCombo.ciudades}" />
	<a4j:support
		event="onchange"
		actionListener="#{multiCombo.ciudadChanged}"/>
</h:selectOneMenu>
<rich:message for="ciudadCombo" styleClass="msg" />



Bien, como pueden observar, son cuatro bloques prácticamente idénticos. El primero pide la carga de los tres restantes. El Segundo de los dos restantes y así sucesivamente. Esta petición ocurre en la línea que contiene la palabra “reRender”.

Lo interesante es que esto ocurre cuando el valor del combo “cambia”. Por eso en la línea que dice “event” está el indicativo “onchange”.

Finalmente, la directiva “ActionListener” indica qué método debe de ser ejecutado cuando el evento definido se verifique. En este caso, se le avisa a la clase MultiCombo (claro, a través de su nombre manejado) que ha cambiado algo. Es decir, una región o un país o un estado o una ciudad.

Ya quedó. Listo!

Sólo un punto mas…. Casi una anécdota:

Lo anterior me salió relativamente rápido. Sin embargo, no jalaba!

Moví todo el código y lo rehice unas 20 veces y nada!

Ya me había desesperado y estaba a punto de darme por vencido y mejor poner una taquería.

No podía hacer que un combo cargara a otro y a cada rato me daba un error que decía algo así como: Error de validación. Valor no correcto.

Invertí unos 3 días en miles de pruebas sólo para darme cuenta de que todo siempre estuvo bien…. Todo!. Simplemente el managed bean tenía que ser de sesión y no de request!!!!

Al final del día me quedé pensando si no hubiera sido mejor idea poner mi taquería….

Saludos!

Goose

© 2017 Goose Workshop Suffusion theme by Sayontan Sinha