A Display Tag Library é uma suíte open source de custom tags que fornecem uma apresentação web de alto nível para ser usada em uma aplicação MVC. Sua biblioteca possibilita um aumento significante de funcionalidade mesmo sendo simples o seu uso.
Com este componente é possível mostrar tabelas, isto é, listar as informações de objetos de uma aplicação. Seus diferenciais são as possibilidades na apresentação dessas tabelas.
É possível criar tabelas com diferenciação nas cores das linhas, ordenação nas colunas, paginação dos dados, agrupamento de informações, exportação dos dados, links e decoração customizável.
O uso mais simples que existe da Display Tag é criar uma tabela com uma lista de objetos. Mesmo assim já podemos ver ganhos de produtividade no momento que podemos criar uma tabela usando apenas uma tag e indicando a coleção que contém os objetos que serão mostrados.
Exemplo:
<% List teste = new ArrayList( 4 ); teste.add( "Test String 1" ); teste.add( "Test String 2" ); teste.add( "Test String 3" ); teste.add( "Test String 4" ); request.setAttribute( "teste", teste ); %> <display:table name="teste" />
Resulta em :
//TODO img
Com a simples instrução <display:table name="test2"/>
geramos uma tabela dinamicamente, de forma extremamente
simples. O que a display tag fez foi iterar na lista "Teste" e chamar o método toString() de cada objeto dessa lista, mostrando
seus valores nas linhas (uma linha para cada objeto).
Pode ser feito a partir do SourceForge em http://sourceforge.net/project/showfiles.php?group_id=73068 inclui o código fonte e as bibliotecas para uso, que são as seguintes:
Para começar a usar a Display Tag, deve seguir os seguintes passos:
<taglib> <taglib-uri>http://displaytag.org</taglib-uri> <taglib-location>/WEB-INF/displaytag.tld</taglib-location> </taglib>
A principal tag é a table. Ela gera um tabela html baseada em uma coleção de objetos. Cada item é formatado de acordo com a tag column aninhada dentro dela.
Exemplo:
<display:table name="person"> <display:column property="id" title="ID" /> <display:column property="name" /> <display:column property="email" /> <display:column property="status" /> <display:column property="description" title="Comments"/> </display:table>
No exemplo acima vemos o código que irá gerar uma tabela que conterá os dados de uma java.util.List chamada "person". Cada column irá mostrar o valor de um atributo de uma instância de person, isto é feito com property.
Atributos de <display:table>
Atributo | Descrição | Tipo |
---|---|---|
cellpadding | O mesmo da HTML. Pode ser definido no css como "padding" | String |
cellspacing | O mesmo da HTML | String |
class | O mesmo da HTML | String |
decorator | Nome completo da classe java que implemente um TableDecorator. É usado para fornecer operações customizadas na lista, como somas de valores. Deve herdar de org.displaytag.decorator.TableDecorator. | String |
defaultsort | O index da coluna que será usada como default para ordenação | int |
export | habilita(true) / desabilita(false) a exportação de dados | boolean |
frame | O mesmo da HTML | String |
id | Uma variável implícita será criada com o nome dado e colocada no escopo da tag. O objecto também é colocado no pageContext com este nome. | String |
length | número de registros que serão mostrados | String |
list | Referencia ao objeto usado como fonte de dados para a tabela. Pode ser uma expressão como requestScope.object.property. Deve ser definido ou o atributo name ou o atributo list. List é sugerido. | String |
name | Referencia ao objeto usado como fonte de dados para a tabela. Pode ser uma expressão como requestScope.object.property. Deve ser definido ou o atributo name ou o atributo list. List é sugerido. | String |
offset | index do primeiro registro que deve ser mostrado | String |
pagesize | número de registros em uma página | String |
requestURI | When the present, links for sorting, exports, and paging are formed by adding any tag generated parameters to the value of requestURI attribute. | String |
rules | O mesmo da HTML | String |
sort | Use 'page' se você quiser ordenar somente os registros visíveis ou 'list' se if you want to sort the full list | String |
style | O mesmo da HTML | String |
summary | O mesmo da HTML | String |
Mostra uma propriedade de um objeto em uma linha dentro da tabela. DEVE ser colocada dentro da tag table. O valor mostrado será o resultado de um decorator (se houver um), ou valor de uma propriedade do objeto acessada pelo atributo property.
DEVE ser colocada dentro de uma tag table, serve para setar uma determinada propriedade para a tabela. Como alternativa pode-se criar um arquivo de propriedades para toda a aplicação, mais informações no javadoc de DisplayPropertiesLoaderServlet.
As propriedades e os valores possíveis estão em http://displaytag.sourceforge.net/configuration.html
Exemplo:
<display:table name="person"> <display:setProperty name="basic.show.header" value="false"/> <display:column property="id" title="ID" /> <display:column property="name" /> <display:column property="email" /> <display:column property="status" /> <display:column property="description" title="Comments"/> </display:table>
Acima, indicamos que a tabela gerada não deve mostrar a linha de título, onde ficam os nomes das colunas.
Os únicos atributos de <display:setProperty>
são 'name' e 'value' que representam respectivamente o
nome da propriedade de <display:table>
e o valor que irá receber.
Algumas das principais propriedades usadas na tag <display:setProperty>
. Estas são utilizadas para
paginação, a lista completa pode ser vista on-line na URL
http://displaytag.sourceforge.net/configuration.html
Propriedade | Default | Valores válidos | Descrição |
---|---|---|---|
paging.banner.placement | top | top, bottom, both | Indica onde deve ser colocado o banner com informações da paginação. A posição é em relaçãoa tabela. |
paging.banner.item_name | item | String | O nome do item no singular |
paging.banner.items_name | items | String | O nome do item no plural |
paging.banner.some.items_found | <span class="pagebanner">{0} {1} found, displaying {2} to {3}.</span> | String | Mensagem mostrando o número de itens encontrados em uma relação parcial. Exemplo:
Total {0} {1}. Exibindo de {2} ate {3} Onde: {0} total de objetos {1} nome no plural dos objetos {2} num do prim obj da listagem atual {3} num do ult obj da listagem atual |
Esta tag também DEVE estar dentro da tag table, ela irá fornecer um rodapé customizado para a tabela. Seu resultado é gerado na tabela com um tfooter.
Exemplo:
<display:table name="someList"> <display:column property="mail"/> <display:column property="total"/> <display:footer> <tr> <td>total:</td> <td><%= someList.size() %></td> <tr> </display:footer> </display:table>
No exemplo abaixo vemos como é possível modificar o estilo da tabela dinamicamente. Para isso, é passado o nome do estilo que deve ser aplicado na tabela através de um link.
<% request.setAttribute( "test", new TestList( 10 ) ); %> <% String lClass = "isis"; if( request.getParameter( "class" ) != null ) { lClass = request.getParameter( "class" ); if (!("isis".equals(lClass) || "its".equals(lClass) || "mars".equals(lClass) || "simple".equals(lClass) || "report".equals(lClass) || "mark".equals(lClass))) { lClass=""; } } %> <ul id="stylelist"> <li><a href="example-styles.jsp?class=isis">ISIS</a></li> <li><a href="example-styles.jsp?class=its">ITS</a></li> <li><a href="example-styles.jsp?class=mars">Mars</a></li> <li><a href="example-styles.jsp?class=simple">Simple</a></li> <li><a href="example-styles.jsp?class=report">Report</a></li> <li><a href="example-styles.jsp?class=mark">Mark Column</a></li> </ul> <display:table name="test" class="<%=lClass%>"> <display:column property="id" title="ID" class="idcol"/> <display:column property="name" /> <display:column property="email" /> <display:column property="status" class="tableCellError" /> <display:column property="description" title="Comments"/> </display:table>
É possível criar um objeto implicitamente na tabela ou no escopo da página usando o atributo 'id'. Desta forma poderemos usar o objeto dado por uma linha da tabela (um objeto que pertença a lista usada como fonte de dados) dentro de um scriptlet ou dentro de outra tag.
Por exemplo, seja a lista "funcionarios" que contém objetos do tipo FuncVO, ao iterar nesta lista, poderemos acessar cada objeto do tipo FuncVO e usá-lo em um scriptlet. Isto é util se precisarmos acessar os valores de mais de uma propriedade em uma linha, para fazer algum cálculo por exemplo.
<%@ page import="br.com.neki.vo.FuncVO"%> <display:table name="funcionarios" id="func"> <display:column title="Salario Bruto" property="salario" /> <display:column title="Descontos" property="desconto" /> <display:column title="Salario Liquido"> <%((FuncVO)func).getSalario() - (FuncVO)func).getDesconto()%> </display:column> </display:table>
Acima mostramos o salário líquido calculado a partir de salario e desconto.
Um decorator é um design pattern onde um objeto fornece funcionalidades básicas encapsuladas para outros objetos.
Imagine que tenhamos uma lista de objetos de negócio que queremos mostrar, e que estes objetos contêm propriedades que não retornam valores do tipo String. Queremos controlar como estes valores serão apresentados, sejam datas, valores monetários, números, etc, para isso usamos um Decorator, que irá formatar esses valores de acordo com a nossa necessidade.
Para criar uma classe wrapper que atue como um Decorator, 4 pontos devem ser observados:
Veja o exemplo a seguir:
Seja o javabean ListObject com seus atributos, getter's e setter's:
package org.displaytag.sample; import java.util.ArrayList; import java.util.Date; import java.util.Random; import org.apache.commons.lang.StringUtils; public class ListObject extends Object{ private static Random random = new Random(); //random number generator private int id = -1; private String name; private Date date; private double money; // ... (getters and setters) ... public ListObject(){ // Constructor for ListObject this.id = random.nextInt(99998) + 1; this.money = (random.nextInt(999998) + 1) / 100; this.name = StringUtils.capitalize(RandomSampleUtil.getRandomWord()); this.date = RandomSampleUtil.getRandomDate(); }
Este é um Decorator para ListObject, ele irá formatar os atributos money e date.
package org.displaytag.sample; import java.text.DecimalFormat; import java.text.SimpleDateFormat; import org.displaytag.decorator.TableDecorator; public class Wrapper extends TableDecorator{ private SimpleDateFormat mDateFormat = null; private DecimalFormat mMoneyFormat = null; /** * Cria um novo Wrapper decorator que irá reformatar alguns dados de ListObject */ public Wrapper(){ super(); // Formata os atributos date e money. this.mDateFormat = new SimpleDateFormat("MM/dd/yy"); this.mMoneyFormat = new DecimalFormat("$ #,###,###.00"); } /** * Returns the date as a String in MM/dd/yy format * @return String */ public String getDate() { return this.mDateFormat.format(((ListObject) this.getCurrentRowObject()).getDate()); } /** * Returns the money as a String in $ #,###,###.00 format * @return String */ public String getMoney() { return this.mMoneyFormat.format(((ListObject) this.getCurrentRowObject()).getMoney()); } }
O método getCurrentRowObject irá retornar o objeto da linha corrente (aquele que terá algum valor formatado.
Para usar na <display:table>
é simples, basta usar o atributo decorator passando o nome
completo da classe wrapper que acabamos de implementar:
<display:table name="test" decorator="org.displaytag.sample.Wrapper" > <display:column property="id" title="ID" /> <display:column property="name" /> <display:column property="date" /> <display:column property="money" /> </display:table>
Para os atributos id e name, serão mostrados os valores sem formatação nenhuma, pois no nosso wrapper decorator não foi feito overload dos métodos getId() e getName(). Para os atributos date e money, os valores mostrados serão aqueles formatados pelos métodos getDate() e getMoney() da classe Wrapper.
Também é possível criar um Decorator que deverá atuar em apenas uma coluna, ColumnDecorator. Assim poderemos criar
formatações diferentes da data por exemplo, podendo depois simplesmente mudar o decorator escolhido. Para utilizá-lo
colocamos o atributo decorator em <display:column>
:
<display:table name="test" > <display:column property="id" title="ID" /> <display:column property="name" /> <display:column property="money" /> <display:column property="date" decorator="org.displaytag.sample.LongDateWrapper" /> </display:table>
Veja o código de LongDateDecorator, ele deve implementar a interface ColumnDecorator:
package org.displaytag.sample; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Date; import org.displaytag.decorator.ColumnDecorator; public class LongDateWrapper implements ColumnDecorator { private DateFormat mDateFormat = new SimpleDateFormat("MM/dd/yyyy HH:mm:ss"); /** * Method decorate * @param pColumnValue Object * @return String */ public final String decorate(Object pColumnValue) { Date lDate = (Date) pColumnValue; return mDateFormat.format(lDate); } }
Para criar links dinâmicos podemos usar duas formas diferentes. Uma forma é como no struts (chamaremos esta forma de Struts-like) e a outra é usando o Decorator.
Esta forma é boa quando o link que queremos criar é baseado em uma simples propriedade do objeto que está sendo mostrado.
A tag column tem cinco atributos parecidos com struts que podem ser setados para criar o link dinâmico:
Geralmente, paramName e paramScope não são usados, deixando-os null, indicamos que será usado o objeto correspondente a linha corrente.
Exemplo:
<display:table name="details"> <display:column property="id" title="ID" href="details.jsp" paramId="id" /> <display:column property="email" href="details.jsp" paramId="action" paramName="testparam" paramScope="request" /> <display:column property="status" href="details.jsp" paramId="id" paramProperty="id" /> </display:table>
Esta forma é boa quando um link é formado de várias partes diferentes de informações ou quando queremos mudar o texto do link, por exemplo: "view | edit | delete"
Imagine que um link deste tipo deve ter um código mais ou menos assim:
"<a href=\"details.jsp?id=" + lId + "&action=view\">View</a> | " + "<a href=\"details.jsp?id=" + lId + "&action=edit\">Edit</a> | " + "<a href=\"details.jsp?id=" + lId + "&action=delete\">Delete</a>";
Assim como podemos fazer a formatação de dados no Decorator e retornar um String com o valor formatado, podemos também retornar algo bem mais "amigável" do que o mostrado acima:
<display:column property="link" title="Actions" />
Veja que é muito melhor colocarmos property="link2" do que escrever todo o código anterior para criar um link.
No Wrapper colocamos um método chamado getLink() por exemplo, contendo todo o código para gerar o link:
public String getLink(){ ListObject lObject = (ListObject) getCurrentRowObject(); int lId = lObject.getId(); return "<a href=\"details.jsp?id=" + lId + "&action=view\">View</a> | " + "<a href=\"details.jsp?id=" + lId + "&action=edit\">Edit</a> | " + "<a href=\"details.jsp?id=" + lId + "&action=delete\">Delete</a>"; }
Pronto ! Agora é só colocar na tag <display:table>
o nome completo da classe do nosso Wrapper e chamar
a propriedade link ( para que seja invocado o método getLink() ).
<display:table name="List" decorator="org.displaytag.sample.Wrapper" > <display:column property="id" title="ID" /> <display:column property="email" /> <display:column property="link" title="Actions" /> </display:table>
O resultado será:
ID | Actions | |
---|---|---|
93833 | vero-sanctus@erat.com | View | Edit | Delete |
70383 | nonumy-sed@kasd.com | View | Edit | Delete |
6283 | voluptua-gubergren@no.com | View | Edit | Delete |
O Decorator fornece mais do que a habilidade de reformatar dados antes de serem mostrados, ele também pode lhe dar a possibilidade de pegar estes dados e executar ações na tabela depois de cada linha ser processada. Isto permite que possamos intervir no processamento da tabela e inserir uma linha adicional com totais, etc. Esta funcionalidade é útil para fazer agrupamento e montar relatórios com uma melhor interface.
Vamos ver primeiro o agrupamento:
CITY | PROJECT | HOURS | TASK |
---|---|---|---|
Carthago | Army | 260.0 | sit consetetur dolores At |
_ | Arts | 644.0 | duo accusam sadipscing sanctus |
Neapolis | Arts | 209.0 | takimata nonumy elitr et |
_ | Gladiators | 619.0 | At tempor sanctus est |
_ | Taxes | 975.0 | sed et sit diam |
_ | _ | 944.0 | nonumy elitr et elitr |
_ | _ | 555.0 | justo diam eos kasd |
_ | _ | 125.0 | Stet ea duo ut |
_ | _ | 7.0 | diam nonumy sit clita |
Olympia | Army | 581.0 | sit dolores diam elitr |
_ | Arts | 695.0 | ipsum sed magna diam |
_ | Gladiators | 36.0 | et sed sed diam |
_ | Taxes | 834.0 | sea takimata diam aliquyam |
Roma | Army | 977.0 | takimata magna clita tempor |
_ | _ | 469.0 | clita et et eos |
_ | Arts | 859.0 | est aliquyam clita nonumy |
Note que na tabela acima foi feito um agrupamento primeiro pelo nome da cidade e depois pelo projeto. Para gerar uma
tabela desse tipo, é muito simples, basta usar o atributo group dentro de <display:column>
:
<display:table name="test" class="simple"> <display:column property="city" title="CITY" group="1"/> <display:column property="project" title="PROJECT" group="2"/> <display:column property="amount" title="HOURS"/> <display:column property="task" title="TASK"/> </display:table>
Agora vamos fazer uma tabela, baseada na anterior, que terá somente o nome da cidade, do projeto e o total de horas de cada cidade.
//TODO table
O código para esta tabela é o seguinte:
<display:table name="test" decorator="org.displaytag.sample.TotalWrapper" > <display:column property="city" title="CITY" group="1" /> <display:column property="project" title="PROJECT" group="2" /> <display:column property="amount" title="HOURS" /> <display:column property="task" title="TASK" /> </display:table>
A classe TotalWrapper usada como Decorator deve fazer overload do método finishRow() que é chamado no final da geração de cada linha.
Daí na implementação do código, testamos se foi terminada a última linha de uma cidade, e antes de passar para a próxima colocamos o código HTML que montará uma nova linha na tabela com o a soma desejada. Veja:
public class TotalWrapper extends TableDecorator { private double mCityTotal = 0; private double mGrandTotal = 0; /** * After every row completes we evaluate to see if we should be drawing a * new total line and summing the results from the previous group. * @return String */ public final String finishRow() { int lListindex = ((List) getDecoratedObject()).indexOf(this.getCurrentRowObject()); ReportableListObject lReportableObject = (ReportableListObject) this.getCurrentRowObject(); String lNextCity = ""; mCityTotal += lReportableObject.getAmount(); mGrandTotal += lReportableObject.getAmount(); if (lListindex == ((List) getDecoratedObject()).size() - 1){ { lNextCity = "XXXXXX"; // Last row hack, it's only a demo folks... } else { { lNextCity = ((ReportableListObject) ((List) getDecoratedObject()).get(lListindex + 1)).getCity(); } StringBuffer lBuffer = new StringBuffer(1000); // City subtotals... if (!lNextCity.equals(lReportableObject.getCity())) { lBuffer.append("\n<tr>\n<td> </td><td> </td><td><hr noshade size=\"1\"></td>"); lBuffer.append("\n<td> </td></tr>"); lBuffer.append("\n<tr><td> </td>"); lBuffer.append("\n<td align=\"right\"><b>" + lReportableObject.getCity() + " Total:</b></td>\n<td><b>"); lBuffer.append(mCityTotal); lBuffer.append("</b></td>\n<td> </td>\n</tr>"); lBuffer.append("\n<tr>\n<td colspan=\"4\"> \n</td>\n</tr>"); mCityTotal = 0; } // Grand totals... if (getViewIndex() == ((List) getDecoratedObject()).size() - 1) { lBuffer.append("<tr><td colspan=\"4\"><hr noshade size=\"1\"></td></tr>"); lBuffer.append("<tr><td> </td>"); lBuffer.append("<td align=\"right\"><b>Grand Total:</b></td><td><b>"); lBuffer.append(mGrandTotal); lBuffer.append("</b></td><td> </td></tr>"); } return lBuffer.toString(); } }
ReportableListObject :
package org.displaytag.sample; import java.util.Random; /** * A test class that has data that looks more like information that comes back * in a report... */ public class ReportableListObject extends Object implements Comparable { private static Random mRandom = new Random(); // random number producer private String city; private String project; private String task; private double amount; private static String[] mCities = {"Roma", "Olympia", "Neapolis", "Carthago"}; private static String[] mProjects = {"Taxes", "Arts", "Army", "Gladiators"}; /** * Constructor for ReportableListObject */ public ReportableListObject() { this.amount = (mRandom.nextInt(99999) + 1) / 100; this.city = mCities[mRandom.nextInt(mCities.length)]; this.project = mProjects[mRandom.nextInt(mProjects.length)]; this.task = RandomSampleUtil.getRandomSentence(4); } public String toString(){ return "ReportableListObject(" + city + ":" + project + ":" + amount + ")"; } public int compareTo(Object anotherObject) { ReportableListObject object1 = this; ReportableListObject object2 = (ReportableListObject) anotherObject; if (object1.city.equals(object2.city)) { if (object1.project.equals(object2.project)) { return (int) (object2.amount - object1.amount); } else { return object1.project.compareTo(object2.project); } } else { return object1.city.compareTo(object2.city); } } }
Podemos escolher uma ou mais colunas para definirem a ordenação, inclusive dinamicamente. Ou seja, o usuário poderá escolher por qual das colunas a tabela será ordenada.
Basta colocar o atributo soteable="true" na tag <display:column>
da colunas que poderão ser
escolhidas para ordenação. Se o usuário ainda não escolheu nenhuma, podemos definir uma como sendo a coluna default
de ordenação, basta colocar o atributo defaultsort e o indice da coluna (começado por zero) na tag <display:table>
.
<display:table name="test" defaultsort="1"> <display:column property="id" title="ID" sortable="true" headerClass="sortable" /> <display:column property="name" sortable="true" headerClass="sortable"/> <display:column property="email" /> <display:column property="status" sortable="true" headerClass="sortable"/> </display:table>
Por favor, dê a sua opinião a respeito do conteúdo apresentado. Correções, sugestões ou qualquer outra informação relevante é de suma importância para nós e será muito bem vinda.
Contato: owner@hotwork.dev.java.net