quarta-feira, 11 de dezembro de 2013

Destaque (highlight) do texto filtrado na coluna do Primefaces DataTable


Neste post vou mostrar como destacar na coluna do dataTable do Primefaces o texto que o usuário digita no filtro. São 5 passos bem simples, mostrados a seguir:

i) Criar no managed bean o método removeAcentos, como foi mostrado no post anterior.

ii) Fazer um binding da tabela:

1. <p:dataTable id="Tabela"
2.                              binding="#{controlador.tabela}"


1.     private DataTable tabela;
2. 
3.     public DataTable getTabela() {
4.         return tabela;
5.     }
6. 
7.     public void setTabela(DataTable tabela) {
8.         this.tabela = tabela;
9.     }

iii) Chamar um método para retornar o texto destacado, e usar escape="false":

1. <p:column headerText="Título"
2.            filterBy="#{registro.titulo}#{controlador.removeAcentos(registro.titulo)}"
3.            filterMatchMode="contains" >
4.            <h:outputText value="#{controlador.highlight(registro.titulo,'titulo}#{controlador.removeAcentos(registro.titulo)')}"
5.                            escape="false" />
6. </p:column>

O segundo parâmetro do método é o conteúdo do filterBy sem o identificador inicial e sem a chave final (ou seja, usar somente a parte destacada em verde, ignorando as partes vermelhas):

 #{registro.   titulo}#{controlador.removeAcentos(registro.titulo)    }

Este comportamento é do médoto getFilters da tabela, como poderemos ver posteriormente no quinto passo.

iv) Criar, no managed bean, o método para montar o texto destacado:

1.     public String buildHighlight(String filtro, String texto) {
2.         String retorno = "";
3.         Integer posicao = 0, tam = filtro.length();
4.         while (true) {
5.             if (posicao > texto.length()) {
6.                 break;
7.             }
8.             if ((texto.length() - posicao) < tam) {
9.                 retorno += StringUtils.substring(texto, posicao);
10.                 break;
11.             }
12.             if (removeAcentos(StringUtils.substring(texto, posicao, posicao + tam)).equalsIgnoreCase(removeAcentos(filtro))) {
13.                 retorno += <span style="color: red;'>" + StringUtils.substring(texto, posicao, posicao + tam) + "</span>";
14.                 posicao += tam;
15.             } else {
16.                 retorno += StringUtils.substring(texto, posicao, posicao + 1);
17.                 posicao++;
18.             }
19.         }
20.         return retorno;
21.     }

v) Criar, no managed bean, o método para obter o valor digitado e retornar o destaque:

1.     public String highlight(String texto, String filtro) {
2.         String retorno = "";
3.         if (texto != null) {
4.             Map<String, String> filtros = tabela.getFilters();
5.             String textoDigitado = filtros.get(filtro);
6.             if (textoDigitado != null) {
7.                 retorno = buildHighlight(textoDigitado, texto);
8.             } else {
9.                 return texto;
10.             }
11.         }
12.         return retorno;
13.     }

Veja um exemplo que filtra o texto "Aneis" (sem acentos!) e destaca o texto encontrado (com acentos!):


Este recurso foi implementado a partir da necessidade de visualizar mais facilmente palavras que são digitadas sem acento e que aparecem no texto da coluna com acento. Quando as palavras são bem específicas, a visualização é mais fácil, mas quando são palavras pequenas e o texto da coluna tem várias linhas, o destaque mostrado acima é essencial.

terça-feira, 10 de dezembro de 2013

Como ignorar acentos usando o filtro do Primefaces


Um dos mais comuns pedidos dos usuários de sistemas é que possam pesquisar dados em tabelas sem a preocupação com acentos. Há várias maneiras de implementar esse recurso, mas vou mostrar hoje uma maneira bastante simples e direta.

Dois passos simples para filtrar dados ignorando os acentos, independente de tecnologia de bancos de dados, utilizando Primefaces:

1 - Criar um método no ManagedBean para gerar uma string sem acentos:

1.     public String removeAcentos(String s) {
2.         if (s == null) {
3.             return "";
4.         }
5.         String semAcentos = s.toLowerCase();
6.         semAcentos = semAcentos.replaceAll("[áàâãä]", "a");
7.         semAcentos = semAcentos.replaceAll("[éèêë]", "e");
8.         semAcentos = semAcentos.replaceAll("[íìîï]", "i");
9.         semAcentos = semAcentos.replaceAll("[óòôõö]", "o");
10.         semAcentos = semAcentos.replaceAll("[úùûü]", "u");
11.         semAcentos = semAcentos.replaceAll("ç", "c");
12.         semAcentos = semAcentos.replaceAll("ñ", "n");
13.         return semAcentos;
14.     }

2- Utilizar o filtro do Primefaces com uma expressão que tenha a string original (com acentos) e a string sem acentos, como pode ser visto na linha 16 abaixo:

1. <p:dataTable id="Tabela" style="width: 100%; font-size: 0.95em"
2.                                  widgetVar="wTabela"
3.                                  rowIndexVar="rownumber"
4.                                  var="item"
5.                                  value="#{bean.pessoas}"
6.                                  paginator="true"
7.                                  lazy="false"
8.                                  currentPageReportTemplate="{currentPage} de {totalPages}"
9.                                  rows="10"
10.                                  pageLinks="7"
11.                                  rowsPerPageTemplate="10,20,50,100">
12. ...
13.                         <p:column sortBy="#{item.nome}"
14.                                   style="white-space: normal"
15.                                   filterMatchMode="contains"
16.                                  filterBy="#{item.nome}#{bean.removeAcentos(item.nome)}" >
17.                                   <h:outputText value="#{item.nome}" />
18.                         </p:column>

Desta maneira, podemos pesquisar tanto José como Jose, pois a comparação vai ser feita com a string "joséjose", lembrando que o Primefaces já ignora maiúsculas e minúsculas.

quarta-feira, 27 de novembro de 2013

Primefaces: atualizar componentes JSF nos clientes usando p:socket


Em uma série de posts anterior, mostrei como usar o componente poll do Primefaces para atualizar periodicamente os clientes de uma aplicação. Desta vez vou mostrar como disparar uma atualização imediata para todos os clientes usando o componente socket do Primefaces. Este componente monitora um canal de mensagens, e quando colocado em uma página, cria uma comunicação em tempo real com todos os navegadores dos clientes da aplicação. São cinco passos muito simples:

I - Incluir no projeto a biblioteca atmosphere-runtime-2.0.4.jar

Eu estou usando JBoss versão 6.1.0-Final, mas acredito que não haja problemas com outras versões.

II - Configurar o arquivo web.xml, acrescentando o seguinte trecho:

1.     <servlet>
2.         <servlet-name>Push Servlet</servlet-name>
3.         <servlet-class>org.primefaces.push.PushServlet</servlet-class>
4.     </servlet>
5.     <servlet-mapping>
6.         <servlet-name>Push Servlet</servlet-name>
7.         <url-pattern>/primepush/*</url-pattern>
8.     </servlet-mapping>

Este trecho é fixo, pode ser usado em qualquer outro projeto, sem necessidade de fazer modificações.

III - Na página que deve ser atualizada, criar um socket, pode ser no final da página, antes da tag </html>:

1.         <p:socket channel="/atualizar">
2.             <p:ajax event="message"
3.                          update=":frmPrincipal:pnlTabela"
4.                          oncomplete="wTabela.filter()" />
5.         </p:socket>

Neste exemplo, o canal de mensagens foi chamado de atualizar, o componente que será atualizado com um update é :frmPrincipal:pnlTabela (que é um p:outputPanel, no meu caso) e ainda será executado o método filter() do widget wTabela (que no meu caso é um p:dataTable).

IV - Criar um método para mandar uma mensagem por um canal:

1.     public synchronized void atualizarClientes(String canal) {
2.         PushContext pushContext =
3.                PushContextFactory.getDefault().getPushContext();
4.         pushContext.push("/"+canal, "Enviando mensagem");
5.     }

Este método pode ser colocado em alguma classe estratégica, de preferência com escopo de aplicação. Se o seu projeto tiver um façade para distribuir os serviços, lá é um bom lugar.

V - Mandar uma mensagem pelo canal desejado sempre que for necessária uma atualização:

1. atualizarClientes("atualizar");


Sempre que a chamada acima for feita, será enviada uma mensagem pelo canal atualizar. No momento, o conteúdo da mensagem não é importante, e sim o efeito causado, já que qualquer cliente da aplicação que estiver em uma página com um socket monitorando o canal atualizar, será comunicado e uma requisição ajax do evento message poderá ser disparada para qualquer fim desejado..

Observação: consulte a classe aplicacao da série anterior de posts e o método atualizarLista da mesma. A chamada atualizarClientes("atualizar") poderia ser a última linha daquele método, garantindo que sempre que houver uma operação de CRUD, será enviada uma mensagem para o canal atualizar. Lembrando que se for usada esta tecnologia com socket, o componente poll colocado nas páginas da série não será mais necessário.