Opening a modal with a message for the user

Description

Show a feedback message for the user in a new modal by calling a java or javascript api.

The modal has a title, one or more messages, a severity for each message:

Notification modal
Figure 1. Notification modal

Prerequisites

The HTML page must contain the following placeholder:

<div th:include="/yada/modalNotify :: modal" class="modal fade" id="yada-notification" role="dialog"></div>
<script th:if="${YADA_NBODY!=null}" type="text/javascript">
	$(document).ready(function() {
		$('#yada-notification').modal('show');
	});
</script>

<script type="text/javascript">
        $('#yada-notification').on('hidden.bs.modal', function (e) {
                $('#yada-notification .glyphicon').addClass("hidden");
        });
</script>

This is usually inserted in a common footer so that you don’t need to remember adding it to each page.

make a dialect tag for this

Java API

The modal can be opened after a standard or ajax HTTP request and can be shown on the browser after a normal HTTP response or after a redirect. The notification can also close itself after a timeout, reload the current page when the user dismisses it, or perform a redirect on dismiss. When used in an ajax request it has some optional functionality.

Notifications are handled by the YadaNotify @Component and therefore need this autowiring:

@Autowired private YadaNotify yadaNotify;

You can then add messages in your @RequestMapping methods with a syntax like the following:

yadaNotify.title("Login success", model).ok().message("You have been logged in").add();

The title() method is the first one to call because it starts the builder. It has many variations, some of which are used for localization (see the Javadoc). The message() method accepts HTML tags so you will need to escape all HTML reserved characters.

You can set the message severity using one of these methods:

ok()

Normal severity

info()

Can either be a warning or something that needs special attention from the user

error()

Error message

All of them accept a boolean value so that the severity can be set conditionally. For example:

boolean failed=false;
yadaNotify.title("Login " + failed?"failed":"success", model).ok(!failed).error(failed).message("You have {}been logged in", failed?"not ":"").add();

The previous example shows the use of slf4j placeholders in the message() method.

When calling the add() method you’re actually creating a message with the values previously stored in the builder, adding it to the current HTTP Request. The add() method also terminates the builder chain and you can’t call further methods after it. If you want to return more than one message, you can use yadaNotify as many times as you want, even in different controllers:

yadaNotify.title("Login success", model).ok().message("You have been logged in").add();
yadaNotify.title("Forbidden", model).info().message("Access to the dashboard has been revoked").add();

The resulting modal shows a panel for each message, and a top icon corresponding to the highest severity found in those messages:

Multiple notifications
Figure 2. Multiple notifications

Normal request / normal response

In a normal request/response, just use the Model as seen above. You can then return the thymeleaf view as usual: just after showing the target view, the browser will open the modal.

@RequestMapping("/dashboard")
public String dashboard(Model model) {
        yadaNotify.title("Login success", model).ok().message("You have been logged in").add();
        return "/dashboard";

Normal request / redirect response

If you want to perform a redirect when exiting the controller, use RedirectAttributes instead of Model:

@RequestMapping("/dashboard")
public String dashboard(RedirectAttributes redirectAttributes) {
        yadaNotify.title("Login success", redirectAttributes).ok().message("You have been logged in").add();
        return "redirect:/console";

The browser will perform a redirect, call the server again, display the target view then show the modal. The server can test if a modal is pending with the following methods:

isNotificationPending(...)
isErrorSet(...)

Redirect on modal close

You can also activate a redirect when the modal is closed by the user with redirectOnClose(). This can happen both after a normal response and a redirect response. Be careful to use the appropriate parameter to the title() method: Model for a normal response and RedirectAttributes for a redirect response. The first example will show the modal on the "/dashboardPage" and redirect to "/console" on close; the second example will show the modal after a redirect to "/dashboard" and redirect again to "/console" on close:

@RequestMapping("/dashboard")
public String dashboard(Model model) {
        yadaNotify.title("Login success", model).ok().message("You have been logged in").redirectOnClose("/console").add();
        return "/dashboardPage";

@RequestMapping("/dashboard")
public String dashboard(RedirectAttributes redirectAttributes) {
        yadaNotify.title("Login success", redirectAttributes).ok().message("You have been logged in").redirectOnClose("/console").add();
        return "redirect:/dashboard";

Please note that the redirectOnClose() url does not need to have the language prefix (when applicable), so "/console" is correct while "/en/console" is not.

Ajax request

Returning from the Controller

Ajax requests work roughly the same as normal requests. The notification will be shown only if the result contains the "/yada/modalNotify" modal. This can be done in one of the following alternative ways:

return "/yada/modalNotify";
return YadaViews.AJAX_NOTIFY;
return yadaNotify.title("Login success", model).ok().message("You have been logged in").add();

The first version should of course be avoided for future compatibility. The last version is very convenient when returning a message at the end of the @RequestMapping method.

Returning additional HTML

The problem with the above approach is that you might want to also return some other HTML, for example the original form with validation errors, or something to insert in the page. The solution is to add a conditional include of the modalNotify in your returned view. If the returned view is also used in normal requests, an ajax check can be used:

<!-- Some other html that you need goes before or after -->
<div th:if="${@yadaWebUtil.AjaxRequest}" class="yadaResponseData"> (1)
        <div th:if="${YADA_NBODY}" (2)
        	th:include="/yada/modalNotify :: body" th:remove="tag">
        </div>
</div>
1 only add the notification modal in ajax requests (normal request might have it already embedded)
2 only add the notification modal if there is a message to show

The yadaResponseData element is hidden by yada.css.

Replace /yada/modalNotify with YadaViews.AJAX_NOTIFY in the yadaResponseData example above

Returning additional data

You might want to return, together with a notification, some key-value pairs for use in a javascript handler defined with yada:successHandler (see Postprocessing).

You can achieve this by placing a Map called "resultMap" in the Model:

Map<String, String> resultMap = new HashMap<>();
resultMap.put("deletedTaskId", taskId);
model.addAttribute("resultMap", resultMap);

The data can be retrieved in the javascript handler with yada.getEmbeddedResult:

function editTaskHandler(responseText, responseHtml, form, button) {
        var result = yada.getEmbeddedResult(responseHtml);
        var taskId = result['deletedTaskId'];
        $('#taskRow' + taskId).remove();
}

Redirect

To show a notification with a redirect when returning from an ajax call, the only option is to perform the redirect on modal close with Redirect on modal close.

What happens if the controller returns "redirect:/xxx" on an ajax call?

Other functionality

Vertically Center

If you’re using Bootstrap 4 you can vertically center the modal with the method center():

yadaNotify.title("Login success", model).ok().message("You have been logged in").center().add();

Generic modal classes

You can add any class to the "modal-dialog" div by setting the extraDialogClasses Model attribute:

model.addAttribute("extraDialogClasses", "myclass1 myclass2");
return yadaNotify.title("Saved", model).ok().message("Item saved").add();
Clearing all previous messages, "Chiamare javascript arbitrario decidendo lato server", Autoclose, modalReloadOnClose,

Javascript API

The notification modal can also be opened in javascript:

yada.showOkModal(title, message, redirectUrl)
yada.showInfoModal(title, message, redirectUrl)
yada.showErrorModal(title, message, redirectUrl)
title

the modal title

message

the modal message

redirectUrl

optional url to redirect when the modal is closed

Customization

Modal icons

Notification modals use a "severity icon" on the top left and a "close icon" on the top right. These can be customized using the following CSS classes: yadaIcon, yadaNotify, yadaIcon-ok, yadaIcon-warning, yadaIcon-error, close, yadaIcon-close. The full list of provided icons can be found in /YadaWeb/src/main/resources/net/yadaframework/views/yada/css/yada.css. The current icons are implemented using Font Awesome 5 Free. A different icon set can be easily used by setting the proper font family on yadaIcon and the right content on the other classes. Example:

.yadaIcon {
	font-family: 'Font Awesome 5 Free';
	font-weight: 900;
}

.yadaIcon-ok:before {
    content: "\f00c";
}

Full customization

The notification modal can either be customized via CSS or by implementing a new html file. In the second case you should copy the original file from /YadaWeb/src/main/resources/net/yadaframework/views/yada/modalNotify.html and change it while preserving some key elements that are used as reference to add content.

explain what to preserve

The file should be placed somewhere in your views folder and its path added to the configuration with something like

<config>
        <paths>
                <notificationModalView>/myModalNotify</notificationModalView>

It should also be included in the footer in place of the original one

<div th:include="/myModalNotify :: modal" class="modal fade" id="yada-notification" role="dialog"></div>