1

I would like to trigger Spring MVC controller method from Thymeleaf view without returning anything from the method and without refreshing the page.

I have the following HTML page:

enter image description here

<!-- 'block item' button code--> <div class="row"> <form method="get" th:action="@{'/items/block/' + ${item.itemId}}"> <button type="submit" class="btn btn-warning">block item</button> </form> </div> 

The related controller looks as:

@GetMapping("/items/block/{itemId}") @ResponseStatus(value = HttpStatus.OK) public void blockItem(@PathVariable String itemId) { itemService.blockItem(itemId); } 

The itemService.blockItem(itemId) call just changes the corresponding boolean attribute of the related item object in database.

Currently every time when I trigger this method by pressing block item button I am being redirected to the URL like http://localhost:8080/item/block/7C4A3744375523.

I would like not to do any redirection but just execute the related service method logic. I also need to remove the related item div from the view but this I can do pretty straightforward with JavaScript.
How can I do it? Thanks.

2
  • This is a pretty straightforward case for AJAX... use a javascript method to call the url when you click the button (instead of a form). Commented May 9, 2021 at 14:19
  • You need to use JavaScript to call the URL (for example: stackoverflow.com/questions/29775797/fetch-post-json-data), or go with something like HTMX (htmx.org) Commented May 10, 2021 at 11:01

2 Answers 2

1
+50

Your button is defined as type submit.

As a consequence, when you click it the form is submitted.

To prevent that default behavior you need several things: the main idea is to define a handler for your form submit event and use Javascript to perform the actual server invocation.

You can do it in different ways, although I think it is preferable to use an event handler to define the desired behavior and separate your HTML code from Javascript as much as possible.

In addition, you seem to be iterating over a list of items.

With those two things in mind consider the following code snippet:

<script> // We use the DOMContentLoaded event (https://developer.mozilla.org/en-US/docs/Web/API/Document/DOMContentLoaded_event) // but please, register the form event as you consider appropriate document.addEventListener('DOMContentLoaded', () => { // Register form submit event handler for every form in your page const forms = document.querySelectorAll('form'); if (forms) { forms.forEach(f => { let action = f.action; f.addEventListener('submit', (event) => { // prevent default behavior, i.e., form submission event.preventDefault(); // perform server side request. you can use several options // see https://developers.google.com/web/updates/2015/03/introduction-to-fetch, for instance // For example, let's use fetch (https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch) fetch(action) .then((response) => { if (!response.ok) { throw new Error('Network response was not ok'); } console.log('Request successfully completed'); }) .catch((error) => { console.error('There has been a problem while contacting server:', error); }); }); }); } }); </script> 

In fact, you can get rid of the forms. Please, consider a slightly different approach. First, when iterating over your items, generate the following HTML blocks (you can use a data or any custom attribute to store the current processed item id if you prefer to):

<!-- 'block item' button code--> <div class="row"> <button type="button" class="btn btn-warning item-button" th:id="${item.itemId}">block item</button> </div> 

Now, in a similar way as described above, register handlers for every item button:

<script th:inline="javascript"> document.addEventListener('DOMContentLoaded', () => { // Register handlers for every button const buttons = document.querySelectorAll('.item-button'); if (buttons) { buttons.forEach(b => { b.addEventListener('click', (event) => { let itemId = b.getAttribute('id'); let link = /*[[@{/items/block/}]]*/ 'link'; // Perform server side request fetch(link + itemId) .then((response) => { if (!response.ok) { throw new Error('Network response was not ok'); } console.log('Request successfully completed'); }) .catch((error) => { console.error('There has been a problem while contacting server:', error); }); }); } } }); </script> 
Sign up to request clarification or add additional context in comments.

6 Comments

Thanks - I think this is really interesting approach, but there is something non-compilable in your <!-- 'block item' button code--> example, did you mean something like below in the Thymeleaf template: <button type="button" class="btn btn-warning item-button" th:id="${item.itemId}/>">block item</button>? Could you please clarify?
You are welcome @DimaSan. Yes, exactly. Sorry, I normally use the JSLT standard taglib for attribute processing, but yes, it should be equivalent. I updated the answer with thymeleaf specific implementation. Please, sorry again.
@DimaSan I simplified the code to be dependent only on the button id attribute. I hope it helps.
Thank you for your edit, it calls JS function but because of line fetch('<c:url value="/items/block/"/>' + itemId) - in Chrome console, it failed to load resource, and I got 404 error. I changed it to fetch('/items/block/' + itemId), now it is not complaining but nothing neither happens and controller is not triggered.
Sorry, again JSLT. Please, can you try the updated answer? Honestly, I never tried inlined javascript with thymeleaf, but I think it should work. Please, see the accepted answer in this helpful SO question.
|
1

It's not a Thymeleaf or Spring MVC that causes redirection but submit button in your form tag. You need to overwrite the behaviour of this button to send the form data through Ajax and handle the response by yourself (basically - ignore it) - e.g. using jQuery it could look like:

<!-- 'block item' button code--> <div class="row"> <form id="someFormId" method="get" th:action="@{'/items/block/' + ${item.itemId}}"> <button type="submit" class="btn btn-warning">block item</button> </form> </div> //... $('#someFormId').submit(function(e) { e.preventDefault(); $.ajax({ type: 'GET', url: '@{'/items/block/' + ${item.itemId}}', data: $(this).serialize(), // here you can define some actions for beforeSend, success, etc... // for example to just ignore: success: function(){} }); }) 

There are many ideas to solve this issue. Please read e.g.

3 Comments

Thank you for your response, but when using your approach the GUI still tries to redirect me to the URL like http://localhost:8080/items/block/44A13B4122343A which does not exist. Maybe there is something I need to change on the controller side as well?
in my answer I missed id attribute of form tag - probably this is why it's not working
anyway thank you, I believe your answer is very useful as well

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.