I'm working on a Spring Boot + Bootstrap project.
I've created a modal window to confirm the removal of itens, but after I added Spring Security to the project, the modal stopped working.
How can I properly configure the CSRF token in a modal window?
I have tried some approaches, with no success.
I get the following error when deleting itens:
There was an unexpected error (type=Forbidden, status=403). Invalid
CSRF Token 'null' was found on the request parameter '_csrf' or header
'X-CSRF-TOKEN'.
Thanks for the help!
confirmRemove.html
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
<div class="modal fade" id="confirmRemove" tabindex="-1"
data-keyboard="false" data-backdrop="static">
<div class="modal-dialog">
<form th:attr="data-url-base=#{/restaurants}" method="POST">
<input type="hidden" name="_method" value="DELETE" />
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal"
aria-label="Close">
<span aria-hidden="true">×</span>
</button>
<h4 class="modal-title">Você tem certeza?</h4>
</div>
<div class="modal-body">
<span>Are you sure you want to delete this restaurant?</span>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-link" data-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-primary">Delete</button>
</div>
</div>
</form>
</div>
</div>
</html>
restaurantpoll.js
$('#confirmRemove').on(
'show.bs.modal',
function(event) {
var button = $(event.relatedTarget);
var codeRestaurant = button.data('id');
var nameRestaurant = button.data('name');
var modal = $(this);
var form = modal.find('form');
var action = form.data('url-base');
if (!action.endsWith('/')) {
action += '/';
}
form.attr('action', action + codeRestaurant);
modal.find('.modal-body span').html(
'Are you sure you want to delete <strong>'
+ nameRestaurant + '</strong>?')
});
RestaurantController (only the relevant part)
package com.matmr.restaurantpoll.controller;
import java.util.Arrays;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.validation.Errors;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import com.matmr.restaurantpoll.exception.RestaurantNotFoundException;
import com.matmr.restaurantpoll.model.Category;
import com.matmr.restaurantpoll.model.Restaurant;
import com.matmr.restaurantpoll.model.filter.RestaurantFilter;
import com.matmr.restaurantpoll.service.RestaurantService;
#Controller
#RequestMapping("/restaurants")
public class RestaurantController {
#Autowired
private RestaurantService restaurantService;
#Autowired
public RestaurantController(RestaurantService restaurantService) {
this.restaurantService = restaurantService;
}
#RequestMapping(value = "{id}", method = RequestMethod.DELETE)
public String delete(#PathVariable Long id, RedirectAttributes attributes) throws RestaurantNotFoundException {
restaurantService.deleteById(id);
attributes.addFlashAttribute("message", "The restaurant was successfully deleted.");
return "redirect:/restaurants";
}
}
restaurantList.html
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://ultraq.net.nz/thymeleaf/layout"
layout:decorator="Layout">
<head>
<title>Pesquisa de Restaurantes</title>
</head>
<section layout:fragment="conteudo">
<div class="panel panel-primary">
<div class="panel-heading">
<div class="clearfix">
<h1 class="panel-title liberty-title-panel">Pesquisa de
Restaurantes</h1>
<a class="btn btn-link liberty-link-panel"
th:href="#{/restaurants/new}">Cadastrar Novo Restaurante</a>
</div>
</div>
<div class="panel-body">
<form method="GET" class="form-horizontal"
th:action="#{/restaurants}" th:object="${filter}">
<div layout:include="MensagemGeral"></div>
<div layout:include="MensagemErro"></div>
<div class="form-group">
<div class="col-sm-4">
<div class="input-group">
<input class="form-control"
placeholder="Qual restaurante você está procurando?"
autofocus="autofocus" th:field="*{name}"></input> <span
class="input-group-btn">
<button type="submit" class="btn btn-default">
<i class="glyphicon glyphicon-search"></i>
</button>
</span>
</div>
</div>
</div>
</form>
<div class="table-responsive">
<table class="table table-bordered table-striped">
<thead>
<tr>
<th class="text-left col-md-1">#</th>
<th class="text-left col-md-2">Nome</th>
<th class="text-left col-md-3">Descrição</th>
<th class="text-left col-md-2">Categoria</th>
<th class="col-md-1"></th>
</tr>
</thead>
<tbody>
<tr th:each="restaurant : ${restaurants}">
<td class="text-left" th:text="${restaurant.id}"></td>
<td class="text-left" th:text="${restaurant.name}"></td>
<td class="text-left" th:text="${restaurant.description}"></td>
<td class="text-left"
th:text="${restaurant.category.description}"></td>
<td class="text-center"><a class="btn btn-link btn-xs"
th:href="#{/restaurants/{id}(id=${restaurant.id})}"
title="Editar" rel="tooltip" data-placement="top"> <span
class="glyphicon glyphicon-pencil"></span>
</a> <a class="btn btn-link btn-xs" data-toggle="modal"
data-target="#confirmRemove"
th:attr="data-id=${restaurant.id}, data-name=${restaurant.name}"
title="Excluir" rel="tooltip" data-placement="top"> <span
class="glyphicon glyphicon-remove"></span>
</a></td>
</tr>
<tr>
<td colspan="5" th:if="${#lists.isEmpty(restaurants)}">Nenhum
restaurante foi encontrado!</td>
</tr>
</tbody>
</table>
</div>
</div>
<div layout:include="confirmRemove"></div>
</div>
</section>
</html>
Adding th:action="#{/restaurants}" to the modal's form tag did the trick:
<form th:action="#{/restaurants}" th:attr="data-url-base=#{/restaurants}" method="POST">
Related
I'm having troubles with thymeleaf and spring boot to display data in table after a form filled succesfuly. Here is what i done:
Form:
<div class="container">
<div class="row">
<div class="col-md-12">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Cargar Agenda</h3>
</div>
<div th:if="${error != null}" th:text="${error}" class="alert alert-danger" role="alert">
</div>
<div class="panel-body">
<form th:action="#{/guardar}" method="POST">
<div class="form-group">
<input type="text" class="form-control" name="nombre" id="nombre" th:value="${nombre}" placeholder="Nombre">
</div>
<div class="form-group">
<input type="text" class="form-control" name="apellido" id="apellido" th:value="${apellido}" placeholder="Apellido">
</div>
<div class="form-group">
<input type="text" class="form-control" name="telefono" id="telefono" th:value="${telefono}" placeholder="Teléfono">
</div>
<div class="form-group">
<input type="text" class="form-control" name="email" id="email" th:value="${email}" placeholder="E-mail">
</div>
<div class="form-group">
<input type="text" class="form-control" name="domicilio" id="domicilio" th:value="${domicilio}" placeholder="Domicilio">
</div>
<div class="d-grid gap-2">
<button type="submit" class="btn btn-primary">Guardar</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
Table:
<div class="table-responsive">
<table class="table table-striped table-bordered table-hover table-sm">
<thead class="thead-dark">
<tr class="bg-primary" scope="row">
<th scope="col">Nombre</th>
<th scope="col">Apellido</th>
<th scope="col">Teléfono</th>
<th scope="col">E-mail</th>
<th scope="col">Domicilio</th>
</tr>
</thead>
<tbody>
<tr scope="row" th:each="agenda : ${listaAgendas}">
<td scope="col" th:text="${agenda.nombre}"></td>
<td scope="col" th:text="${agenda.apellido}"></td>
<td scope="col" th:text="${agenda.telefono}"></td>
<td scope="col" th:text="${agenda.email}"></td>
<td scope="col" th:text="${agenda.domicilio}"></td>
</tr>
</tbody>
</table>
</div>
Controller PostMapping:
#PostMapping("/guardar")
public String guardar(ModelMap map, #RequestParam String nombre, #RequestParam String apellido,
#RequestParam Long telefono, #RequestParam String email,
#RequestParam String domicilio) {
try {
agendaService.crear(nombre, apellido, telefono, email, domicilio);
} catch (AgendaException ex) {
map.put("error", ex.getMessage());
map.put("nombre", nombre);
map.put("apellido", apellido);
map.put("telefono", telefono);
map.put("email", email);
map.put("domicilio", domicilio);
return "carga";
}
return "lista-agendas";
}
Controller GetMapping:
#GetMapping("/lista-agendas")
public String listaAgendas(ModelMap map) {
List<Agenda> listaAgendas = agendaService.listaAgendas();
map.put("listaAgendas", listaAgendas);
return "lista-agendas";
}
lista-agendas-html render this by his self
Filled form
but lista-agendas.html after clicking the form button, display table without data:
no data in the table after clicking form button to submit
Solved ! just replaced in #PostMapping guardar method
return "lista-agendas";
with:
return "redirect:/lista-agendas";
In my application built on Java, JSP with BootStrap, and Servlet, there is the issue to send the data from Servlet to BootStrap Modal.
Based on my project, a user clicks the 'check button' in each table container and that table_id is caught in javascript and send it to Servlet with that value. Then Servlet operates on grabbing the data from the database based on its table_id. The result(orders in the code and results are displayed properly when printing out them) of the operation needed to send to the BootStrap modal section.
TableMonitor.jsp
<%# page language="java" contentType="text/html; charset=EUC-KR"
pageEncoding="EUC-KR"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport"
content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<meta name="author" content="">
<link rel="icon" href="/docs/4.0/assets/img/favicons/favicon.ico">
<title>Migarock Table Monitoring System</title>
<link rel="canonical"
href="https://getbootstrap.com/docs/4.0/examples/pricing/">
<!-- Bootstrap core CSS -->
<link href="../../dist/css/bootstrap.min.css" rel="stylesheet">
<!-- Custom styles for this template -->
<link href="pricing.css" rel="stylesheet">
<link rel="stylesheet"
href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"
integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T"
crossorigin="anonymous">
<link href="./css/tableMonitor.css" rel="stylesheet">
<%# taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
</head>
<body>
<!--
<div class="d-flex flex-column flex-md-row align-items-center p-3 px-md-4 mb-3 bg-white border-bottom box-shadow">
<h3 class="my-0 mr-md-auto font-weight-normal">Migarock Table Monitoring System</h3>
<a class="btn btn-outline-primary" href="#">Lock</a>
</div> -->
<div class="row">
<div class="col-2">
<div class="container">
<div class="card-deck mb-3 text-center ">
<div class="card mb-4 box-shadow ">
<div class="card-header status1">
<h3 class="my-0 font-weight-normal ">
<strong>Table_01</strong>
</h3>
</div>
<div class="card-body">
<a class="btn btn-outline-primary btn-lg btn-block mx-1 mt-1"
data-toggle="modal" href="#tableModal" data-table-id="1">Check</a>
</div>
</div>
</div>
</div>
</div>
<div class="col-2">
<div class="container">
<div class="card-deck mb-3 text-center">
<div class="card mb-4 box-shadow">
<div class="card-header">
<h3 class="my-0 font-weight-normal">
<strong>Table_02</strong>
</h3>
</div>
<div class="card-body">
<a class="btn btn-outline-primary btn-lg btn-block mx-1 mt-1"
data-toggle="modal" href="#tableModal" data-table-id="2">Check</a>
</div>
</div>
</div>
</div>
</div>
<div class="col-2">
<div class="container">
<div class="card-deck mb-3 text-center">
<div class="card mb-4 box-shadow">
<div class="card-header status2">
<h3 class="my-0 font-weight-normal">
<strong>Table_03</strong>
</h3>
</div>
<div class="card-body">
<a class="btn btn-outline-primary btn-lg btn-block mx-1 mt-1"
data-toggle="modal" href="#tableModal" data-table-id="3">Check</a>
</div>
</div>
</div>
</div>
</div>
<div class="col-2">
<div class="container">
<div class="card-deck mb-3 text-center">
<div class="card mb-4 box-shadow">
<div class="card-header">
<h3 class="my-0 font-weight-normal">
<strong>Table_04</strong>
</h3>
</div>
<div class="card-body">
<a class="btn btn-outline-primary btn-lg btn-block mx-1 mt-1"
data-toggle="modal" href="#tableModal" data-table-id="4">Check</a>
</div>
</div>
</div>
</div>
</div>
<div class="col-2">
<div class="container">
<div class="card-deck mb-3 text-center">
<div class="card mb-4 box-shadow">
<div class="card-header">
<h3 class="my-0 font-weight-normal">
<strong>Table_05</strong>
</h3>
</div>
<div class="card-body">
<a class="btn btn-outline-primary btn-lg btn-block mx-1 mt-1"
data-toggle="modal" href="#tableModal" data-table-id="5">Check</a>
</div>
</div>
</div>
</div>
</div>
<div class="col-2">
<div class="container">
<div class="card-deck mb-3 text-center">
<div class="card mb-4 box-shadow ">
<div class="card-header status3">
<h3 class="my-0 font-weight-normal">
<strong>Table_06</strong>
</h3>
</div>
<div class="card-body">
<a class="btn btn-outline-primary btn-lg btn-block mx-1 mt-1"
data-toggle="modal" href="#tableModal" data-table-id="6">Check</a>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="modal fade" id="tableModal" tabindex="1" role="dialog"
aria-labelledby="modal" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h2 class="modal-title" id="modal">Table Status</h2>
<button type="button" class="close" data-dismiss="modal"
aria-label="close">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="controlBar">
<div class="row">
<div class="col-8" style="text-align: left;">
<button type="button" class="btn btn-outline-success btn-lg"
data-dismiss="modal">Add item</button>
<button type="button" class="btn btn-outline-secondary btn-lg"
data-dismiss="modal">Change Status</button>
</div>
<div class="col-4" style="text-align: right; padding-right: 10%;">
<button type="button" class="btn btn-outline-warning btn-lg"
data-dismiss="modal">Help</button>
<button type="button" class="btn btn-outline-info btn-lg"
data-dismiss="modal">Bill</button>
</div>
</div>
<div>
<div class="modal-body">
<h3>My name is ${test}</h3>
<!-- <form action="./evaluationRegisterAction.jsp" method="post"> -->
<div class="scrollbar scrollbar-primary">
<!-- <div> -->
<table class="table">
<thead>
<tr style="text-align: center;">
<th scope="col"><input type="checkbox"
class="select-all checkbox" name="select-all" /></th>
<th scope="col">OrderTime</th>
<th scope="col">Item</th>
<th scope="col">QTY</th>
<th scope="col">Price</th>
<th scope="col">Status</th>
</tr>
</thead>
<tbody>
<c:out value="${requestScope.orders}">
<c:forEach items="${orders}" var="order">
<tr style="text-align: center;">
<td style="text-align: center;">
<div class="form-check">
<input type="checkbox" class="select-item checkbox"
name="select-item">
</div>
</td>
<td><span id="orders"></span> ${order.getTimeStamp()}</td>
<td>{order.getOrderItem()}</td>
<td>{order.getOrderQty()}</td>
<td>{order.getOrderPrice()}</td>
<td>{order.getOrderStatus()}</td>
<td><a class="btn btn-light btn-sm mx-1 mt-2"
data-toggle="modal" href="#changeStatus">Ordered</a> <!-- <a class="btn btn-dark btn-sm mx-1 mt-2" data-toggle="modal"
href="#changeStatus">Delivered</a> -->
</td>
</tr>
</c:forEach>
</c:out>
</tbody>
</table>
</div>
</div>
<div>
<table class="table">
<tr style="text-align: center;">
<td colspan="3">
<h4>Total</h4>
</td>
<td colspan="3">
<h4>(total)</h4>
</td>
</tr>
</table>
</div>
<div class="controlBar">
<div class="row">
<div class="col-6" style="text-align: left;">
<button type="button" class="btn btn-danger btn-lg"
data-dismiss="modal">Close Session</button>
</div>
<div class="col-6"
style="text-align: right; padding-right: 10%;">
<button type="button" class="btn btn-outline-danger btn-lg"
data-dismiss="modal">Close Window</button>
</div>
</div>
</div>
</div>
<!-- </form> -->
</div>
</div>
</div>
</div>
<footer class="pt-4 my-md-5 pt-md-5 border-top"> </footer>
</div>
<!-- Bootstrap core JavaScript
================================================== -->
<!-- Placed at the end of the document so the pages load faster -->
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js"
integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN"
crossorigin="anonymous"></script>
<script>
window.jQuery
|| document
.write('<script src="../../assets/js/vendor/jquery-slim.min.js"><\/script>')
</script>
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js"
integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo"
crossorigin="anonymous"></script>
<script
src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js"
integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1"
crossorigin="anonymous"></script>
<script
src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"
integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM"
crossorigin="anonymous"></script>
<script>
Holder.addTheme('thumb', {
bg : '#55595c',
fg : '#eceeef',
text : 'Thumbnail'
});
</script>
<script>
$(function() {
//button select all or cancel
$("#select-all").click(function() {
var all = $("input.select-all")[0];
all.checked = !all.checked
var checked = all.checked;
$("input.select-item").each(function(index, item) {
item.checked = checked;
});
});
//button select invert
$("#select-invert").click(function() {
$("input.select-item").each(function(index, item) {
item.checked = !item.checked;
});
checkSelected();
});
//button get selected info
$("#selected").click(
function() {
var items = [];
$("input.select-item:checked:checked").each(
function(index, item) {
items[index] = item.value;
});
if (items.length < 1) {
alert("no selected items!!!");
} else {
var values = items.join(',');
console.log(values);
var html = $("<div></div>");
html.html("selected:" + values);
html.appendTo("body");
}
});
//column checkbox select all or cancel
$("input.select-all").click(function() {
var checked = this.checked;
$("input.select-item").each(function(index, item) {
item.checked = checked;
});
});
//check selected items
$("input.select-item").click(function() {
var checked = this.checked;
console.log(checked);
checkSelected();
});
//check is all selected
function checkSelected() {
var all = $("input.select-all")[0];
var total = $("input.select-item").length;
var len = $("input.select-item:checked:checked").length;
console.log("total:" + total);
console.log("len:" + len);
all.checked = len === total;
}
});
$('#tableModal')
.on(
'show.bs.modal',
function(e) {
var tableID = $(e.relatedTarget).data('table-id');
console.log("test: ", tableID)
$(e.currentTarget).find('input[name="tableID"]')
.val(tableID);
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
// Typical action to be performed when the document is ready:
document.getElementById("response").value = xhttp.responseText;
}
};
xhttp.open("POST", "TableMonitor", true);
xhttp.setRequestHeader("Content-type",
"application/x-www-form-urlencoded");
xhttp.send("tableId=" + tableID);
var orders = $(this).data('orders');
$('#orders').html(orders);
});
</script>
</body>
</html>
TableMonitor.java(Servlet)
package servlets;
import java.io.IOException;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import brokers.TableBrokder;
import model.Order;
/**
* Servlet implementation class TableMonitor
*/
#WebServlet("/TableMonitor")
public class TableMonitor extends HttpServlet {
private static final long serialVersionUID = 1L;
/**
* #see HttpServlet#HttpServlet()
*/
public TableMonitor() {
super();
// TODO Auto-generated constructor stub
}
/**
* #see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse
* response)
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
getServletContext().getRequestDispatcher("/TableMonitor.jsp").forward(request, response);
}
/**
* #see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse
* response)
*/
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String table_id = request.getParameter("tableId");
String action = request.getParameter("action");
System.out.println("servlet: " + table_id);
List<Order> orders = null;
TableBrokder tb = new TableBrokder();
try {
orders = tb.getOrderAll(Integer.parseInt(table_id));
} catch (NumberFormatException | SQLException e) {
e.printStackTrace();
}
for (int i = 0; i < orders.size(); i++) {
orders.get(i).toString();
}
// response.sendRedirect("TableMonitor");
RequestDispatcher dispatcher = request.getRequestDispatcher("/TableMonitor.jsp");
request.setAttribute("orders", orders);
request.setAttribute("test", "test");
dispatcher.forward(request, response);
}
}
Even though I google it but didn't get clear answers. Could you help me with this issue?
Looks like you are making an ajax call in your js but in your servlet you are not returning the response. I highly recommend checking out this answer on how to make ajax calls with servlets.
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String table_id = request.getParameter("tableId");
String action = request.getParameter("action");
System.out.println("servlet: " + table_id);
List<Order> orders = null;
TableBrokder tb = new TableBrokder();
try {
orders = tb.getOrderAll(Integer.parseInt(table_id));
} catch (NumberFormatException | SQLException e) {
e.printStackTrace();
}
for (int i = 0; i < orders.size(); i++) {
orders.get(i).toString();
}
//convert your list of orders to json
String json = new Gson().toJson(orders);
//add json to response
response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
response.getWriter().write(json);
}
When you make ajax calls you won't be able to do things like this:
request.setAttribute("orders", orders);
request.setAttribute("test", "test");
Because when you call this servlet url through ajax, you are not loading the jsp page again so the HttpServletRequest won't be passed to the jsp page. Hope this helps understand it some more.
I'm leaving the JSF environment and I have some doubts when using Spring mvc with Thymeleaf. As in the example below, I have the account object related to a tariff list, what I am trying to accomplish is to add the tariffs of the account being registered in the tariff list that is in my object account, so, when saving the object of the account, fees are recorded through the waterfall. What is the best way to implement this simple funcinality with spring and thymeleaf.
Account Class
public class Account {
private Integer accountNumber;
private BigDecimal balance;
#ManyToOne(targetEntity=Rate.class, cascade={CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REMOVE})
#JoinColumn(name="RateCode", foreignKey= #ForeignKey(name = "fk_account_rate"))
private List<Rate> rates;
// get and sets
}
Rate
public class Rate {
private Long rateCode;
private String rateName;
private BigDecimal value;
// get and sets
}
Controller Account
#Controller
#RequestMapping("/account")
public class AccountController {
#Autowired AccountRepository accountRepository;
#GetMapping
public ModelAndView new(Account account) {
ModelAndView model = new ModelAndView("page/account/cadAccount");
model.addObject("rate", new Rate());
model.addObject(account);
return model;
}
}
Page Account
<form th:object="${account}" method="POST" th:action="#{/account/save}">
<gff:message/>
<div class="box-body">
<input type="hidden" th:field="*{accountCode}"/>
<div class="row">
<div class="col-md-12">
<div class="nav-tabs-custom">
<ul class="nav nav-tabs">
<li class="active">Account</li>
<li>Rates</li>
</ul>
<div class="tab-content">
<div class="tab-pane active" id="tab_conta">
<div class="row">
<div class="col-md-4">
<div class="form-group" gff:classforerror="name">
<label>Account name</label>
<input type="text" th:field="*{name}" class="form-control input-sm">
</div>
</div>
<div class="form-group col-md-4" gff:classforerror="balance">
<label>Balance</label>
<div class="input-group">
<span class="input-group-addon"><i class="fa fa-dollar"></i></span>
<input type="text" th:field="*{balance}" data-thousands="." data-decimal="," data-prefix="R$ " class="form-control currency input-sm">
</div>
</div>
</div>
</div>
<div class="tab-pane" id="tab_tarifas">
<div class="row">
<div class="col-md-5">
<div class="form-group">
<label>Rate Name</label>
<input id="nomeTarifa" type="text" th:field="${rate.name}" class="form-control input-sm">
</div>
</div>
<div class="form-group col-md-3">
<label>Value</label>
<div class="input-group">
<span class="input-group-addon"><i class="fa fa-dollar"></i></span>
<input id="valorTarifaID" type="text" th:field="${rate.value}" data-thousands="." data-decimal="," data-prefix="R$ " class="form-control currency input-sm">
</div>
</div>
<div class="form-group col-md-2">
<a class="btn btn-info pull-left" th:href="#{/account/add}">
<i class="fa fa-plus"></i>
Add
</a>
</div>
<div class="col-md-12">
<table class="table table-hover">
<thead>
<tr>
<th>Name</th>
<th>Value</th>
</tr>
</thead>
<tbody>
<tr>
<td data-title="Name"></td>
<td data-title="Value"></td>
</tr>
</tbody>
<tfoot>
<tr>
<th></th>
<th></th>
<th></th>
</tr>
</tfoot>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="box-footer">
<button id="salveButtonID" type="submit" class="btn btn-primary ajax">Salve</button>
<a class="btn btn-default ajax" th:href="#{/}">Cancel</a>
</div>
</form>
I just started with Spring two month ago and never did Ajax or JavaScript before. So i'm pretty new to this. What i want to do is load data from a GET Method in my Controller to populate this into a modal. I'm using ajax for this. Basicly i did what this guy https://qtzar.com/2017/03/24/ajax-and-thymeleaf-for-modal-dialogs/ is doing. But it's not working.
Hope somebody can help me with this.
Here is my Controller:
#RequestMapping(path="/reservations/details/{reservationId}", method=RequestMethod.GET)
public #ResponseBody String getReservationDetails(#PathVariable("reservationId") String reservationId, Model model, Principal principal, HttpServletRequest request){
LOGGER.info(LogUtils.getDefaultInfoStringWithPathVariable(request, Thread.currentThread().getStackTrace()[1].getMethodName(), " reservationId ", reservationId.toString()));
User authenticatedUser = (User) ((Authentication) principal).getPrincipal();
if(authenticatedUser.getAdministratedRestaurant() == null) {
LOGGER.error(LogUtils.getErrorMessage(request, Thread.currentThread().getStackTrace()[1].getMethodName(), "The user " + authenticatedUser.getUsername() + " has no restaurant. A restaurant has to be added before offers can be selected."));
return null;
}
Reservation reservation = reservationRepository.findOne(Integer.parseInt(reservationId));
if(reservation == null){
return null;
}
List<ReservationOffers> reservationOffers = reservation.getReservation_offers();
if(reservationOffers == null){
return null;
}
model.addAttribute("offers", reservationOffers);
return "reservations :: reservationTable";
}
This is the button which calls the JavaScript within the "reservation.html"
<button type="button" class="btn btn-success" th:onclick="'javascript:openReservationModal(\''+*{reservations[__${stat.index}__].id}+'\');'">
<span class="glyphicon glyphicon-search" aria-hidden="true"></span>
</button>
Here is the modal I want to show:
<div id="reservationModal" class="modal fade" role="dialog" th:fragment="reservationTable">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">×</button>
<h4 class="modal-title" th:text="'Reservation Details'">Modal Header</h4>
</div>
<div class="modal-body">
<table class="table table-hover" id="reservationTable">
<thead>
<tr>
<td th:text="'Name'"></td>
<td th:text="'Amount'"></td>
</tr>
</thead>
<tbody>
<tr th:each="offer : ${offers}">
<td th:text="${offer.getOffer().getTitle()}"></td>
<td th:text="${offer.getAmount()}"></td>
</tr>
</tbody>
</table>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
There is a empty div in my reservation.html
<div id="modalHolder">
</div>
And there is the JavaScript with Ajax:
<script th:inline="javascript" type="text/javascript">
function openReservationModal(id) {
$.ajax({
url: "/reservations/details/"+id,
success: function(data) {
console.log(data);
$("#modalHolder").html(data);
$("#reservationModal").modal("show");
}
});
}
</script>
Thank you guys!
EDIT:
Here is the Table which contains the button:
<form action="#" th:object="${wrapper}" method="post">
<div style="height: 190px; overflow: auto;">
<table class="table table-hover" id="reservationTable">
<thead>
<tr>
<th th:text="#{reservations.label.oderId}">Id</th>
<th th:text="#{reservations.label.customername}">name</th>
<th th:text="#{reservations.label.datetime}">date with time</th>
<th th:text="#{reservations.label.price}">price</th>
<th th:text="#{reservations.label.donation}">customer donation</th>
<th th:text="#{reservations.label.priceWithDonation}">price included with Donation</th>
<!-- <th th:text="#{reservations.label.confirmed}">finished reservation</th> -->
<!-- <th th:text="#{reservations.label.isfree}">free reservation</th> -->
<th th:text="#{reservations.label.choice}">reservation selection</th>
<th th:text="#{reservations.label.details}">reservation details</th>
</tr>
</thead>
<tbody>
<tr th:each="reservation, stat: *{reservations}">
<div th:switch="${reservation.isUsedPoints()}">
<div th:case="false">
<td th:text="${reservation.getReservationNumber()}"></td>
<td th:text="${reservation.getUser().getUsername()}"></td>
<td th:text="${#dates.format(reservation.reservationTime, 'HH:mm')}"></td>
<td><span th:text="${#numbers.formatDecimal(reservation.getTotalPrice(), 1, 'POINT', 2, 'COMMA')}"> </span> €</td>
<td><span th:text="${#numbers.formatDecimal(reservation.getDonation(), 1, 'POINT', 2, 'COMMA')}"> </span> €</td>
<td><span th:text="${#numbers.formatDecimal(reservation.getDonation() + reservation.getTotalPrice(), 1, 'POINT', 2, 'COMMA')}"> </span> €</td>
<!-- <td th:text="${reservation.isConfirmed()}"></td> -->
<!-- <td th:text="${reservation.isUsedPoints()}" ></td> -->
<td>
<input type="hidden" th:field="*{reservations[__${stat.index}__].id}" />
<input type="checkbox" th:field="*{reservations[__${stat.index}__].confirmed}"/>
<input type="hidden" th:field="*{reservations[__${stat.index}__].rejected}" />
<input type="hidden" th:field="*{reservations[__${stat.index}__].donation}"/>
<input type="hidden" th:field="*{reservations[__${stat.index}__].totalPrice}"/>
<input type="hidden" th:field="*{reservations[__${stat.index}__].usedPoints}"/>
</td>
</div>
<div th:case="true">
<input type="hidden" th:field="*{reservations[__${stat.index}__].id}" />
<input type="hidden" th:field="*{reservations[__${stat.index}__].confirmed}" />
<input type="hidden" th:field="*{reservations[__${stat.index}__].rejected}" />
<input type="hidden" th:field="*{reservations[__${stat.index}__].donation}"/>
<input type="hidden" th:field="*{reservations[__${stat.index}__].totalPrice}"/>
<input type="hidden" th:field="*{reservations[__${stat.index}__].usedPoints}"/>
</div>
<td>
<button type="button" class="btn btn-success" th:onclick="'javascript:openReservationModal(\''+*{reservations[__${stat.index}__].id}+'\');'">
<span class="glyphicon glyphicon-search" aria-hidden="true"></span>
</button>
</td>
</div>
</tr>
</tbody>
</table>
</div>
<button type="submit" class="btn btn-success" th:text="#{reservations.button.confirm}" name="confrim" style="float: right; margin-right: 25px;"></button>
<button type="reject" class="btn btn-success" th:text="#{reservations.button.reject}" name="reject" style="float: right; margin-right: 25px;"></button>
<input type="hidden"
th:name="${_csrf.parameterName}"
th:value="${_csrf.token}" />
</form>
Okay i found two mistakes by myselfe. First the Table which contians the button had the same id as the modal. The second one was the #ResponseBody Annotation in the controller. Now its returning the right data to the console but still not to the modal.
Here is the output on the console:
Data: <div id="reservationModal" class="modal fade" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">×</button>
<h4 class="modal-title">Reservation Details</h4>
</div>
<div class="modal-body">
<table class="table table-hover" id="reservationOfferTable">
<thead>
<tr>
<td>Name</td>
<td>Amount</td>
</tr>
</thead>
<tbody>
<tr>
<td>Schwarzer Kaffe</td>
<td>1</td>
</tr>
<tr>
<td>Haxn</td>
<td>2</td>
</tr>
</tbody>
</table>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
When you select an Expense object from the data table for editing, the edit method in the controller sends the Expense object to the edit screen that is the same to insert a new record. When the object is sent to the save method, it has nullo code attribute where an insert occurs instead of an update. I do not understand why.
Controller
#Controller
#RequestMapping("/despesas")
public class DespesaController {
#Autowired private DespesaService despesaService;
#Autowired private DespesaRepository despesaRepository;
#GetMapping("/add")
public ModelAndView novo(Despesa despesa) {
ModelAndView model = new ModelAndView("page/cadastro/despesa/cadDespesa");
model.addObject("tiposDespesa", TipoDespesa.values());
model.addObject("formasPagamento", FormaPagamento.values());
model.addObject(despesa);
return model;
}
#PostMapping("/save")
public ModelAndView salvar(Despesa despesa, BindingResult result, RedirectAttributes attributes) {
if (result.hasErrors()) {
return novo(despesa);
}
despesa.setDataDespesa(new Date());
despesaService.salvarDespesa(despesa);
attributes.addFlashAttribute("mensagem", "Despesa Salva com Sucesso!");
return new ModelAndView("redirect:/cadastroDespesa");
}
#GetMapping("/listDespesa")
public ModelAndView listagemDeDespesas() {
ModelAndView model = new ModelAndView("page/cadastro/despesa/listDespesa");
model.addObject("despesas", despesaRepository.findAll());
return model;
}
#GetMapping("/edit{id}")
public ModelAndView editar(#PathVariable("id") Long codigo) {
return novo(despesaRepository.findOne(codigo));
}
}
FormEdit
<form th:object="${despesa}" method="POST" th:action="#{/despesas/save}">
<div class="box-body">
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label>Data</label>
<div class="input-group">
<div class="input-group-addon">
<i class="fa fa-calendar"></i>
</div>
<input type="text" th:field="*{dataDespesa}" class="form-control" disabled="disabled">
</div>
</div>
<div class="form-group">
<label>Valor</label>
<div class="input-group">
<span class="input-group-addon"><i class="fa fa-dollar"></i></span>
<input type="text" th:field="*{valor}" class="form-control">
</div>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label>Tipo Despesa</label>
<select class="form-control select2" th:field="*{tipoDespesa}" style="width: 100%;">
<option th:each="tipo : ${tiposDespesa}" th:value="${tipo}" th:text="${tipo}"></option>
</select>
</div>
<div class="form-group">
<label>Forma Pagamento</label>
<select class="form-control select2" th:field="*{formaPagamento}" style="width: 100%;">
<option th:each="forma : ${formasPagamento}" th:value="${forma}" th:text="${forma}"></option>
</select>
</div>
</div>
<div class="col-md-12">
<div class="form-group">
<label>Observação</label>
<input type="text" th:field="*{observacao}" class="form-control">
</div>
</div>
</div>
</div>
<div class="box-footer">
<button type="submit" class="btn btn-primary">Salvar</button>
<a class="btn btn-default" th:href="#{/}">Cancelar</a>
</div>
</form>
Data Table Where I select the object for editing
<table id="example2" class="table table-bordered table-hover">
<thead>
<tr>
<th>Data</th>
<th>Valor</th>
<th>Tipo Despesa</th>
<th>Forma Pagamento</th>
</tr>
</thead>
<tbody>
<tr th:each="obj : ${despesas}">
<td data-title="Data" th:text="${#calendars.format(obj.dataDespesa, 'dd/MM/yyyy HH:mm:ss')}"></td>
<td data-title="Valor" th:text="${#numbers.formatCurrency(obj.valor)}"></td>
<td data-title="Tipo Despesa" th:text="${obj.tipoDespesa}"></td>
<td data-title="Forma Pagamento" th:text="${obj.formaPagamento}"></td>
<td><a th:href="#{/despesas/edit{id} (id=${obj.codigoDespesa})}"><i class="glyphicon glyphicon-pencil"></i></a></td>
</tr>
</tbody>
</table>
As sr.praneeth said you need to add the fields you want to populate into the form, usually the ID are not visible but you need to send them.
<form th:object="${despesa}" method="POST" th:action="#{/despesas/save}">
<div class="box-body">
<input type="hidden" th:field="*{id}"/>
...
</form>
Then in your Controller you will be able to retrieve the id value, null if its a creation, or informed if its an update