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&_=1425242466260This can be simplified to: (and still produce the same result).
http://localhost:8080/user/datatable.jquery?draw=2&page=1&size=5The 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 Mapdatatable(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'); }); }); });
No comments:
Post a Comment