Wizard

Wizard

Wizards should be used when the user needs to go through a sequential set of steps to complete a task. A wizard is a good way to break a complex task into sub-tasks that can be more readily understood and completed. More complex wizards can have branching which takes the user to a different step depending on their settings/answers to previous questions. Wizards should appear in a Modal.

Wizard Modal

Wizard example image

Wizard Mobile View

Responsive wizard example

Wizard

Loading Screen

Wizard showing a loading screen

  1. Title Bar: The title given to the wizard should convey the purpose of the wizard and the process the user is going through. It can be the action label on the button, link, or menu that invokes the wizard, but should also take advantage of the ability for something more descriptive while remaining concise.
  2. Loading Wizard State (optional): For cases when the wizard will take a few moments to load, we recommend using a loading indicator and short message informing the user that the wizard is loading. Otherwise, the wizard should be empty.

First Step

Wizard showing step one

  1. Main Steps: The main steps of the wizard are shown in the bar along the top of the screen. The user can always see all of the primary steps in the flow at all times. The main step labels can be used to jump between steps in a non-sequential manner as long as previous steps have been completed. This should also be enabled for next steps as long as all required information has been completed on the current step and there are no dependencies between the steps. Main steps can be broken down into sub-steps.
  2. Sub-Steps for the Selected Main Step: Sub-steps are optional. If they are used, every effort should be made to provide sub-steps for each step in order to maintain a consistent layout and expectation from the users. The sub-step labels can be used to jump between sub-steps in a non-sequential manner as long as previous sub-steps have been completed. This should also be enabled for next sub-steps as long as all required information has been completed on the current sub-step and there are no dependencies between the sub-steps.
  3. Button Bar: The button bar provides the appropriate action buttons. These are usually a combination of Cancel, Back, Next, and Finish. The Back button should always be enabled except for the first step. The Next button should become enabled once all required information has been entered for the current step and/or sub-step. The Next button will move the user through any sub-steps before it moves the user to the next main step. The Back button will also behave the same way. In the example above, the back button is disabled since this shows the first sub-step within the first step. The Cancel button will close the wizard dialog.

Usage Note: Main step and sub-step names should be kept as short and descriptive as possible. Preferably only one or two words.

Next Step

Wizard showing step two

  1. Main Steps: The step that the user is currently on should be highlighted in some way to appear different than the other steps. If other steps are enabled, the user can click to go to that step either previous or next depending on the completion of or need for required inputs.

Final Step - Summary (optional)

Wizard showing a summary step

  1. Main Steps: The last step in the wizard can be a Review step that shows a summary of the information selected and/or set in the previous steps of the wizard. A Review step is optional but can provide a place to show a summary of the settings the user has gone through. Exact wording of the step and sub-steps can change depending on what makes sense for the particular task. Review along with Summary and Progress are only suggestions.
  2. Expand/Collapse Information (optional): Progressive disclosure can be used in the main content area of the summary step in order to accommodate more information. While this can be used anywhere in the wizard, it is recommended to be used only as necessary or on the summary review step.
  3. Completion Button: Once all required information has been provided, the Next button becomes the completion button with wording that makes sense for the particular task (i.e., Finish, Close, or Deploy). In some cases the Completion button will close the wizard dialog. In others the button becomes disabled and the text changes to indicate that the wizard is processing or finishing.

Final Step – Progress (optional)

Wizard showing a deployment in progress

  1. Progress Indicator: If it takes a few moments to load the information into the page, a progress indicator can be used. In most instances when this occurs, the Back and completion buttons should be disabled. The Cancel button can be enabled if canceling the process is supported by the wizard.
  2. Completion Button: While the final step is processing, the Completion button should be disabled, and the wording may change to indicate that the task is in progress.

Completion Page (optional)

Wizard showing successful completion

  1. Completion Message: If the completion button does not close the wizard, a completion message can be used to provide users with feedback that the wizard has been successfully completed or if any errors have occurred.
  2. Action Button (optional): An optional action button may be added to the completion page that takes the user to a page in the application where they can view the information submitted in the wizard.
  3. Completion Button: The wording on the completion button can change once processing of the content in the wizard is complete. This may be Close or some other word that makes sense for the particular use case. If the user has the option to go back and make alterations and resubmit the process, then the Back button should be enabled.

Responsive States

Mobile Wizard

Collapsed responsive wizard

  1. Current Main Step: The currently active main step is displayed at the top of the form along with the step number.
  2. Current Sub-step (optional): If the current main step has sub-steps, the name of the sub-step appears next to it at the top of the page. If the main and sub-step names are long enough that truncation is required, the sub-step name should be truncated before the main step name unless the sub-step name is critical to filling in the form.
  3. Steps Dropdown: Clicking on this dropdown reveals all steps in the wizard and enables users to switch between them if applicable.
  4. Button Bar: Wizard actions are available on the button bar which is fixed at the bottom of the page except for in smaller screen sizes, which requires the user to scroll the page for the button bar.

Mobile Wizard - Steps Dropdown

Expanded responsive wizard

  1. Main Steps: Main wizard steps are shown vertically when the steps dropdown is expanded. Clicking on a different step will display its sub-steps, or switch to it if it does not have any sub-steps.
  2. Sub-steps for the Selected Main Step (optional): Clicking on a sub-step will switch to that sub-step. The current sub-step is highlighted.

Simplified Mobile Wizard

If an application does not require the ability to switch between or view all steps from mobile devices, a simplified version of the wizard without a dropdown can be used instead.

Simplified responsive wizard

  1. Current Main Step: The currently active main step is displayed at the top of the form along with the step number.
  2. Current Sub-step (optional): If the current main step has sub-steps, the name of the sub-step appears next to it at the top of the page.

What’s not covered in the current design:

  1. In certain cases, the wizard will need to show step by step progress. This functionality is not covered.
  2. In some cases, it may be advantageous to the user to be able to jump to the review page without having completed previous steps.
  3. For more complex and time-consuming tasks, a wizard can have an optional save to let the user leave the wizard and return later. Some considerations for this feature are auto-saving and what happens if a session times out.
  4. For more complex wizards, there may be more steps or text than can be shown on the screen at one time. This pattern does not address the scalability of the main step bar.

PatternFly Core Example


Complete Wizard

Reference Markup

<button class="btn btn-default wizard-pf-open wizard-pf-complete" data-target="#complete">Launch wizard</button>
<div class="modal" id="complete" tabindex="-1" role="dialog">
  <div class="modal-dialog modal-lg wizard-pf">
    <div class="modal-content">
      <div class="modal-header">
        <button type="button" class="close" data-dismiss="modal" aria-hidden="true" aria-label="Close">
          <span class="pficon pficon-close"></span>
        </button>
        <h4 class="modal-title">Wizard Title</h4>
      </div>
      <div class="modal-body wizard-pf-body clearfix">
        <div class="wizard-pf-steps hidden">
          <ul class="wizard-pf-steps-indicator wizard-pf-steps-alt-indicator active">

            <li class="wizard-pf-step active">
              <a href="#"><span class="wizard-pf-step-number">1</span><span class="wizard-pf-step-title">First Step</span>
                <span class="wizard-pf-step-title-substep active">details</span>
                <span class="wizard-pf-step-title-substep">Settings</span>
              </a>
            </li>

            <li class="wizard-pf-step disabled">
              <a href="#">
                <span class="wizard-pf-step-number">2</span>
                <span class="wizard-pf-step-title">Second Step</span>
                <span class="wizard-pf-step-title-substep">details</span>
                <span class="wizard-pf-step-title-substep">settings</span>
              </a>
            </li>

            <li class="wizard-pf-step disabled">
              <a href="#">
                <span class="wizard-pf-step-number">3</span>
                <span class="wizard-pf-step-title">Review</span>
                <span class="wizard-pf-step-title-substep">summary</span>
                <span class="wizard-pf-step-title-substep">progress</span>
              </a>
            </li>
          </ul>

          <ul class="wizard-pf-steps-alt">
            <li class="wizard-pf-step-alt active">
              <a href="#">
                <span class="wizard-pf-step-alt-number">1</span>
                <span class="wizard-pf-step-alt-title">First Step</span>
              </a>
                  <ul>
                    <li class="wizard-pf-step-alt-substep active"><a href="">1A. Details</a></li>
                    <li class="wizard-pf-step-alt-substep disabled"><a href="#">1B. Settings</a></li>
                  </ul>
            </li>

            <li class="wizard-pf-step-alt">
              <a href="#">
                <span class="wizard-pf-step-alt-number">2</span>
                <span class="wizard-pf-step-alt-title">Second Step</span>
              </a>
                  <ul class="hidden">
                    <li class="wizard-pf-step-alt-substep disabled"><a href="#">2A. Details</a></li>
                    <li class="wizard-pf-step-alt-substep disabled"><a href="#">2B. Settings</a></li>
                  </ul>
            </li>

            <li class="wizard-pf-step-alt">
              <a href="#">
                <span class="wizard-pf-step-alt-number">3</span>
                <span class="wizard-pf-step-alt-title">Review</span>
              </a>
              <ul class="hidden">
                <li class="wizard-pf-step-alt-substep disabled"><a href="#">3A. Summary</a></li>
                <li class="wizard-pf-step-alt-substep disabled wizard-pf-progress-link"><a href="#">3B. Progress</a></li>
              </ul>
            </li>
          </ul>
        </div>

        <div class="wizard-pf-row">
          <div class="wizard-pf-sidebar hidden">
            <ul class="list-group">
              <li class="list-group-item active">
                <a href="#">
                  <span class="wizard-pf-substep-number">1A.</span>
                  <span class="wizard-pf-substep-title">Details</span>
                </a>
              </li>
              <li class="list-group-item disabled">
                <a href="#">
                  <span class="wizard-pf-substep-number">1B.</span>
                  <span class="wizard-pf-substep-title">Settings</span>
                </a>
              </li>
            </ul>
            <ul class="list-group hidden">
              <li class="list-group-item disabled">
                <a href="#">
                  <span class="wizard-pf-substep-number">2A.</span>
                  <span class="wizard-pf-substep-title">Details</span>
                </a>
              </li>
              <li class="list-group-item disabled">
                <a href="#">
                  <span class="wizard-pf-substep-number">2B.</span>
                  <span class="wizard-pf-substep-title">Settings</span>
                </a>
              </li>
            </ul>
            <ul class="list-group hidden">
              <li class="list-group-item disabled">
                <a>
                  <span class="wizard-pf-substep-number">3A.</span>
                  <span class="wizard-pf-substep-title">Summary</span>
                </a>
              </li>
              <li class="list-group-item disabled wizard-pf-progress-link">
                <a>
                  <span class="wizard-pf-substep-number">3B.</span>
                  <span class="wizard-pf-substep-title">Progress</span>
                </a>
              </li>
            </ul>
          </div> <!-- /.wizard-pf-sidebar -->
          <div class="wizard-pf-main">
            <div class="wizard-pf-loading blank-slate-pf">
              <div class="spinner spinner-lg blank-slate-pf-icon"></div>
              <h3 class="blank-slate-pf-main-action">Loading Wizard</h3>
              <p class="blank-slate-pf-secondary-action">Lorem ipsum dolor sit amet, porta at suspendisse ac, ut wisi
                vivamus, lorem sociosqu eget nunc amet. </p>
            </div>
            <div class="wizard-pf-contents hidden">
              <form class="form-horizontal">
                <!-- replacing id with data-id to pass build errors -->
                <div class="form-group required">
                  <label class="col-sm-2 control-label required-pf" for="textInput-markup" required>Name</label>
                  <div class="col-sm-10">
                    <input id="detailsName" type="text" data-id="textInput-markup" class="form-control">
                  </div>
                </div>
                <div class="form-group">
                  <label class="col-sm-2 control-label" for="descriptionInput-markup">Description (Optional)</label>
                  <div class="col-sm-10">
                    <textarea data-id="descriptionInput-markup" class="form-control" rows="2"></textarea>
                  </div>
                </div>
              </form>
            </div>
            <div class="wizard-pf-contents hidden">
              <form class="form-horizontal">
                <div class="form-group required">
                  <label class="col-sm-2 control-label" for="lorem">Lorem ipsum</label>
                  <div class="col-sm-10">
                    <input type="text" id="lorem" class="form-control">
                  </div>
                </div>
                <div class="form-group">
                  <label class="col-sm-2 control-label" for="dolor">Dolor (Optional)</label>
                  <div class="col-sm-10">
                    <textarea id="dolor" class="form-control" rows="2"></textarea>
                  </div>
                </div>
              </form>
            </div>
            <div class="wizard-pf-contents hidden">
              <form class="form-horizontal">
                <div class="form-group required">
                  <label class="col-sm-2 control-label" for="aliquam">Aliquam</label>
                  <div class="col-sm-10">
                    <input type="text" id="aliquam" class="form-control">
                  </div>
                </div>
                <div class="form-group">
                  <label class="col-sm-2 control-label" for="fermentum">Fermentum</label>
                  <div class="col-sm-10">
                    <textarea id="fermentum" class="form-control" rows="2"></textarea>
                  </div>
                </div>
              </form>
            </div>
            <div class="wizard-pf-contents hidden">
              <form class="form-horizontal">
                <div class="form-group required">
                  <label class="col-sm-2 control-label" for="consectetur">Consectetur</label>
                  <div class="col-sm-10">
                    <input type="text" id="consectetur" class="form-control">
                  </div>
                </div>
                <div class="form-group">
                  <label class="col-sm-2 control-label" for="adipiscing">Adipiscing</label>
                  <div class="col-sm-10">
                    <textarea id="adipiscing" class="form-control" rows="2"></textarea>
                  </div>
                </div>
              </form>
            </div>
            <div class="wizard-pf-contents hidden">
              <div class="wizard-pf-review-steps">
                <ul class="list-group">
                  <li class="list-group-item">
                    <a onclick="$(this).toggleClass('collapsed'); $('#reviewStep1').toggleClass('collapse');">First Step</a>
                    <div id="reviewStep1" class="wizard-pf-review-substeps">
                      <ul class="list-group">
                        <li class="list-group-item">
                          <a onclick="$(this).toggleClass('collapsed'); $('#reviewStep1Substep1').toggleClass('collapse');">
                            <span class="wizard-pf-substep-number">1A.</span>
                            <span class="wizard-pf-substep-title">Details</span>
                          </a>
                          <div id="reviewStep1Substep1" class="wizard-pf-review-content">
                            <form class="form">
                              <div class="wizard-pf-review-item">
                                <span class="wizard-pf-review-item-label">Name:</span>
                                <span class="wizard-pf-review-item-value">First Last</span>
                              </div>
                              <div class="wizard-pf-review-item">
                                <span class="wizard-pf-review-item-label">Description:</span>
                                <span class="wizard-pf-review-item-value">This is the description</span>
                              </div>
                            </form>
                          </div>
                        </li>
                        <li class="list-group-item">
                          <a onclick="$(this).toggleClass('collapsed'); $('#reviewStep1Substep2').toggleClass('collapse');">
                            <span class="wizard-pf-substep-number">1B.</span>
                            <span class="wizard-pf-substep-title">Settings</span>
                          </a>
                          <div id="reviewStep1Substep2" class="wizard-pf-review-content">
                            <form class="form">
                              <div class="wizard-pf-review-item">
                                <div class="wizard-pf-review-item-field">Setting A</div>
                                <div class="wizard-pf-review-item-field">Setting B</div>
                              </div>
                            </form>
                          </div>
                        </li>
                      </ul>
                    </div>
                  </li>
                  <li class="list-group-item">
                    <a onclick="$(this).toggleClass('collapsed'); $('#reviewStep2').toggleClass('collapse');">Second Step</a>
                    <div id="reviewStep2" class="wizard-pf-review-substeps">
                      <ul class="list-group">
                        <li class="list-group-item">
                          <a onclick="$(this).toggleClass('collapsed'); $('#reviewStep2Substep1').toggleClass('collapse');">
                            <span class="wizard-pf-substep-number">2A.</span>
                            <span class="wizard-pf-substep-title">Details</span>
                          </a>
                          <div id="reviewStep2Substep1" class="wizard-pf-review-content">
                            <form class="form">
                              <div class="wizard-pf-review-item">
                                <span class="wizard-pf-review-item-label">Name:</span>
                                <span class="wizard-pf-review-item-value">First Last</span>
                              </div>
                              <div class="wizard-pf-review-item">
                                <span class="wizard-pf-review-item-label">Description:</span>
                                <span class="wizard-pf-review-item-value">This is the description</span>
                              </div>
                            </form>
                          </div>
                        </li>
                        <li class="list-group-item">
                          <a onclick="$(this).toggleClass('collapsed'); $('#reviewStep2Substep2').toggleClass('collapse');">
                            <span class="wizard-pf-substep-number">2B.</span>
                            <span class="wizard-pf-substep-title">Settings</span>
                          </a>
                          <div id="reviewStep2Substep2" class="wizard-pf-review-content">
                            <form class="form">
                              <div class="wizard-pf-review-item">
                                <div class="wizard-pf-review-item-field">Setting A</div>
                                <div class="wizard-pf-review-item-field">Setting B</div>
                              </div>
                            </form>
                          </div>
                        </li>
                      </ul>
                    </div>
                  </li>
                </ul>
              </div>
            </div>
            <div class="wizard-pf-contents hidden">
              <div class="wizard-pf-process blank-slate-pf">
                <div class="spinner spinner-lg blank-slate-pf-icon"></div>
                <h3 class="blank-slate-pf-main-action">Deployment in progress</h3>
                <p class="blank-slate-pf-secondary-action">Lorem ipsum dolor sit amet, porta at suspendisse ac, ut wisi
                  vivamus, lorem sociosqu eget nunc amet. </p>
              </div>
              <div class="wizard-pf-complete blank-slate-pf hidden">
                <div class="wizard-pf-success-icon"><span class="glyphicon glyphicon-ok-circle"></span></div>
                <h3 class="blank-slate-pf-main-action">Deployment was successful</h3>
                <p class="blank-slate-pf-secondary-action">Lorem ipsum dolor sit amet, porta at suspendisse ac, ut wisi
                  vivamus, lorem sociosqu eget nunc amet. </p>
                <button type="button" class="btn btn-lg btn-primary">
                  View Deployment
                </button>

              </div>
            </div>
          </div><!-- /.wizard-pf-main -->
        </div>

      </div><!-- /.wizard-pf-body -->

      <div class="modal-footer wizard-pf-footer">
        <button type="submit" class="btn btn-primary wizard-pf-next disabled" disabled="disabled">
          <span class="wizard-pf-button-text">
            Next
          </span>
          <span class="i fa fa-angle-right"></span>
        </button>
        <button type="button" class="btn btn-default wizard-pf-back disabled" disabled="disabled">
          <span class="i fa fa-angle-left"></span>
          <span class="wizard-pf-button-text">
            Back
          </span>
        </button>
        <button type="button" class="btn btn-default btn-cancel wizard-pf-cancel wizard-pf-dismiss">Cancel</button>
        <button type="button" class="btn btn-primary hidden wizard-pf-close wizard-pf-dismiss">Close</button>
      </div><!-- /.wizard-pf-footer -->

    </div><!-- /.modal-content -->
  </div><!-- /.modal-dialog -->
</div><!-- /.modal -->

<script>

  $(document).ready(function() {
    //initialize wizard
    var completeWizard = new wizard(".btn.wizard-pf-complete");
  });

  var wizard = function(id) {
    var self = this, modal, tabs, tabCount, tabLast, currentGroup, currentTab, contents;
    self.id = id;

    $(self.id).click(function() {
        self.init(this)
    });

    this.init = function(button){
      // get id of open modal
      self.modal = $(button).data("target");

      self.resetToInitialState();

      // open modal
      $(self.modal).modal('show');

      // assign data attribute to all tabs
      $(self.modal + " .wizard-pf-sidebar .list-group-item").each(function() {
          // set the first digit (i.e. n.0) equal to the index of the parent tab group
          // set the second digit (i.e. 0.n) equal to the index of the tab within the tab group
          $(this).attr("data-tab", ($(this).parent().index() +1 + ($(this).index()/10 + .1)));
      });
      // assign data attribute to all tabgroups
      $(self.modal + " .wizard-pf-sidebar .list-group").each(function() {
          // set the value equal to the index of the tab group
          $(this).attr("data-tabgroup", ($(this).index() +1));
      });

      // assign data attribute to all step indicator steps
      $(self.modal + " .wizard-pf-steps-indicator  .wizard-pf-step").each(function() {
        // set the value equal to the index of the tab group
        $(this).attr("data-tabgroup", ($(this).index() +1));
      });
      // assign data attribute to all step indicator substeps
      $(self.modal + " .wizard-pf-steps-indicator .wizard-pf-step-title-substep").each(function() {
        // set the first digit (i.e. n.0) equal to the index of the parent tab group
        // set the second digit (i.e. 0.n) equal to the index of the tab within the tab group
        $(this).attr("data-tab", ($(this).parent().parent().index() + 1 + (($(this).index() - 2)/10 + .1)));
      });

      // assign data attribute to all alt step indicator steps
      $(self.modal + " .wizard-pf-steps-alt .wizard-pf-step-alt").each(function() {
        // set the value equal to the index of the tab group
        var tabGroup = $(this).index() + 1;
        $(this).attr("data-tabgroup", tabGroup);
        $(this).find('.wizard-pf-step-alt-substep').each(function() {
          $(this).attr("data-tab", (tabGroup + (($(this).index() + 1)/10)));
        });
      });

      // assign active and hidden states to the steps alt classes
      $(self.modal + " .wizard-pf-steps-alt-indicator").removeClass('active');
      $(self.modal + " .wizard-pf-steps-alt").addClass('hidden');
      $(self.modal + " .wizard-pf-steps-alt-indicator").on('click', function() {
        $(self.modal + ' .wizard-pf-steps-alt-indicator').toggleClass('active');
        $(self.modal + ' .wizard-pf-steps-alt').toggleClass('hidden');
      });
      $(self.modal + " .wizard-pf-step-alt > ul").addClass("hidden");

      // create array of all tabs, using the data attribute, and determine the last tab
      self.tabs = $(self.modal + " .wizard-pf-sidebar .list-group-item" ).map(function()
        {
          return $(this).data("tab");
        }
      );
      self.tabCount = self.tabs.length;
      self.tabSummary = self.tabs[self.tabCount - 2]; // second to last tab displays summary
      self.tabLast = self.tabs[self.tabCount - 1]; // last tab displays progress
      // set first tab group and tab as current tab
      // if someone wants to target a specific tab, that could be handled here
      self.currentGroup = 1;
      self.currentTab = 1.1;

      setTimeout(function() {
        // hide loading message
        $(self.modal + " .wizard-pf-loading").addClass("hidden");
        // show tabs and tab groups
        $(self.modal + " .wizard-pf-steps").removeClass("hidden");
        $(self.modal + " .wizard-pf-sidebar").removeClass("hidden");
        // remove active class from all tabs
        $(self.modal + " .wizard-pf-sidebar .list-group-item.active").removeClass("active");

        self.updateToCurrentPage();
      }, 3000);

      //initialize click listeners
      self.tabGroupSelect();
      self.tabSelect();
      self.altStepClick();
      self.altSubStepClick();
      self.backBtnClicked();
      self.nextBtnClicked();
      self.cancelBtnClick();
      self.disableDefaultClick();

      // Listen for required value change
      self.detailsNameChange();

      // Handle form submit event
      self.formSubmitted();
    };

    // update which tab group is active
    this.updateTabGroup = function() {
      $(self.modal + " .wizard-pf-step.active").removeClass("active");
      $(self.modal + " .wizard-pf-step[data-tabgroup='" + self.currentGroup + "']").addClass("active");
      $(self.modal + " .wizard-pf-sidebar .list-group").addClass("hidden");
      $(self.modal + " .list-group[data-tabgroup='" + self.currentGroup + "']").removeClass("hidden");
      $(self.modal + " .wizard-pf-step-alt")
        .not("[data-tabgroup='" + self.currentGroup + "']").removeClass("active").end()
        .filter("[data-tabgroup='" + self.currentGroup + "']").addClass("active");
      $(self.modal + " .wizard-pf-step-alt > ul").addClass("hidden");
      $(self.modal + " .wizard-pf-step-alt[data-tabgroup='" + self.currentGroup + "'] > ul").removeClass("hidden");
    };

    // enable a button
    this.enableBtn = function($el) {
      $el.removeClass("disabled").removeAttr("disabled");
    };

    // disable a button
    this.disableBtn = function($el) {
      $el.addClass("disabled").attr("disabled", "disabled");
    };

    // update which tab is active
    this.updateActiveTab = function() {
      $(self.modal + " .list-group-item.active").removeClass("active");
      $(self.modal + " .list-group-item[data-tab='" + self.currentTab + "']").addClass("active");

      // Update steps indicator to handle mobile mode
      $(self.modal + " .wizard-pf-steps-indicator .wizard-pf-step-title-substep").removeClass("active");
      $(self.modal + " .wizard-pf-steps-indicator .wizard-pf-step-title-substep[data-tab='" + self.currentTab + "']").addClass("active");

      // Update steps alt indicator to handle mobile mode
      $(self.modal + " .wizard-pf-step-alt .wizard-pf-step-alt-substep").removeClass("active");
      $(self.modal + " .wizard-pf-step-alt .wizard-pf-step-alt-substep[data-tab='" + self.currentTab + "']").addClass("active");

      self.updateVisibleContents();
    };

    // update which contents are visible
    this.updateVisibleContents = function() {
      var tabIndex = ($.inArray(self.currentTab, self.tabs));
      // displaying contents associated with currentTab
      $(self.modal + " .wizard-pf-contents").addClass("hidden");
      $(self.modal + " .wizard-pf-contents:eq(" + tabIndex + ")").removeClass("hidden");
      // setting focus to first form field in active contents
      setTimeout (function() {
        $(".wizard-pf-contents:not(.hidden) form input, .wizard-pf-contents:not(.hidden) form textarea, .wizard-pf-contents:not(.hidden) form select").first().focus(); // this does not account for disabled or read-only inputs
      }, 100);
    };

    // update display state of Back button
    this.updateBackBtnDisplay = function() {
      var $backBtn = $(self.modal + " .wizard-pf-back");
      if (self.currentTab == self.tabs[0]) {
        self.disableBtn($backBtn)
      } else {
        self.enableBtn($backBtn)
      }
    };

    // update display state of next/finish button
    this.updateNextBtnDisplay = function() {
      if (self.currentTab == self.tabSummary) {
        $(self.modal + " .wizard-pf-next").focus().find(".wizard-pf-button-text").text("Deploy");
      } else {
        $(self.modal + " .wizard-pf-next .wizard-pf-button-text").text("Next");
      }
    };

    // update display state of buttons in the footer
    this.updateWizardFooterDisplay = function() {
      self.updateBackBtnDisplay();
      self.updateNextBtnDisplay();
    };


    this.updateToCurrentPage = function() {
      self.updateTabGroup();
      self.updateActiveTab();
      self.updateVisibleContents();
      self.updateWizardFooterDisplay();
    };

    // when the user clicks a step, then the tab group for that step is displayed
    this.tabGroupSelect = function() {
      $('body').on('click', self.modal + " .wizard-pf-step:not(.disabled) > a", function(e) {
        self.currentGroup = $(this).parent().data("tabgroup");
        // update value for currentTab -- if a tab is already marked as active
        // for the new tab group, use that, otherwise set it to the first tab
        // in the tab group
        self.currentTab = $(self.modal + " .list-group[data-tabgroup='" + self.currentGroup + "'] .list-group-item.active").data("tab");
        if (self.currentTab === undefined) {
          self.currentTab = self.currentGroup + 0.1;
        }

        self.updateToCurrentPage();
      });
    };

    // when the user clicks a tab, then the tab contents are displayed
    this.tabSelect = function() {
      $('body').on('click', self.modal + " .wizard-pf-sidebar .list-group-item:not(.disabled) > a", function(e) {
        // update value of currentTab to new active tab
        self.currentTab = $(this).parent().data("tab");
        self.updateToCurrentPage();
      });
    };

    this.altStepClick = function() {
      $(self.modal + " .wizard-pf-step-alt").each(function() {
        var $this = $(this);
        $(this).find('> a').on('click', function(e) {
          var subStepList = $this.find('> ul');
          if (subStepList && (subStepList.length > 0)) {
            $this.find('> ul').toggleClass('hidden');
          } else {
            self.currentGroup = $this.data("tabgroup");
          }
        });
      });
    };

    this.altSubStepClick = function() {
      $('body').on('click', self.modal + " .wizard-pf-step-alt .wizard-pf-step-alt-substep:not(.disabled) > a", function(e) {
        // update value of currentTab to new active tab
        self.currentTab = $(this).parent().data("tab");
        self.currentGroup = $(this).parent().parent().parent().data("tabgroup");
        self.updateToCurrentPage();
      });
    };

    // Back button clicked
    this.backBtnClicked = function() {
      $('body').on('click', self.modal + " .wizard-pf-back", function() {
        // if not the first page
        if (self.currentTab != self.tabs[0]) {
          // go back a page (i.e. -1)
          self.wizardPaging(-1);
          // show/hide/disable/enable buttons if needed
          self.updateWizardFooterDisplay();
        }
      });
    };

    // Next button clicked
    this.nextBtnClicked = function() {
      $('body').on('click', self.modal + " .wizard-pf-next", function() {
        if (self.currentTab == self.tabSummary) {
          self.wizardPaging(1);
          self.finish();
        } else {
          // go forward a page (i.e. +1)
          self.wizardPaging(1);
          // show/hide/disable/enable buttons if needed
          self.updateWizardFooterDisplay();
        }
      });
    };

    // Form submitted
    this.formSubmitted = function() {
      $('form').on('submit', function(e) {
        e.preventDefault();
        $('button[type="submit"]:not(.disabled)').trigger('click');
      });
    };

    // Disable click events
    this.disableDefaultClick = function() {
      $(self.modal + " .wizard-pf-step > a")
        .add(self.modal + " .wizard-pf-step-alt > a")
        .add(self.modal + " .wizard-pf-step-alt .wizard-pf-step-alt-substep > a")
        .add(self.modal + " .wizard-pf-sidebar .list-group-item > a").on('click', function(e) {
          e.preventDefault()
      });
    }

    this.validateRequired = function($el) {
      var $nextBtn = $(self.modal + " .wizard-pf-next"),
          $step = $(self.modal + " .wizard-pf-step"),
          $stepAltSubStep = $(self.modal + " .wizard-pf-step-alt-substep:not(.wizard-pf-progress-link)"),
          $sidebarItem = $(self.modal + " .wizard-pf-sidebar .list-group-item:not(.wizard-pf-progress-link)");

        if ($el.val()) {
          $stepAltSubStep.removeClass('disabled');
          $step.removeClass('disabled');
          $sidebarItem.removeClass('disabled');
          self.enableBtn($nextBtn);
        } else {
          $stepAltSubStep.not('.active').addClass('disabled');
          $step.not('.active').addClass('disabled');
          $sidebarItem.not('.active').addClass('disabled');
          self.disableBtn($nextBtn);
        }
    }

    this.detailsNameChange = function() {
      var $el = $(self.modal + " #detailsName");
      $el.on('change keyup load focus', function() {
        self.validateRequired($el);
      });
    };

    this.resetToInitialState = function() {
      // drop click event listeners
      $(self.modal + " .wizard-pf-steps-alt-indicator").off('click');
      $(self.modal + " .wizard-pf-step-alt > a").off('click');
      $(self.modal + " #detailsName").off('change');
      $("form").off("submit");
      $("body").off("click");

      // reset final step
      $(self.modal + " .wizard-pf-process").removeClass("hidden");
      $(self.modal + " .wizard-pf-complete").addClass("hidden");
      // reset loading message
      $(self.modal + " .wizard-pf-contents").addClass("hidden");
      $(self.modal + " .wizard-pf-loading").removeClass("hidden");
      // remove tabs and tab groups
      $(self.modal + " .wizard-pf-steps").addClass("hidden");
      $(self.modal + " .wizard-pf-sidebar").addClass("hidden");
      // reset buttons in final step
      $(self.modal + " .wizard-pf-close").addClass("hidden");
      $(self.modal + " .wizard-pf-cancel").removeClass("hidden");
      $(self.modal + " .wizard-pf-next").removeClass("hidden").find(".wizard-pf-button-text").text("Next");
      // reset input fields
      $(self.modal + " .form-control").val("");
    };

    // Cancel/Close button clicked
    this.cancelBtnClick = function() {
      $(self.modal + " .wizard-pf-dismiss").click(function() {
        // close the modal
        $(self.modal).modal('hide');
        self.resetToInitialState();
      });
    };

    // when the user clicks Next/Back, then the next/previous tab and contents display
    this.wizardPaging = function(direction) {
      // get n.n value of next tab using the index of next tab in tabs array
      var tabIndex = ($.inArray(self.currentTab, self.tabs)) + direction;
      var newTab = self.tabs[tabIndex];
      // add/remove active class from current tab group
      // included math.round to trim off extra .000000000002 that was getting added
      if (newTab != Math.round(10*(direction*.1 + self.currentTab))/10) {
        // this statement is true when the next tab is in the next tab group
        // if next tab is in next tab group (e.g. next tab data-tab value is
        // not equal to current tab +.1) then apply active class to next
        // tab group and step, and update the value for var currentGroup +/-1
        self.currentGroup = self.currentGroup + direction;
        self.updateTabGroup();
      }
      self.currentTab = newTab;
      // remove active class from active tab in current tab group
      $(self.modal + " .list-group[data-tabgroup='" + self.currentGroup + "'] .list-group-item.active").removeClass("active");
      // apply active class to new current tab and associated contents
      self.updateActiveTab();
    };

    // This code keeps the same contents div active, but switches out what
    // contents display in that div (i.e. replaces process message with
    // success message).
    this.finish = function() {
      self.disableBtn($(self.modal + " .wizard-pf-back")); // if Back remains enabled during this step, then the Close button needs to be removed when the user clicks Back
      self.disableBtn($(self.modal + " .wizard-pf-next"));
      // disable progress link navigation
      $(self.modal + " .wizard-pf-step").addClass('disabled');
      $(self.modal + " .wizard-pf-step-alt").addClass('disabled');
      $(self.modal + " .wizard-pf-step-alt .wizard-pf-step-alt-substep").addClass('disabled');
      $(self.modal + " .wizard-pf-sidebar .list-group-item").addClass('disabled');
      // code for kicking off process goes here
      // the next code is just to simulate the expected experience, in that
      // when the process is complete, the success message etc. would display
      setTimeout (function() {
        $(self.modal + " .wizard-pf-cancel").addClass("hidden");
        $(self.modal + " .wizard-pf-next").addClass("hidden");
        $(self.modal + " .wizard-pf-close").removeClass("hidden");
        $(self.modal + " .wizard-pf-process").addClass("hidden");
        $(self.modal + " .wizard-pf-complete").removeClass("hidden");
      }, 3000);
    };
  };

</script>