Implementing a JavaScript toaster plugin to display JSF messages

A current – and also permanent – limitation of the XPages environment is the inability to refresh multiple blocks on the page within a single server trip. This inevitabily forces to trade efficiency for some low-hanging shortcut.

The most common example is when you want to notify the user with one or more messages that you added during the server side process. If <xp:messages /> isn’t contained in the refreshed block your user is never going to see what you wrote. So in order to avoid that you refresh a bigger chunk – if not the whole page – just for the sake of that one tag that displays messages. Certainly <xp:messages /> has its place and I keep on using it but, depending on the kind of operation, it might not be the best tool for the job considered the above mentioned XPages limitation.

I found that an alternative and solid solution is the one of serializing the messages in a JSON string and serving them through JavaScript. Regardless of the page portion size to be refreshed any server trip can come with new JavaScript instructions to be executed on the client and that were added from the server side. This gives an interesting edge in solving our messages problem. It can be solved in three steps:

1. Serializing the message

First of all we must choose the right moment to gather all the messages that were added throughout the server side cycle and convert them in the JSON string. I think the best moment and place is in the beforePhase of a render response listener. The following example shows how:


public class RenderPhaseListener implements PhaseListener {

	private static final long serialVersionUID = 1L;

	@Override
	public PhaseId getPhaseId() {
		return PhaseId.RENDER_RESPONSE;
	}

	@Override
	public void beforePhase(PhaseEvent phaseEvent) {
		FacesContext facesContext = phaseEvent.getFacesContext();
		Iterator<?> iter = facesContext.getMessages(null);

		if (iter.hasNext()) {
			JsonArray jsonArray = new JsonArray();

			while (iter.hasNext()) {
				FacesMessage message = (FacesMessage) iter.next();
				JsonObject jsonObject = new JsonObject();
				jsonObject.addProperty("summary", message.getSummary());
				jsonObject.addProperty("detail", message.getDetail());
				jsonObject.addProperty("severity", message.getSeverity().getOrdinal());
				jsonArray.add(jsonObject);
			}

			UIViewRootEx viewRoot = (UIViewRootEx) facesContext.getViewRoot();
			viewRoot.postScript("XSP.addOnLoad(function() { hub.toast(" + jsonArray.toString() + "); });");
		}
	}

}

In my implementation I made use of Google Gson in order to produce the JSON string but you can simply use the included com.ibm.commons.util.io.json classes for that. Apart from this Java library choice what the code does is making sure whether there are any general messages (see the null argument) to be serialized (if not no JavaScript will be output). Subsequently the messages get translated in an array of objects that hold 3 properties each: summary, detail and severity. After serialization and proper escaping the string is served as an argument for a JavaScript client side function, thus minimizing any further printing of JavaScript code in this phase.

2. Implementing a toaster JavaScript plugin

I chose Toastr for the job.

2. Outputting the message

The client side JavaScript function processes the messages and serves them to Toastr for consumption.


var hub = {
		toast : function(response) {
		var messages = JSON.parse(response);
		var options = {
			positionClass : "toast-top-center",
			timeOut : 30000
		};

		for ( var i = 0; i < messages.length; i++) {
			var severity;

			switch (messages[i].severity) {
			case 0:
				severity = "info";
				break;
			case 1:
				severity = "warning";
				break;
			case 2:
			case 3:
				severity = "error";
				break;
			default:
				severity = "success";
			}

			if (messages[i].summary.length > 0 && messages[i].detail.length > 0) {
				toastr[severity](messages[i].summary, messages[i].detail,
						options);
			} else {
				toastr[severity](
						messages[i].summary.length > 0 ? messages[i].summary
								: messages[i].detail, "", options);
			}
		}
	}
}

Thanks to this approach no worries about making sure whether <xp:messages /> is included in the refresh block. No more workarounds to making them appear let alone losing them.

Leave a Reply

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.