Goal: Make my web application react whenever a specific list changes. These changes updates a thymeleaf < p> html element.
Problem: Without an overkill solution, this element should auto update its text with the list size whenever the list size changes.
MRE
The list is a JpaRepository list or a SimpUserRegistry user list (as my application extensively uses websocket as we are going to see in the MRE) respectivaly:
List<User> findByConnectionNotNull()
simpUserRegistry.getUserCount() // return an int of the webSocket connected clients
Thyemeleaf endpoint:
private final SimpUserRegistry simpUserRegistry;
private final UserRepository userRepository;
@GetMapping("/")
public String onConnectedUsersAmmountChanged(Model model) {
// any of the two implementations are acceptable, but only one should be used
model.addAttribute("message", "Connected clients: " + simpUserRegistry.getUserCount());
model.addAttribute("message", "Connected clients: " + userRepository.findByConnectionNotNull().size());
return "hello";
}
Also, whenever an user connect or disconnect, a SessionEvent is triggered (that could be useful as WebFlux might not even be necessary here)
@EventListener
public void onDisconnectEvent(SessionDisconnectEvent event) {
log.warn("Client with username {} disconnected", event.getUser().getName());
}
@EventListener
public void onConnectedEvent(SessionConnectEvent event) {
log.warn("Client with username {} connected", event.getUser().getName());
}
Additional info: reloading the page refreshes the UI, logically with the correct user connections amount. I have even tried to directly call for the end point by evoking the controller, no success. Also tried to make a RestTemplate request passing the attributes as argument (just as the endpoint do with addAttribute) inside the event listeners as it automatically updates the user connections but nothing works. It should be simple but I simply can't make the changes reflect in UI.
no WebFlux MRE?
Every WebFlux tutorial like this one don't use JpaRepository and it's not nearly similar to what I need, considering the complexity to make it work. I know it do its job, but every resource I found is just overkill.
After investigating the issue, I came up with a solution:
@Service
@RequiredArgsConstructor
public class ReactiveUserServiceImpl {
private final SimpUserRegistry simpUserRegistry;
public Flux<Integer> refreshConnectedUsersAmmount() {
return Flux.fromStream(Stream.generate(simpUserRegistry::getUserCount)).delayElements(Duration.ofSeconds(1));
}
}
Then I added a new endpoint to my controller:
@Controller
@RequiredArgsConstructor
public class HomeController {
private final ReactiveUserServiceImpl reactiveUserService;
@GetMapping(value = "/", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Publisher<Integer> refreshConnectedUsersAmmount() {
return reactiveUserService.refreshConnectedUsersAmmount();
}
}
And added this code to my html file:
<div class="container">
<div id="update">
<h1>Connected clients</h1>
<h2>amount</h2>
</div>
</div>
<script>
window.addEventListener('load', function (e) {
const update = document.getElementById('update');
const h2 = update.querySelector('h2');
const es = new EventSource('http://localhost:8080/');
es.addEventListener('message', function (msg) {
h2.textContent = msg.data;
})
})
</script>