*******
Atualização: Nova postagem com suporte a versão 1.10.x do DataTables e uso do VRaptor 4 já disponível!
*******
Partindo do princípio que tenhamos o conhecimento prévio do comportamento do VRaptor 3 (maiores detalhes aqui), vou demonstrar como podemos usar o DataTables de maneira simples. Ele é um fantástico plug-in que permite a visualização de dados (server-side e/ou client-side) e a sua manipulação com algumas linhas de código, e o melhor, tudo sem muito esforço e com um enorme suporte pela comunidade e no fórum do plug-in. Sugiro, a princípio, para quem não conhece muito ainda, a leitura desses dois artigos:
Agora caso um Controller herde a segunda classe abstrata cujo nome é CommonController, ele terá que sobre escrever o seguinte método:
Classe CommonController:
Supondo que na página tenhamos um formulário com o id "usersDataTable", o nosso arquivo javascript deverá ter, por exemplo, a seguinte configuração:
Execução:
JSON retornado:
Pesquisa pelo nome "Malc":
O laboratório pode ser obtido no github aqui. Coloquei internacionalização básica e manipulei a última coluna para duas ações (alterar e excluir) como itens extras para exemplificar como é fácil trabalhar com o DataTables. Informações adicionais:
Aqlbras! Ao som de Come Together, Beatles.
Atualização: Nova postagem com suporte a versão 1.10.x do DataTables e uso do VRaptor 4 já disponível!
*******
Partindo do princípio que tenhamos o conhecimento prévio do comportamento do VRaptor 3 (maiores detalhes aqui), vou demonstrar como podemos usar o DataTables de maneira simples. Ele é um fantástico plug-in que permite a visualização de dados (server-side e/ou client-side) e a sua manipulação com algumas linhas de código, e o melhor, tudo sem muito esforço e com um enorme suporte pela comunidade e no fórum do plug-in. Sugiro, a princípio, para quem não conhece muito ainda, a leitura desses dois artigos:
Vou resumir bem a ideia, o ideal é que seja executado o laboratório e que esse artigo sirva para tirar dúvidas gerais do processo. Bom, para facilitar a vida no VRaptor, eu criei duas classes abstratas, uma para as entidades que desejamos que seus dados sejam usados pelo plug-in e outra para os Controllers que herdarão os serviços prontos, eles permitirão que os dados sejam entregues a View. Caso uma classe herde a primeira cujo nome é DataTables, ela terá que sobre escrever dois métodos (o toListString não é necessário, já é tratado de maneira genérica):
- toListString(): Seu propósito é retornar um lista que contenha uma lista de Strings de um determinado objeto. Pensando nessa estrutura de dados, é como se existisse um nó com diversos troncos, e cada tronco tem um nó no final com seus galhos (atributos). Vejam a figura abaixo, ela retrata o exemplo do laboratório para a classe User. Com essa estrutura poderemos manter no padrão esperado pelo DataTables para o campo aaData (detalhes e exemplos aqui) quando o método Results.json() do VRaptor serializá-lo.
- getAttributes(): O nó que tem no final de cada tronco precisa ser preenchido com atributos e, como cada classe pode ter mais ou menos atributos, esse método deve ser sobre escrito para tratar cada propriedade de uma determinada classe. É por meio dela também que determinamos a sequência dos dados que serão mostrados na tabela, veremos posteriormente no laboratório.
- getColumnNameById(): Ao se criar uma tabela no banco de dados, ela pode ter, por exemplo, 2 colunas, a primeira Id e a outra Name. Portanto, nesse sentido, o ID seria o 0 e o Name o 1. O DataTable permite que seja feita a ordenação das colunas e ele nomeia a primeira coluna da esquerda com o índice 0, a segunda como 1 e a última como N. Então, quando clicarmos em uma coluna, um evento chamará o método responsável por ordenar por aquela determinada coluna, se clicarmos na segunda, o índice 1 será enviado e, para pesquisar no banco de dados, é necessário saber o nome do atributo que está nessa coluna, é aí que esse método entra em ação.
![]() |
Estrutura de dados List<List<String>> para o campo aaData |
- paginate(): Teremos um script em jQuery que terá um gatilho que será ativado toda vez que uma página for aberta e tiver um componente HTML com um determinado Id (#meuForm, por exemplo), ao ser ativado, ele chamará uma URL pelo método HTTP POST solicitando os dados para serem mostrados nesse componente. O método paginate() retornará uma estrutura de dados no formato JSON suportado pelo DataTables com seus campos obrigatórios (sEcho, iTotalRecords, iTotalDisplayRecords, aaData, etc.). Dentro desse método usaremos um outro do CommonController chamado getParametersDataTable() cuja necessidade é traduzir o que o plug-in DataTables enviou da View em objetos manipuláveis pelo Java. O debug para entender é essencial.
Classe DataTables:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package br.com.willianantunes.utils.datatables; | |
import java.util.ArrayList; | |
import java.util.List; | |
/** | |
* This class can be inherited by another class to expose information compatible with | |
* jQuery DataTables v1.9.4. The constructor of the class that will inherit this one, must set some attributes to | |
* enable DataTables plugin work properly. | |
* @author Willian Antunes | |
* @version 1.0.0 | |
* @see <a href="https://datatables.net/usage/options">DataTables Options</a> | |
* @see <a href="http://datatables.net/usage/server-side">DataTables server-side</a> | |
*/ | |
public abstract class DataTables<T> | |
{ | |
private List<List<String>> aaData; | |
private long iTotalRecords; | |
private long iTotalDisplayRecords; | |
private String sSearch; | |
private String sEcho; | |
public DataTables() | |
{ | |
} | |
public DataTables(List<T> t, long iTotalRecords, long iTotalDisplayRecords, String sSearch, String sEcho) | |
{ | |
setAaData(toListString( | |
t == null ? | |
new ArrayList<T>() : | |
t | |
)); | |
setiTotalRecords(iTotalRecords); | |
setiTotalDisplayRecords(iTotalDisplayRecords); | |
setSSearch(sSearch); | |
setSEcho(sEcho); | |
} | |
@Override | |
public String toString() { | |
return "DataTables [aaData=" + aaData + ", iTotalRecords=" | |
+ iTotalRecords + ", iTotalDisplayRecords=" | |
+ iTotalDisplayRecords + ", sSearch=" + sSearch + ", sEcho=" | |
+ sEcho + "]"; | |
} | |
@Override | |
public boolean equals(Object obj) | |
{ | |
if (obj == null) | |
return false; | |
return this.hashCode() == obj.hashCode(); | |
} | |
@Override | |
public int hashCode() { | |
return this.toString().hashCode(); | |
} | |
public String getString(Object object) | |
{ | |
return object.toString(); | |
} | |
protected void setAaData(List<List<String>> aaData) | |
{ | |
if (aaData == null) | |
aaData = new ArrayList<List<String>>(); | |
this.aaData = aaData; | |
} | |
protected void setiTotalRecords(long iTotalRecords) | |
{ | |
if (iTotalRecords < 0) | |
iTotalRecords = 0; | |
this.iTotalRecords = iTotalRecords; | |
} | |
protected void setiTotalDisplayRecords(long iTotalDisplayRecords) | |
{ | |
if (iTotalDisplayRecords < 0) | |
iTotalDisplayRecords = 0; | |
this.iTotalDisplayRecords = iTotalDisplayRecords; | |
} | |
protected void setSSearch(String sSearch) | |
{ | |
if (sSearch == null) | |
sSearch = ""; | |
this.sSearch = sSearch; | |
} | |
protected void setSEcho(String sEcho) | |
{ | |
this.sEcho = sEcho; | |
} | |
protected List<List<String>> toListString(List<T> listOfT) | |
{ | |
List<List<String>> list = new ArrayList<List<String>>(); | |
for(T t : listOfT) | |
{ | |
list.add(getAttributes(t)); | |
} | |
return list; | |
} | |
/** | |
* This method must feel a list that will be used by jQuery DataTables in "aaData" property. Example of implementation: | |
* <pre> | |
* private List<String> getAttributes(User user) | |
* { | |
* List<String> list = new ArrayList<String>(); | |
* | |
* list.add(this.getString(user.getId())); | |
* list.add(this.getString(user.getName())); | |
* list.add(this.getString(user.getEmail())); | |
* list.add(user.getRegisterDate().toString(DateTimeFormat.forPattern("dd/MM/yyyy HH:mm:ss"))); | |
* list.add(this.getString(user.getUserProfile().getUserProfileType())); | |
* | |
* return list; | |
* } | |
* </pre> | |
* @param t | |
* @return A {@code List<List<String>>} used by jQuery DataTables in "aaData" property. | |
*/ | |
protected abstract List<String> getAttributes(T t); | |
/** | |
* This method must implement a switch-case (example) that will return the column name in the database. Example of implementation: | |
* <pre> | |
* public static String getColumnNameById(int id) | |
* { | |
* switch (id) | |
* { | |
* case 1: | |
* return "name"; | |
* case 2: | |
* return "email"; | |
* case 3: | |
* return "registerDate"; | |
* case 4: | |
* // Criteria alias | |
* return "up.userProfileType"; | |
* default: | |
* return "id"; | |
* } | |
* } | |
* </pre> | |
* @param id | |
* @return A column name that match the column id in the database | |
*/ | |
protected abstract String getColumnNameById(int id); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package br.com.willianantunes.utils.datatables; | |
import java.util.ArrayList; | |
import java.util.HashMap; | |
import java.util.List; | |
import java.util.Map; | |
import javax.servlet.http.HttpServletRequest; | |
import org.apache.log4j.LogManager; | |
import org.apache.log4j.Logger; | |
/** | |
* This controller class (MVC pattern) get all the information compatible with jQuery DataTables v1.9.4. | |
* @author Willian Antunes | |
* @version 1.0.0 | |
* @see <a href="http://datatables.net/usage/server-side">DataTables server-side</a> | |
*/ | |
public abstract class CommonController | |
{ | |
protected HttpServletRequest request; | |
private final Logger logger = LogManager.getLogger(CommonController.class); | |
/** | |
* It's responsible to provide data to be filled in the datatable jquery plugin | |
* @param sSearch Global search field | |
* @param sEcho Information for DataTables to use for rendering. | |
* @param iDisplayStart Display start point in the current data set. | |
* @param iDisplayLength Number of records that the table can display in | |
* the current draw. It is expected that the number of | |
* records returned will be equal to this number, unless | |
* the server has fewer records to return. | |
*/ | |
protected abstract void paginate(); | |
public CommonController() | |
{ | |
} | |
public CommonController(HttpServletRequest request) | |
{ | |
this.request = request; | |
} | |
/** | |
* This method read all the parameters sent by jQuery DataTables. Example of usage on vRaptor v3.5.3: | |
* | |
* <pre> | |
* {@code | |
* Map<String, Object> parametersDataTable = super.getParametersDataTable(); | |
* | |
* String sSearch = (String) parametersDataTable.get("allSearch"); | |
* String sEcho = (String) parametersDataTable.get("sEcho"); | |
* int iDisplayStart = (Integer) parametersDataTable.get("iDisplayStart"); | |
* int iDisplayLength = (Integer) parametersDataTable.get("iDisplayLength"); | |
* String fdSortCol = UserDataTables.getColumnNameById(Integer.parseInt((String) ((ArrayList<Object>) parametersDataTable.get("fdSortCol")).get(0))); | |
* String fdSortDir = (String) ((ArrayList<Object>) parametersDataTable.get("fdSortDir")).get(0); | |
* | |
* List<User> users = sSearch != null ? (List<User>) this.userRepository. | |
* findIntervalByAttribute("name", sSearch, iDisplayStart, iDisplayLength, fdSortDir, fdSortCol) : | |
* (List<User>) this.userRepository.findTheRange(iDisplayStart, iDisplayLength); | |
* | |
* UserDataTables usrDataTable = new UserDataTables( | |
* users, // AaData | |
* users.size(), // iTotalRecords | |
* this.userRepository.totalRecords("id"), // iTotalDisplayRecords | |
* sSearch, // sSearch | |
* sEcho // sEcho | |
* ); | |
* | |
* result.use(Results.json()).withoutRoot().from(usrDataTable).include("aaData").serialize(); | |
* | |
* }</pre> | |
* @return {@code Map<String, Object>} | |
*/ | |
@SuppressWarnings({ "rawtypes", "unchecked" }) | |
protected Map<String, Object> getParametersDataTable() | |
{ | |
Map<String, Object> ret = new HashMap<String, Object>(); | |
Integer iDisplayLength = new Integer(request.getParameter("iDisplayLength") == null ? "0" : request.getParameter("iDisplayLength")); | |
Integer iDisplayStart = new Integer(request.getParameter("iDisplayStart") == null ? "0" : request.getParameter("iDisplayStart")); | |
Integer iColumns = new Integer(request.getParameter("iColumns") == null ? "0" : request.getParameter("iColumns")); | |
String allSearch = (request.getParameter("sSearch") == null ? "" : request.getParameter("sSearch")); | |
Boolean allRegex = new Boolean((request.getParameter("bRegex") == null ? false : true)); | |
Boolean allSearchable = new Boolean((request.getParameter("bSearchable") == null ? false : true)); | |
Boolean allSortable = new Boolean((request.getParameter("bSortable") == null ? false : true)); | |
String sEcho = new String((request.getParameter("sEcho"))); | |
List fdRegex = new ArrayList(); | |
List fdSearchable = new ArrayList(); | |
List fdSortable = new ArrayList(); | |
List fdDataProp = new ArrayList(); | |
List fdSearch = new ArrayList(); | |
List fdSortCol = new ArrayList(); | |
List fdSortDir = new ArrayList(); | |
for (int i = 0; i < iColumns; i++) | |
{ | |
fdRegex.add(new Boolean((request.getParameter("bRegex_" + i) == null ? false : true))); | |
fdSearchable.add(new Boolean((request.getParameter("bSearchable_" + i) == null ? false : true))); | |
fdSortable.add(new Boolean((request.getParameter("bSortable_" + i) == null ? false : true))); | |
fdDataProp.add((request.getParameter("mDataProp_" + i) == null ? "" : request.getParameter("mDataProp_" + i))); | |
fdSearch.add((request.getParameter("sSearch_" + i) == null ? "" : request.getParameter("sSearch_" + i))); | |
fdSortCol.add((request.getParameter("iSortCol_" + i) == null ? "" : request.getParameter("iSortCol_" + i))); | |
fdSortDir.add((request.getParameter("sSortDir_" + i) == null ? "" : request.getParameter("sSortDir_" + i))); | |
} | |
this.logger.trace("[DATA TABLE PARAMETERS]"); | |
this.logger.trace(String.format("iDisplayLength..: (%s)", iDisplayLength.toString())); | |
this.logger.trace(String.format("iDisplayStart...: (%s)", iDisplayStart.toString())); | |
this.logger.trace(String.format("iColumns........: (%s)", iColumns.toString())); | |
this.logger.trace(String.format("allSearch.......: (%s)", allSearch.toString())); | |
this.logger.trace(String.format("allRegex........: (%s)", allRegex.toString())); | |
this.logger.trace(String.format("allSearchable...: (%s)", allSearchable.toString())); | |
this.logger.trace(String.format("allSortable.....: (%s)", allSortable.toString())); | |
this.logger.trace(String.format("fdRegex.........: (%s)", fdRegex.toString())); | |
this.logger.trace(String.format("fdSearchable....: (%s)", fdSearchable.toString())); | |
this.logger.trace(String.format("fdSortable......: (%s)", fdSortable.toString())); | |
this.logger.trace(String.format("fdDataProp......: (%s)", fdDataProp.toString())); | |
this.logger.trace(String.format("fdSearch........: (%s)", fdSearch.toString())); | |
this.logger.trace(String.format("fdSortCol.......: (%s)", fdSortCol.toString())); | |
this.logger.trace(String.format("fdSortDir.......: (%s)", fdSortDir.toString())); | |
this.logger.trace(String.format("sEcho.......: (%s)", sEcho.toString())); | |
this.logger.trace(String.format("[/DATA TABLE PARAMETERS]")); | |
ret.put("iDisplayLength", iDisplayLength); | |
ret.put("iDisplayStart", iDisplayStart); | |
ret.put("iColumns", iColumns); | |
ret.put("allSearch", allSearch); | |
ret.put("allRegex", allRegex); | |
ret.put("allSearchable", allSearchable); | |
ret.put("allSortable", allSortable); | |
ret.put("fdRegex", fdRegex); | |
ret.put("fdSearchable", fdSearchable); | |
ret.put("fdSortable", fdSortable); | |
ret.put("fdDataProp", fdDataProp); | |
ret.put("fdSearch", fdSearch); | |
ret.put("fdSortCol", fdSortCol); | |
ret.put("fdSortDir", fdSortDir); | |
ret.put("sEcho", sEcho); | |
return ret; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
$(document).ready(function(){ | |
$.LABDT = new Object(); | |
$.LABDT.url = "<c:url value="/"/>"; | |
$.LABDT.dataTables_LanguageFile = $.LABDT.url + "js/dataTables/languages/" + "dataTables." + "<fmt:message key="config.language"/>" + ".txt"; | |
$.LABDT.dataTables_SearchName = "<fmt:message key="config.datatables.search.name"/>"; | |
$('#usersDataTable').dataTable({ | |
"bAutoWidth":true, | |
"bPaginate": true, | |
"bFilter": true, | |
"bSort": true, | |
"bInfo": true, | |
"bJQueryUI": false, | |
"sPaginationType": "full_numbers", | |
"aoColumns": [ | |
null, // Id | |
null, // Name | |
{ | |
"mData": null, | |
"bSortable": false, | |
"fnRender": function(oObj) | |
{ | |
return "<a href='" + $.LABDT.url + "users/delete/" + oObj.aData[0] + "'>Excluir</a> | " | |
+ "<a href='" + $.LABDT.url + "users/" + oObj.aData[0] + "'>Editar</a>"; | |
} | |
} | |
], | |
"bProcessing": true, | |
"oLanguage": { | |
"sUrl": $.LABDT.dataTables_LanguageFile, | |
"sSearch": $.LABDT.dataTables_SearchName | |
}, | |
"bServerSide": true, | |
"sAjaxSource": '/labdatatables/users/json/datatables/paginate', | |
"sServerMethod": "POST" | |
}); | |
}); |
JSON retornado:
Pesquisa pelo nome "Malc":
O laboratório pode ser obtido no github aqui. Coloquei internacionalização básica e manipulei a última coluna para duas ações (alterar e excluir) como itens extras para exemplificar como é fácil trabalhar com o DataTables. Informações adicionais:
- apache-maven-3.1.1
- apache-tomcat-7.0.53
- jdk1.7.0_45
- MySQL do XAMPP 1.8.1
- JQuery + Datatable + tradução
- VRaptor - Montando JSON estilo Datatables
- Geração de JSON Array via VRaptor
- Paginação com VRaptor
- VRaptor Paginação
*******
Atualização: Nova postagem com suporte a versão 1.10.x do DataTables e uso do VRaptor 4 já disponível!
*******
*******
Parabéns, estou implementando. :)
ResponderExcluirMuito obrigado pela iniciativa.