One benefit of server-side rendering is leveraging server exceptions to display useful information to the user.
In this lab, we will catch Exceptions using a Spring @ControllerAdvice
and show them to the user in a toast message.
Exceptions
We start by creating an InfoException base class in the de.tschuehly.easy.spring.auth.web.exception
package:
Copy package de . tschuehly . easy . spring . auth . web . exception ;
public class InfoException extends RuntimeException {
public InfoException ( String message) {
super(message);
}
}
Then we create a UserNotFoundException
that extends the InfoException in the de.tschuehly.easy.spring.auth.user
package:
Copy package de . tschuehly . easy . spring . auth . user ;
import de . tschuehly . easy . spring . auth . web . exception . InfoException ;
public class UserNotFoundException extends InfoException {
public UserNotFoundException ( String message) {
super(message);
}
}
UserService
Now we will adjust the UserService.searchUser
method to throw the UserNotFoundException
when no users are found.
Copy public List< EasyUser > searchUser( String searchString) {
List < EasyUser > easyUsers = easyUserList . stream () . filter (
it -> it . uuid . toString () . contains (searchString)
|| it . username . contains (searchString)
|| it . password . contains (
searchString)
) . toList ();
if ( easyUsers . isEmpty ()) {
throw new UserNotFoundException( "No user found with the searchString: \"" + searchString + "\"" ) ;
}
return easyUsers;
}
LayoutComponent
Now we need to create a container element for the message in the LayoutComponent
.
We first define a constant TOAST_CONTAINER_ID
:
Copy public static final String TOAST_CONTAINER_ID = "toastContainer" ;
We first import the TOAST_CONTAINER_ID
in the LayoutComponent.jte
Copy @ import static de . tschuehly . easy . spring . auth . web . layout . LayoutComponent . TOAST_CONTAINER_ID
Then we add a <div>
element that has the ID set to TOAST_CONTAINER_ID
after the <body>
element.
Copy < div id = "${TOAST_CONTAINER_ID}" >
</ div >
MessageComponent
Now we create a MessageComponent
ViewComponent in the de.tschuehly.easy.spring.auth.web.message
package.
We also define an MessageType
enum that has a severity
method.
Copy package de . tschuehly . easy . spring . auth . web . message ;
import de . tschuehly . spring . viewcomponent . core . component . ViewComponent ;
import de . tschuehly . spring . viewcomponent . jte . ViewContext ;
@ ViewComponent
public class MessageComponent {
public ViewContext renderInfoToast ( String message) {
return new MessageContext(message , MessageType . INFO ) ;
}
public record MessageContext ( String message , MessageType type) implements
ViewContext {}
public enum MessageType {
TOAST_ERROR ,
NONE ,
INFO;
public String severity () {
return switch ( this ) {
case TOAST_ERROR -> "error" ;
case INFO -> "info" ;
default -> "" ;
};
}
}
}
In the severity()
function we could also define conditional CSS to style the toast
Now we will create the corresponding MessageComponent.jte
Copy @import de.tschuehly.easy.spring.auth.web.message.MessageComponent.MessageContext
@import java.util.Date
@param MessageContext messageContext
!{var id = String.valueOf(new Date().getTime());}
<div role="alert" id="${id}" <%-- (1) --%>
style="position: fixed; margin: 2rem; top: 0; left: 0; border-radius: 1rem; background-color: antiquewhite; padding: 1rem;">
<button onclick="document.getElementById('${id}').style.display = 'none'"> <%-- (2) --%>
<i class="">X</i>
</button>
<div>
<h2>${messageContext.type().severity()}:
${messageContext.message()}</h2> <%-- (3) --%>
</div>
</div>
(1): In the template, we create an <div>
element where we set the id
to a timestamp, we define as a local variable with the !{var}
syntax
(2): We use the <button onclick>
attribute to hide the toast when the user clicks on the X
(3): We create a <h2>
element in it that shows the severity and the message of the exception.
ControllerAdvice
Now we will create an ExceptionAdvice.java
class in the de.tschuehly.easy.spring.auth.web.advice
package:
Copy package de . tschuehly . easy . spring . auth . web . advice ;
import de . tschuehly . easy . spring . auth . htmx . HtmxUtil ;
import de . tschuehly . easy . spring . auth . web . exception . InfoException ;
import de . tschuehly . easy . spring . auth . web . layout . LayoutComponent ;
import de . tschuehly . easy . spring . auth . web . message . MessageComponent ;
import de . tschuehly . spring . viewcomponent . jte . ViewContext ;
import io . github . wimdeblauwe . htmx . spring . boot . mvc . HxSwapType ;
import org . springframework . web . bind . annotation . ControllerAdvice ;
import org . springframework . web . bind . annotation . ExceptionHandler ;
@ ControllerAdvice // (1)
public class ExceptionAdvice {
private final MessageComponent messageComponent;
public ExceptionAdvice ( MessageComponent messageComponent) { // (2)
this . messageComponent = messageComponent;
}
@ ExceptionHandler ( InfoException . class ) // (3)
public ViewContext handle ( InfoException e) {
HtmxUtil . retarget ( HtmxUtil . idSelector ( LayoutComponent . TOAST_CONTAINER_ID )); // (4)
HtmxUtil . swap ( HxSwapType . INNER_HTML ); // (5)
return messageComponent . renderInfoToast ( e . getMessage ()); // (6)
}
}
(1): We annotate the class with the@ControllerAdvice
annotation.
(2): We autowire the MessageComponent
(3): We define an @ExceptionHandler
method that handles the InfoException.class
(4): We retarget the response to the <div>
we created earlier with the TOAST_CONTAINER_ID
(5): We set the Swap method to INNER_HTML
Sucess!
Restart the application and navigate to http://localhost:8080 .
Now search for a string not present in the data, and then you will see the toast message with the exception message.
Lab-8 Checkpoint 1
If you are stuck you can resume at this checkpoint with:
git checkout tags/lab-8-checkpoint-1 -b lab-8-c1
Last updated 6 months ago