So I wanted to use the nice jQuery Datatable and integrate it with Spring-Boot backend using Spring MVC Restcontroller and Spring Data.
The jQuery datatable from http://datatables.net/ is a very nice fully functional javascript datatable with pagination, searching, sorting, etc. There are several customizations that make it very nice but also a little complicated to get setup depending on what you want. I wanted to have a "rest" endpoint provided by Spring MVC produce the results for the datatable.
The spring controller allowed me to pass in a "Pageable" org.springframework.data.domain.Pageble;. Which can be passed directly to the Spring Data repository without extracting the data and packaging it again for a query. Notice in the UserRepository.java there is no "userRespository.findAll(pageable);" as needed by line 46 in the UserController.java this is provided by PagingAndSortingRepository in SpringData. The "Page<User>" object gets directly returned in the response again eliminating an annoying unpackaging / repackaging scenario. The request URL looks very nasty. Because of all the fields the datatable adds on. I decided not to try and fight that.
http://localhost:8080/user/datatable.jquery?draw=2&columns%5B0%5D%5Bdata%5D=id&columns%5B0%5D%5Bname%5D=&columns%5B0%5D%5Bsearchable%5D=true&columns%5B0%5D%5Borderable%5D=true&columns%5B0%5D%5Bsearch%5D%5Bvalue%5D=&columns%5B0%5D%5Bsearch%5D%5Bregex%5D=false&columns%5B1%5D%5Bdata%5D=firstName&columns%5B1%5D%5Bname%5D=&columns%5B1%5D%5Bsearchable%5D=true&columns%5B1%5D%5Borderable%5D=true&columns%5B1%5D%5Bsearch%5D%5Bvalue%5D=&columns%5B1%5D%5Bsearch%5D%5Bregex%5D=false&columns%5B2%5D%5Bdata%5D=lastName&columns%5B2%5D%5Bname%5D=&columns%5B2%5D%5Bsearchable%5D=true&columns%5B2%5D%5Borderable%5D=true&columns%5B2%5D%5Bsearch%5D%5Bvalue%5D=&columns%5B2%5D%5Bsearch%5D%5Bregex%5D=false&columns%5B3%5D%5Bdata%5D=email&columns%5B3%5D%5Bname%5D=&columns%5B3%5D%5Bsearchable%5D=true&columns%5B3%5D%5Borderable%5D=true&columns%5B3%5D%5Bsearch%5D%5Bvalue%5D=&columns%5B3%5D%5Bsearch%5D%5Bregex%5D=false&columns%5B4%5D%5Bdata%5D=creationDate&columns%5B4%5D%5Bname%5D=&columns%5B4%5D%5Bsearchable%5D=true&columns%5B4%5D%5Borderable%5D=true&columns%5B4%5D%5Bsearch%5D%5Bvalue%5D=&columns%5B4%5D%5Bsearch%5D%5Bregex%5D=false&columns%5B5%5D%5Bdata%5D=&columns%5B5%5D%5Bname%5D=&columns%5B5%5D%5Bsearchable%5D=true&columns%5B5%5D%5Borderable%5D=true&columns%5B5%5D%5Bsearch%5D%5Bvalue%5D=&columns%5B5%5D%5Bsearch%5D%5Bregex%5D=false&order%5B0%5D%5Bcolumn%5D=0&order%5B0%5D%5Bdir%5D=asc&start=5&length=5&search%5Bvalue%5D=&search%5Bregex%5D=false&page=1&size=5&sort=id%2Casc&_=1425242466260
This can be simplified to: (and still produce the same result).
http://localhost:8080/user/datatable.jquery?draw=2&page=1&size=5
The page and size fields make up the Pageable model object.
On lines 10 thru 12 I configured the datatable to pass page, size, and sort to the the MVC controller. On the way out of the @RestController I package the response the way the datatable expects it in fields named "data", "draw", "recordsTotal", and "recordsFiltered".
Lastly I wanted view, edit, and delete links in the right column. This would be ugly code configured inline with the datatable so on line 28 I add small reference tags "<a class='dt-edit'></a>" to be used later.
User.java (Entity Bean)
package com.jot.model;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.hibernate.annotations.Type;
import org.joda.time.DateTime;
import javax.persistence.*;
import java.io.Serializable;
import java.util.Collections;
import java.util.List;
/**
* @author Paul Mefford
* @since 9/1/14
*
*/
@Data //lombok
@Entity
@Table(name = "user")
@EqualsAndHashCode(exclude = {"addresses"})
public class User implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "user_id")
private Long id;
@Column(name = "user_name")
private String username;
@Column(name = "password")
private String password;
@Column(name = "firstname")
private String firstName;
@Column(name = "lastname")
private String lastName;
@Column(name = "user_type")
private String userType;
@Column(name = "email")
private String email;
@Column(name = "phone1")
private String phone1;
@Column(name = "phone2")
private String phone2;
@Column(name = "status")
private String status;
@Column(name = "creation_date")
@Type(type = "org.jadira.usertype.dateandtime.joda.PersistentDateTime")
private DateTime creationDate;
@Column(name = "security_role")
private Integer role;
@OneToMany(fetch = FetchType.EAGER, mappedBy = "userId")
private List<useraddress> addresses = Collections.EMPTY_LIST;
}
UserRepository.java (Spring Data)
package com.jot.repository;
import com.jot.model.Company;
import com.jot.model.User;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.stereotype.Repository;
import java.util.Collection;
import java.util.List;
/**
* @author Paul Mefford
* @since 9/6/14
*/
@Repository
public interface UserRespository extends PagingAndSortingRepository {
User findByUsername(String username);
}
UserController.java (Spring MVC RestController)
package com.jot.web;
import com.google.common.collect.Lists;
import com.jot.model.LinkedUsers;
import com.jot.model.User;
import com.jot.repository.LinkedUserRepository;
import com.jot.repository.UserRespository;
import com.jot.services.UserService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestWrapper;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.ui.ModelMap;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;
import javax.mail.MessagingException;
import javax.validation.Valid;
import java.security.Principal;
import java.util.*;
import java.util.function.Predicate;
import java.util.stream.Collectors;
/**
* @author Paul Mefford
* @since 9/6/14
*/
@RequestMapping("/user")
@Controller
public class UserController {
Logger logger = LoggerFactory.getLogger(UserController.class);
@Autowired
private UserRespository userRespository;
@RequestMapping("datatable.jquery")
public @ResponseBody Map
datatable(Model model, Pageable pageable, @RequestParam("draw") Integer draw , @RequestParam(value = "search", defaultValue = "") String search){
Map data = new HashMap<>();
Page page = userRespository.findAll(pageable);
data.put("data",page.getContent());
data.put("draw",draw);
data.put("recordsTotal",page.getTotalElements());
data.put("recordsFiltered",page.getTotalElements());
return data;
}
}
HTML (Thymeleaf template engine)
<table class="display datatable" id="userTable" cellspacing="0" width="100%">
<thead>
<tr>
<th>Id</th>
<th>First Name</th>
<th>Last Name</th>
<th>Email</th>
<th>Created</th>
<th width="10%"> </th>
</tr>
</thead>
<tfoot>
<tr>
<th> </th>
<th> </th>
<th> </th>
<th> </th>
<th> </th>
<th width="10%">
<a th:href="@{'/user?form'}">
<img src="../../../images/add.png" th:src="@{/images/add.png}" style="float: right"/>
</a>
</th>
</tr>
</tfoot>
<tbody>
</tbody>
</table>
JavaScript
<script>
//<![CDATA[
$(document).ready(function() {
$('#userTable').dataTable( {
"ajax": {
"url":"user/datatable.jquery",
"data":function(d) {
var table = $('#userTable').DataTable()
d.page = (table != undefined)?table.page.info().page:0
d.size = (table != undefined)?table.page.info().length:5
d.sort = d.columns[d.order[0].column].data + ',' + d.order[0].dir
}
},
"searching":false,
"processing": true,
"serverSide": true,
"lengthMenu": [[5, 10, 15,30,50,75,100], [5, 10, 15,30,50,75,100]],
"columns": [
{ "data": "id" },
{ "data": "firstName" },
{ "data": "lastName" },
{ "data": "email" },
{ "data": "creationDate" },
{ "": "" }
],
"columnDefs": [
{ "data": null, "targets": -1, "defaultContent":"<h4><a class='dt-view'></a><a class='dt-edit'></a><a class='dt-delete'></a></h4>" }
],
"pagingType": "full_numbers"
} );
} );
//]]>
</script>
Add Links / Icons to last Column
$("#userTable").on('draw.dt',function(){
$(".dt-view").each(function(){
$(this).addClass('text-success').append("<span class='glyphicon glyphicon-search' aria-hidden='true'></span>");
$(this).on('click',function(){
var table = $('#userTable').DataTable();
var data = table.row( $(this).parents('tr') ).data();
window.location = 'user/'+data.id;
});
});
$(".dt-edit").each(function(){
$(this).addClass('text-default').append("<span class='glyphicon glyphicon-edit' aria-hidden='true'></span>");
$(this).on('click',function(){
var table = $('#userTable').DataTable();
var data = table.row( $(this).parents('tr') ).data();
var path = 'user/'+data.id+'/update';
$("<form action='"+path+"'></form>").appendTo('body').submit();
});
});
$(".dt-delete").each(function(){
$(this).addClass('text-danger').append("<span class='glyphicon glyphicon-remove' aria-hidden='true'></span>");
$(this).on('click',function(){
var table = $('#userTable').DataTable();
var data = table.row( $(this).parents('tr') ).data();
var path = 'user/'+data.id+'/delete';
$("<form action='"+path+"'></form>").appendTo('body').submit();
$.simplyToast('danger', data.firstName+ ' has been deleted');
});
});
});