How to add an element id to the url of the Thymeleaf template in the controller in Spring - spring

I have a Spring Application and Server Side Rendering with Thymeleaf as Templating language.
A button sends a get or post request to the controller in Spring, which puts some message to the view, which is rendered into the HTML file and send back to the client. The message should be optional. Thats why the template must also be able to be called without the message.
Next i want the client browser to scroll down to the part of the page where this message is rendered into, which is normally very easy. You would just have to append the id of the element to the url like following example.
https://stackoverflow.com/#footer
In this example the browser scrolls down to the footer of the page.
Below is what i tried. Unfortunately it doesnt't work like that. Spring/Thymeleaf tries to find a index#messagebox template which can't be found. Hence a Whitelabel Error Page error is thrown/shown.
Page.html
<section>
<h2>Form to send request</h2>
<form action="showmessage" method="get">
<input type="submit" value="Click for message">
</form>
</section>
Controller.java
#GetMapping("showmessage")
public ModelAndView showMessage(){
return new ModelAndView("index#messagebox",Map.of("optionalmessage","Some message that is optioal"));
}
src/main/resources/templates/index.html
<body>
<h1>Index Page</h1>
<div id="messagebox" th:fragment="message" th:with="optionalmessage=${optionalmessage}">
<p th:if="${optionalmessage!=null}">[[${optionalmessage}]]</p>
</div>
</body>

The problem can be solved with Flashmessages and Redirects. The html basically keeps the same. If the message attribute is set, you render it.
src/main/resources/templates/index.html
<div th:if="${msg}">
<div class="message" >
<p>[[${msg}]]</p>
</div>
</div>
The most important changes had to be made in the controller. First a parameter of type RedirectAttributes is added to the Controller that handles the request.
If wanted the message is added with the RedirectAttributes.addFlashAttribute function as shown below. Finally a redirect is returned, which contains the needed tag. A second controller is also needed that handles the Get Request of the Redirect with a Model as input parameter and returns the needed Template. The #tag is simply passed throuhg to the client browser.
Controller.java
#GetMapping("showMessage")
public String postNotification(RedirectAttributes redirectAttributes) {
redirectAttributes.addFlashAttribute("optinalMessage", "Hello I am an optional message");
return "redirect:/index#footer";
}
#GetMapping("index")
public String getIndex(Model model) {
return "index";
}

You add id in URL by using ?id=value.
And in controller #RequestMapping("/path/{id}")
to access that variable #PathVariable

Related

Pass data from Thymeleaf template to springboot controller

I have simple web application written using Springboot and Thymeleaf templates. Report controller receives the data from form and builds the TestPlanReportResponse object which is added as model attribute like this:
#PostMapping("/report")
public String homeSubmit(#ModelAttribute HomeFormInput homeFormInput, Model model, Errors errors) {
final TestPlanReportResponse response = new TestPlanReportResponse(homeFormInput);
model.addAttribute("allData", response);
return "charts";
}
I can work with that data in "charts" thymeleaf template and show the data I need, but I need to send exactly the same object back to controller when button is clicked, but i getting TestPlanReportResponse
object as parameter with nulls set.
#PostMapping("/report/send")
public String sendReport(#ModelAttribute TestPlanReportResponse reportData, Model model) {
//reportData contains just nulls
}
Here is how my button is set in charts template:
<form action="#" th:action="#{/report/send}" th:object="${allData}" method="post">
<button type="submit">Send the report</button>
</form>
So my question is how to send the object back from thymeleaf template? Should i create a hidden input and put there the "allData" object just to send it back? It looks for me like dirty hack. What would be the appropriate way to pass data back? I want to have this app stateless so don't to store the data on a server side.
When I used to work with Spring and Thymeleaf and form, we had the same issue, passing the data back and forth between a form, the template, and different controllers.
And what you suggest is what we did, we used hidden input as dirty as it may look,it was the standard suggested answer, we did not find anything better.
You need to create an input, with a type a value and link it to a field, like this:
<form action="#" th:action="#{/report/send}" th:object="${allData}" method="post">
<input type="hidden" th:value="*{allDataValue1}" th:field="*{allDataField1}" />
//Do this for all your attributes/values that you wish to pass to the controller
<button class="btn btn-info btn-lg btn-block" type="submit">Send the report</button>
</form>
Though, i found this answer, you can try looking into this thread

Create a fragment for a form in Thymeleaf (and spring)

First I created a form in a simple HTML with Thymeleaf and everything was fine. Then moved to step 2 and I relocated my form to a fragment. I called the fragment like this:
<div th:insert="~{address::form($(address)}"></div>
and the fragment (which is in address.html) is like this:
<form th:fragment="form(address)" action="#" th:action="#{/address}" th:object="${address}" method="post">
<label>Street<label><span><input type="text" th:field="*{street}"></span>
<label>County:</label>
<span>
<select th:field="*{county}">
<option th:each="s : ${countyList}" th:value="${s.value}" th:text="${s.text}"></option>
</select>
</span>
...
but this is not working and it's throwing:
java.lang.IllegalStateException: Neither BindingResult nor plain target object for bean name 'street' available as request attribute
Please help with how to move an entire form into a Thymeleaf fragment? Also please add ideas for combos, so I can get their list from the model bean. Thank you!
PS the controller looks like this for now (add and edit ) buti will work to make it 1 function instead:
#GetMapping(value="/address")
public String newAddress(AddressModel address, Model model) {
model.addAttribute("address", address);
model.addAttribute("countyList", countyService.listCombo());
...
return "index";
}
#GetMapping(value="/address/{id}")
public String editAddress(Model model, #PathVariable("id") Long id) {
AddressModel address = addressService.load(id);
model.addAttribute("address", address);
model.addAttribute("countyList", countyService.listCombo());
...
return "index";
}
In my case the problem was coming from an unexpected direction. I have put 2 fragments address::form (for creating the form) and address::list (for showing a list of addresses) in the same file and somehow, for some yet unknown reason these 2 fragments are not working fine when in the same file. And the error was not very helpful. But as soon as I put them in 2 separate files like this: addressForm::form and addressList::list everything was ok. Interesting that if I keep the fragments in the same file but swap the order in which they are defined I can render the page correctly but I still get the error in the logs. Strange things.

Spring: redirect from POST method doesn't work

I'm trying to get my Spring controller recieve POST request. I want to get the body of post request and show it on web page. I send requests via Postman. My controller receives it, but somehow, when I try to redirect to another page as usual, Spring renders the html template and sends it back to Postman.
My code:
#GetMapping("/webhooks")
public String webhooks(Model model) {
model.addAttribute("response", "webhooks");
return "connected";
#PostMapping("/webhooks")
public String webhooks(String payload, Model model) {
model.addAttribute("response", payload);
return "connected";
}
My connected.html template:
<body>
<a href="/" >Home</a>
<h3>Connected!</h3>
<div>
<button onclick="refreshToken()">Refresh Token</button>
<br /><br />
<button onclick="newCustomer()">Create new customer</button>
<button onclick="invoice()">Create new invoice</button>
<br />
<div><code id="result" th:text="${response}"></code></div>
</div>
</body>
This is what I see in Postman window:
But I expect to see the same form (as on the picture) in my browser and not in Postman. I want to redirect to this page and not to send this page back to Postman. I have a few similar methods in other controllers which are completely identical, but they work correctly.
What am I doing wrong? Thank you in advance.
I have the same issue as you have, I tried different ways to fix this problem but none of them worked for me. But you could give it a try:
#GetMapping("/webhooks")
public RedirectView webhooks(Model model) {
//Do what you want
return new RedirectView("/connected");
#PostMapping("/webhooks")
public RedirectView webhooks(String payload, Model model) {
//Do what you want
return new RedirectView("/connected");
}
This didn't work out for me but it might help you out. You use the RedirectView class from Spring and enter the URL to the endpoint.
Please let me know if it worked for you!

Simple AJAX example with Grails

I'm pretty new to Grails and I'm trying to learn how AJAX works in Grails. For this I'm trying to modify the multiply AJAX example from here so that my application displays the input string with AJAX on the page. (To clarify this: The user enters for example "foo" and the page should display "foo" under the search field)
This is my code so far:
My index.gsp template which contains an input form and shall display the string which is typed in the form:
<!doctype html>
<html>
<head>
</head>
<body>
<div id="search">
<g:render template="searchForm"/>
</div>
<div id="results">
<g:render template="searchResultForm"/>
</div>
</body>
</html>
My _searchForm.gsp template which includes the search form:
<g:form>
<label for="suchen"></label><g:textField name="suchen"/>
<g:submitToRemote url="[controller:'search', action:'search']" update="results" value="Suchen"/>
</g:form>
My _searchResultForm.gsp template which shall display all results (in my case just the search string)
${results}
My SearchController which shall get the search string and return the same string:
class SearchController {
...
def search(String s) {
return s
}
}
My problem is that after I write something into the input form and press the send button, nothing happens. No error but it also don't displays the input string under the input form.
You may wonder why I'm trying to do this with AJAX: My purpose is to realize an AJAX search. When the AJAX part works it shouldn't be a problem to add the search logic to the controller.
You should show your searchResultForm template. But I guess your problem is you don't send the model correctly to the gsp, and you haven't set the gsp in the response also
If this is your template gsp:
<div>${s}</div>
You action have to be:
def search(String s) {
render template: 'searchResultForm', model:[s:s]
}
By this way, the action generates the html to be sent with the given model, which is rendered by jquery into the div.

Spring MVC 3.2 Thymeleaf Ajax Fragments

I'm building application with Spring MVC 3.2 and Thymeleaf templating engine. I'm a beginner in Thymeleaf.
I have everything working, including Thymeleaf but I was wondering if anyone knows of a simple and clear toturial on how to do simple Ajax request to controller and in result rendering only a part of a template (fragment).
My app has everything configured (Spring 3.2, spring-security, thymeleaf, ...) and works as expected. Now I would like to do Ajax request (pretty simple with jQuery but I don't wan't to use is since Thymeleaf in its tutorial, chapter 11: Rendering Template Fragments (link) mentiones it can be done with fragments.
Currently I have in my Controller
#RequestMapping("/dimensionMenuList")
public String showDimensionMenuList(Model model) {
Collection<ArticleDimensionVO> articleDimensions;
try {
articleDimensions = articleService.getArticleDimension(ArticleTypeVO.ARTICLE_TYPE);
} catch (DataAccessException e) {
// TODO: return ERROR
throw new RuntimeException();
}
model.addAttribute("dimensions", articleDimensions);
return "/admin/index :: dimensionMenuList";
}
the part of the view where I would like to replace <ul></ul> menu items:
<ul th:fragment="dimensionMenuList" class="dropdown-menu">
<li th:unless="${#lists.isEmpty(dimensions)}" th:each="dimension : ${dimensions}">
</li>
</ul>
Any clue is greatly appreciated. Especially if I don't have to include any more frameworks. It's already too much for java web app as it is.
Here is an approach I came across in a blog post:
I didn't want to use those frameworks so in this section I'm using jQuery to send an AJAX request to the server, wait for the response and partially update the view (fragment rendering).
The Form
<form>
<span class="subtitle">Guest list form</span>
<div class="listBlock">
<div class="search-block">
<input type="text" id="searchSurname" name="searchSurname"/>
<br />
<label for="searchSurname" th:text="#{search.label}">Search label:</label>
<button id="searchButton" name="searchButton" onclick="retrieveGuests()" type="button"
th:text="#{search.button}">Search button</button>
</div>
<!-- Results block -->
<div id="resultsBlock">
</div>
</div>
</form>
This form contains an input text with a search string (searchSurname) that will be sent to the server. There's also a region (resultsBlock div) which will be updated with the response received from the server.
When the user clicks the button, the retrieveGuests() function will be invoked.
function retrieveGuests() {
var url = '/th-spring-integration/spring/guests';
if ($('#searchSurname').val() != '') {
url = url + '/' + $('#searchSurname').val();
}
$("#resultsBlock").load(url);
}
The jQuery load function makes a request to the server at the specified url and places the returned HTML into the specified element (resultsBlock div).
If the user enters a search string, it will search for all guests with the specified surname. Otherwise, it will return the complete guest list. These two requests will reach the following controller request mappings:
#RequestMapping(value = "/guests/{surname}", method = RequestMethod.GET)
public String showGuestList(Model model, #PathVariable("surname") String surname) {
model.addAttribute("guests", hotelService.getGuestsList(surname));
return "results :: resultsList";
}
#RequestMapping(value = "/guests", method = RequestMethod.GET)
public String showGuestList(Model model) {
model.addAttribute("guests", hotelService.getGuestsList());
return "results :: resultsList";
}
Since Spring is integrated with Thymeleaf, it will now be able to return fragments of HTML. In the above example, the return string "results :: resultsList" is referring to a fragment named resultsList which is located in the results page. Let's take a look at this results page:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org" lang="en">
<head>
</head>
<body>
<div th:fragment="resultsList" th:unless="${#lists.isEmpty(guests)}" id="results-block">
<table>
<thead>
<tr>
<th th:text="#{results.guest.id}">Id</th>
<th th:text="#{results.guest.surname}">Surname</th>
<th th:text="#{results.guest.name}">Name</th>
<th th:text="#{results.guest.country}">Country</th>
</tr>
</thead>
<tbody>
<tr th:each="guest : ${guests}">
<td th:text="${guest.id}">id</td>
<td th:text="${guest.surname}">surname</td>
<td th:text="${guest.name}">name</td>
<td th:text="${guest.country}">country</td>
</tr>
</tbody>
</table>
</div>
</body>
</html>
The fragment, which is a table with registered guests, will be inserted in the results block.
Rendering only Thymeleaf fragments also works well with ModelAndView.
Your controller
#RequestMapping(value = "/feeds", method = RequestMethod.GET)
public ModelAndView getFeeds() {
LOGGER.debug("Feeds method called..");
return new ModelAndView("feeds :: resultsList");
}
Your view
<!DOCTYPE html SYSTEM "http://www.thymeleaf.org/dtd/xhtml1-strict-thymeleaf-spring4-4.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head></head>
<body>
<div th:fragment="resultsList" id="results-block">
<div>A test fragment</div>
<div>A test fragment</div>
</div>
</body>
</html>
What's actually rendered
<div id="results-block">
<div>A test fragment</div>
<div>A test fragment
</div>
</div>
As an alternate version to Sohail's great answer, I want to give a version that using javascript can send the whole th:object to the controller, integrating Thymeleaf in your forms, not having to use #PathVariable which becomes messy or not usable at all when you've forms with many fields.
For the form (using an example which returns an object which has an id and a name Strings, and feeds a combobox with a Map that has some of those objects as values) we have:
<form method="post" th:action="#{/yourMapping}" th:object="${object}" id="yourFormId">
<select th:field="*{mapOfObjects}">
<option
th:each="entry: ${mapOfObjects}"
th:value="${entry.value.id}"
th:text="${entry.value.name}" >
</option>
</select>
<p>Name:
<input type="text" th:field="*{name}" />
</p>
</form>
When this form is submited (using a button with type submit for example) the whole document will be replaced. However we can intercept this submit with javascript and do it the ajax-way. To achieve this, we will add an interceptor to our form using a function. First call the function that adds the interceptor right after the form:
<script>formInterceptor("yourFormId");</script>
And the function looks like this (place it in the head of the document or wherever suits your needs):
<script>
function formInterceptor(formName) {
var $form = $("#" + formName);
$form.on('submit', function(e) {
e.preventDefault();
$.ajax({
url : $form.attr('action'),
type : 'post',
data : $form.serialize(),
success : function(response) {
if ($(response).find('.has-error').length) {
$form.replaceWith(response);
}
else{
$("#ajaxLoadedContent").replaceWith(response);
}
}
});
});
};
</script>
Now whenever the form is submited, this function will trigger, and it will:
Prevent the original form submit
Make an ajax call using the url defined in the form's th:action
Serialize the form data. Your controller will be able to recieve this in an object
Replace the part of your html code with the returned fragment
The replaced part should look like this
<div id="ajaxLoadedContent"></div>
And the controller can recieve the th:object in the form, with it's values filled, like this (Replace Object with your object's type and "object" with a proper name):
#PostMapping(value = /yourMapping)
public String ajaxCtrlExample(#ModelAttribute("object") Object object, Model model) {
return yourFragmentPath;
}
And that's everything. Call the function that adds the interceptor after every form you need in ajax-version.

Resources