Sobre esse Tutorial

Néki Technologies.

Agradecimentos ao Néki Team!

Introdução

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).

Download

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:

  • displaytag.jar - biblioteca que contém as classes de taglibs.
  • displaytag.tld - o arquivo tld que contém o tag library descriptor.
  • displaytag.war - documentação e exemplos de aplicações que aparecem no site oficial

Instalação

Para começar a usar a Display Tag, deve seguir os seguintes passos:

  1. Colocar o arquivo displaytag.tld no diretório WEB-INF da sua aplicação
  2. Colocar o displaytag.jar no WEB-INF/LIB
  3. Possuir no CLASSPATH da aplicação as seguintes bibliotecas usadas por Display Tag:
    • commons-logging.jar
    • commons-lang.jar (versão 2.0)
    • commons-collections.jar
    • commons-beanutils.jar
    • log4j.jar
  4. Registrar no deployment descriptor a taglib desta forma:
    <taglib>
        <taglib-uri>http://displaytag.org</taglib-uri>
        <taglib-location>/WEB-INF/displaytag.tld</taglib-location>
    </taglib>
    

Tag Reference

Table - http://displaytag.sourceforge.net/tagreference.html#table

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>

AtributoDescriçãoTipo
cellpaddingO mesmo da HTML. Pode ser definido no css como "padding" String
cellspacingO mesmo da HTMLString
class O mesmo da HTML String
decoratorNome 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
defaultsortO 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
idUma 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
requestURIWhen 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

Column - http://displaytag.sourceforge.net/tagreference.html#column

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.

setProperty - http://displaytag.sourceforge.net/tagreference.html#setProperty

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.

Propriedades

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 DefaultValores válidos Descrição
paging.banner.placement toptop, 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_nameitemStringO nome do item no singular
paging.banner.items_nameitemsStringO nome do item no plural
paging.banner.some.items_found <span class="pagebanner">{0} {1} found, displaying {2} to {3}.</span>StringMensagem 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

footer - http://displaytag.sourceforge.net/tagreference.html#footer

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>

Exemplos

Modificando o estilo dinamicamente

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>

Criando objeto implicito na tabela

É 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.

Usando Decorator para transformar dados

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:

  • O wrapper criado deve ser subclasse de TableDecorator
  • Para aumentar a performance, deve-ser criar os formatters no método construtor
  • Os métodos de retorno de valores devem sobrecarregar os métodos de retorno do objeto de negócio onde o decorator está atuando. Por exemplo, se temos dentro da classe de negócio um método getMoney() que retorna um valor monetário, no Decorator para esta classe, o método que irá formatar este dado deverá também se chamar getMoney().
  • Não é preciso fazer overload de todos os métodos no Decorator, somente daquele que serão formatados. o método getXXX do Decorator sempre será chamado primeiro para uma property, mas se ele não existir, é chamado o método da classe de negócio.

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);
  }
}

Criando links dinâmicos

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.

Links dinâmicos Struts-like

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:

  • href - a URL usada para construir o link
  • paramID - o nome do parâmetro que será adicionado a URL
  • paramName - nome do bean que contem o dado que será colocado na URL
  • paramProperty - a propriedade chamada no objeto que retornará o valor que ficará na URL

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>

Links dinâmicos usando Decorator

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á:

IDEmailActions
93833vero-sanctus@erat.com View | Edit | Delete
70383nonumy-sed@kasd.com View | Edit | Delete
6283voluptua-gubergren@no.com View | Edit | Delete

Mostrando somas

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:

CITYPROJECT HOURS TASK
CarthagoArmy260.0sit consetetur dolores At
_Arts644.0duo accusam sadipscing sanctus
NeapolisArts209.0 takimata nonumy elitr et
_Gladiators 619.0At tempor sanctus est
_Taxes975.0 sed et sit diam
__944.0 nonumy elitr et elitr
__555.0justo diam eos kasd
__125.0Stet ea duo ut
__7.0diam nonumy sit clita
Olympia Army 581.0 sit dolores diam elitr
_Arts695.0 ipsum sed magna diam
_Gladiators 36.0et sed sed diam
_Taxes834.0 sea takimata diam aliquyam
Roma Army 977.0takimata magna clita tempor
__469.0clita et et eos
_Arts859.0est 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>&nbsp;</td><td>&nbsp;</td><td><hr noshade size=\"1\"></td>");
			lBuffer.append("\n<td>&nbsp;</td></tr>");

			lBuffer.append("\n<tr><td>&nbsp;</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>&nbsp;</td>\n</tr>");
			lBuffer.append("\n<tr>\n<td colspan=\"4\">&nbsp;\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>&nbsp;</td>");
			lBuffer.append("<td align=\"right\"><b>Grand Total:</b></td><td><b>");
			lBuffer.append(mGrandTotal);
			lBuffer.append("</b></td><td>&nbsp;</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);
        }
    }
}

Ordenando por colunas e escolhendo por qual ordenar dinamicamente

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>

Feedback

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