Usando el componente PickList de Primefaces

4
33301

Usando el componente PickList de Primefaces

0. Índice de contenidos.

1. Introducción

En este tutorial vamos a presentar el componente PickList de Primefaces, y cómo podemos aprovecharnos de su estructura para evitar realizar consultas innecesarias a base de datos en nuestros Converters. Además veremos como evitar algún bug de este componente, para que se comporte como realmente deseamos y esperamos.

Este componente nos ofrece la funcionalidad típica de disponer de dos listas a la que podemos ir ańadiendo o quitando elementos para pasarlos de una a otra sin tener que llevar nosotros el control de esto. Como ejemplo báse podéis mirar el showcase de primefaces.

Aquí os dejo una captura de pantalla de este componente para que veáis la funcionalidad que nos cubre:


2. Entorno

Este tutorial está escrito usando el siguiente entorno:

  • Hardware: Portátil MacBook Pro 15′ (2 GHz Intel Core i7, 8GB DDR3 SDRAM).
  • Sistema Operativo: Mac OS X Lion 10.7.1
  • Primefaces 2.2.1
  • JSF 2.1.2

3. Caso de ejemplo.

Aprovechando el inicio del mundial de rugby el 9 de septiembre vamos a hacer un poco de promoción de este magnífico deporte tán poco conocido por nuestro país. Así que el ejemplo consiste en elegir de cada grupo los equipos que se van a clasificar para la siguiente fase (sólamente hacemos la funcionalidad de pasar equipos de «eliminados» a «clasificados» y viceversa, sin validaciones de cuantos se pueden clasificar), aunque esto en realidad valdría para cualquier otro deporte.

Nuestras entidades serán:

Para los grupos:

package com.autentia.tutoriales;

import java.util.List;

public class Pool {

	private final List<Team> knockOuts;
	private final List<Team> classifieds;
	private final String identifier;
	
	public Pool(final String identifier, final List<Team> knockOuts, final List<Team> classifieds){
		this.identifier=identifier;
		this.knockOuts=knockOuts;
		this.classifieds=classifieds;
	}

	public String getIdentifier(){
		return this.identifier;
	}
	
	public List<Team> getKnockOuts(){
		return this.knockOuts;
	}
	
	public List<Team> getClassifieds(){
		return this.classifieds;
	}
	
}

Para Los equipos:

package com.autentia.tutoriales;

import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;

public class Team {

	private final Integer identifier;
	
	private final String name;
	
	public Team(final Integer identifier,final String name){
		this.identifier=identifier;
		this.name=name;
	}

	public Integer getIdentifier() {
		return identifier;
	}

	public String getName() {
		return name;
	}
}

4. El DualListModel

Como todos los componentes, el PickList tiene una clase que define el componente (org.primefaces.component.picklist.PickList) y un renderer(org.primefaces.component.picklist.PickListRenderer) que encargado de pintar una instancia de la clase del componente. Además de esto, este componente hace uso de DualListModel (org.primefaces.model.DualListModel) que es donde nosotros realmente informaremos de cuáles son las dos listas con las que tiene que trabajar el componente.

Volviendo a nuestro caso de ejemplo, en nuestro controlador rellenaremos el DualListModel cuando se vaya a editar uno de los grupos, con los equipos del grupos seleccionado que tenemos en un mapa que rellenaremos al crear la vista.

...
@ManagedBean
@ViewScoped
public class PickListView implements Serializable {
...
	private DualListModel<Team> teamsDualListModel;
	private final Map<String,Pool> pools=new HashMap<String,Pool>();
	@PostConstruct
	public void init() {
		loadPools();	
		teamsDualListModel= new DualListModel<Team>(new ArrayList<Team>(), new ArrayList<Team>());
	}
	
	private void loadPools() {
		... // rellenamos el mapa de los grupos
	}
	...
	public void editPoolListener(String poolIdentifier) {
			poolSelected=pools.get(poolIdentifier);
			this.teamsDualListModel = new DualListModel<Team>(poolSelected.getKnockOuts(),poolSelected.getClassifieds()); 
	}
...
}

Como podéis ver al DualListModel le pasamos directamente los objetos de nuestro modelo, que tendremos que convertir como veremos en el siguiente punto.

5. Converter accediendo al PickList

El componente PickList acepta en su atributo «converter» el identificador del conversor de los objetos que le pasamos. Estos objetos normalmente tendrán un conjunto de atributos entre los que estará un identificador que será el valor real en el HTML y el que se enviará al servidor para que con éste sepamos a que objeto concreto hace referencia. En nuestro ejemplo tenemos en el Equipo (Team), el identificador (identifier) y el nombre(name).

Comúnmente se recupera el objeto haciendo una consulta a base de datos pasando el identificador recibido; pero veremos que esto no es necesario, ya que ese objeto ya lo hemos tenido que recuperar antes para pintarlo en el componente, así que sólo tenemos que pedirle al componente que nos lo devuelva.

Para hacer esto nos crearemos el siguiente converter:

package com.autentia.tutoriales;

import java.util.List;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.convert.Converter;
import javax.faces.convert.ConverterException;
import javax.faces.convert.FacesConverter;
import org.primefaces.component.picklist.PickList;
import org.primefaces.model.DualListModel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@FacesConverter("teamPickListConverter")
public class TeamPickListConverter implements Converter {

	private static final Logger LOG = LoggerFactory.getLogger(TeamPickListConverter.class);

	public Object getAsObject(FacesContext context, UIComponent component, String value) {
		LOG.trace("String value: {}", value);

		return getObjectFromUIPickListComponent(component,value);
	}

	public String getAsString(FacesContext context, UIComponent component, Object object) {
		String string;
		LOG.trace("Object value: {}", object);
		if(object == null){
			string="";
		}else{
			try{
				string = String.valueOf(((Team)object).getIdentifier());
			}catch(ClassCastException cce){
				throw new ConverterException();
			}
		}
		return string;
	}

	@SuppressWarnings("unchecked")
	private Team getObjectFromUIPickListComponent(UIComponent component, String value) {
		final DualListModel<Team> dualList;
		try{
			dualList = (DualListModel<Team>) ((PickList)component).getValue();
			Team team = getObjectFromList(dualList.getSource(),Integer.valueOf(value));
			if(team==null){
				team = getObjectFromList(dualList.getTarget(),Integer.valueOf(value));
			}
			
			return team;
		}catch(ClassCastException cce){
			throw new ConverterException();
		}catch(NumberFormatException nfe){
			throw new ConverterException();
		}
	}

	private Team getObjectFromList(final List<?> list, final Integer identifier) {
		for(final Object object:list){
			final Team team = (Team) object;
			if(team.getIdentifier().equals(identifier)){
				return team;
			}
		}
		return null;
	}
}

Como se puede ver, en el método getAsString(…) lo que hacemos es devolver el identificador del equipo igual que se hace comúnmente. Es en el método getAsObject(…) donde nos aprovechamos de la estructura del componente PickList para pedirle el objeto DualListModel y luego buscar en sus listas el equipo que se corresponde con la clave sin necesidad de ir a base de datos.

Finalmente en nuestro xhtml hacemos uso del componente PickList indicando el converter que acabamos de crear.

...
	<p:pickList value="#{pickListView.teamsDualListModel}"
		var="team" converter="teamPickListConverter"
		itemLabel="#{team.name}" itemValue="#{team}"
		addLabel="A&nacute;adir" addAllLabel="A&nacute;adir todos"
		removeLabel="Quitar" removeAllLabel="Quitar todos"
		style="text-align:center;" widgetVar="teamsPickList" 
		id="teamsPickListComponent">
		<f:facet name="sourceCaption">
			<h:outputLabel value="Eliminados" />
		</f:facet>
		<f:facet name="targetCaption">
			<h:outputLabel value="Clasificados" />
		</f:facet>
	</p:pickList>
...

6. PickList en un contenedor

Como ya os avancé en la introducción, este componente nos evita gran parte del trabajo no teniendo que llevar el control de las listas, pero tiene algún problemilla. En nuestro caso lo sufrimos cuando reutilizábamos el componente en un DialogBox, que nos multiplicaba los elementos de la lista. Esto se producía al refrescar el contenido dependiendo de un parámetro, ya que en la primerqa ejecución funcionaba correctamente.

Al parecer, si el componente está en un contenedor, ya sea un DialogBox (<p:dialog>) o cualquier otro tipo de panel o contenedor, y lo que hacemos en la acción que selecciona el parámetro es enviar este parámetro al servidor y con su respuesta refrescar el contenedor que contiene al PickList, es cuando tenemos el problema. La solución pasaría por no refrescar el contenedor, si no los elementos del mismo que varían según la selección; además de esta forma podemos reducir el tama&nacute;o de la respuesta del servidor. Este bug está documentado en http://code.google.com/p/primefaces/issues/detail?id=2435.

En nuestro caso los distintos botones que de edición de los grupos para seleccionar los equipos que se clasifican los tenemos en un formulario. Cómo se puede ver no refrescamos en contenedor del PickList para evitar tener este problema, lo wque hacemos es refrescar el panel del nombre del grupo y el propio PickList.

...
<h:form id="poolsForm" prependId="false">
					
	<p:commandButton oncomplete="editPool.show();" 
		actionListener="#{pickListView.editPoolListener('A')}"
		value="Grupo A" update="teamNamePanel,teamsPickListComponent"/>
	<p:commandButton oncomplete="editPool.show();"
		actionListener="#{pickListView.editPoolListener('B')}"
		value="Grupo B" update="teamNamePanel,teamsPickListComponent"/>
	<p:commandButton oncomplete="editPool.show();"
		actionListener="#{pickListView.editPoolListener('C')}"
		value="Grupo C" update="teamNamePanel,teamsPickListComponent"/>
	<p:commandButton oncomplete="editPool.show();"
		actionListener="#{pickListView.editPoolListener('D')}"
		value="Grupo D" update="teamNamePanel,teamsPickListComponent"/>
</h:form>
...
<h:form id="editPoolForm" prependId="false">
		 <p:dialog header="Selección de jugadores" widgetVar="editPool"
			modal="true" width="600" height="500" id="editPoolDialog"
			closable="false" closeOnEscape="false"
			visible="false" 
			resizable="true">
			<p:spacer height="30" />

			<p:panel style="text-align:left; border:0px;" id="teamNamePanel">
				<h:outputLabel value="Grupo #{pickListView.poolSelected.identifier}" />
			</p:panel>
			<p:spacer height="30" />

			<p:pickList value="#{pickListView.teamsDualListModel}"
				var="team" converter="teamPickListConverter"
				itemLabel="#{team.name}" itemValue="#{team}"
				addLabel="A&nacute;adir" addAllLabel="A&nacute;adir todos"
				removeLabel="Quitar" removeAllLabel="Quitar todos"
				style="text-align:center;" widgetVar="teamsPickList" id="teamsPickListComponent">
				<f:facet name="sourceCaption">
					<h:outputLabel value="Eliminados" />
				</f:facet>
				<f:facet name="targetCaption">
					<h:outputLabel value="Clasificados" />
				</f:facet>
			</p:pickList>

			<p:spacer height="30" />
			<p:panel style="text-align:center; border:0px;">
				<p:commandButton value="Aceptar"
					action="#{pickListView.savePoolState()}"
					 oncomplete="editPool.hide();"/>
			</p:panel>
		 </p:dialog>
</h:form>
...

7. Conclusiones

Bueno, pues como podéis ver hay muchos frameworks que nos ofrecen sus componentes con los que podemos trabajar, pero no tenemos que esperar que hagan todo el trabajo por nosotros. Así que si investigamos un poco como están hechos esos componentes podemos sacarles un mayor partido.

Por otro lado, tampoco hay que fiarse por completo de estos componentes ya que a veces tienen errores, aunque eso no signifique que no podamos usarlos, sólo hay que saber como evitar esos errores en nuestros proyectos. Así que es probable que en versiones más modernas ya estén corregidos y aprovecharnos de toda su potencia, además al ser la mayoría de ellos código abierto siempre podremos retocarlos a nuestra medida 😉

Espero que os sirva a alguno y ya sabéis, cualquier duda o sugerencia podeis comentarlo.

Saludos.

4 COMENTARIOS

  1. Muchas gracias por el manual, gracias a el he podido implementar el pickList en mi aplicación, ya que la documentación de primefaces es bastante poca.

  2. Muy buen tutorial, tienes idea de como puedo crear un listener que me permita notificar cuando hay cambios en la lista target?

    un saludo

  3. Interesante, pero lo extraño es que no nos muestras como guardar el estado final de las dos tablas…porque?
    ¿Donde está pickListView.savePoolState()?

DEJA UNA RESPUESTA

Por favor ingrese su comentario!

He leído y acepto la política de privacidad

Por favor ingrese su nombre aquí

Información básica acerca de la protección de datos

  • Responsable:
  • Finalidad:
  • Legitimación:
  • Destinatarios:
  • Derechos:
  • Más información: Puedes ampliar información acerca de la protección de datos en el siguiente enlace:política de privacidad