The Wonderful Wizard of AngularJS

Boy am I diggin’ AngularJS. There are so many cool bits to it. And the bits that don’t exist are fun to build…once you get the hang of it that is. Case in point, a nifty little wizard module. We here at RealCrowd use wizards on a few pages in our app. They are a nice mechanism to guide users thru multi-step or complex tasks. Translating a complex UI component into an AngularJS module can be a bit tricky. But because of the modular approach we’ve taken to development, we were able to develop a fairly robust implementation. For those more interested in code than an dissertation, here is a shortcut to our Github.

I personally have been known to re-invent the wheel from time to time, but when I find a good simple plug-in I like to use it. RealCrowd is also a fan of responsive design and use Twitter Bootstrap. We therefore choose to use the unofficial third-party Bootstrap Wizard Plug-in to base our angular module on. However, the current version was designed for Bootstrap version 2.3.2 and we use version 3.0.0. There needed to be some slight modifications for this to work with 3.0.0. We have forked the current plug-in here with a fix and have also submitted a pull request to the source branch (Update: The fix has now been merged into the source branch).

As with most projects, things seem relatively simple when looking at the high-level view. We just need a directive to apply our wizard plug-in. However, when we consider the nuances of the UI, things get a bit more complex. What about the backward and forward buttons? We want to hide the backward at sometimes and the forward at others. What if we want to validate at each step? What if we want to call a web-service and not move forward until completed? And what if you want to do all these things and not have a bomb or spaghetti code go off? Well lucky for you that bomb already went off for us, so we went back and cleaned-up the mess and came up with what we believe is a nice flexible, straight-forward approach.

Before we get into building this module, it is recommended you have a basic understanding of AngularJS and directives. Things can get a bit technical, but if you understand the basics it should be pretty informative. Now let’s start with some mark-up to define our wizard. The Bootstrap Wizard Plug-in utilizes the official Bootstrap JQuery Tab plug-in, so we just have to include the Bootstrap stylesheets and javascript (note: We are using Bootstrap v3.0.0 which is quite different from the old v.2.3.2. If not using v3.0.0, you’ll have to do a bit of work to translate this):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
    <div ng-app="SampleWizardApp" ng-controller="SampleWizardController">
      <ul class="nav nav-pills">
        <li class="active">
         <a class="active" href="#first" data-toggle="tab">
           <span class="badge">1</span>
           <span>First Step</span>
         </a>
       </li>
       <li>
         <a href="#second" data-toggle="tab">
           <span class="badge">2</span>
           <span>Second Step</span>
         </a>
       </li>
       <li>
         <a href="#last" data-toggle="tab">
           <span class="badge">3</span>
           <span>Last Step</span>
         </a>
       </li>
      </ul>
      <div class="tab-content">
        <div class="tab-pane active" id="first">
          <h2>Enter first step data</h2>
          <div class="form-group">
            <label class="control-label">First Name</label>
            <input name="firstName" class="form-control" type="text" 
              ng-model="user.firstName" />
          </div>
          <div class="form-group">
            <label class="control-label">Last Name</label>
            <input name="lastName" class="form-control" type="text" 
              ng-model="user.lastName" />
          </div>
        </div>
        <div class="tab-pane" id="second">
          <h2>Enter second step data</h2>
          <div class="form-group">
            <label class="control-label">Street Address</label>
            <input name="streetAddress" class="form-control" type="text" 
              ng-model="user.streetAddress" />
          </div>
          <div class="form-group">
            <label class="control-label">City</label>
            <input name="city" class="form-control" type="text" 
              ng-model="user.city" />
          </div>
          <div class="form-group">
            <label class="control-label">State</label>
            <input name="state" class="form-control" type="text" 
              ng-model="user.state" />
          </div>
          <div class="form-group">
            <label class="control-label">City</label>
            <input name="postalCode" class="form-control" type="text" 
              ng-model="user.postalCode" />
          </div>
        </div>
        <div class="tab-pane" id="last">
          <h2>Finish last step</h2>
          <div class="form-group">
            <label class="control-label">First Name:</label>
            <p class="form-control-static">{{ user.firstName }}</p>
          </div>
          <div class="form-group">
            <label class="control-label">Last Name:</label>
            <p class="form-control-static">{{ user.lastName }}</p>
          </div>
          <div class="form-group">
            <label class="control-label">Address:</label>
            <p class="form-control-static">
              {{ user.streetAddress }}
              <br />
              {{ user.city }}, {{ user.state }} {{ user.postalCode }}
            </p>
          </div>
        </div>
      </div>
      <div class="form-group">
        <div class="pull-right">
          <a class="btn btn-default">Back</a>
          <a class="btn btn-primary">Continue</a>
        </div>
      </div>
    </div>

So we basically are starting out with an ordinary tab layout with some buttons at the bottom. To be clear, all of the style classes used are from Bootstrap. When we use custom styling we will prefix our CSS classes with an ‘rc-‘. Because of Bootstrap’s many styling options, our tab control already looks more wizard-y than tab-y. Thanks Bootstrap! Next we need to start our wizard directive to wizard-ify the tabs’ behavior. In our directive we will need a controller to wrap the basic wizard features and track some state. We want to make the state available so we will make the controller available on the scope. This is similar to what we do for the rcSubmit controller (which we describe the ‘why’ and ‘how’ in Advanced AngularJS Form Validation). The start of our wizard looks something like this (A quick note on naming convention for those new to the blog; all our constructs are prefixed with ‘rc’ for ‘RealCrowd’. We use this as a namespace, the same way AngularJS uses ‘ng’):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
    var rcWizardDirective = {
     'rcWizard': function () {
       return {
         restrict: 'A',
         controller: ['$scope', function ($scope) {
 
           var self;
           var wizardElement;
           var wizardOptions = {};
 
           this.currentIndex = 0;
           this.firstIndex = 0;
           this.navigationLength = 0;
 
           this.forward = function () {
             self.onForward();
           };
 
           var onForward = function () {
             wizardElement.bootstrapWizard('next');
           };
 
           this.backward = function () {
             wizardElement.bootstrapWizard('previous');
           };
 
           var onTabChange = function (activeTab, navigation, currentIndex, nextTab) {
 
             self.currentIndex = nextTab;
             self.firstIndex = wizardElement.bootstrapWizard('firstIndex');
             self.navigationLength = wizardElement.bootstrapWizard('navigationLength');
 
             if (!$scope.$$phase) $scope.$apply();
           };
 
           var onTabClick = function (activeTab, navigation, currentIndex, clickedIndex) {
             return false;
           };
 
           var onTabShow = function (activeTab, navigation, currentIndex) {
 
             if (currentIndex > 0) {
               wizardElement
                 .find('.nav li:gt(' + (currentIndex - 1) + ')')
                 .removeClass("success");
               wizardElement
                 .find('.nav li:lt(' + currentIndex + ')')
                 .addClass("success");
             } else {
               wizardElement
                 .find('.nav li')
                 .removeClass("success");
             }
           };
 
           var updateWizard = function (options) {
 
             wizardOptions = options;
 
             if (wizardElement) {
               wizardElement.bootstrapWizard(options);
               self.currentIndex = wizardElement.bootstrapWizard('currentIndex');
               self.firstIndex = wizardElement.bootstrapWizard('firstIndex');
               self.navigationLength = wizardElement.bootstrapWizard('navigationLength');
 
               if (!$scope.$$phase) $scope.$apply();
             }
           };
 
           this.setWizardElement = function (element) {
             wizardElement = element;
             self = this;
             updateWizard({
               'onTabChange': onTabChange,
               'onTabShow': onTabShow,
               'onTabClick': onTabClick
             });
           };
         }],
         compile: function (cElement, cAttributes, transclude) {
           return {
             pre: function (scope, formElement, attributes, wizardController) {
               // put a reference to the wizardcontroller on the scope so we can use some of the 
               // properties in the markup
               scope.rc = scope.rc || {};
               scope.rc[attributes.rcWizard] = wizardController;
             },
             post: function (scope, element, attributes, wizardController) {
               // let the controller know about the element
               wizardController.setWizardElement(element);
               if (!scope.$$phase) scope.$apply();
             }
           };
         }
       }
     }
   };

As you can see we are just applying and configuring the Bootstrap Wizard plug-in to the element and wrapping some of its functionality (namely the ‘forward‘ and ‘backward‘ methods). The forward method is double-wrapped. This will be helpful later on. We also are providing access to some state (i.e. currentIndex, firstIndex, navigationLength) that will also be useful. You may notice we added a handler for the onTabClick events and return false. This is so the user can’t use the tab headings (now step headings) to navigate. Another handler we are using is onTabShow. We do this to add/remove a success CSS style class where appropriate which will allow us do some additional styling (Note: ‘success‘ is a contextual CSS class in Bootstrap). Now we just need to add a couple items to our markup to take advantage of our new directive (also don’t forget the reference to the Bootstrap Wizard plug-in):

1
2
3
    <div ng-app="SampleWizardApp" ng-controller="SampleWizardController" 
      rc-wizard="sampleWizard">
      <ul class="nav nav-pills">

77
78
79
80
81
82
83
84
85
86
87
88
89
      <div class="form-group">
        <div class="pull-right">
          <a class="btn btn-default" 
            ng-click="rc.sampleWizard.backward()"
            ng-hide="rc.sampleWizard.currentIndex > rc.sampleWizard.firstIndex">Back</a>
          <a class="btn btn-primary" 
            ng-click="rc.sampleWizard.forward()"
            ng-show="rc.sampleWizard.currentIndex < rc.sampleWizard.navigationLength">
            Continue
          </a>
        </div>
      </div>
    </div>

Now our wizard is behaving more like a proper wizard. The ‘Back’ and ‘Continue’ buttons not only work, they also hide themselves when they aren’t needed. This is the point where most blogs would probably leave you: with a simple directive that does basically what you want it to do. But that is always annoying because when you go to implement it there are all these practical requirements missing. In the case of this wizard, a big missing piece is validation.

Wizard validation is a bit tricky. At first it may seem easy: just place a form around the wizard and handle it like any other form. However, in a wizard, you don’t want to just validate the wizard as a whole, you want to validate it per step. And until a step is valid you want to prevent navigation to the next step. Luckily we have some experience with form validation and disabling UI while busy that will come in handy here. We will be able to use the rcSubmit controller to tap into the validation process, namely onSubmitComplete. Since we need to validate on each step there will be a rcSubmit controller for every step. In order for our rcWizard controller to be able to handle that, we need to provide a way to track every step:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
    var rcWizardDirective = {
     'rcWizard': function () {
       return {
         restrict: 'A',
         controller: ['$scope', function ($scope) {
 
           var self;
           var wizardElement;
           var wizardOptions = {};
           var steps = []; 
           this.currentIndex = 0;
           this.firstIndex = 0;
           this.navigationLength = 0;
 
           this.addStep = function (step) {              steps.push(step);              if (!step.element || !step.submitController) return;              // if a rcSubmitController is being used, automatically add a _hidden_              // submit button so that               // in order to place an submit button that is still functional it              // has to technically be visible, so instead when place it way off              // screen             jQuery(step.element)               .append('<input type="submit" tabindex="-1"' +                       'style="position: absolute; left: -9999px; width: 1px; height: 1px;" />')               .attr('action', 'javascript:void(0);');              // bind to the submit complete event on the rcSubmitController and              // if the action was successful, trigger a next on the wizard.             step.submitController.onSubmitComplete(function (evt) {               if (evt.success) {                 self.onForward(step);               }             });           };
 
           this.forward = function () {
              var currentStep = (steps.length > self.currentIndex) ?                                steps[self.currentIndex] : null;              if (!currentStep) return;              if (currentStep.submitController) {               currentStep.submitController.submit();             } else {               self.onForward(currentStep);
             }
           };            var onForward = function(currentStep) {              if (currentStep.formController && currentStep.formController.$invalid) return; 
             wizardElement.bootstrapWizard('next');
           };

The addStep method takes in a ‘step‘ object that has useful information such as the step‘s element and its rcSubmit controller if it has one. Using this information we can tap into that step‘s submit logic using onSubmitComplete. Using that, we can now base moving forward in the wizard on the successful completion of a form submission. We can also do some other nifty things to make a better user experience. For instance, if these steps are represented by forms, when can automatically add a hidden submit input so that submit-on-enter behavior works. You’ll note this code is designed to work with or without the rcSubmit controller. We try not to require additional modules unless their functionality is being used.

The rcWizard controller now has methods to properly track our enhanced steps. Next we need code that actually adds the steps to the rcWizard‘s controller. This will involve another new directive, ‘rcStep‘:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
    var rcWizardStepDirective = {
      'rcStep': function () {
        return {
          restrict: 'A',
          require: ['^rcWizard', '?form', '?rcSubmit'],
          link: function (scope, element, attributes, controllers) {
 
            var wizardController = controllers[0];
 
            // find all the optional controllers for the step
            var formController = controllers.length > 1 ? controllers[1] : null;
            var submitController = controllers.length > 2 ? controllers[2] : null;
 
            // add the step to the wizard controller
            var step = wizardController.addStep({ 
              'element': element, 
              'attributes': attributes, 
              'formController': formController,
              'submitController': submitController });
          }
        };
      }
    };

This directive is simple enough. It merely calls the addStep method on the rcWizard controller including the element, and the optional ngFormController and rcSubmit controller. These are optional because they are not always necessary for a basic wizard to function.

That’s enough javascript coding. We now need to update the markup to take advantage of our new functionality:

1
2
3
4
5
6
7
8
    <div ng-app="SampleWizardApp" ng-controller="SampleWizardController"
         rc-wizard="sampleWizard">
      <ul class="nav nav-pills">
        <li class="active">
          <a class="active" href="#first" data-toggle="tab">
            <span>1</span> <span>First Step</span>
          </a>
        </li>

19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
      <div class="tab-content">
        <form class="tab-pane active" id="first" name="firstForm" novalidate rc-submit rc-step>
          <h2>Enter first step data</h2>
          <div class="form-group">
            <label class="control-label">First Name</label>
            <input name="firstName" class="form-control" type="text" 
              ng-model="user.firstName" required />
          </div>
          <div class="form-group">
            <label class="control-label">Last Name</label>
            <input name="lastName" class="form-control" type="text" 
              ng-model="user.lastName" required />
          </div>
        </form>
        <form class="tab-pane" id="second" name="secondForm" novalidate rc-submit rc-step>
          <h2>Enter second step data</h2>
          <div class="form-group">
            <label class="control-label">Street Address</label>
            <input name="streetAddress" class="form-control" type="text" 
              ng-model="user.streetAddress" />
          </div>
          <div class="form-group">
            <label class="control-label">City</label>
            <input name="city" class="form-control" type="text" 
              ng-model="user.city" />
          </div>
          <div class="form-group">
            <label class="control-label">State</label>
            <input name="state" class="form-control" type="text" 
              ng-model="user.state" />
          </div>
          <div class="form-group">
            <label class="control-label">City</label>
            <input name="postalCode" class="form-control" type="text" 
              ng-model="user.postalCode" />
          </div>
        </form>
        <form class="tab-pane" id="last" name="lastForm" novalidate rc-submit rc-step>
          <h2>Finish last step</h2>
          <div class="form-group">
            <label class="control-label">First Name:</label>
            <p class="form-control-static">{{ user.firstName }}</p>
          </div>
          <div class="form-group">
            <label class="control-label">Last Name:</label>
            <p class="form-control-static">{{ user.lastName }}</p>
          </div>
          <div class="form-group">
            <label class="control-label">Address:</label>
            <p class="form-control-static">
              {{ user.streetAddress }}
              <br />
              {{ user.city }}, {{ user.state }} {{ user.postalCode }}
            </p>
          </div>
        </form>
      </div>

All we did was convert all the tab-pane’s from div elements to form elements. We added our rcSubmit and rcStep directives as well. Now each step functions as its own validating form. In the first step we added some required attributes. Now if we attempt to continue to the next step it will not let us move forward until we fill out those fields.

This is all great and does exactly what we want to do. But since we are building based on some pretty cool directives, we can easily add some very helpful features. First, we can use our submission attempt tracking from this blog to highlight error’d fields when the user attempts to move forward too soon. Second, if we have a service call between steps that we need to wait for before moving forward and we want to disable the UI while we do so, we have rcDisabled from this blog. Using all these together we along with a little custom styling (i.e the rc-nav-wizard CSS class), we have an uber-wizard:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
   <div ng-controller="SampleWizardController" 
        rc-wizard="sampleWizard" rc-disabled="rc.firstForm.submitInProgress">
     <ul class="nav rc-nav-wizard">
       <li class="active">
         <a class="active" href="#first" data-toggle="tab">
           <span class="badge">1</span>
           <span>First Step</span>
         </a>
       </li>
       <li>
         <a href="#second" data-toggle="tab">
           <span class="badge">2</span>
           <span>Second Step</span>
         </a>
       </li>
       <li>
         <a href="#last" data-toggle="tab">
           <span class="badge">3</span>
           <span>Last Step</span>
         </a>
       </li>
     </ul>
     <div class="tab-content">
       <form class="tab-pane active" id="first" name="firstForm" 
             rc-submit="saveState()" rc-step novalidate>
         <h2>Enter first step data</h2>
         <div class="form-group"
              ng-class="{'has-error': rc.firstForm.needsAttention(firstForm.firstName)}">
           <label class="control-label">First Name</label>
           <input name="firstName" class="form-control" type="text" required
                  ng-model="user.firstName"/>
         </div>
         <div class="form-group"
              ng-class="{'has-error': rc.firstForm.needsAttention(firstForm.lastName)}">
           <label class="control-label">Last Name</label>
           <input name="lastName" class="form-control" type="text" required
                  ng-model="user.lastName" />
         </div>
       </form>

And there you have it. A nice little wizard module. This is also a great example of how building modular components can really boost future development. As usual, we’ve made a Plunker and the source and examples are available on our Github. (Please note, all solutions presented in this post are based on AngularJS 1.0.8 and Bootstrap 3.0.0)

  • Sriram Viswanathan

    Good idea. I also have a ‘wizard’ sort of interface but using the angular ui-router (essential a state machine for views/html) and then make decisions based on state changes.

    • Andy Norborg

      Thanks! I haven’t used ui-router myself, but looked it over. Sounds like a good approach as well.

      • Bhrami

        How would you recommend to split the components into different html, for each step. If I plan on using this Wizard, I want each step to be in own html, like step1.html, step2.html. Can you please give you feed back on that.

  • Diogenes Pendergast

    Quite an interesting article. But correct me if I’m wrong, the current code only disables the wizard on the first form and is not able to dynamically disable the other steps?

    rc-wizard=”sampleWizard” rc-disabled=”rc.firstForm.submitInProgress”

    • Andy Norborg

      Thank you for the feedback. Yes, only the first step is disabled in the example to show it can be done on a step by step basis. Perhaps not all steps will require the disable functionality, but you can put the directive on any element you wish.

      • Andy Norborg

        For clarity, I meant the submitInProgress is on each form. You could modify the directive to do it for any of them by rc-disabled=”rc.firstForm.submitInProgress || rc.secondForm.submitInProgress…” etc.

      • Diogenes Pendergast

        Right. Cool stuff.

  • Paasan26

    Nice post! I’m trying to use your code and dynamically build the steps with ng-repeat on the navigation items and the forms in tab-content. This has led to a couple of problems. When adding the navigation and forms with ng-repeat the rcWizard do not know about the number of steps when the rcWizard code is run and gives errors because of this. Is there any way I can initialize an empty wizard and dynamically add steps?

    • Andy Norborg

      Please see my response in the question above.

  • Paasan26

    Is it possible to dynamically build the wizard by ie. reading steps with questions from a JSON? I tried but failed to do so because the rcWizard and jquery.bootstrap.wizard didn’t know about the navigationLength, number of steps etc. since they were added by using ng-repeat on the navigation and form elements.

    • Andy Norborg

      Unfortunately there is a bug in the jquery.bootstrap.wizard preventing it from fully supporting dynamic steps (https://github.com/VinceG/twitter-bootstrap-wizard/issues/35). I did submit a pull request a couple months back with a possible fix, but it has not been accepted.

      You can attempt to grab the code from the pull request and use that. Then create an accessor method on the wizardController called ‘reset’ that calls the bootstrapWizard.resetWizard method (similar to what we do with the backward method).

      From there you could create a ‘rcStepSrc’ directive. Similar to the ‘rcStep’, have it require the ‘rcWizard’ as a parent, but also a ‘ngRepeat’ as a sibling. The do a scope.$watch(attributes.ngRepeat, function() { wizardController.reset() }). This will call the new reset method when the ngRepeat is changed. This should update the bootstrapWizard appropriately to be aware of your dynamic steps.

      I hope that helps. Its difficult to explain these solutions in a few sentences sometimes.

      • Paasan26

        Thanks for the input.
        I’ve created a plunker for it: http://plnkr.co/edit/A2zZxSBvG6ZD1awIJhUG?p=preview
        but it doesn’t work. I didn’t quite understand the part about require ‘ngRepeat’ as sibling.

        Steps taken:
        * Updated jquery.bootstrap.wizard.js from pull request
        * Define dynamic steps in sampleWizardController.js
        * Use ng-repeat on li and form in index.html
        * Added reset function in rcWizard.js:69-71
        * Added rcWizardStepSrcDirective in rcWizard.js:174-201
        * Added scope.$watch(..) in rcWizardStepSrcDirective in rcWizard.js:184-186
        * Used rc-step-src directive in form in index.html:45

        • Andy Norborg

          You were very close. Just a couple changes I was able to get it working. For rcStepSrc, forget about ngRepeat. I forgot that has a complex expression that cannot be automatically evaluated on the scope. Instead, just use scope.$watch(rcStepSrc, …). If you don’t set a value for rcStepSrc it’ll be called for every scope change, so I would recommend setting it to ‘steps’ in your example. I also removed the additional code you copied from rcStep. You just needed the rcWizard controller. And finally the method for reseting the wizard in bootstrapWizard was ‘resetWizard’, not just ‘reset’. Here is an working updated form of your plunker: http://plnkr.co/edit/8e7ji8sAQXF0CqC2nMdi

          • Paasan26

            That’s awesome 🙂 Thanks for the help!

            And holiday greetings from Norway!

          • Johnathan Hair

            Thanks Andy and Paasan26,

            The conversation and the code on plunker is going to save me considerable time and helped me understand AngularJS a little more, specifically in relation to directives.

          • Pratik

            This Plnker is not working as per requirement

  • Tyler Harrington

    How are the indexes determined? How do you count the navigation length? I built a 3 step wizard but it thinks the second step is really the last step.

  • Michael Randall

    Hi, I love this wizard. It is (almost) perfect for my requirements.

    I have a situation though, when based on a user selection in the first step, that they need to skip the second step. I have down everything I can think off (this is not that much given that I’m new to angular) in the controller and the wizard directive to make this happen, but with no success.

    Anybody able to tackle this problem for me? Or, at least point me in the right direction? Thanks!!

    • Pratik

      I am facing the same problem?? Any solution you got for it???

  • Guest

    Hey Andy,
    First of all – great work! so usaful! (this and the form validation)

    I have a question, i might be missing something.
    How can I validate each field by it content? (email validator, password validtor etc)?

    Thanks

  • http://www.kieran.co Kieran Whiteman

    Great article and wizard.

    One thing to note that happens even in the demo is that you can’t tab to the next or back button as it doesn’t have a href attached to it, so it doesn’t appear in the tab order. Fix for this is simple just create a href of href=”javascript:void(0);”

    Hope this helps somebody else out.

  • Stefan Koch

    Hi,
    First of all – thank you so much for sharing your awesome work with us! Thanks!

    I was wondering how I can inject the wizard to a controller. Sorry for this question,
    but I’m a newbie with AngularJS. And sorry for my English as well 😮

  • Marco

    is there a way to skip the next or previous tab if its disable. This should go to the next,prev tab that is enabled (jumping over the disabled onces)

  • Sudheer Polavarapu

    Firstly, thanks for the wonderful wizard utility. It really eased lot of complexity for the users.

    Actually we are using the wizard within a pop-up. When we open first time, all is good as it is opening with first form. But subsequent open is always going to the page where we left on previous open. I am trying to solve this by pointing to the first page while opening the wizard, but without luck. Any help or pointers will be helpful.

    Really appreciate your help

  • Derick Mayberry

    Hey Andy, I join Marco in needed a way to skip or disable a step or multiple steps… my wizard is doing alot of things based off of what you select on the first few forms and disabling with $(‘#rootWizard’).bootstrapWizard(‘disable’,1); doesn’t work because of a bug in the boostrapwizard code that disabled the step but doesn’t skip it, hiding does the same thing…hides the wizard nav but still lets the nav pane show. so I’ve tried everything in my not so significant power to get this working but have been unsuccessful. Just a direction would be nice.

    ng-if on both the li and the form works for removing but when it gets shown again its not working.

  • Smilie

    Hi Andy i used Your code to design my Wizard application but its not working for me .Actually I have four Wizard tabs with first and second wizard tabs with Back and Continue buttons those pages need to be saved ,the third tab which takes data from first two tabs with Submit and Back, the last tab is is just diplaying the data from the third sucessfully submitted.But those Continue and Back buttons are those automatically will update to the index tab but its not working at all.Can you plz me with that layout

  • Momoski

    Hi, I really loved your code and it saved me a lot of time. I have a question though what was asked before but not answered. How would you proceed if you want to create separate HTML for each step in order to increase readability and maintainability.

  • ‫خالد جمال‬‎

    Hi There, How can I disable/enable validation based on custom conditions for specific step? Thanks.