I have a Spring + Thymeleaf project. I have a fragment for the header part and some pages which include the header.
Below is the (essential part of) the header fragment. As you can see it includes the activeUserWorkgroup model object
dashboard-header.html
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<body>
<header th:fragment="dashboard-header" class="header">
....
<div class="navbar-right">
<ul class="nav navbar-nav">
<li class="dropdown notifications-menu">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
<span class="user-label" style="margin-left:5px;"
th:text="${activeUserWorkgroup.getWorkgroupLabel()}">
</span>
</a>
</li>
</ul>
</div>
</nav>
</header>
</body>
</html>
In every Spring view where I need the header fragment I add the line
<div th:replace="fragments/dashboard-header :: dashboard-header"></div>
The problem is that in every Controller of every View in which I need this header, I have to give the proper value to the activeUserWorkgroup model object. This is both uncomfortable and error prone.
Is there any way to let Spring give the proper model value to the activeUserWorkgroup object without having to write the code in every Controller?
Thank you
This probably what you are looking for. What I would do is create controller that return just the Model object activeUseWorkgroup. Then have all the other controllers extend it.
#Controller
public ControllerA
{
#ModelAttribute("activeUserWorkgroup")
public Object getActiveUserWorkgroup()
{
return new Object();
}
}
#Controller
public ControllerB extends ControllerA
{
}
Maybe is better to create a ControllerAdvice, read the documentation at this link http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/bind/annotation/ControllerAdvice.html
#ControllerAdvice
public class GlobalControllerAdvice extends ResponseEntityExceptionHandler {
#ModelAttribute("activeUserWorkgroup ")
public String getActiveWorkgroup() {
//YOUR CODE
}
}
Related
I would like to make an api that could accept two Path variables and one of them could be optional.
In this article they said that we can achieve this by using Optional, but this is not working.
here is my Controller
#GetMapping("/users/{dateDu}/{dateAu}")
public ResponseEntity<List<User>> getAllUsersByDate(
#PathVariable LocalDate dateDu,
#PathVariable(required = false) Optional<LocalDate> dateAu,
#org.springdoc.api.annotations.ParameterObject Pageable pageable
) {
if(dateAu.isEmpty()){
dateAu = Optional.of(LocalDate.now());
}
Page<User> page = userRepository.getUsersByDate(dateDu, dateAu.get(), pageable);
HttpHeaders headers = PaginationUtil.generatePaginationHttpHeaders(ServletUriComponentsBuilder.fromCurrentRequest(), page);
return ResponseEntity.ok().headers(headers).body(page.getContent());
}
in this image of swagger the two parameters are required even if the second one has Optional
Since I generated my spring boot app with JHipster, on postman when I don't give a the value of the Optional parameter. I got this error
<!DOCTYPE html>
<html class="no-js" lang="fr" dir="ltr">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title></title>
<meta name="description" content="Description for " />
<meta name="google" content="notranslate" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
<meta name="theme-color" content="#000000" />
<link rel="icon" href="favicon.ico" />
<link rel="manifest" href="manifest.webapp" />
<link rel="stylesheet" href="content/css/loading.css" />
<!-- jhipster-needle-add-resources-to-root - JHipster will add new resources here -->
<base href="/">
</head>
<body>
<!--[if lt IE 9]>
<p class="browserupgrade">
You are using an <strong>outdated</strong> browser. Please upgrade your browser to improve
your experience.
</p>
<![endif]-->
<div id="root">
<div class="app-loading">
<div class="lds-pacman">
<div>
<div></div>
<div></div>
<div></div>
</div>
<div>
<div></div>
<div></div>
<div></div>
</div>
</div>
</div>
<div class="app-loading">
<div id="jhipster-error" style="display: none">
<!-- This content is for troubleshooting purpose and will be removed when app renders -->
<h1>An error has occurred :-(</h1>
<h2>Usual error causes</h2>
<ol>
<li>
You started the application from an IDE and you didn't run
<code style="color: red">npm start</code> or
<code style="color: red">npm run webapp:build</code>.
</li>
<li>
You had a network error while running <code style="color: red">npm install</code>. If you are
behind a corporate proxy, it is
likely that this error was caused by your proxy. Have a look at the JHipster error logs, you
will probably have the cause of
the error.
</li>
<li>
You installed a Node.js version that doesn't work with JHipster: please use an LTS (long-term
support) version, as it's the
only version we support.
</li>
</ol>
<h2>Building the client side code again</h2>
<p>If you want to go fast, run <code style="color: red">./mvnw</code> to build and run everything.</p>
<p>If you want to have more control, so you can debug your issue more easily, you should follow the
following steps:</p>
<ol>
<li>Install npm dependencies with the command <code style="color: red">npm install</code></li>
<li>
Build the client with the command <code style="color: red">npm run webapp:build</code> or
<code style="color: red">npm start</code>
</li>
<li>Start the server with <code style="color: red">./mvnw</code> or using your IDE</li>
</ol>
<h2>Getting more help</h2>
<h3>If you have a question on how to use JHipster</h3>
<p>
Go to Stack Overflow with the
<a href="http://stackoverflow.com/tags/jhipster" target="_blank"
rel="noopener noreferrer">"jhipster"</a> tag.
</p>
<h3>If you have a bug or a feature request</h3>
<p>
First read our
<a href="https://github.com/jhipster/generator-jhipster/blob/main/CONTRIBUTING.md" target="_blank"
rel="noopener noreferrer">contributing guidelines</a>.
</p>
<p>
Then, fill a ticket on our
<a href="https://github.com/jhipster/generator-jhipster/issues/new/choose" target="_blank"
rel="noopener noreferrer">bug tracker</a>, we'll be happy to resolve your issue!
</p>
<h3>If you want to chat with contributors and other users</h3>
<p>
Join our chat room on
<a href="https://gitter.im/jhipster/generator-jhipster" target="_blank"
rel="noopener noreferrer">Gitter.im</a>. Please note
that this is a public chat room, and that we expect you to respect other people and write in a
correct English language!
</p>
<!-- end of troubleshooting content -->
</div>
</div>
</div>
<noscript>
<h1>You must enable JavaScript to view this page.</h1>
</noscript>
<script type="text/javascript">
// show an error message if the app loading takes more than 4 sec
window.onload = function () {
setTimeout(showError, 4000);
};
function showError() {
var errorElm = document.getElementById('jhipster-error');
if (errorElm && errorElm.style) {
errorElm.style.display = 'block';
}
}
</script>
<!-- uncomment this for adding service worker
<script>
if ('serviceWorker' in navigator) {
window.addEventListener('load', function() {
navigator.serviceWorker.register('/service-worker.js')
.then(function () {
console.log('Service Worker Registered');
});
});
}
</script>
-->
<!-- Google Analytics: uncomment and change UA-XXXXX-X to be your site's ID.
<script>
(function(b,o,i,l,e,r){b.GoogleAnalyticsObject=l;b[l]||(b[l]=
function(){(b[l].q=b[l].q||[]).push(arguments)});b[l].l=+new Date;
e=o.createElement(i);r=o.getElementsByTagName(i)[0];
e.src='//www.google-analytics.com/analytics.js';
r.parentNode.insertBefore(e,r)}(window,document,'script','ga'));
ga('create','UA-XXXXX-X');ga('send','pageview');
</script>-->
<script defer src="app/vendors.bundle.js"></script>
<script defer src="app/main.bundle.js"></script>
</body>
</html>
I would appreciate if any one could help me to understand why this is happening and/or how I could solve it. Thank you in advance.
This is a composite question. I found some issues in your question.
First, you should change your path to
#GetMapping(value = {"/users/{dateDu}", "/users/{dateDu}/{dateAu}"})
Otherwise you will get 404 error if you don't provide dateAu in your request.
Second, you should add #DateTimeFormat(iso = DateTimeFormat.ISO.DATE) to your parameters to tell Spring how to convert String to LocalDate. Otherwise you will get a Bad Request with error message.
Failed to convert value of type 'java.lang.String' to required type 'java.time.LocalDate'
You didn't notice this issue because Swagger have already handled it for you, I guess.
I try to fix your code:
#GetMapping(value = {"/users/{dateDu}", "/users/{dateDu}/{dateAu}"})
public ResponseEntity<List<User>> getAllUsersByDate(
#PathVariable #DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate dateDu,
#PathVariable(required = false) #DateTimeFormat(iso = DateTimeFormat.ISO.DATE) Optional<LocalDate> dateAu,
#org.springdoc.api.annotations.ParameterObject Pageable pageable) {
if (dateAu.isEmpty()) {
dateAu = Optional.of(LocalDate.now());
}
Page<User> page = userRepository.getUsersByDate(dateDu, dateAu.get(), pageable);
HttpHeaders headers = PaginationUtil
.generatePaginationHttpHeaders(ServletUriComponentsBuilder.fromCurrentRequest(), page);
return ResponseEntity.ok().headers(headers).body(page.getContent());
}
I think it should be work if you use postman to test.
Third, according to this post, all of #PathVariable MUST be required in Swagger. It means your question is nothing to do with combination of #PathVariable and Optional in Java. It's a spec/limitation of Swagger. Thus, if you really want to use Swagger to test your API, you have 2 choices:
Split it to 2 endpoints, as #tucuxi said.
Use QueryString style instead.
Last, it seems like you probably don't need an Optional. Optional is designed to be used as a return type in most case. But this is a controversial design issue. So, if you really want to use Optional, you can follow the best practice described by this excellent article
Maybe try adding the 2 paths to the #GetMapping?
#GetMapping("/users/{dateDu}", "/users/{dateDu}/{dateAu}")
Not sure if that solves your problem, but you should probably remove the false flag in the annotation of your param:
#PathVariable Optional<LocalDate> dateAu and add both paths: #GetMapping(value = {"/users/{dateDu}", "/users/{dateDu}/{dateAu}"}).
Java/Spring Boot web app here using Thymeleaf as the templating engine.
My bean:
public class InventoryItem {
private String modelNumber;
private String name;
// getters, setters and ctors omitted for brevity
}
My Spring controller:
#Controller
#RequestMapping("/inventory")
public class InventoryController {
#GetMapping("/{inventoryId}")
public String viewInventory(#PathVariable("inventoryId") String inventoryId, Model model) {
List<InventoryItem> inventory = getSomehow(inventoryId);
model.addAttribute("inventory", inventory);
return "inventory";
}
}
And a snippet from the inventory.html file that Thymeleaf must template:
<div class="col-md-4 mt-5">
<div class="panel-body">Inventory Items</div>
<ul>
<li th:each="item :${inventory}" th:text="${item.name}"></li>
</ul>
</div>
At runtime this produces a nice unordered list of inventory item names.
What I want now is to make this an unordered list of hyperlinks (<a/>) such that the rendered HTML looks like so:
<ul>
<li>Goose</li>
<li>Duck</li>
<!-- etc. -->
</ul>
Where 12345 and 23456 are InventoryItem#modelNumbers and where Goose and Duck are InventoryItem#names. I've asked the Google Gods high and low and cannot find a working example of using Thymeleaf to render a list (ordered/unordered alike) of hyperlinks. Any ideas?
Something like this would work ...
<div class="col-md-4 mt-5">
<div class="panel-body">Inventory Items</div>
<ul>
<li th:each="item :${inventory}">
<a th:href="#{/inventoryDetails/{modelNumber}(modelNumber=${item.modelNumber})}" th:text="${item.name}">Goose</a>
</li>
</ul>
</div>
If you would like to get more information on how to work with Link URLs, follow Thymeleaf documentation.
Hi Guys!
I have been implementing service in Spring Boot which
allows users to send anonymouse questionaries to server.
I have already implemented most of the backend like adding users etc. and right now I have been struggling with one action which take answers from user and sends into server (save in database).
Object containing answers (filledSurvey) is being sent as empty. In this same logic in logging users fields from form are corectly send forward.
This endpoint displays questionary:
#RequestMapping(path = {"/try", "/try/{id}"})
public String tryCompletingSurvey(Model model, #PathVariable("id") Long id) {
Connection connection = connectionService.getConnection(id);
FilledSurvey filledSurvey = connection.getSurvey().getTemplate();
for (FilledQuestion filledQuestion : filledSurvey.getFilledQuestions()) {
filledQuestion.getFilledAnswers().get(0).setCheck(true);
}
model.addAttribute("filledSurvey", filledSurvey);
return "completing/completing";
}
This is thymeleaf html:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Completing survey</title>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css">
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.4.1/css/all.css">
</head>
<body>
<center>
<form action="#" th:action="#{/user/surveys/finish}" th:object="${filledSurvey}" method="post">
<!-- <div th:each="question, questionStat : ${survey.getFilledQuestions()}" >-->
<!-- <p th:text="${question.getQuestion()}"></p>-->
<!-- <div th:each="answer, answerStat: ${question.getFilledAnswers()}" >-->
<!-- <input type="radio"-->
<!-- th:name="question+${questionStat.index}"-->
<!-- th:field="*{}"-->
<!-- th:value="${true}">-->
<!-- <label th:text="${answer.answer}">-->
<!-- </label>-->
<!-- </div>-->
<!-- </div>-->
<h2>Survey name: </h2>
<h3 th:text="${filledSurvey.getSurveyName()}"></h3>
<h2>Number of questions: </h2>
<h3 th:text="${filledSurvey.filledQuestions.size()}"></h3>
<div class="col-md-6">
<input type="submit" style="align-content: center" class="btn btn-primary" value=" Send ">
</div>
</form>
</center>
</body>
</html>
And this is endpoint which stores empty object from thymeleaf:
#RequestMapping(path = "/finish", method = RequestMethod.POST)
public String getHash(FilledSurvey filledSurvey) {
StringBuilder sb = new StringBuilder();
for (FilledQuestion question : filledSurvey.getFilledQuestions()) {
for (FilledAnswer answer : question.getFilledAnswers()) {
if (answer.isCheck()) sb.append(answer.getAnswer());
}
}
LocalDateTime date = LocalDateTime.now();
sb.append(date);
String hash = sb.toString();
hash = Base64.getEncoder().encodeToString(sb.toString().getBytes());
filledSurvey.setHash(hash);
surveyMagazinService.addSurveyToMagazin(filledSurvey);
return "completing/finish";
}
I changed code to automaticly mark answers for now.
This the picture of the object in the next endpoint:
filledSurvey object
I am aware that this is common question but i have been looking for the answer for a while now and couldn't figure it out. I have no errors in the console as well. I would appreciate any help or feedback.
If I understood correctly, I see following issue:
You are using a form to submit the survey data and use the th:object="${filledSurvey}" to bind the data. But there is actually not data send back to the controller, when the form is submitted, because there are no input fields defined that have the th:field attribute applied.
The request that will be send to the server on a submit, will contain form encoded data of all fields that you assign a th:field attribute to. The controller will map the form encoded data to a FilledSurvey object using java bean convention in the getHash method.
EDIT: can you try adding the #ModelAttribute annotation:
#RequestMapping(path = "/finish", method = RequestMethod.POST)
public String getHash(#ModelAttribute FilledSurvey filledSurvey) {
...
Try adding an input field like this inside your form:
<input type="hidden" th:field="*{surveyId}" >
This should give you at least an FilledSurvey object with the id set on your "/finish" endpoint. You can then use the id to fetch the survey like its done in the first code snippet.
The way you are using the th:field within your list of questions will not work, because spring cannot map this kind of structure. See https://spring.io/guides/gs/handling-form-submission/ to understand how form submission works with spring mvc.
I hope this helps a bit, best regards ;)
I am working on a Car Dealership project utilizing:
Java 8
Spring boot
Thymeleaf
AWS mySQL database
my current problem is that i an having trouble using my nav bar to move between html pages.
i have 5 html pages( index.html, customer.html, employee.html, vehicle.html, and transaction.html)
code from index.html:
<a class="active" th:href="#{/index.html}"><i class="(Put css class here)"></i> Home</a>
<a th:href="#{/vehicle.html}"><i class="(Put css class here)"></i> Vehicles </a>
<a th:href="#{/customer.html}"><i class="(Put css class here)"></i> Customers </a>
<a th:href="#{/employee.html}"><i class="(Put css class here)"></i> Employees </a>
<a th:href="#{/transaction.html}"><i class="(Put css class here)"></i> Transactions </a>
code from IndexController:
#Controller
public class IndexController {
#RequestMapping("/index")
public String index(){
return "index";
}
}
the page loads normally at localhost:8088 when first opened, but when i click "home" or any other button i get a 404.
if anyone could simply point me in a general direction i would greatly appreciate it.
Instead, try to make GetMapping requests in the controller for those different links.
Example:
#GetMapping("/customer")
public String getCustomer(){
return "customer.html";
}
And in your html class (nav bar):
<a th:href="#{/customer}"><i class="(Put css class here)"></i> Customers </a>
*Make sure to remove the .html, but this depends on how you've configured it in your config, but I believe this is the default configuration.
I am trying to get a list of objects that are sent to the server from the html form as parameters for my list, then I will loop through those entries and then return them through the springboot th:each. But it doesn't seem to be working at all. On load the form appears but when I enter a value in it, then it returns an error page and the URL however turns:
http://localhost:8080/#%7B/%7D?%24%7Bcontent%7D=hello
this output in eclipse says:
Expression "content" is not valid: only variable expressions ${...} or selection expressions *{...} are allowed in Spring field bindings
Note: content here is the value property in my form.
My controller looks like this:
#Controller
public HelloList() {
this.addUs = new ArrayList <>();
}
#RequestMapping("/")
public String getlist(#RequestParam (required = false) String content, Model model) {
if (content != null && !content.trim().isEmpty()) {
this.addUs.add(content);
}
model.addAttribute("list",addUs);
return "index";
}
the index.html looks like this
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>
<title>Insert title here</title>
</head>
<body>
<div>
<ul>
<li th:each="amHere: ${addUs}">
<span th:text="${amHere}">hello! world</span>
</li>
</ul>
<form action="#{/}" method="GET">
<input type="text" name="content"/>
<input type="submit" value="Submit"/>
</form>
</div>
</body>
</html>
This might be a duplicate but it seems like most of the solutions I came across are not helping. So any help is mostly appreciated. Thank you in advance.
Turns out I was missing the initialization of my list in the constructor. I initialized the list by adding a value to it first in the constructor like this.
this.addUs.add("Hello World");
Because the #RequestMapping is mapped to the home path in my case index.html, any request gets sent there automatically.
working example
action = "#{/}" should be th:action="#{/}". That's the reason you're seeing the weird url (because it's url encoding #{/}). Thymeleaf only evaluates expressions that start with th:.
I'm not sure about the other error. It looks like the html you've pasted doesn't match up with the error you are getting.
If you urldecode http://localhost:8080/#%7B/%7D?%24%7Bcontent%7D=hello, you get http://localhost:8080/#{/}?${content}=hello, which doesn't line up with your form.