Issue
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:
src="https://i.stack.imgur.com/XZ3cqm.png" alt="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.
Solution
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>
Answered By - jccampanero
Answer Checked By - Katrina (JavaFixing Volunteer)