SpringBoot Ajax Pagination Example (Filtering and Sorting) using Bootstrap Table – JQuery Ajax Tutorial

SpringBoot Ajax Pagination Example with MySQL

When we have a large dataset and we want to present it to the user in smaller chunks, pagination and sorting is often helpful solution. So in the tutorial, I introduce how to create “SpringBoot Ajax Pagination Example” use JQuery Ajax and Bootstrap to build a table solution for pagination, filtering and sorting the fetching data with SpringBoot RestAPIs examples.

Related posts:


Overview Springboot Ajax Pagination Example with Bootstrap Table and JQuery

SpringBoot Architecture Diagram

JQuery Ajax Pagination Filtering Sorting Tutorial with SpringBoot RestAPIs Example - Bootstrap Table
JQuery Ajax Pagination Filtering Sorting Tutorial with SpringBoot RestAPIs Example – Bootstrap Table

– We create a “SpringBoot Ajax Pagination Example” project to provide pagination, filtering and sorting data from MySQL/PostgreSQL via RestAPIs with the helpful from PagingAndSorting Repository of Spring Data JPA.
– We implement a Bootstrap Html view with Ajax scripts to fetch data from the RestAPIs with pagination, filtering and sorting functions.

Goal

– Make a request at API: /api/customers/custom/pageable with pagination, filtering and sorting params as below:

  • page: 0 – first page
  • size: 5 – size of a page
  • salary: 4000 – filtering by salary field
  • agesorting: true – sorting by age
  • desc: true – descending or ascending sorting

– Result:

SpringBoot RestAPIs Pagination Filtering and Sorting request
SpringBoot RestAPIs Pagination Filtering and Sorting request

– JQuery Ajax Bootstrap Frontend Pagination with Filtering and Sorting table:

JQuery Ajax Pagination Filtering Sorting Tutorial - Results
JQuery Ajax Pagination Filtering Sorting Tutorial – Results

For doing the pagination, we use PagingAndSortingRepository class that is an extension of CrudRepository to provide additional methods to retrieve entities using the pagination and sorting abstraction. Here is the sourcecode of it:


@NoRepositoryBean
public interface PagingAndSortingRepository<T, ID> extends CrudRepository<T, ID> {
	Iterable<T> findAll(Sort sort);
	Page<T> findAll(Pageable pageable);
}
  • findAll(Sort sort) returns all entities sorted by the given options.
  • findAll(Pageable pageable) Returns a page of entities meeting the paging restriction provided in the Pageable object.

Here is the hierarchy of PagingAndSortingRepository :

PagingAndSortingRepository hierarchy
PagingAndSortingRepository hierarchy

So JpaRepository of Spring JPA is an alternative solution for PagingAndSortingRepository:


@NoRepositoryBean
public interface JpaRepository<T, ID> extends PagingAndSortingRepository<T, ID>, QueryByExampleExecutor<T> {

Spring JPA Pagination

For pagination with Spring JPA, we use the methods:
Page<T> findAll(Pageable pageable);

It returns a Page of entities meeting the paging restriction provided in the Pageable object.

– Here is how the Page interface is defined:

Page Interface
Page Interface

– Here is how to Pageable interface is defined:

Pagable Interface for pagination, filtering and sorting
Pagable Interface for pagination, filtering and sorting

Examples coding:


Pageable requestedPage = PageRequest.of(0, 5);
Page<Customer> customers  = customerRepository.findAll(requestedPage);

We use the PageRequest to construct a Pageable object then pass it to the findAll() method of PagingAndSortingRepository.

How to Pagining and Filtering?

For filtering data with pagination, Spring JPA provides many useful Query Creation from method names:


Slice findAllBySalary (double salary, Pageable pageable);
Page findAllByAgeGreaterThan(int age, Pageable pageable);
  • GreaterThan examples findAllByAgeGreaterThan(int age, Pageable pageable); means … where x.age> ?1
  • Is, Equals examples findAllBySalary (double salary, Pageable pageable) means … where x.salary = ?1

Paging and Sorting

For sorting with Spring data JPA, we use the function: Iterable<T> findAll(Sort sort);.

We can use public static methods by() to build the Sort objects:

  • public static Sort by(String... properties) creates a new Sort for the given properties.
  • public static Sort by(List≶Order> orders) creates a new Sort for the given list Orders
  • public static Sort by(Order... orders) create a new Sort for the given Orders
  • public static Sort by(Direction direction, String... properties)

If we want to both sort and page our data, We can do that by passing the sorting details into our PageRequest object itself:

Pageable sortedBySalary = 
  PageRequest.of(0, 3, Sort.by("salary"));
 
Pageable sortedBySalaryDesc = 
  PageRequest.of(0, 3, Sort.by("salary").descending());
 
Pageable sortedBySalaryDescAgeAndFirstnameAsc = 
  PageRequest.of(0, 5, Sort.by("salary").descending().and(Sort.by("age")).and(Sort.by("firstname")));

For more details about how to builld a SpringBoot RestAPIs for pagination, filtering and sorting, please follow the link at here.

How to build Spring Boot Pagination Project

Create SpringBoot project

We create a SpringBoot project with 3 dependencies:

  • Spring Web is used to handle RestAPIs
  • Spring JPA is used to do pagination, filtering and sorting with database’s data
  • MySQL/PostgreSQL driver is used to work with dabase

Check the pom.xml file:

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
	<groupId>mysql</groupId>
	<artifactId>mysql-connector-java</artifactId>
	<scope>runtime</scope>
</dependency>		

Configure Database

Open the application.properties to configure the working database environment:

spring.datasource.url=jdbc:mysql://localhost:3306/loizenaidb
spring.datasource.username=root
spring.datasource.password=12345
spring.jpa.generate-ddl=true

#drop & create table again, good for testing, comment this in production
spring.jpa.hibernate.ddl-auto=create

Create JPA Data Model

We create a Customer model class with 7 attributes: id, firstname, lastname, address, age, salary, copyrightBy.

Customer Model class
Customer Model class
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name="customer")
public class Customer {
	@Id
	@GeneratedValue(strategy = GenerationType.AUTO)
	private long id;
	
	@Column
	private String firstname;
	
	@Column
	private String lastname;

	@Column
	private String address;
	
	@Column
	private int age;
	
	@Column
	private double salary;
	
	@Column(columnDefinition = "varchar(255) default '@ https://loizenai.com'")
	private String copyrightBy;

@Column specifies the mapped column for a persistent property or field. If no Column annotation is specified, the default values apply.
javax.persistence.Id specifies the primary key of an entity.
javax.persistence.Entity specifies that the class is an entity. This annotation is applied to the entity class.

Define JPA Paging and Sorting Repository

Create interface CustomerRepository extends PagingAndSortingRepository:

package com.loizenai.springboot.pagingansorting.repository;

import java.util.List;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.stereotype.Repository;

import com.loizenai.springboot.pagingansorting.model.Customer;

@Repository
public interface CustomerRepository extends PagingAndSortingRepository<Customer, Long>{	
	Page<Customer> findAllBySalary (double salary, Pageable pageable);
	
	@Query("SELECT DISTINCT c.salary FROM Customer c")
	List<Double> findDistinctSalary();
}

Implement SpringBoot Pagination Filtering and Sorting RestAPI

We implement a SpringBoot RestAPI at URL /api/customers/custom/pageable to do pagination, filtering and sorting Customer with 5 params:

  • page is used to get the right page
  • size is a size of a page
  • salary is used to filtering
  • agesorting is used to sort with age column or not
  • desc is used to define the sorting direction – descending or default with ascending sorting
  • Coding:

    package com.loizenai.springboot.pagingansorting.restapis;
    
    import java.util.Arrays;
    import java.util.List;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.data.domain.Page;
    import org.springframework.data.domain.PageRequest;
    import org.springframework.data.domain.Pageable;
    import org.springframework.data.domain.Sort;
    import org.springframework.data.repository.query.Param;
    import org.springframework.web.bind.annotation.CrossOrigin;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    
    import com.loizenai.springboot.pagingansorting.model.Customer;
    import com.loizenai.springboot.pagingansorting.model.Response;
    import com.loizenai.springboot.pagingansorting.repository.CustomerRepository;
    
    @org.springframework.web.bind.annotation.RestController
    @RequestMapping("/api/customers")
    @CrossOrigin(origins = "http://localhost:4200")
    public class RestController {
    		
    	@Autowired
    	CustomerRepository customerRepository;
    		
    	@GetMapping("/custom/pageable")
    	public Response retrieveCustomer(@Param(value = "salary") int salary,
    										@Param(value = "page") int page, 
    										@Param(value = "size") int size,
    										@Param(value = "agesorting") boolean agesorting,
    										@Param(value = "desc") boolean desc){
    		
    		Page<Customer> customers = null;
    		
    		// not filtering with salary
    		if(salary < 0) {
    			// not sorting with age
    			if(agesorting == false) {
    				Pageable requestedPage = PageRequest.of(page, size);
    				customers = customerRepository.findAll(requestedPage);	
    			}else {
    				// sorting with age and ascending
    				if(false == desc) {
    					Pageable requestedPage = PageRequest.of(page, size, Sort.by("age"));
    					customers  = customerRepository.findAll(requestedPage);
    				}
    				// sorting with age and descending
    				else {
    					Pageable requestedPage = PageRequest.of(page, size, 
    												Sort.by("age").descending());
    					customers  = customerRepository.findAll(requestedPage);
    				}
    			}
    		// Filtering with salary
    		} else {			
    			// not sorting with age
    			if(agesorting == false) {
    				Pageable requestedPage = PageRequest.of(page, size);
    				// fitering request
    				customers = customerRepository.findAllBySalary(salary, requestedPage);	
    			}else {
    				// sorting with age and ascending
    				if(false == desc) {
    					Pageable requestedPage = PageRequest.of(page, size, Sort.by("age"));
    					// filtering request
    					customers  = customerRepository.findAllBySalary(salary, requestedPage);
    				}
    				// sorting with age and descending
    				else {
    					Pageable requestedPage = PageRequest.of(page, size, 
    												Sort.by("age").descending());
    					// filtering request
    					customers  = customerRepository.findAllBySalary(salary, requestedPage);
    				}
    			}
    		}
    		
    		Response res = new Response(customers.getContent(), customers.getTotalPages(),
    										customers.getNumber(), customers.getSize());
    		
    		return res;
    	}
    		
    	@GetMapping("/salaries")
    	public List<Double> getListSalaries() {
    		try {
    			return customerRepository.findDistinctSalary();
    		}catch(Exception e) {
    			// Log errors to user monitoring
    			System.out.println(e);
    			return Arrays.asList();
    		}
    	}
    }

    Implement SpringBoot Main Class

    In the main class of SpringBoot project, We implement the CommandLineRunner interface and inject the CustomerRepository repository to save a list Customer to database MySQL/PostgreSQL.

    Coding:

    package com.loizenai.springboot.pagingansorting;
    
    import java.util.Arrays;
    import java.util.List;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.CommandLineRunner;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    
    import com.loizenai.springboot.pagingansorting.model.Customer;
    import com.loizenai.springboot.pagingansorting.repository.CustomerRepository;
    
    @SpringBootApplication
    public class SpringBootPagingAndSortingApplication implements CommandLineRunner{
    
    	@Autowired
    	CustomerRepository customerRepository;
    	
    	public static void main(String[] args) {
    		SpringApplication.run(SpringBootPagingAndSortingApplication.class, args);
    	}
    
    	@Override
    	public void run(String... args) throws Exception {
    		List<Customer> customers = Arrays.asList(	new Customer("Jack", "Smith", "Massachusetts", 23, 4000)
    						, new Customer("Adam", "Johnson", "New York", 27, 3000)
    						, new Customer("Katherin", "Carter", "Washington DC", 26, 4000)
    						, new Customer("Jack", "London", "Nevada", 33, 4000)
    						, new Customer("Jason", "Bourne", "California", 36, 3000)
    						, new Customer("Blade", "Johnson", "Ohio", 18, 3000)
    						, new Customer("Carol", "Carter", "Florida", 23, 4000)
    						, new Customer("Avery", "Alvin", "Washington DC", 41, 3000)
    						, new Customer("Dana", "Bay", "Texas", 32, 4000)
    						, new Customer("Tom", "Bourne", "Colorado", 28, 3500)
    						, new Customer("Ardys", "Bean", "Alaska", 23, 4000)
    						, new Customer("Carol", "Carter", "Arizona", 26, 3500)
    						, new Customer("Avery", "Zane", "Virginia", 31, 4000)
    						, new Customer("Barric", "Weldin", "Oregon", 52, 3000)
    						, new Customer("Galen", "Wayt", "Missouri", 48, 4000)
    						, new Customer("Mayer", "Acomb", "Wisconsin", 32, 4000)
    						, new Customer("Sadie", "Albright", "Tennessee", 23, 3500)
    						, new Customer("Norvell", "Baily", "Oregon", 41, 4000)
    						, new Customer("Tanzi", "Baney", "North Dakota", 26, 4000)
    						, new Customer("Osric", "Callender", "New Mexico", 29, 3000)
    						, new Customer("Trudy", "Zane", "Vermont", 31, 3500)
    						, new Customer("Reynold", "Zone", "Wyoming", 43, 3000)
    						, new Customer("Udele", "Wheetley", "Michigan", 37, 3500)
    						, new Customer("Blackburn", "Atkinson", "Illinois", 19, 4000)
    						, new Customer("Cotovatre", "Bailey", "Delaware", 26, 4000));
    		
    		customerRepository.saveAll(customers);
    	}
    }
    

    Testing SpringBoot Pagination RestAPI

    RestAPI Testcase 1 – Get All Distinct Salaries

    – Make a request to ULR: /api/customers/salaries

    SpringBoot RestAPI - Get a list distinct salaries
    SpringBoot RestAPI – Get a list distinct salaries

    RestAPI Testcase 2 – Pagination Filtering and Sorting RestAPI

    – Make a request at API: /api/customers/custom/pageable with pagination, filtering and sorting params as below:

    • page: 0 – first page
    • size: 5 – size of a page
    • salary: 4000 – filtering by salary field
    • agesorting: true – sorting by age
    • desc: true – descending or ascending sorting

    – Result:

    SpringBoot RestAPIs Pagination Filtering and Sorting request
    SpringBoot RestAPIs Pagination Filtering and Sorting request

    SpringBoot Ajax Pagination Example Frontend Development

    Review Pagination View Page

    JQuery Ajax Pagination Filtering Sorting Tutorial - Layout of View Page
    JQuery Ajax Pagination Filtering Sorting Tutorial – Layout of View Page

    We define the view page with 4 parts:

    • Bootstrap Table is used to display Customer list with table format
    • Pagination Bar is used to list all data pages
    • Filtering Selection is used to select a specific salary to do the pagination with filtering
    • Sorting Checkboxes is used to determine whether the sorting with age field or Not and sorting with descending or ascending(default value) direction

    Html Bootstrap Pagination View Implementation

    We use Bootstrap framework to style our pagination view, so we need to add the below bootstrap links to head tag in index.html file:

    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css">
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.16.0/umd/popper.min.js"></script>
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js"></script>

    Pagination Table

    We define a Bootstrap table with 7 columns to show all the properties of a Customer object:

    • Id
    • Firstname
    • Lastname
    • Age
    • Salary
    • Address
    • Copyright
    <table id="customerTable" class="table table-hover table-sm">
    	<thead class="thead-dark">
    		<tr>
    			<th>Id</th>
    			<th>Firstname</th>
    			<th>Lastname</th>
    			<th>Age</th>
    			<th>Salary</th>
    			<th>Address</th>
    			<th>Copyright By</th>
    		</tr>
    	</thead>
    	<tbody>
    	</tbody>
    </table>
    

    Filtering Select

    We use the html select form to create a salary filtering list as below code:

    <div class="form-group">
      <label for="slected_form"><h5>Filtering with Salary:</h5></label>
      <select class="form-control" id="selected_form">
      </select>
    </div>

    Sorting Checkboxes

    We use html checkboxes to define 2 fields to determine for sorting or not:

    • agesorting = true (means be checked) -> do the sorting by age field
    • desc = true (mean be checked) -> do sorting with descending direction, (default is ascending direction)

    Here is the html coding:

    <form>
      <div class="custom-control custom-checkbox mb-3">
        <input type="checkbox" class="custom-control-input" id="age_sorting" name="age_sorting">
        <label class="custom-control-label" for="age_sorting">Age</label>
      </div>
      <input type="checkbox" id="desc_sorting" name="desc_sorting" disabled>
      <label for="defaultCheck">Desc</label>
      <br>
    </form>
    <button id="sortingbtn" class="btn btn-primary" disabled>Sort Now</button>

    Full HTML view page

    We create a index.html file in src/main/resources as below code:

    <!DOCTYPE html>
    <html lang="en">
    <head>
    <title>Ajax Paging Table</title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css">
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.16.0/umd/popper.min.js"></script>
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js"></script>
    <script type="text/javascript" src="/js/table.js"></script>
    </head>
    <body>
    	<div class="container">
    		<div class="row">
    			<div class="col-sm-7"
    					style="background-color: #e6f9ff; margin: 10px; padding: 10px; border-radius: 5px">
    				<div class="alert alert-danger">
    					<h3>Bootstrap + Ajax + SpringBoot Pagination</h3>
    					<hr>
    					<p>
    						<strong>@Copyright</strong> by <span style="color: blue">
    						<a href="https://loizenai.com">https://loizenai.com
    						</a></span>
    						 <br> 
    						<strong>youtube</strong>: <span style="color: crimson">
    						<a href="https://www.youtube.com/channel/UChkCKglndLes1hkKBDmwPWA">loizenai
    						</a></span>
    					</p>
    				</div>
    				<h5>Do you want sorting by salary?</h5>
    			    <form>
    			      <div class="custom-control custom-checkbox mb-3">
    			        <input type="checkbox" class="custom-control-input" id="age_sorting" name="age_sorting">
    			        <label class="custom-control-label" for="age_sorting">Age</label>
    			      </div>
    			      <input type="checkbox" id="desc_sorting" name="desc_sorting" disabled>
    			      <label for="defaultCheck">Desc</label>
    			      <br>
    			    </form>
    			    <button id="sortingbtn" class="btn btn-primary" disabled>Sort Now</button>
    				<hr>
    				<div class="form-group">
    				  <label for="slected_form"><h5>Filtering with Salary:</h5></label>
    				  <select class="form-control" id="selected_form">
    				  </select>
    				</div>
    	
    				<table id="customerTable" class="table table-hover table-sm">
    					<thead class="thead-dark">
    						<tr>
    							<th>Id</th>
    							<th>Firstname</th>
    							<th>Lastname</th>
    							<th>Age</th>
    							<th>Salary</th>
    							<th>Address</th>
    							<th>Copyright By</th>
    						</tr>
    					</thead>
    					<tbody>
    					</tbody>
    				</table>
    				
    				<ul class="pagination justify-content-center" style="margin:20px 0">
    				</ul>
    			</div>
    		</div>
    	</div>
    </body>
    </html>

    JQuery Ajax Implementation

    We use Ajax of JQuery to fetch data from SpringBoot restAPIs with pagination, filtering and sorting.
    And using JQuery Javascript to control the data in html Bootstrap view.

    Ajax Fetching data from remote RestAPI

    For getting data with pagination, filtering and sorting from SpringBoot restAPIs, we implement a JQuery Fuction with Ajax code that includes a list of 5 needed parameters:
    – Signature of the function: function fetchCustomers(page, size, salary, agesorting, desc)

    • page: number of page for requesting
    • size: a size of a data page for requesting
    • salary: a selected salary for filtering
    • agesorting: a flag to determine to do the sorting by age field or not
    • desc: a flag to determine the direction for sorting, desc = true for descending sorting direction and desc = false for ascending sorting direction (default value)
    function fetchCustomers(page, size, salary, agesorting, desc){
    	let pageNumber = (typeof page !== 'undefined') ?  page : 0;
    	let sizeNumber = (typeof size !== 'undefined') ?  size : 5;
    	let selectedsalary = (typeof salary !== 'undefined') ?  salary : -1;
    	let ageSorted = (typeof agesorting !== 'undefined') ?  agesorting: false;
    	let descDirection = (typeof desc !== 'undefined') ?  desc: false;
    
    	/**
    	 * Do a fetching to get data from Backend's RESTAPI
    	 */
        $.ajax({
            type : "GET",
            url : "/api/customers/custom/pageable",
            data: { 
                page: pageNumber, 
                size: sizeNumber,
                salary: selectedsalary,
                agesorting: ageSorted,
                desc: descDirection
            },
            success: function(response){
              $('#customerTable tbody').empty();
              // add table rows
              $.each(response.customers, (i, customer) => {  
                let tr_id = 'tr_' + customer.id;
                let customerRow = '<tr>' +
          	  						  '<td>' + customer.id + '</td>' +
    		                		  '<td>' + customer.firstname + '</td>' +
    		                		  '<td>' + customer.lastname + '</td>' +
    		                		  '<td>' + customer.age + '</td>' +
    		                          '<td>' + '$' + customer.salary + '</td>' +
    		                          '<td>' + customer.address + '</td>' +
    		                          '<td>' + '<a href="https://loizenai.com">' + customer.copyrightBy + '</a>' + '</td>' +
    		                       '</tr>';
                $('#customerTable tbody').append(customerRow);
              });              
              
              if ($('ul.pagination li').length - 2 != response.totalPages){
              	  // build pagination list at the first time loading
            	  $('ul.pagination').empty();
                  buildPagination(response.totalPages);
              }
            },
            error : function(e) {
              alert("ERROR: ", e);
              console.log("ERROR: ", e);
            }
        });    	
    }
    

    The fetchCustomers function calls an Ajax request to the URL api/customers/custom/pageable:

    $.ajax({
        type : "GET",
        url : "/api/customers/custom/pageable",
        data: { 
            page: pageNumber, 
            size: sizeNumber,
            salary: selectedsalary,
            agesorting: ageSorted,
            desc: descDirection
        },
    

    If successfully, it will render a list of response’s Customers on defined-table by code:

    success: function(response){
      $('#customerTable tbody').empty();
      // add table rows
      $.each(response.customers, (i, customer) => {  
        let tr_id = 'tr_' + customer.id;
        let customerRow = '<tr>' +
      						  '<td>' + customer.id + '</td>' +
                    		  '<td>' + customer.firstname + '</td>' +
                    		  '<td>' + customer.lastname + '</td>' +
                    		  '<td>' + customer.age + '</td>' +
                              '<td>' + '$' + customer.salary + '</td>' +
                              '<td>' + customer.address + '</td>' +
                              '<td>' + '<a href="https://loizenai.com">' + customer.copyrightBy + '</a>' + '</td>' +
                           '</tr>';
        $('#customerTable tbody').append(customerRow);
      });  

    And building the pagination list via the response variable’s property totalPages as below code:

      if ($('ul.pagination li').length - 2 != response.totalPages){
      	  // build pagination list at the first time loading
    	  $('ul.pagination').empty();
          buildPagination(response.totalPages);
      }

    buildPagination(totalPages) function:

    /**
     * 
     * Build the pagination Bar from totalPages
     */
    function buildPagination(totalPages){
        // Build paging navigation
        let pageIndex = '<li class="page-item"><a class="page-link">Previous</a></li>';
        $("ul.pagination").append(pageIndex);
        
        // create pagination
        for(let i=1; i <= totalPages; i++){
      	  // adding .active class on the first pageIndex for the loading time
      	  if(i==1){
          	  pageIndex = "<li class='page-item active'><a class='page-link'>"
      				+ i + "</a></li>"            		  
      	  } else {
          	  pageIndex = "<li class='page-item'><a class='page-link'>"
    	  				+ i + "</a></li>"
      	  }
      	  $("ul.pagination").append(pageIndex);
        }
        
        pageIndex = '<li class="page-item"><a class="page-link">Next</a></li>';
        $("ul.pagination").append(pageIndex);
    }

    Click function with Pagination Bar

    When do a click on the pagination bar, we have 3 cases to determine:

    • Next
    • Previous
    • a Specific Index Page

    And we need to re-fecth the right pagination data from the remote RestAPI to build fill again the right data in html view page:

    $(document).on("click", "ul.pagination li a", function() {
    	let agesorting = false;
    	let desc = false;
    	let selectedSalary = getSeletedSalary();
    	if($("#age_sorting"). prop("checked") == true){
    		agesorting = true;
    	}
    	
    	if($("#desc_sorting"). prop("checked") == true){
    		desc = true;
    	}
    	
    	let val = $(this).text();
    	
    	// click on the NEXT tag
      	if(val.toUpperCase()==="NEXT"){
      		let activeValue = parseInt($("ul.pagination li.active").text());
      		let totalPages = $("ul.pagination li").length - 2; // -2 beacause 1 for Previous and 1 for Next 
      		if(activeValue < totalPages){
      			let currentActive = $("li.active");
      			fetchCustomers(activeValue, 5, selectedSalary, agesorting, desc); // get next page value
      			// remove .active class for the old li tag
      			$("li.active").removeClass("active");
      			// add .active to next-pagination li
      			currentActive.next().addClass("active");
      		}
      	} else if(val.toUpperCase()==="PREVIOUS"){
      		let activeValue = parseInt($("ul.pagination li.active").text());
      		if(activeValue > 1){
      			// get the previous page
      			fetchCustomers(activeValue-2, 5, selectedSalary, agesorting, desc);
      			let currentActive = $("li.active");
      			currentActive.removeClass("active");
      			// add .active to previous-pagination li
      			currentActive.prev().addClass("active");
      		}
      	} else {
      		fetchCustomers(parseInt(val) - 1, 5,  selectedSalary, agesorting, desc);
      		// add focus to the li tag
      		$("li.active").removeClass("active");
      		$(this).parent().addClass("active");
      	}
    });

    Do the Filtering with Selected Salary

    We need to build the list salaries with distinct values, so we need to call an Ajax function getListSalaries() at the initial time:

    /**
     * Get a list of distinct salaries
     */
    function getListSalaries(){
    	$.ajax({
            type : "GET",
            url : "/api/customers/salaries",
            success: function(response){
              $("#selected_form").empty();
              $('#selected_form').append("<option>All</option>");
              $.each(response.sort().reverse(), (i, salary) => {
            	// <option>All</option>
                let optionElement = "<option>" + salary + "</option>";
                $('#selected_form').append(optionElement);
              });              
            },
            error : function(e) {
              alert("ERROR: ", e);
              console.log("ERROR: ", e);
            }
    	});
    }    

    If having any change from the salay filtering select-form, the fetchCustomers() function will be invokes again with the selected salary for filtering the right data to paging and sorting. Here is the detail function:

    /**
     * Select a salary for pagination & filtering
     */
    $("select").change(function() {
    	let salary = -1;
    	
    	if(isNumeric(this.value)){
    		salary = this.value;
    	}
    	
    	let agesorting = false;
    	let desc = false;
    	
    	if($("#age_sorting"). prop("checked") == true){
    		agesorting = true;
    	}
    	
    	if($("#desc_sorting"). prop("checked") == true){
    		desc = true;
    	}
    	
    	// re-fetch customer list again 
        fetchCustomers(0, 5, salary, agesorting, desc);
    });
    

    JQuery process the sorting

    With the sorting checkboxes, we implement 2 functions for handling the pagination and sorting:

    • $('#age_sorting').on('change', function(){...})
    • $(document).on("click", "#sortingbtn", function() {...});

    $('#age_sorting').on('change', function(){...}) is used to control the disable attribute and the property checkbox checked of desc and sortingbtn button

    /**
     * age_sorting checkbox is changed
     */
    $('#age_sorting').on('change', function() {
        if(this.checked){
        	$("#desc_sorting").removeAttr("disabled");
        	$("#sortingbtn").removeAttr("disabled");
        }else {
        	$("#desc_sorting").attr("disabled", true);
        	$("#desc_sorting").prop("checked", false);
        	$("#sortingbtn").attr("disabled", true);
        }
    }); 

    $(document).on("click", "#sortingbtn", function() {...}); is used to fetch againt the pagination function with sorting parameters agesorting and desc: fetchCustomers(selectedPageIndex, 5, selectedSalary, agesorting, desc);

    – Coding:

    $(document).on("click", "#sortingbtn", function() {
    	let agesorting = false;
    	let desc = false;
    	let selectedSalary = getSeletedSalary();
    	
    	//get value of check boxes
    	
    	/* agesorting checkbox */
    	if($("#age_sorting"). prop("checked") == true){
    		agesorting = true;
    	}
    	
    	/* desc checkbox */
    	if($("#desc_sorting"). prop("checked") == true){
    		desc = true;
    	}
    	
    	// get the active index of pagination bar 
    	let selectedPageIndex = parseInt($("ul.pagination li.active").text()) - 1;
    	
    	// just fetch again customers from SpringBoot RestAPIs when agesorting checkbox is checked
    	if(agesorting){
    		fetchCustomers(selectedPageIndex, 5, selectedSalary, agesorting, desc); // get next page value	
    	}
    });

    Full Ajax script

    We implement all JQuery Ajax code in a file table.js which are placed at /static/js/ folder. Here is the full sourcecode:

    $(document).ready(function(){	
    	function fetchCustomers(page, size, salary, agesorting, desc){
    		let pageNumber = (typeof page !== 'undefined') ?  page : 0;
    		let sizeNumber = (typeof size !== 'undefined') ?  size : 5;
    		let selectedsalary = (typeof salary !== 'undefined') ?  salary : -1;
    		let ageSorted = (typeof agesorting !== 'undefined') ?  agesorting: false;
    		let descDirection = (typeof desc !== 'undefined') ?  desc: false;
    	
    		/**
    		 * Do a fetching to get data from Backend's RESTAPI
    		 */
    		$.ajax({
    		    type : "GET",
    		    url : "/api/customers/custom/pageable",
    		    data: { 
    		        page: pageNumber, 
    		        size: sizeNumber,
    		        salary: selectedsalary,
    		        agesorting: ageSorted,
    		        desc: descDirection
    		    },
    			success: function(response){
    			  $('#customerTable tbody').empty();
    			  // add table rows
    			  $.each(response.customers, (i, customer) => {  
    			    let tr_id = 'tr_' + customer.id;
    			    let customerRow = '<tr>' +
    			  						  '<td>' + customer.id + '</td>' +
    			                		  '<td>' + customer.firstname + '</td>' +
    			                		  '<td>' + customer.lastname + '</td>' +
    			                		  '<td>' + customer.age + '</td>' +
    			                          '<td>' + '$' + customer.salary + '</td>' +
    			                          '<td>' + customer.address + '</td>' +
    			                          '<td>' + '<a href="https://loizenai.com">' + customer.copyrightBy + '</a>' + '</td>' +
    			                       '</tr>';
    			    $('#customerTable tbody').append(customerRow);
    			  });              
    	          
    			  if ($('ul.pagination li').length - 2 != response.totalPages){
    			  	  // build pagination list at the first time loading
    				  $('ul.pagination').empty();
    			      buildPagination(response.totalPages);
    			  }
    	        },
    	        error : function(e) {
    	          alert("ERROR: ", e);
    	          console.log("ERROR: ", e);
    	        }
    	    });    	
    	}
    
        /**
         * Check a string value is a number or NOT
         */
        function isNumeric(value) {
            return /^-{0,1}\d+$/.test(value);
        }
        
    	/**
    	 * Select a salary for pagination & filtering
    	 */
    	$("select").change(function() {
    		let salary = -1;
    		
    		if(isNumeric(this.value)){
    			salary = this.value;
    		}
    		
    		let agesorting = false;
    		let desc = false;
    		
    		if($("#age_sorting"). prop("checked") == true){
    			agesorting = true;
    		}
    		
    		if($("#desc_sorting"). prop("checked") == true){
    			desc = true;
    		}
    		
    		// re-fetch customer list again 
    	    fetchCustomers(0, 5, salary, agesorting, desc);
    	});
    
    	/**
    	 * Get a list of distinct salaries
    	 */
    	function getListSalaries(){
    		$.ajax({
    	        type : "GET",
    	        url : "/api/customers/salaries",
    	        success: function(response){
    	          $("#selected_form").empty();
    	          $('#selected_form').append("<option>All</option>");
    	          $.each(response.sort().reverse(), (i, salary) => {
    	        	// <option>All</option>
    	            let optionElement = "<option>" + salary + "</option>";
    	            $('#selected_form').append(optionElement);
    	          });              
    	        },
    	        error : function(e) {
    	          alert("ERROR: ", e);
    	          console.log("ERROR: ", e);
    	        }
    		});
    	}    
        
    	/**
    	 * age_sorting checkbox is changed
    	 */
    	$('#age_sorting').on('change', function() {
    	    if(this.checked){
    	    	$("#desc_sorting").removeAttr("disabled");
    	    	$("#sortingbtn").removeAttr("disabled");
    	    }else {
    	    	$("#desc_sorting").attr("disabled", true);
    	    	$("#desc_sorting").prop("checked", false);
    	    	$("#sortingbtn").attr("disabled", true);
    	    }
    	}); 
        
        /**
         * Click on sorting Button
         */
    	$(document).on("click", "#sortingbtn", function() {
    		let agesorting = false;
    		let desc = false;
    		let selectedSalary = getSeletedSalary();
    		
    		//get value of check boxes
    		
    		/* agesorting checkbox */
    		if($("#age_sorting"). prop("checked") == true){
    			agesorting = true;
    		}
    		
    		/* desc checkbox */
    		if($("#desc_sorting"). prop("checked") == true){
    			desc = true;
    		}
    		
    		// get the active index of pagination bar 
    		let selectedPageIndex = parseInt($("ul.pagination li.active").text()) - 1;
    		
    		// just fetch again customers from SpringBoot RestAPIs when agesorting checkbox is checked
    		if(agesorting){
    			fetchCustomers(selectedPageIndex, 5, selectedSalary, agesorting, desc); // get next page value	
    		}
    	});
        
    	/**
    	 * 
    	 * Build the pagination Bar from totalPages
    	 */
    	function buildPagination(totalPages){
    	    // Build paging navigation
    	    let pageIndex = '<li class="page-item"><a class="page-link">Previous</a></li>';
    	    $("ul.pagination").append(pageIndex);
    	    
    	    // create pagination
    	    for(let i=1; i <= totalPages; i++){
    	  	  // adding .active class on the first pageIndex for the loading time
    	  	  if(i==1){
    	      	  pageIndex = "<li class='page-item active'><a class='page-link'>"
    	  				+ i + "</a></li>"            		  
    	  	  } else {
    	      	  pageIndex = "<li class='page-item'><a class='page-link'>"
    		  				+ i + "</a></li>"
    	  	  }
    	  	  $("ul.pagination").append(pageIndex);
    	    }
    	    
    	    pageIndex = '<li class="page-item"><a class="page-link">Next</a></li>';
    	    $("ul.pagination").append(pageIndex);
    	}
        
        /**
         * Get the selectedSalary for filtering
         */
        function getSeletedSalary(){
        	if(!isNumeric($("select").val())){
        		return -1;
        	}else return parseInt($("select").val());
        }
        
        /**
         * 
         * Fetching the Customers from SpringBoot RestAPI at the initial time
         */
        (function(){
        	// get first-page at initial time
        	fetchCustomers(0);
        	
        	// get the distinct values of customer's salaries
        	getListSalaries();
        })();
            
        /**
         * Fetch again the customer's data from RestAPI when 
         * 		having any click on pagination bar for pagination filtering and sorting 
         */
    	$(document).on("click", "ul.pagination li a", function() {
    		let agesorting = false;
    		let desc = false;
    		let selectedSalary = getSeletedSalary();
    		if($("#age_sorting"). prop("checked") == true){
    			agesorting = true;
    		}
    		
    		if($("#desc_sorting"). prop("checked") == true){
    			desc = true;
    		}
    		
    		let val = $(this).text();
    		
    		// click on the NEXT tag
    	  	if(val.toUpperCase()==="NEXT"){
    	  		let activeValue = parseInt($("ul.pagination li.active").text());
    	  		let totalPages = $("ul.pagination li").length - 2; // -2 beacause 1 for Previous and 1 for Next 
    	  		if(activeValue < totalPages){
    	  			let currentActive = $("li.active");
    	  			fetchCustomers(activeValue, 5, selectedSalary, agesorting, desc); // get next page value
    	  			// remove .active class for the old li tag
    	  			$("li.active").removeClass("active");
    	  			// add .active to next-pagination li
    	  			currentActive.next().addClass("active");
    	  		}
    	  	} else if(val.toUpperCase()==="PREVIOUS"){
    	  		let activeValue = parseInt($("ul.pagination li.active").text());
    	  		if(activeValue > 1){
    	  			// get the previous page
    	  			fetchCustomers(activeValue-2, 5, selectedSalary, agesorting, desc);
    	  			let currentActive = $("li.active");
    	  			currentActive.removeClass("active");
    	  			// add .active to previous-pagination li
    	  			currentActive.prev().addClass("active");
    	  		}
    	  	} else {
    	  		fetchCustomers(parseInt(val) - 1, 5,  selectedSalary, agesorting, desc);
    	  		// add focus to the li tag
    	  		$("li.active").removeClass("active");
    	  		$(this).parent().addClass("active");
    	  	}
    	});
    });

    Integration Testing – JQuery Ajax Bootstrap Table & SpringBoot RestAPI

    Now it’s time for integrative testing for the tutorial: “SpringBoot Ajax Pagination Example”.

    Testcase 1 – Pagination View

    – Start Loading time:

    Testcase 1 - Ajax SpringBoot Pagination - Loading Time
    Testcase 1 – Ajax SpringBoot Pagination – Loading Time

    – Select page 3:

    Testcase 1 - Ajax SpringBoot Pagination - Page 3
    Testcase 1 – Ajax SpringBoot Pagination – Page 3

    Testcase 2 – Pagination and Filtering View

    – Pagination and Filtering with salary is $4000:

    Testcase 1 - Ajax SpringBoot Pagination - Filtering with salary = $4000
    Testcase 1 – Ajax SpringBoot Pagination – Filtering with salary = $4000

    – Pagination and Filtering with Salary = $3500:

    Testcase 1 - Ajax SpringBoot Pagination - Filtering with salary = $3500
    Testcase 1 – Ajax SpringBoot Pagination – Filtering with salary = $3500

    Testcase 3 – Pagination Filtering and Sorting View

    – Pagination and Filtering with salary is $3000 and Sorting by Age:

    Testcase 1 - Ajax SpringBoot Pagination - Sorting with Age and Filtering with Salary is $3000
    Testcase 1 – Ajax SpringBoot Pagination – Sorting with Age and Filtering with Salary is $3000

    – Pagination and Filtering with salary = $3500 and sorting by Age with Desc direction:

    Testcase 1 - Ajax SpringBoot Pagination - Sorting with Age by Descending direction and Filtering with Salary is $3500
    Testcase 1 – Ajax SpringBoot Pagination – Sorting with Age by Descending direction and Filtering with Salary is $3500

    Sourcecode

    I attach the running and clearly sourcecode for the tutorial: “SpringBoot Ajax Pagination Example” on the site and in Github.

    – Here is the full sourcecode with below implemented features:

    SpringBootPagingAndSorting

    – Github Sourcecode:

    SpringBoot + Ajax + Bootstrap Table – Paging Filtering and Sorting

    Further Reading

    Related posts:


One thought on “SpringBoot Ajax Pagination Example (Filtering and Sorting) using Bootstrap Table – JQuery Ajax Tutorial”

Leave a Reply

Your email address will not be published. Required fields are marked *