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("&times;");
        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/

jsf bootstrap alert closable icon messages

And when we close a couple of messages, we get the following result

jsf bootstrap alert closable icon messages

References

Download

You may also like...

  • Osama

    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.