Como Criar Tag Libs
Este artigo foi criado diante da necessidade de adaptar o Fusion Charts, o Google Maps, e também o
YouTube ao JSF com
RichFaces .
Durante as pesquisas e tutoria à equipe da Strata Engenharia, foram feitas diversas pequisas na internet e foi encontrado um post no forum em
http://www.fusioncharts.com/forum/Topic8629-33-1.aspx, infelizmente ainda foi possivel identificar exatamente o nome do autor, mas seu apelido no forum é "srividya_sharma".
Este artigo auxíliou muito nesta tarefa, mas faltava algumas coisas, como por exemplo, o uso da tag com Facelets, mais integração com outros componentes, no caso
GoogleMaps e
YouTube . Nossa equipe na Strata Engenharia precisava de uma expansão do componente
GoogleMap do
RichFaces e outro componente para exibição de videos do
YouTube com o SWFObject.
Além disto tudo, presisavamos de um aprofundamento nos detalhes de como criar componentes, o que não era objetivo do post do "srividya_sharma". Diante disto acreditamos ser válido por aqui toda a experiência adquirida nesta empreitada.
Os livros "JavaServer Faces - the Complete Referente" e "Core
JavaServer Faces" - Second Edition, foram usados como referência para criação inicial das Tags Libs e o artigo
http://www.jsfcentral.com/articles/facelets_2-2.html além de diversos outros sites foram usados para solucionar o acesso as TAGs através do Facelets.
Vamos então ao trabalho
Como Funciona o sistema de TAGs do JSF e Facelets
Na imagem abaixo é exibida a relação entre os itens utilizados para criar um componente no JSF incluindo o Facelets.
No arquivo TLD que pode ter qualquer nome, mas obrigatoriamente deve ter a extenção .tld é feito a referência à classe que mantem o estado do componente, esta classe armazena as propriedades quando usado em uma página JSP, esta classe deve extender a classe
UIComponentTag ou
UIComponentELTag , tal classe possui dois metodos cada um responsavel respectivamente por informar o identificador para se obter a classe que representa o componente para o JSF e o respectivo Rederizador.
Tanto a classe que representa o componente para o JSF, os rederizadores e também as classes que atuam como validadores podem ser referenciados no arquivo faces-config.xml.
Quando usando Facelets é necessário termos um arquivo extra, que pode ter qualquer nome, mas deve ter o sufixo .taglib.xml, que faz referência a detalhes dos componentes, este arquivo faz referência ao código que identifica o componente conforme definido no faces-config.xml. É interessante observar que estes arquivos tanto o "*.taglib.xml" como o arquivo "faces-config.xml" podem estar na pasta web-inf do projeto que a utiliza, ou em uma pasta META-INF de um arquivo jar que empacota o componente, este arquivo deverá ser encontrado pelo classpath do projeto.
Sugiro que crie o arquivo ".taglib.xml" e ".tld" com o mesmo prefixo, por exemplo:
- fusioncharts.taglib.xml
- fusioncharts.tld
Criando um projeto de componentes JSF no Eclipse
Usaremos o Eclipse para criar nosso componente, e iremos nos basear no post comentado acima. Portanto criaremos um componente para uso do framework
FusionChart dentro do JSF. (Com o amadurecimento do artigo, não podemos esquecer que isto aqui é um wiki-wiki, iremos introduzindo outros tipos de componentes)
Vemos ao lado a janela de criação de um projeto Java no Eclipse.
Eu criei um projeto chamado
FusionChartComponents -Tutorial como pode ser visto na imagem ao lado, o eclipse usado é o Ganymede. Caso ainda não saiba como criar um projeto java no Eclipse, sugiro que procure no google um tutorial de como criar projetos java com Eclipse.
Em seguida adicionei ao projeto as bibliotecas básicas para podemos trabalhar com servlets e a api do JSF, veja na imagem como ficou o meu projeto.
Criando as Classes do Componente
Vamos começar pela criação das Classes do componente. em seguida criaremos os arquivos necessários para reconhecimento do novo componente pelo JSF e Facelets, e finalmente, falaremos rapidamente dos rederizadores extras para cada tipo de dispositivo/navegador.
Precisamos de duas classes para termos um componente básico funcional. A classe que representa a TAG no JSP processando seus atributos e a classe que rederiza o componente quando este for apresentado no dispositivo destino, armazena seu estado, e processa as entradas.
Caso deseje criar mais components é importante se aprofundar nas seguintes classes:
- javax.faces.component.UIComponent
- javax.faces.webapp.UIComponentELTag
- javax.faces.context.FacesContext
- javax.faces.application.Application
- javax.faces.context.ResponseWriter
Veja o Tópico
ClassesImportantesCriarComponentes para mais detalhes.
Criando a Classe que representa a TAG
A classe que representa a TAG deve extender a classe
UIComponentTag ou
UIComponentELTag caso vá trabalhar somente com Faces 1.2 e Containers compátiveis com JSP Version 2.1.
Iremos chama-la de
ChartTag e a colocarei no pacote "br.com.strata.charts" já que estou criando esta tag com objetivo de atender os projetos da "
Strata Engenharia". Observe que a classe tem como posfixo o termo Tag, assim fica fácil identifica-la.
Segue o código usado para criar a classe
package br.com.strata.charts;
import javax.faces.component.UIComponent;
import javax.faces.webapp.UIComponentELTag;
import javax.faces.webapp.UIComponentTag;
public class ChartTag extends UIComponentELTag {
String chartId;
String filename;
String xml;
String url;
String width;
String height;
String debugMode;
String registerWithJS;
public String getChartId() {
return chartId;
}
public void setChartId(String id) {
this.chartId = id;
}
public String getFilename() {
return filename;
}
public void setFilename(String filename) {
this.filename = filename;
}
public String getXml() {
return xml;
}
public void setXml(String xml) {
this.xml = xml;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getWidth() {
return width;
}
public void setWidth(String width) {
this.width = width;
}
public String getHeight() {
return height;
}
public void setHeight(String height) {
this.height = height;
}
public String getDebugMode() {
return debugMode;
}
public void setDebugMode(String debugMode) {
this.debugMode = debugMode;
}
public String getRegisterWithJS() {
return registerWithJS;
}
public void setRegisterWithJS(String registerWithJS) {
this.registerWithJS = registerWithJS;
}
public void release() { // the super class method should be called
super.release();
chartId = null;
filename = null;
xml = null;
url = null;
width = null;
height = null;
debugMode = null;
registerWithJS = null;
}
protected void setProperties(UIComponent component) {
// the super class method should be called
super.setProperties(component);
if (chartId != null)
component.getAttributes().put("id", chartId);
if (filename != null)
component.getAttributes().put("filename", filename);
if (xml != null)
component.getAttributes().put("xml", xml);
if (url != null)
component.getAttributes().put("url", url);
if (width != null)
component.getAttributes().put("width", width);
if (height != null)
component.getAttributes().put("height", height);
if (debugMode != null)
component.getAttributes().put("debugMode", debugMode);
if (registerWithJS != null)
component.getAttributes().put("registerWithJS", registerWithJS);
}
public String getComponentType() {
return "br.com.strata.charts.jsf.FusionChartComponent";
}
public String getRendererType() {
// null means the component renders itself
return null;
}
}
Iremos futuramente descrever em detalhes cada parte da classe, de imediato vamos comentar apenas o método
public String getComponentType()
pois este método está diretamente ligado comas configurações que iremos promover neste tutorial.
O método
getComponentType()
retorna o identificador do Component que está registrado no arquivo faces-config.xml, iremos ver mais detalhes de como intervir no arquivo faces-config.xml logo abaixo.
Criando a Classe que rederiza o componente
Neste Tutorial não iremos em primeira instancia entra em detalhes sobre rederizadores especializados. Esta classe é o caração do componente pois ela não so pode cuidar da rederização do componente quando não existem Rederizadores especializados, como também cuida de outros detalhes das funcionalidades do componente.
Abaixo segue o código da classe:
package br.com.strata.charts;
import java.io.IOException;
import javax.faces.component.UIOutput;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
public class UIChart extends UIOutput {
public void encodeBegin(FacesContext context) throws IOException {
ResponseWriter writer = context.getResponseWriter();
String chartId = getClientId(context);
writer.startElement("div", this);
writer.writeAttribute("id", chartId + "_Div", null);
writer.writeAttribute("align", "center", null);
writer.writeText("Chart.", null);
writer.endElement("div");
String chartSWF = (String) getAttributes().get("filename");
String url = (String) getAttributes().get("url");
String xml = (String) getAttributes().get("xml");
String chartWidth = (String) getAttributes().get("width");
String chartHeight = (String) getAttributes().get("height");
String debugMode = (String) getAttributes().get("debugMode");
int debugModeInt = boolToNum(new Boolean(debugMode));
String registerWithJS = (String) getAttributes().get("registerWithJS");
int regWithJSInt = boolToNum(new Boolean(registerWithJS));
writer.startElement("script", this);
writer.writeAttribute("type", "text/javascript", null);
writer.writeText("var chart_" + chartId + " = new FusionCharts('"
+ chartSWF + "', '" + chartId + "', '" + chartWidth + "', '"
+ chartHeight + "', '" + debugModeInt + "', '" + regWithJSInt
+ "');", null);
if (xml == null || xml.equals(""))
writer.writeText("chart_" + chartId + ".setDataURL(\"" + url
+ "\");", null);
else
writer.writeText("chart_" + chartId + ".setDataXML(\"" + xml
+ "\");", null);
// Finally render the chart
writer.writeText(
"chart_" + chartId + ".render('" + chartId + "_Div');", null);
}
public void encodeEnd(FacesContext context) throws IOException {
ResponseWriter writer = context.getResponseWriter();
writer.endElement("script");
}
/**
* Converts a Boolean value to int value<br>
*
* @param bool
* Boolean value which needs to be converted to int value
* @return int value correspoding to the boolean : 1 for true and 0 for
* false
*/
private int boolToNum(Boolean bool) {
int num = 0;
if (bool.booleanValue()) {
num = 1;
}
return num;
}
@Override
public String getFamily() {
return "br.com.strata.charts.jsf.family.FusionChartComponents";
}
}
Observe que esta classe possui o prefixo UI, é indicado usa-lo em todas as classes que representam o core do componente.
Esta classe possui mais métodos interessantes para nos analisarmos agora do que a Classe
ChartTag , portanto iremos dar uma maior atenção a estes metodos e iremos começar pelos mais simples.
O método getFamily()
O método encodeBegin(FacesContext context)
Este método é responsável pelo inicio da rederização do componente permitindo a criação das tags que compoem o inicio do componente.
Neste método existem 3 partes muito interessantes e fundamentais para nosso trabalho que serão usadas para nos ajudar a montar o codigo que irá apresentar a página no dispositivo:
obtendo o objeto para escrever a resposta
Para efetuar a construção da resposta que este componente irá fornecer em sua posição na página é necessário através do objeto
FacesContext fornecido como parametro do método, obter o objeto
ResponseWriter que permite construir a resposta adequada ao tipo de dispositivo esperado.
ResponseWriter writer = context.getResponseWriter();
.
.
.
writer.startElement("div", this);
writer.writeAttribute("id", divId, null);
writer.writeAttribute("align", "center", null);
String msg = (String) getAttributes().get("msg");
.
.
.
writer.writeText("Fusion Chart - Strata JSF Component.", null);
writer.endElement("div");
No fragmento de código acima pode ser visto alguns métodos do objeto
ResponseWriter sendo usados para construir a página conforme os parametros informados tais parametros podem ser obtidos conforme descrito logo abaixo, agora vamos ver rapidamente os metodos do Objeto.
writer.startElement("div", this);
Este método inicia a tag div na resposta a requisição a página, construindo assim uma tag div (
String chartId = getClientId(context);
String chartSWF = (String) getAttributes().get("filename");
Criando um Arquivo TLD
Bem agora vamos criar nosso arquivo TLD, se vc tiver o plugin "JBoss Tools" instalado em seu Eclipse esta tarefa é bem mais fácil, caso contrario basta copiar o codigo abaixo em um arquivo com o nome
FusionCharts .tld. Você pode escolher qualquer nome que desejar somente o posfixo (extenção) deverá ser obrigatoriamente ".tld" em minusculo.
O Arquivo TLD é importante para defininir o componente para que seja usado com JSP
O codigo abaixo não está completo ele apenas descreve a biblioteca de TAGs que usaremos.
<taglib version="2.1" 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 web-jsptaglibrary_2_1.xsd">
<display-name>Fusion Charts</display-name>
<tlib-version>1.0</tlib-version>
<short-name>FC</short-name>
http://www.full.srv.br/taglibs/fusioncharts
No codigo acima temos as seguintes TAGs (caso não conheça de XML, sugiro que pesquise no google um tutorial sobre o tema).
- display-name -- Identifica o nome que será exibido como sendo um nome amigavel na IDE
- tlib-version -- Versão da Taglib adotada
- short-name -- Nome curto da taglib sugerido
- uri -- Name Space adotado para identificar esta tag lib
Log abaixo da TAG "uri" iremos adiconar o seguinte codigo:
fusionchart
<tag-class>br.srv.full.taglibs.FusionChart</tag-class>
<body-content>JSP</body-content>
<display-name>Fusion Chart</display-name>
Permite a adoção do framework FusionChart com o JSF.
Este bloco de codigo pode ser repetido para definir cada TAG que iremos criar, como so estamos criando uma tag de nome fusionchart, teremos apenas um bloco.
Neste bloco temos as seguintes informações definidas:
- name -- nome da TAG que será usado no arquivo jsp ou xhtml
- tag-class -- nome da classe java que representa o estado da TAG, no nosso caso será a tag "fusionchart"
- body-content -- tipo de conteúdo da TAG
- display-name -- nome amigável a ser utilizado pela IDE
- description -- Descrição a ser apresentada pela IDE
Informando ao JSF da existência do novo Componente
Agora informando ao Faceletes sobre o novo Componente
Outros Rederizadores
Links Úteis
Agradecimentos a ajuda do Juliano desenvolvedor da Chart Informática.
--
CarlosDelfino - 09 Oct 2008