Search code examples
javaspring-bootthymeleaf

handling multiple row submit with checkbox in thymeleaf and spring boot controller


Here is my thymeleaf

<!DOCTYPE html>
<html xmlns:th="www.thymeleaf.org">
<head>
<meta charset="ISO-8859-1">
<!-- Latest compiled and minified CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css">

<!-- jQuery library -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>

<!-- Latest compiled JavaScript -->
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js"></script>
<script type="text/javascript">
$(function(){
    $("#submit").on("click" , function(){
        var frmData = $("#frm").serialize();
        $.ajax({
            url : "./getData",
            data : frmData,
            cache : false,
            method : "POST",
            success : function(data){
                if(data == "success"){
                    location.reload();
                }
            },
            error : function(jhXHR){
                alert(" Something went wrong!! "+jhXHR);
            }
        })
    }) 
    
})

$(function(){
    $("#delete").on("click" , function(){
        var frmData = $("#frm").serialize();
        $.ajax({
            url : "./deleteData",
            data : frmData,
            cache : false,
            method : "POST",
            success : function(data){
                if(data == "deleted"){
                    location.reload();
                }
            },
            error : function(jhXHR){
                alert(" Something went wrong!! "+jhXHR);
            }
        })
    }) 
    
})

$(function(){
    var i = 0;
    $("#add").on("click" , function(){
         var tbody=document.getElementById("table_id").getElementsByTagName("tbody").item(0);
            var tr=document.createElement("tr");

            td = document.createElement("td");
            td.innerHTML= '';
            tr.appendChild(td);
            
            td=document.createElement("td");
            td.setAttribute('style', 'text-align:center');
            td.innerHTML="<input type='checkbox' checked onclick='return false;'>";
            tr.appendChild(td);

            td=document.createElement("td");
            td.setAttribute('style', 'text-align:center');
            td.innerHTML="";
            tr.appendChild(td);


            td=document.createElement("td");
            td.setAttribute('style', 'text-align:center');
            td.setAttribute('nowrap', 'nowrap');
            td.innerHTML="<input type='text'  class='form-control' name='bookList["+i+"].bookName'>";
            tr.appendChild(td);
            
            td=document.createElement("td");
            td.setAttribute('style', 'text-align:center');
            td.setAttribute('nowrap', 'nowrap');
            td.innerHTML="<input type='text'  class='form-control' name='bookList["+i+"].bookPrice'>";
            tr.appendChild(td);
            
            tbody.appendChild(tr);
            
            i++;
    });
    
});

$(function(){
    $(':checkbox').click(function() {
        var checkbox = $(this);
        var row = checkbox.closest('tr');
        if (checkbox.is(':checked')) {
             row.children().find('label').hide();
             row.children().find('input').show();
             row.children().find('input').prop("disabled" , false)
        }
        else {
             row.children().find('label').show();
             row.children().find('input').not("input[type='checkbox']").hide();
             row.children().find('input').not("input[type='checkbox']").prop("disabled" , true)
        }
    });
})
</script>
<style type="text/css">
.margin-gutter{
    margin-top: 10px;
}

input[type="text"].form-control.edit{
    display: none;
}

.form-control{
    width: auto;
}
</style>
</head>
<body>
<div class="container">
<div class="row">
<div class="col-md-12 page-header">
<h4 class="text-center">Book Details</h4>
</div>
</div>
<form id="frm" method="post" th:object="${booksemp}">
<div class="row">
<div class="col-md-4">
<button type="button" id="add" class="btn btn-primary">Add</button>
<button type="button" id="delete" class="btn btn-warning">Delete</button>
<button type="button" id="submit" class="btn btn-warning">Submit</button>
</div>
</div>
<div class="row margin-gutter">
<div class="col-md-12">
<table class="table table-striped table-bordered" id="table_id">
<thead>
<tr>
<th>Sl no</th>
<th>Checkbox</th>
<th>Book id</th>
<th>Book Name</th>
<th>Book Price</th>
</tr>
</thead>
<tbody>
<tr th:each="book, bookStat : ${books}">
<td th:text="${bookStat.count}"></td>
<td style="text-align: center;"><input type="checkbox"></td>
<td>
<label th:text="${book.bookId}"></label>
<input type="text" disabled readonly class="form-control edit" th:name="|bookListNew[${bookStat.index}].bookId|" th:value="${book.bookId}">
</td>
<td><label th:text="${book.bookName}"></label>
<input type="text" disabled class="form-control edit" th:name="|bookListNew[${bookStat.index}].bookName|" th:value="${book.bookName}">
</td>
<td>
<label th:text="${book.bookPrice}"></label>
<input type="text" disabled class="form-control edit" th:name="|bookListNew[${bookStat.index}].bookPrice|" th:value="${book.bookPrice}">
</td>
</tr>
</tbody>
</table>
</div>
</div>
</form>
</div>
</body>
</html>

Here is my Book class and BookGroup class

package com.project.binding;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

import lombok.Data;

@Data
@Entity
public class Book {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private String bookId;
    private String bookName;
    private String bookPrice;
}

package com.project.binding;

import java.util.ArrayList;
import java.util.List;

import lombok.Data;

@Data
public class BookGroup {

    private List<Book> bookList;
    private List<Book> bookListNew;
    
    
    public BookGroup() {
        bookList = new ArrayList<Book>();
        bookListNew = new ArrayList<Book>();
    }
}

Here is my controller

    package com.project.controller;

import java.util.Collections;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import com.project.binding.Book;
import com.project.binding.BookGroup;
import com.project.service.BookService;

@Controller
public class BookController {
    
    @Autowired
    private BookService bookService;

    @GetMapping(value = "/books")
    public String displayAllBooks(Model model) {
        List<Book> allBooks = bookService.getAllBook();
        BookGroup allBooksGroup = new BookGroup();
        model.addAttribute("books", allBooks);
        model.addAttribute("booksemp", allBooksGroup);
        return "books";
    }
    
    @PostMapping(value = "/getData")
    public @ResponseBody String getData(@ModelAttribute("books") BookGroup book, Model model) {
        System.out.println(book.getBookList().removeAll(Collections.singleton(null)));
        System.out.println(book.getBookListNew().removeAll(Collections.singleton(null)));
        
        if(null != book.getBookList() || !book.getBookList().isEmpty()) {
            bookService.saveBook(book.getBookList());
        }
        
        if(null !=book.getBookListNew() || !book.getBookListNew().isEmpty()) {
            System.out.println(book.getBookListNew());
            bookService.saveBook(book.getBookListNew());
        }
        
        
        return "success" ;
    }
    
}

When adding new row it's working good and fine as expected. When it comes to to edit, the problem I am facing is suppose I checked serial no 2 or 3 rather anything greater than 1, those many empty objects are going with it and getting inserted.

Edit row Example

So here i am editing row number 3 and when I submit book 3 gets edit successfully but 2 more rows add up because of 2 empty List object

After editing row 3

As you can see over here in console printing List Object [Book(bookId=null, bookName=null, bookPrice=null), Book(bookId=null, bookName=null, bookPrice=null), Book(bookId=3, bookName=BOOK3, bookPrice=1500$)]

What mistake am I doing , Any suggestion with be very much helpful


Solution

  • So i solved it , but I don't know that is it the most efficient way to do it or not

    So here is my updated Book class where i have declared isChecked as a Transient so that it does not maps to entity.

    package com.project.binding;
    
    import javax.persistence.Entity;
    import javax.persistence.GeneratedValue;
    import javax.persistence.GenerationType;
    import javax.persistence.Id;
    import javax.persistence.Transient;
    
    import lombok.Data;
    
    @Data
    @Entity
    public class Book {
    
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private String bookId;
        private String bookName;
        private String bookPrice;
        @Transient
        private String isChecked;
    }
    

    Here is my controller. Here I am basically taking the List and iterating with Book type and getting each object and seeing that the checkbox is checked or not. If checked that is not null that adding it to a new List that is sendForEdit and sending for update. Hope it helps someone. :) Please do improve my answer if there is a place of improvement.

    package com.project.controller;
    
    import java.util.ArrayList;
    import java.util.Collections;
    import java.util.List;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Controller;
    import org.springframework.ui.Model;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.ModelAttribute;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.ResponseBody;
    
    import com.project.binding.Book;
    import com.project.binding.BookGroup;
    import com.project.service.BookService;
    
    @Controller
    public class BookController {
        
        @Autowired
        private BookService bookService;
    
        @GetMapping(value = "/books")
        public String displayAllBooks(Model model) {
            List<Book> allBooks = bookService.getAllBook();
            BookGroup allBooksGroup = new BookGroup();
            model.addAttribute("books", allBooks);
            model.addAttribute("booksemp", allBooksGroup);
            return "books";
        }
        
        @PostMapping(value = "/getData")
        public @ResponseBody String getData(@ModelAttribute("books") BookGroup book, Model model) {
            
            if(null != book.getBookList() || !book.getBookList().isEmpty()) {
                bookService.saveBook(book.getBookList());
            }
            
            List<Book> sendForEdit = new ArrayList<Book>();
            if(null !=book.getBookListNew() || !book.getBookListNew().isEmpty()) {
                for(Book object : book.getBookListNew()) {
                    if(object.getIsChecked() != null) {
                        sendForEdit.add(object);
                    }
                }
                bookService.saveBook(sendForEdit);
            }
            
            
            return "success" ;
        }
        
    }