Creación de un componente en JSF2: separando la renderización del propio componente.
0. Índice de contenidos.
- 1. Introducción
- 2. Entorno
- 3. Dependencias de librerías
- 4. Creación del componente y el renderizador
- 5. Referencias
- 6. Conclusiones
1. Introducción
Sobre la base del tutorial que hace poco publicó mi compi César sobre cómo crear un componente en JSF 2, en este tutorial vamos a ver cómo podemos separar la renderización del propio componente.
En el tutorial de César vimos cómo crear un componente simple al que podíamos incluso pasar un parámetro y ahora, sobre la misma base, vamos a analizar cómo recibir un parámetro evaluándolo con Expression Language, delegando la renderización en un renderer específico.
La mayoría de componentes visuales que usamos, haciendo uso de jsf, son de librerías de terceros, cuando necesitamos algo más complejo usamos plantillas y componentes por composición, pero, en ocasiones, por la complejidad de lo que queremos pintar es más recomendable bajar un peldaño más y escribir el código «a mano». A quién haya trabajado con servlets y jsp le sonará eso de escribir directamente en la salida 😉
2. Entorno
El tutorial está escrito usando el siguiente entorno:
- Hardware: Portátil MacBook Pro 17′ (2.93 GHz Intel Core 2 Duo, 4GB DDR3 SDRAM).
- Sistema Operativo: Mac OS X Snow Leopard 10.6.1
- Maven 3
- JSF2 (Mojarra 2.1.2)
- Tomcat 7
3. Dependencias de librerías
Lo primero como siempre, trabajando con Maven, es declarar las dependencias de las librerías con las que vamos a trabajar:
<dependency> <groupId>com.sun.faces</groupId> <artifactId>jsf-api</artifactId> <version>2.1.2</version> </dependency> <dependency> <groupId>com.sun.faces</groupId> <artifactId>jsf-impl</artifactId> <version>2.1.2</version> </dependency> <dependency> <groupId>javax.el</groupId> <artifactId>el-api</artifactId> <version>2.2</version> <scope>provided</scope> </dependency>
Las de JSF están como compile porque trabajamos con Tomcat y no las provee por defecto, sin embargo la de Expression Language sí y está como provided. Tened en cuenta que ésta última es la versión de EL que provee Tomcat 7, aunque también podemos trabajar con dicha versión en un Tomcat 6.
4. Creación del componente y el renderizador
El objetivo es disponer de un componente que no se pinte a sí mismo y que defina, como propiedades, los parámetros que permitirán parametrizarlo.
Siguiendo con el ejemplo de un campo de salida, vamos a definir un atributo value y a proporcionar información sobre la familia a la que pertenecerá el componente y la clase que lo renderizará.
package com.autentia.tutoriales.jsf.components; import javax.faces.component.FacesComponent; import javax.faces.component.UIComponentBase; @FacesComponent(value = "HtmlCustomComponent") public class HtmlCustomComponent extends UIComponentBase { private String value; @Override public String getFamily() { return "com.autentia.tutoriales.jsf.components"; } @Override public String getRendererType() { return "com.autentia.tutoriales.jsf.renderers.HtmlCustomRenderer"; } public String getValue() { return value; } public void setValue(String value) { this.value = value; } }
Si lo comparamos con lo que teníamos hasta ahora estamos tipando el parámetro value y ya no incluimos en el componente el método encodeAll que autopintaba.
En el método getRendererType devolvemos la clase que sabrá pintar el componente, que puede ser algo como lo siguiente:
package com.autentia.tutoriales.jsf.renderers; import java.io.IOException; import javax.faces.component.UIComponent; import javax.faces.context.FacesContext; import javax.faces.context.ResponseWriter; import javax.faces.render.FacesRenderer; import javax.faces.render.Renderer; import com.autentia.tutoriales.jsf.components.HtmlCustomComponent; @FacesRenderer(componentFamily="com.autentia.tutoriales.jsf.components", rendererType="com.autentia.tutoriales.jsf.renderers.HtmlCustomRenderer") public class HtmlCustomRenderer extends Renderer { @Override public void encodeBegin(FacesContext context, UIComponent component) throws IOException { final HtmlCustomComponent customComponent = (HtmlCustomComponent) component; final ResponseWriter writer = context.getResponseWriter(); writer.startElement("div", component); writer.writeAttribute("style", "color : red", null); final String message = customComponent.getValue(); if (message == null) { writer.writeText("Hola adictosaltrabajo, hoy es: " + new java.util.Date(), null); } else { writer.writeText(message, null); } writer.endElement("div"); } }
Hacemos hincapié en:
- El método encodeBegin que recibe la instancia del componente a pintar, con lo que podemos acceder directamente al valor de las propiedades o parámetros del mismo,
- La anotación @FacesRenderer en la que se define la familia de componentes que podrá tratar y en el que nombramos al propio renderizador. Dicha configuración vía anotaciones
suple la siguiente configuración que tendríamos a hacer a través de xml:<render-kit> <renderer> <component-family>com.autentia.tutoriales.jsf.components</component-family> <renderer-type>com.autentia.tutoriales.jsf.renderers.HtmlCustomRenderer</renderer-type> <renderer-class>com.autentia.tutoriales.jsf.renderers.HtmlCustomRenderer</renderer-class> </renderer> </render-kit>
Con ello, seguimos sin tener la necesidad de incluir casi configuración en el faces-config.xml.
Lo siguiente sería configurar el componente en la librería de tags. En la siguiente ubicación /src/main/resources/META-INF deberíamos incluir un fichero tnt.taglib.xml con el siguiente contenido:
<?xml version='1.0' encoding='UTF-8'?> <facelet-taglib xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facelettaglibrary_2_0.xsd" version="2.0"> <namespace>http://www.autentia.com/jsf/components/ <tag> <tag-name>customOutput <component> <component-type>HtmlCustomComponent</component-type> <renderer-type>com.autentia.tutoriales.jsf.renderers.HtmlCustomRenderer</renderer-type> </component> </tag> </facelet-taglib>
Con todo ello, ya podríamos hacer uso del componente dentro de nuestras páginas del siguiente modo:
<?xml version="1.0" encoding="UTF-8"?> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:f="http://java.sun.com/jsf/core" xmlns:h="http://java.sun.com/jsf/html" xmlns:tnt="http://www.autentia.com/jsf/components/" <tnt:customOutput value="otro mensaje personalizado..." /> </html>
El resultado será algo como lo siguiente:
4.1. Proporcionando soporte de Expression Language
El valor del atributo, por defecto, no soportará Expression Language; quién hará uso de nuestro componente seguramente esperará que sus propiedades lo soporten con lo que lo ideal será incluirlo.
Para ello podríamos sustituir el método getValue del componente por algo similar a lo siguiente:
public String getValue() { final ValueExpression valueExpression = getValueExpression("value"); if (valueExpression != null) { final String elValue = (String) valueExpression.getValue( getFacesContext().getELContext()); return elValue; } return value; }
Ahora ya podríamos hacer uso del componente con el soporte de Expression Language:
<?xml version="1.0" encoding="UTF-8"?> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:f="http://java.sun.com/jsf/core" xmlns:h="http://java.sun.com/jsf/html" xmlns:tnt="http://www.autentia.com/jsf/components/" <tnt:customOutput value="un parámetro de la request: #{param.test}" /> </html>
El resultado será algo como lo siguiente:
5. Referencias
6. Conclusiones
El objetivo de crear nuestros propios componentes jsf, y distribuirlos empaquetados, vemos que en JSF 2 es bastante más sencillo que en la versión 1.2. Aún nos queda mucho por mostrar: cómo mantener el estado de un componente de entrada, cómo trabajar con diferentes kits de renderización para que en función del dispositivo se pinte nuestro componente como corresponda, cómo mantener un componente y renderizar su contenido con un componente por composición de facelets,… temas que iremos tratando, como siempre, aquí en adictos al trabajo con el soporte de autentia. Stay tuned!.
Un saludo.
Jose