Calling Bootstrap 3 modal from JSF

Standard

Dialogs or modals can be used in JSF to create more dynamic, and interactive pages. This solution will show how to call a Bootstrap 3 modal from both a JSF commandLink, and a Primefaces commandLink. Each example uses a formId to update the modals content.

JSF h:commandLink

JSF implementation uses an onclick event, which shows the modal, then updates the form inside the modal. While the order of execution can be problamatic in that you could show stale data in your modal for a brief second; this solution will work for most cases.

<h:commandLink value="Show Modal" action="#{backingBean.doAction}"
    onclick="$('#myModal').modal('show');" immediate="true">
    <f:ajax execute="@this" render=":myForm"/>
</h:commandLink>

It also should be noted that using the h:commandLink component requires a backing bean action.

Primefaces p:commandLink

The Primefaces component library has expanded the attributes available in the p:commandLink.  This functions the same as the h:commandLink, but uses less code to accomplish the same task.

<p:commandLink value="Show modal" styleClass="btn btn-primary" onclick="$('#myModal').modal('show');" update=":myForm" immediate="true" />

Bootstrap modal

The modal is straight forward. A p:commandLink is used to close the modal, and a form is wrapped around all important content that needs to be updated.

<div class="modal fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModal" aria-hidden="true" data-keyboard="false" data-backdrop="static">
    <div class="modal-dialog modal-sm">
        <div class="modal-content">
            <h:form id="myForm">
                <div class="modal-header">
                    <h4 class="modal-title">Test Modal</h4>
                </div>
                <div class="modal-body">
                    Content area
                </div>
                <h:panelGroup layout="block" styleClass="modal-footer">
                    <p:commandLink value="Close" immediate="true" styleClass="btn btn-default" oncomplete="$('#myModal').modal('hide');" />
                </h:panelGroup>
            </h:form>
        </div>
    </div>
</div>

Self managing your own modals may be more work than using a prebuilt component like primefaces dialog. But the benefit is that the modal is responsive, and you still have full control over the design.

Using Font Awesome icons as CSS content

Standard

Font Awesome is a great web font icon pack to use with the Bootstrap 3 framework. While the implementation suggests using the <i> element; this is not always beneficial.

The solution is to specify the icons using pure CSS. This can be accomplished by substituting content values for the relevant icon within the Font Awesome font-family.

Take this CSS class for example which uses the “check” icon.

.act-submit:before {
    font-family: FontAwesome;
    display: inline-block;
    content: "\f00c";
    padding-right: .4em;
}

Now using the <a> tag you can add a check icon to a Bootstrap 3 button

<a href="#" class="btn btn-primary act-submit">Submit</a>

A complete list of the Font Awesome icon content values can be found on this cheatsheet.

Collapse.JS creating a dropdown icon on panel heading

Standard

By default Bootstrap 3 comes integrated with CollapseJS which offers expandable accordion like panels. These panels are very simple to setup and work great. However, since the font is uncolored it may not be obvious to some users that this panel expands.

Not collapsible

Not user friendly

Using pure CSS a dropdown icon can be placed on all panels that you create. The css places the icon after the link, and switches the icon to a down arrow once the panel has been expanded.

The CSS

.panel-heading a:after {
   font-family: 'Glyphicons Halflings';
   content: "\e114";
   float: right;
   color: grey;
}

.panel-heading a.collapsed:after {
   content: "\e080";
}

The result

appearscollapsible

Appears to be collapsible

 

The result is a more user intuitive collapsible panel. The main difference between using the Primefaces collapsible panel and the bootstrap js version is data accessibility. Primefaces will call a ajax event once the panel is to be rendered. Whereas CollapseJS will have already loaded the data from the backend bean. This may require you to update at the prerendered content manually, but the benefits of self managing the panels far outweighs the negatives.

Bootstrap3 with JSF error handling

Standard

JSF applications often get called ugly, or cookie cutter. The over use of the components and built in CSS creates the standardized look, but lacks flexibility. Using Bootstrap 3 CSS in combination with JSF attributes you can create a modern look. Take for example this salary sample salary calculator program which leverages JSF components.

Bootstrap 3 + JSF

The following code is used to create the amount input area.

<div class="form-group #{!amount.valid ? 'has-error' : 'none'}">
   <h:outputLabel value="Amount" for="amount" />
   <div class="input-group">
      <span class="input-group-addon"><i class="fa fa-dollar"></i></span>
         <h:inputText id="amount" value="#{myBean.amount}" binding="#{amount}" styleClass="form-control" required="true" requiredMessage="Enter dollar amount"/>
   </div>
</div>

This technique uses bindings to get the validation state of the inputText component. More information can about component bindings can be read on my other article “What is a JSF component binding? How can I use it?“. By intermixing both JSF + Bootstrap you can see how simple it is to give your app a modern look.

 

Bootstrap 3 + Primefaces 5 datatable styling

Standard

Using the default theme libraries supplied by Primefaces can make developing a custom look difficult. Having to override / make your own entire primefaces theme becomes painful; bootstrap 3 is the solution. Marrying Primefaces 5 component library and Bootstrap 3 renders a uniform design.

The following is a typical look of the Primefaces “boostrap” theme. Notice the headers on the columns as they have a background image. This design also lacks some functionality with different sized browsers.

Primefaces boostrap theme

Typical primefaces boostrap theme design

Utilizing the bootstrap 3 libraries the table can become responsive, have better design, and require little coding. Notice the difference in various design elements.

Disabled the default theme

Bootstrap 3 with Primefaces 5 theme disabled

To marry primefaces and boostrap 3 it requires 3 steps; modifying the web.xml, setting class properties on the datatable, and overriding some default Primefaces css.

Web.xml

The Primefaces community supplies roughly 40 themes to choose from. We however, are not going to use any of them. Place the following code in your web.xml

<context-param>
   <param-name>primefaces.THEME</param-name>
   <param-value>none</param-value>
</context-param>

Setting the property above assures only the base styling will be rendered.

XHTML Page

On the desired page output a dataTable and attach the tableStyleClass attribute to it. The “table” and “table-striped” are Bootstrap css classes. Surround the entire dataTable with a div tag class “table-responsive”. The dataTabe is now responsive utilizing bootstrap css.

<div class="table-responsive">
   <p:dataTable tableStyleClass="table table-striped" value="#{myBean.itms}" var="itm">
      <p:column headerText="Column Name">
         <h:outputText value="#{itm.title}"/>
      </p:column>
   </p:dataTable>
</div>

Extra CSS

The default Primefaces css may still interact with the above dataTable rendering unwanted borders. The solution is to reset the css ui-datatable class generated by Primefaces like so:

.ui-datatable thead th, .ui-datatable tbody td, .ui-datatable tfoot td {
    border-style: none;
}

JSF Bootstrap style validation

Standard

In this example, I provide a solution to the problem of needing to display errors specific to a form on a page. This solution was developed due to the need to have multiple forms on the same page, while maintaining a simple way to display errors belonging to there ID’s. The point of this article is to show you how to make a simple profile page for submitting a first and last name that is validated.

The most important class is the FacesUtil.java which provides methods for queuing / retrieving facesMessages based on each inputs DOM id. I have defined 3 types for displaying errors based on the bootstrap 3 message classes (success, warning, and danger).

public class FacesUtil {

    /**
     * Options allowed for errors
     */
    private static List errorCodes = new ArrayList() {
        {
            add("danger");
            add("warning");
            add("success");
        }
    };

   /**
     *
     * @param str
     */
    public static void queueMessage(String str) {
        queueMessage(str, "warning");
    }

    /**
     *
     * @param str
     * @param severity
     */
    public static void queueMessage(String str, String severity) {

        // Did they enter a valid severity
        if (!errorCodes.contains(severity)) {
            Logger.getLogger(FacesUtil.class.getName()).log(Level.SEVERE, null, "Improper api usage");
            return;
        }

        FacesMessage message = new FacesMessage(str);

        switch (severity) {
            case "danger":
                message.setSeverity(FacesMessage.SEVERITY_ERROR);
                break;
            case "warning":
                message.setSeverity(FacesMessage.SEVERITY_WARN);
                break;
            default:
                message.setSeverity(FacesMessage.SEVERITY_INFO);
                break;
        }

        FacesContext.getCurrentInstance().addMessage(UIComponent.getCurrentComponent(FacesContext.getCurrentInstance()).getClientId(), message);
    }

    /**
     *
     * @param severity
     * @return
     */
    public static Collection getMessages(String severity) {
        return getMessages("", severity);
    }

    /**
     * Gets all messages in a array list of a given severity type. Specify
     * severity as "error", "warn", or "info"
     *
     * @param formId
     * @param severity
     * @return
     */
    public static Collection getMessages(String formId, String severity) {

        // Get the Faces Context
        FacesContext facesContext = FacesContext.getCurrentInstance();

        // Restore other messages
        restoreMessages(facesContext);

        // Get the root of the UIComponent
        UIViewRoot root = facesContext.getViewRoot();

        // Iterator to iterate through the client ID's
        Iterator iterator = facesContext.getClientIdsWithMessages();

        // Map all the messages by error level
        Map<String, Collection> map = new HashMap<>();

        // Did they enter a valid severity
        if (!errorCodes.contains(severity)) {
            Logger.getLogger(FacesUtil.class.getName()).log(Level.SEVERE, null, "Improper api usage");
            return map.get(severity);
        }

        // Determine error categories
        String errorLevel = "";
        switch (severity) {
            case "danger":
                errorLevel = "ERROR 2";
                break;
            case "warning":
                errorLevel = "WARN 1";
                break;
            default:
                errorLevel = "INFO 0";
                break;
        }

        // Iterate through the messages
        while (iterator.hasNext()) {

            // Retrieve the client ID
            String clientId = iterator.next();

            // Retrieve the item UI component
            UIComponent baseComponent = root.findComponent(clientId);

            // Find the the form UI component
            UIComponent parentForm = findParentForm(baseComponent);

            // If the form ID was specified and its not the true parent, we skip this iteration
            if (!formId.equals("") && (parentForm == null || (!formId.equals("") && !parentForm.getId().equals(formId)))) {
                continue;
            }

            // If the formId is not specified we always display it as a global message
            if (formId.equals("") || baseComponent != null) {

                // Return an Iterator over the FacesMessages
                Iterator facesIterator = facesContext.getMessages(clientId);

                // Build a list to return based on type specified
                while (facesIterator.hasNext()) {

                    FacesMessage facesMessage = facesIterator.next();
                    Collection values = map.get(errorLevel);
                    if (values == null) {
                        values = new ArrayList<>();
                        map.put(errorLevel, values);
                    }

                    // Only add if we don't have that value already
                    if (facesMessage.getSeverity().toString().equals(errorLevel) && !values.contains(facesMessage.getDetail())) {
                        values.add(facesMessage.getDetail());
                    }
                }
            }
        }

        return map.get(errorLevel);
    }

    /**
     *
     * @param component
     * @return
     */
    public static UIComponent findParentForm(UIComponent component) {
        if (component instanceof UIForm) {
            return component;
        }
        if (component == null) {
            return null;
        }
        return findParentForm(component.getParent());
    }

    /**
     * @return the errorCodes
     */
    public static List getErrorCodes() {
        return errorCodes;
    }

    /**
     * @param aErrorCodes the errorCodes to set
     */
    public static void setErrorCodes(List aErrorCodes) {
        errorCodes = aErrorCodes;
    }
}

Interfacing with the FacesUtil.java class is a viewScoped bean called viewProfile.java. This bean extends the methods of the FacesUtil class and is where our submit action lives. It also defines the first and last name that will be submitted from our profile page.

/**
 * Sample profile page
 *
 * @author sixthpoint
 */
@ManagedBean
@ViewScoped
public class ViewProfile implements Serializable {

    private String firstName;
    private String lastName;

    /**
     * Basic constructor
     */
    public ViewProfile() {

    }

    /**
     * On submit, we queue a success message
     */
    public void submit() {

        FacesUtil.queueMessage("we have success", "success");
    }

    /**
     * Returns a array of validation options
     *
     * @return
     */
    public List validationOptions() {
        return FacesUtil.getErrorCodes();
    }

    /**
     * Ask for validation messages based on a formId in the component tree
     * (success, warning, danger)
     *
     * @param formId
     * @param level
     * @return
     */
    public Collection validation(String formId, String level) {
        return FacesUtil.getMessages(formId, level);
    }

    /**
     * Omitted getters and setters
     */

The final page is the actual JSF view. It is simple to display all errors from the faces util class through the viewProfile bean. On inputText I have defined a validateLength and a requireMessage. These have been set to required. If those validation requirements are not met, the error will be displayed in the panelGroup block below. The FacesUtil class has the logic to grab all manually queued messages as well as messages queue from validation inputs.

<h:form id="profileForm">
    <h:inputText value="#{viewProfile.firstName}" p:placeholder="First Name" required="true" requiredMessage="Please enter a first name">
        <f:validateLength minimum="3"/>
    </h:inputText>
    <h:inputText value="#{viewProfile.lastName}" p:placeholder="Last Name" required="true" requiredMessage="Please enter a last name">
        <f:validateLength minimum="3"/>
    </h:inputText>
    <h:commandButton type="submit" value="Submit" action="#{viewProfile.submit}">
        <f:ajax execute="@form" render="@form :messages"/>
    </h:commandButton>
</h:form>

<h:panelGroup layout="block" id="messages">
    <c:forEach var="errorCode" items="#{viewProfile.validationOptions()}">
        <h:panelGroup layout="block" rendered="#{viewProfile.validation('profileForm', errorCode).size() gt 0}">
            <h:panelGroup layout="block" styleClass="alert alert-#{errorCode} alert-dismissable">
                <button type="button" class="close" data-dismiss="alert" aria-hidden="true">&#xD7;</button>
                <ul>
                    <c:forEach var="m" items="#{viewProfile.validation('profileForm', errorCode)}">
                        <li>#{m}</li>
                    </c:forEach>
                </ul>
            </h:panelGroup>
        </h:panelGroup>
    </c:forEach>
</h:panelGroup>

 

Omitted are the bootstrap CDN’s libraries. A full working version of this code can be found on Github.