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 may appear as a modal, or be embedded in a page.

Wizard example image

Embedded Wizard

Embedded wizard example

Wizard Mobile View

Responsive wizard example

Wizard

Loading Screen

Wizard showing a loading screen

  1. Title Bar (optional for embedded wizards): There should be a title bar for the wizard, this is required for modal wizards and optional for embedded wizards. 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 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. Wizard will be empty besides the message and indicator.

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. 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.
  3. Button Bar: The button bar provides the appropriate action buttons, these are usually a combination of Cancel, Back, Next and Finish. The exact wording on the Finish button can be adjusted to be more reflective of the completion action appropriate to what the user will be doing. 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 some cases the completion action button will close the dialog, in others, it views progress in the wizard, and ends up changing the label of that button. In the example above, the back button is disabled since this shows the first sub step within the first step.

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.
  2. Expand/Collapse Information (optional): Progressive disclosure can be used in the main content area of the step in order to accommodate more information. This is recommended to be used only as necessary or on the review step.

Final Step – Progress

Wizard showing a deployment in progress

  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. 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. For wizards that do not remain open while the particular task is being processed, the Completion button would close the wizard. On the last step of the wizard, there is no Next button.
  3. 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 cancelling the process is supported by the wizard.

Completion Page

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. 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.

Embedded Wizard

Embedded wizards should be used when the user must fill out the wizard in order to take any further action in the application. Examples of this include…

  • A new user sign up flow
  • A checkout flow that concludes interaction with the application when finished
  • A system setup flow that takes place before the user can enter the application

Modal wizards should be used when the task is non-necessary or is one of several possible actions that the user could take. Examples of this include…

  • Purchasing add-ons for an already running system
  • Making a new project when the user already has several
  • As an administrator, creating one of several user profiles for use by other people

Embedded wizard

Differences from Modal Wizards Embedded wizards follow the same guidelines as modal wizards, with a few exceptions.

  1. Button Bar: In an embedded wizard, the action buttons are placed in the bottom left corner as they would be in a Full Page Form. The button bar should be fixed to the bottom of wizard page.
  2. Breadcrumbs: If the user has come from somewhere where they may want to return, such as the main body of an application rather than an external site or login page, breadcrumbs should be available above the wizard title

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 for embedded wizards and modals wizards except for in smaller screen sizes, which requires the user to scroll the page for the button bar.

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

The following functionality is not covered in this pattern but will be covered in future iterations:

  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.

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">

            <li class="wizard-pf-step active" data-tabgroup="1">
              <a><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" data-tabgroup="2">
              <a>
                <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" data-tabgroup="3">
              <a>
                <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>
        </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">
                <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">
                <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">
                <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">
                <a>
                  <span class="wizard-pf-substep-number">3A.</span>
                  <span class="wizard-pf-substep-title">Summary</span>
                </a>
              </li>
              <li class="list-group-item">
                <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" for="textInput-markup">Name</label>
                  <div class="col-sm-10">
                    <input 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="button" class="btn btn-default btn-cancel wizard-pf-cancel wizard-pf-dismiss">Cancel</button>
        <button type="button" class="btn btn-default wizard-pf-back disabled">
          <span class="i fa fa-angle-left"></span>
          Back
        </button>
        <button type="button" class="btn btn-primary wizard-pf-next disabled">
          Next
          <span class="i fa fa-angle-right"></span>
        </button>
        <button type="button" class="btn btn-primary hidden wizard-pf-finish">
          Deploy
          <span class="i fa fa-angle-right"></span>
        </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");

      // 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)));
      });


      // 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;
      self.updateTabGroup();
      // 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");
      // apply active class to new current tab and associated contents
      self.updateActiveTab();

      self.updateWizardFooterDisplay();

      //initialize click listeners
      self.tabGroupSelect();
      self.tabSelect();
      self.backBtnClicked();
      self.nextBtnClicked();
      self.finishBtnClick();
      self.cancelBtnClick();


    };

    // 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");
    };

    // update which tab is active
    this.updateActiveTab = function() {
      $(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");

      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() {
      if (self.currentTab == self.tabs[0]) {
        $(self.modal + " .wizard-pf-back").addClass("disabled");
      }
    };

    // update display state of next/finish button
    this.updateNextBtnDisplay = function() {
      if (self.currentTab == self.tabSummary) {
        $(self.modal + " .wizard-pf-next").addClass("hidden");
        $(self.modal + " .wizard-pf-finish").removeClass("hidden");
      } else {
        $(self.modal + " .wizard-pf-finish").addClass("hidden");
        $(self.modal + " .wizard-pf-next").removeClass("hidden");
      }
    };

    // update display state of buttons in the footer
    this.updateWizardFooterDisplay = function() {
      $(self.modal + " .wizard-pf-footer .disabled").removeClass("disabled");
      self.updateBackBtnDisplay();
      self.updateNextBtnDisplay();
    };



    // when the user clicks a step, then the tab group for that step is displayed
    this.tabGroupSelect = function() {
      $(self.modal + " .wizard-pf-step>a").click(function() {
        // remove active class active tabgroup and add active class to the
        // clicked tab group (but don't remove the active class from current tab)
        self.currentGroup = $(this).parent().data("tabgroup");
        self.updateTabGroup();
        // 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.modal + " .list-group[data-tabgroup='" + self.currentGroup + "'] .list-group-item:first-child").data("tab");
          // apply active class to new current tab and associated contents
          self.updateActiveTab();
        } else {
          // use already active tab and just update contents
          self.updateVisibleContents();
        }
        // show/hide/disable/enable buttons if needed
        self.updateWizardFooterDisplay();
      });
    };

    // when the user clicks a tab, then the tab contents are displayed
    this.tabSelect = function() {
      $(self.modal + " .wizard-pf-sidebar .list-group-item>a").click(function() {
        // update value of currentTab to new active tab
        self.currentTab = $(this).parent().data("tab");
        // remove active class from active tab in current active tab group (i.e.
        // don't remove the class from tabs in other groups)
        $(self.modal + " .list-group[data-tabgroup='" + self.currentGroup + "'] .list-group-item.active").removeClass("active");
        // add active class to the clicked tab and the associated contents
        $(this).parent().addClass("active");
        self.updateVisibleContents();
        if (self.currentTab == self.tabLast) {
          $(self.modal + " .wizard-pf-next").addClass("hidden");
          $(self.modal + " .wizard-pf-finish").removeClass("hidden");
          self.finish();
        } else {
          // show/hide/disable/enable buttons if needed
          self.updateWizardFooterDisplay();
        }
      });
    };

    // Back button clicked
    this.backBtnClicked = function() {
      $(self.modal + " .wizard-pf-back").click(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() {
      $(self.modal + " .wizard-pf-next").click(function() {
        // go forward a page (i.e. +1)
        self.wizardPaging(1);
        // show/hide/disable/enable buttons if needed
        self.updateWizardFooterDisplay();
      });
    };

    // Finish button clicked
    // Deploy/Finish button would only display during the second to last step.
    this.finishBtnClick = function() {
      $(self.modal + " .wizard-pf-finish").click(function() {
        self.wizardPaging(1);
        self.finish();
      });
    };

    // Cancel/Close button clicked
    this.cancelBtnClick = function() {
      $(self.modal + " .wizard-pf-dismiss").click(function() {
        // close the modal
        $(self.modal).modal('hide');
        // drop click event listeners
        $(self.modal + " .wizard-pf-step>a").off("click");
        $(self.modal + " .wizard-pf-sidebar .list-group-item>a").off("click");
        $(self.modal + " .wizard-pf-back").off("click");
        $(self.modal + " .wizard-pf-next").off("click");
        $(self.modal + " .wizard-pf-finish").off("click");
        $(self.modal + " .wizard-pf-dismiss").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");
      });
    };

    // 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.modal + " .wizard-pf-back").addClass("disabled"); // if Back remains enabled during this step, then the Close button needs to be removed when the user clicks Back
      $(self.modal + " .wizard-pf-finish").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-finish").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>