JSF Bootstrap Alert Closable Info, Warning and Error Styled Messages
This tutorial shows you how to override the default JSF <h:messages>
renderer with Bootstrap alert closable and with glyphicon icon enhanced JSF info, warning, error and fatal styled messages. As we override the default message renderer we don’t need to specify the styled class every time we use the <h:messages>
element. This message renderer will sort the messages on Severity
; if there are multiple error messages it’ll first print the fatal, error, warning and then the info messages respectively. The messages are also enhanced with JavaScript making them closable. And we add an icon in front of every message.
Bootstrap Message Renderer
As I was searching for this solution I found a great post on github. This message renderer sorted the messages on Severity
and made the messages closable. But it was lacking a glyphicon icon. So I decided to re-post the solution with an appropriate icon in front of the messages.
package com.memorynotfound.jsf;
import java.io.IOException;
import java.util.Iterator;
import javax.faces.application.FacesMessage;
import javax.faces.component.UIComponent;
import javax.faces.component.UIMessages;
import javax.faces.context.FacesContext;
import javax.faces.render.FacesRenderer;
import javax.faces.context.ResponseWriter;
import com.sun.faces.renderkit.Attribute;
import com.sun.faces.renderkit.AttributeManager;
import com.sun.faces.renderkit.RenderKitUtils;
import com.sun.faces.renderkit.html_basic.MessagesRenderer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.faces.application.FacesMessage.Severity;
/**
* Component for twitter bootstrap alerts.
* Overrides default JSF Message renderer with Bootstrap alert design.
*
* @author vlcekmi3 (https://gist.github.com/vlcekmi3/4151211)
*/
@FacesRenderer(componentFamily="javax.faces.Messages", rendererType="javax.faces.Messages")
public class BootstrapMessagesRenderer extends MessagesRenderer {
private static final Attribute[] ATTRIBUTES =
AttributeManager.getAttributes(AttributeManager.Key.MESSAGESMESSAGES);
@Override
public void encodeBegin(FacesContext context, UIComponent component) throws IOException {
super.encodeBegin(context, component);
}
@Override
public void encodeEnd(FacesContext context, UIComponent component) throws IOException {
rendererParamsNotNull(context, component);
if (!shouldEncode(component)) return;
boolean mustRender = shouldWriteIdAttribute(component);
UIMessages messages = (UIMessages) component;
ResponseWriter writer = context.getResponseWriter();
String clientId = ((UIMessages) component).getFor();
if (clientId == null && messages.isGlobalOnly()) {
clientId = "";
}
Iterator messageIt = getMessageIter(context, clientId, component);
if (!messageIt.hasNext()) {
if (mustRender) {
if ("javax_faces_developmentstage_messages".equals(component.getId())) {
return;
}
writer.startElement("div", component);
writeIdAttributeIfNecessary(context, writer, component);
writer.endElement("div");
}
return;
}
writeIdAttributeIfNecessary(context, writer, component);
RenderKitUtils.renderPassThruAttributes(context, writer, component, ATTRIBUTES);
Map<Severity, List<FacesMessage>> msgs = new HashMap<Severity, List<FacesMessage>>();
msgs.put(FacesMessage.SEVERITY_INFO, new ArrayList<FacesMessage>());
msgs.put(FacesMessage.SEVERITY_WARN, new ArrayList<FacesMessage>());
msgs.put(FacesMessage.SEVERITY_ERROR, new ArrayList<FacesMessage>());
msgs.put(FacesMessage.SEVERITY_FATAL, new ArrayList<FacesMessage>());
while (messageIt.hasNext()) {
FacesMessage curMessage = (FacesMessage) messageIt.next();
if (curMessage.isRendered() && !messages.isRedisplay()) {
continue;
}
msgs.get(curMessage.getSeverity()).add(curMessage);
}
List<FacesMessage> severityMessages = msgs.get(FacesMessage.SEVERITY_FATAL);
if (!severityMessages.isEmpty()){
encodeSeverityMessages(context, messages, FacesMessage.SEVERITY_FATAL, severityMessages);
}
severityMessages = msgs.get(FacesMessage.SEVERITY_ERROR);
if (!severityMessages.isEmpty()){
encodeSeverityMessages(context, messages, FacesMessage.SEVERITY_ERROR, severityMessages);
}
severityMessages = msgs.get(FacesMessage.SEVERITY_WARN);
if (!severityMessages.isEmpty()){
encodeSeverityMessages(context, messages, FacesMessage.SEVERITY_WARN, severityMessages);
}
severityMessages = msgs.get(FacesMessage.SEVERITY_INFO);
if (!severityMessages.isEmpty()){
encodeSeverityMessages(context, messages, FacesMessage.SEVERITY_INFO, severityMessages);
}
}
private void encodeSeverityMessages(FacesContext facesContext, UIMessages uiMessages,
Severity severity, List<FacesMessage> messages) throws IOException {
ResponseWriter writer = facesContext.getResponseWriter();
String alertSeverityClass = "";
if (FacesMessage.SEVERITY_INFO.equals(severity)) {
alertSeverityClass = "alert-info";
} else if (FacesMessage.SEVERITY_WARN.equals(severity)) {
alertSeverityClass = "alert-warning";
} else if (FacesMessage.SEVERITY_ERROR.equals(severity)) {
alertSeverityClass = "alert-danger";
} else if (FacesMessage.SEVERITY_FATAL.equals(severity)) {
alertSeverityClass = "alert-danger";
}
writer.startElement("div", null);
writer.writeAttribute("class", "alert " + alertSeverityClass, "alert " + alertSeverityClass);
writer.writeAttribute("role", "alert", "alert");
writer.startElement("a", uiMessages);
writer.writeAttribute("class", "close", "class");
writer.writeAttribute("data-dismiss", "alert", "data-dismiss");
writer.writeAttribute("href", "#", "href");
writer.write("×");
writer.endElement("a");
writer.startElement("ul", null);
for (FacesMessage msg : messages){
String summary = msg.getSummary() != null ? msg.getSummary() : "";
String detail = msg.getDetail() != null ? msg.getDetail() : summary;
writer.startElement("li", uiMessages);
writer.startElement("div", null);
writer.writeAttribute("class",
"glyphicon glyphicon-exclamation-sign", "glyphicon glyphicon-exclamation-sign");
writer.endElement("div");
writer.startElement("div", null);
if (uiMessages.isShowSummary()) {
writer.startElement("strong", uiMessages);
writer.writeText(summary, uiMessages, null);
writer.endElement("strong");
}
if (uiMessages.isShowDetail()) {
writer.writeText(" " + detail, null);
}
writer.endElement("div");
writer.endElement("li");
msg.rendered();
}
writer.endElement("ul");
writer.endElement("div");
}
}
Register Renderer
We need to register the BootstrapMessageRenderer
with JSF using the faces-config.xml. The component-family is javax.faces.Messages
and renderer-type is javax.faces.Messages
. We then specify our new <h:messages>
renderer using the renderer-class element with the fully qualified name of our renderer.
<faces-config xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-facesconfig_2_2.xsd" version="2.2">
<render-kit>
<renderer>
<component-family>javax.faces.Messages</component-family>
<renderer-type>javax.faces.Messages</renderer-type>
<renderer-class>com.memorynotfound.jsf.BootstrapMessagesRenderer</renderer-class>
</renderer>
</render-kit>
</faces-config>
Add FacesMessages
This managed bean will add FacesMessage
to the FacesContext
. For every Severity
type we add a message, namely: info, warning, error and fatal. We can optionally give a client id but we did not, which means that these messages are not bound to a component and are global messages. Next, we specify a summary and a detail. You can configure on the <h:messages>
element which part you want to display on the page.
package com.memorynotfound.jsf;
import javax.faces.application.FacesMessage;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.RequestScoped;
import javax.faces.context.FacesContext;
@ManagedBean
@RequestScoped
public class CourseBean {
public String showMessages(){
// adding info message
FacesMessage infoMessage = new FacesMessage(FacesMessage.SEVERITY_INFO,
"Info summary", "Info detail");
FacesContext.getCurrentInstance().addMessage("", infoMessage);
// adding warning message
FacesMessage warningMessage = new FacesMessage(FacesMessage.SEVERITY_WARN,
"Warning summary", "Warning detail");
FacesContext.getCurrentInstance().addMessage("", warningMessage);
// adding error message
FacesMessage errorMessage = new FacesMessage(FacesMessage.SEVERITY_ERROR,
"Error summary", "Error detail");
FacesContext.getCurrentInstance().addMessage("", errorMessage);
// adding fatal message
FacesMessage fatalMessage = new FacesMessage(FacesMessage.SEVERITY_FATAL,
"Fatal summary", "Fatal detail");
FacesContext.getCurrentInstance().addMessage("", fatalMessage);
return null;
}
}
Display FacesMessages with <h:messages>
To display the messages on the screen we just use the standard <h:messages>
element which we have overridden. We can optionally specify if we want to display the summary and/or detail. We also made some improvements to the stylesheet. These css are included in the page for correct spacing and padding.
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core">
<h:head>
<h:outputStylesheet library="webjars" name="/bootstrap/3.3.6/css/bootstrap.min-jsf.css"/>
<h:outputScript library="webjars" name="/jquery/2.1.4/jquery.min.js"/>
<h:outputScript library="webjars" name="/bootstrap/3.3.6/js/bootstrap.min.js"/>
</h:head>
<h:body>
<style>
ul {
list-style: none;
padding: 0;
}
.alert .glyphicon{
display:table-cell;
}
.alert div,
.alert span{
padding-left: 5px;
display:table-cell;
}
#container {
margin-left: 20px;
margin-right: 20px;
}
</style>
<div id="container">
<h2>JSF messages</h2>
<h:form>
<h:messages id="messages" showSummary="true" showDetail="true"/>
<h:commandButton action="#{courseBean.showMessages}" value="Show messages">
<f:ajax render="@form" />
</h:commandButton>
</h:form>
</div>
</h:body>
</html>
Demo
URL: http://localhost:8081/jsf-bootstrap-messages-renderer/
And when we close a couple of messages, we get the following result
Excellent job memorynotfound. I want the message summary and detail and the message icon to start from right to left. How should I update the code? Many Thanks.
you are cool… really you saved my day! I have faced load of problems since i wanted to use pure bootstrap and fontawesome but had no idea about how to use since configuration is so difficult. I was forced to use either primefaces or bootfaces which i personally don’t want. But now everything is going good ….I will refer your site often. Thanks once again