Search code examples
primefacesjsf-2progress-barwizard

p:wizard with a progress bar


I would like to create a registration form with two PrimeFaces components: Wizard and Progress Bar. For the backing bean, I am using the following one:

Backing bean: UserWizard.xhtml

import java.io.Serializable;
import java.util.Map;
import java.util.TreeMap;

import javax.annotation.PostConstruct;
import javax.faces.application.FacesMessage;
import javax.faces.context.FacesContext;
import javax.faces.event.AjaxBehaviorEvent;
import javax.faces.view.ViewScoped;
import javax.inject.Named;

import org.primefaces.context.RequestContext;
import org.primefaces.event.FlowEvent;

    @ViewScoped
    @Named
    public class UserWizard implements Serializable {

        private User user = new User();

        private boolean skip;

        private Integer progress = 0;

        public User getUser() {
            return user;
        }

        public void setUser(User user) {
            this.user = user;
        }

        public void save() {        
            FacesMessage msg = new FacesMessage("Successful", "Welcome :" + user.getFirstname());
            FacesContext.getCurrentInstance().addMessage(null, msg);
        }

        public boolean isSkip() {
            return skip;
        }

        public void setSkip(boolean skip) {
            this.skip = skip;
        }

        public String onFlowProcess(FlowEvent event) {
            String oldStep = event.getOldStep();
            Integer oldValue = getStepNumber(oldStep);
            String newStep = event.getNewStep();
            Integer newValue = getStepNumber(newStep);
            if(oldValue < newValue)
               progress += 25;
            else
               progress += 25;

            return event.getNewStep();
}

public Integer getStepNumber(String Step) {
    Integer StepNumber;
    switch(Step) {
        case "personal":
            StepNumber = 1;
            break;
        case "address":
            StepNumber = 2;
            break;
        case "contact":
            StepNumber = 3;
            break;
        default:
            StepNumber = 4;
            break;
    }   
    return StepNumber;
}

        public Integer getProgress() {
            return progress;
        }

        public void setProgress(Integer progress) {
            this.progress = progress;
        }

        public void onComplete() {
            FacesContext.getCurrentInstance().addMessage(null, new FacesMessage("Progress Completed"));
        }

        public void cancel() {
            progress = null;
        }
    }

Registration Form: registration.xhtml

<html xmlns="http://www.w3.org/1999/xhtml"
    xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
    xmlns:f="http://xmlns.jcp.org/jsf/core"
    xmlns:p="http://primefaces.org/ui"
    xmlns:h="http://xmlns.jcp.org/jsf/html">
<body>
    <f:view contracts="#{view.locale.language}">
        <ui:composition template="/template.xhtml">
            <ui:define name="centralBody">
<h:form>

<p:growl id="growl" sticky="true" showDetail="true"/>

<p:wizard flowListener="#{userWizard.onFlowProcess}">
    <p:tab id="personal" title="Personal">
        <p:panel header="Personal Details">
            <p:messages />
            <h:panelGrid columns="2" columnClasses="label, value">
                <h:outputText value="Firstname: *" />
                <p:inputText value="#{userWizard.user.firstname}" required="true" label="Firstname"/>

                <h:outputText value="Lastname: *" />
                <p:inputText value="#{userWizard.user.lastname}" required="true" label="Lastname"/>

                <h:outputText value="Age: " />
                <p:inputText value="#{userWizard.user.age}" />

                <h:outputText value="Skip to last: " />
                <h:selectBooleanCheckbox value="#{userWizard.skip}" />
            </h:panelGrid>
        </p:panel>
    </p:tab>

    <p:tab id="address" title="Address">
        <p:panel header="Address Details">
            <p:messages />
            <h:panelGrid columns="2" columnClasses="label, value">
                <h:outputText value="Street: " />
                <p:inputText value="#{userWizard.user.street}" />

                <h:outputText value="Postal Code: " />
                <p:inputText value="#{userWizard.user.postalCode}" />

                <h:outputText value="City: " />
                <p:inputText value="#{userWizard.user.city}" />

                <h:outputText value="Skip to last: " />
                <h:selectBooleanCheckbox value="#{userWizard.skip}" />
            </h:panelGrid>
        </p:panel>
    </p:tab>

    <p:tab id="contact" title="Contact">
        <p:panel header="Contact Information">
            <p:messages />
            <h:panelGrid columns="2" columnClasses="label, value">
                <h:outputText value="Email: *" />
                <p:inputText value="#{userWizard.user.email}" required="true" label="Email"/>

                <h:outputText value="Phone: " />
                <p:inputText value="#{userWizard.user.phone}"/>

                <h:outputText value="Additional Info: " />
                <p:inputText value="#{userWizard.user.info}"/>
            </h:panelGrid>
        </p:panel>
    </p:tab>

    <p:tab id="confirm" title="Confirmation">
        <p:panel header="Confirmation">
            <h:panelGrid id="confirmation" columns="3" columnClasses="grid,grid,grid">
                <h:panelGrid columns="2" columnClasses="label, value">
                    <h:outputText value="Firstname: " />
                    <h:outputText value="#{userWizard.user.firstname}" styleClass="outputLabel"/>

                    <h:outputText value="Lastname: " />
                    <h:outputText value="#{userWizard.user.lastname}" styleClass="outputLabel"/>

                    <h:outputText value="Age: " />
                    <h:outputText value="#{userWizard.user.age}" styleClass="outputLabel"/>
                </h:panelGrid>

                <h:panelGrid columns="2" columnClasses="label, value">
                    <h:outputText value="Street: " />
                    <h:outputText value="#{userWizard.user.street}" styleClass="outputLabel"/>

                    <h:outputText value="Postal: " />
                    <h:outputText value="#{userWizard.user.postalCode}" styleClass="outputLabel"/>

                    <h:outputText value="City: " />
                    <h:outputText value="#{userWizard.user.city}" styleClass="outputLabel"/>
                </h:panelGrid>

                <h:panelGrid columns="2" columnClasses="label, value">
                    <h:outputText value="Email: " />
                    <h:outputText value="#{userWizard.user.email}" styleClass="outputLabel"/>

                    <h:outputText value="Phone " />
                    <h:outputText value="#{userWizard.user.phone}" styleClass="outputLabel"/>

                    <h:outputText value="Info: " />
                    <h:outputText value="#{userWizard.user.info}" styleClass="outputLabel"/>

                    <h:outputText />
                    <h:outputText />
                </h:panelGrid>
            </h:panelGrid>

            <p:commandButton value="Submit" actionListener="#{userWizard.save}" update="growl" process="@this"/>
        </p:panel>
    </p:tab>
</p:wizard>
<p:progressBar id="progressBar" widgetVar="pbAjax" ajax="true" value="#{UserWizard.progress}" labelTemplate="{value}%" styleClass="animated" global="false">
        <p:ajax event="complete" listener="#{UserWizard.onComplete}" update="growl" oncomplete="PF('startButton2').enable()"/>
    </p:progressBar>
</h:form>
        </ui:define>
        </ui:composition>
    </f:view>
</body>
</html>

My purpose is that, when the registration flow goes through the different registration tabs (Personal, Address, Contact and Confirmation), the progress bar in the lower part of the screen to be updated according to the next or back buttons. In order to achieve this, I want to use the method

public String onFlowProcess(FlowEvent event) {
    String oldStep = event.getOldStep();
    Integer oldValue = getStepNumber(oldStep);
    String newStep = event.getNewStep();
    Integer newValue = getStepNumber(newStep);
    if(oldValue < newValue)
        progress += 25;
    else
        progress -= 25;

    return event.getNewStep();
}

public Integer getStepNumber(String Step) {
    Integer StepNumber;
    switch(Step) {
        case "personal":
            StepNumber = 1;
            break;
        case "address":
            StepNumber = 2;
            break;
        case "contact":
            StepNumber = 3;
            break;
        default:
            StepNumber = 4;
            break;
    }   
    return StepNumber;
}

But I do not know how to update the progress bar. I have tried:

  • changing the value of progress bar value (variable progress) in the
    function onFlowProcess. It does not work.
  • <p:wizard ... update="progress_bar">. I have realized that the attribute update is not allowed for the Wizard component.
  • <p:ajax listener="#{UserWizard.onFlowProcess}" update="@this"> within the <p:progressBar> element.

Solution

  • Since the wizard (at least in/upto PrimeFaces 6.0) does not seem to support the update attribute when using a flowListener and no explicit ajax events are supported, the only option I see is to use the PrimeFaces RequestContext to update the other component. Keep in mind that you need to use the 'full absolute path' to the element, so including all the id's of the namingcontainers it is in. It is best then to explicitly assign id's to all namingcontainers (including a form!)

    So using

    RequestContext.getCurrentInstance().update("formId:progress_bar");
    

    like e.g.

    public String onFlowProcess(FlowEvent event) {
        String oldStep = event.getOldStep();
        Integer oldValue = getStepNumber(oldStep);
        String newStep = event.getNewStep();
        Integer newValue = getStepNumber(newStep);
        if(oldValue < newValue)
            progress += 25;
        else
            progress -= 25;
    
        RequestContext.getCurrentInstance().update("formId:progress_bar");        
    
        return event.getNewStep();
    }
    

    in the flowListener should work. Keep in mind that in the updates from beans, the ':' as a prefix for absolute paths is not needed, they are assumed to be always absolute