<!DOCTYPE html>
<html ng-app="app">
<head>
<link data-require="bootstrap-css@3.1.*" data-semver="3.1.1" rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css" />
<link data-require="font-awesome@*" data-semver="4.2.0" rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/font-awesome/4.2.0/css/font-awesome.css" />
<script data-require="angular.js@*" data-semver="1.3.7" src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.3.7/angular.js"></script>
<script data-require="ui-bootstrap@*" data-semver="0.12.0" src="http://angular-ui.github.io/bootstrap/ui-bootstrap-tpls-0.12.0.min.js"></script>
<script data-require="angular-animate@*" data-semver="1.3.5" src="https://code.angularjs.org/1.3.5/angular-animate.js"></script>
<link rel="stylesheet" href="ngWizard.css" />
<script src="app.js"></script>
<script src="ngWizard.js"></script>
</head>
<body ng-controller="wizard">
<h1>ngWizard Demo</h1>
<wizard current-step-number="currentStepNumber" submit="submit()">
<wizard-step title="step 1" entered="stepEntered()">
<p>step 1</p>
<input type="text" ng-model="requiredText" required placeholder="Required Field"/>
</wizard-step>
<wizard-step title="step 2" required-step-number="0" entered="stepEntered()">
<p>step 2</p>
<input type="email" ng-model="requiredText" required placeholder="Required Email Address"/>
</wizard-step>
<wizard-step ng-repeat="step in dynamicSteps" required-step-number="{{$index + 1}}" title="{{step}}">
<p>{{step}}</p>
<input type="text" required ng-model="localScope"/>
</wizard-step>
</wizard>
<br/>
<br/>
<p> --- End of Wizard --- </p>
<input type="text" ng-model="newStep"/>
<button class="btn btn-primary" ng-click="addNewStep(newStep)">Add New Step</button>
</body>
</html>
angular.module("ngWizard", [ "ui.bootstrap", "ngAnimate", "templates" ]).directive("wizard", [ "$window", "$q", function($window, $q) {
"use strict";
return {
restrict: "E",
transclude: true,
scope: {
currentStepNumber: "=",
submit: "&"
},
templateUrl: "src/wizardTemplate.html",
controller: function($scope) {
$scope.currentStepNumber = $scope.currentStepNumber || 0;
$scope.getCurrentStep = function() {
return $scope.steps[$scope.currentStepNumber];
};
this.getCurrentStep = $scope.getCurrentStep;
$scope.goToStepByReference = function(step) {
var stepNumber = $scope.steps.indexOf(step);
return $scope.goToStep(stepNumber);
};
var isValidStepNumber = function(stepNumber) {
return stepNumber < $scope.steps.length && stepNumber >= 0;
};
$scope.canGoToStep = function(stepNumber) {
if (!isValidStepNumber(stepNumber)) {
return false;
}
var newStep = $scope.steps[stepNumber];
return $scope.getStepState(newStep) != $scope.stepStatesEnum.disabled;
};
$scope.goToStep = function(stepNumber) {
if ($scope.canGoToStep(stepNumber)) {
$scope.currentStepNumber = stepNumber;
return true;
}
return false;
};
$scope.getStepState = function(step) {
if (step.requiredStepNumber && $scope.getStepState($scope.steps[step.requiredStepNumber]) != $scope.stepStatesEnum.complete) {
return $scope.stepStatesEnum.disabled;
} else if (step.stepForm.$valid) {
return $scope.stepStatesEnum.complete;
} else return $scope.stepStatesEnum.ready;
};
$scope.stepStatesEnum = {
disabled: 0,
ready: 1,
complete: 2
};
$scope.goToNext = function() {
$scope.goToStep($scope.currentStepNumber + 1);
};
$scope.hasNext = function() {
return $scope.steps.length > $scope.currentStepNumber + 1 && $scope.getStepState($scope.steps[$scope.currentStepNumber + 1]) != $scope.stepStatesEnum.disabled;
};
$scope.goToPrevious = function() {
$scope.goToStep($scope.currentStepNumber - 1);
};
$scope.hasPrevious = function() {
return $scope.currentStepNumber > 0;
};
$scope.getProgressPercentage = function() {
var completeSteps = $scope.steps.filter(function(step) {
return $scope.getStepState(step) == $scope.stepStatesEnum.complete;
});
return completeSteps.length / $scope.steps.length * 100;
};
$scope.steps = [];
this.registerStep = function(stepScope) {
$scope.steps.push(stepScope);
};
this.unregisterStep = function(stepScope) {
var index = $scope.steps.indexOf(stepScope);
if (index >= 0) {
$scope.steps.splice(index, 1);
}
};
$scope.isSubmittable = function() {
return $scope.steps.every(function(step) {
return $scope.getStepState(step) == $scope.stepStatesEnum.complete;
});
};
$scope.submitting = false;
$scope.onSubmitClicked = function() {
$scope.submitting = true;
$q.when($scope.submit()).then(function() {
$scope.submitting = false;
});
};
$scope.$watch("currentStepNumber", function(val, oldVal) {
if (val != oldVal) {
if (!$scope.canGoToStep(val)) {
if (oldVal && $scope.canGoToStep(oldVal)) {
$scope.currentStepNumber = oldVal;
} else $scope.currentStepNumber = 0;
} else {
$scope.getCurrentStep().entered();
}
}
});
$scope.$watch("steps.length", function() {
if (!$scope.getCurrentStep()) {
$scope.currentStepNumber = 0;
}
}, true);
}
};
} ]).directive("wizardStep", function() {
return {
require: "^wizard",
restrict: "E",
transclude: true,
scope: {
title: "@",
requiredStepNumber: "@",
entered: "&",
animation: "@"
},
template: "<ng-form name='stepForm' ng-show='isActive()' class='wizard-step animate' ng-class='animation || \"slide\"'><ng-transclude></ng-transclude></ng-form>",
link: function($scope, element, attrs, wizardCtrl) {
wizardCtrl.registerStep($scope);
$scope.isActive = function() {
return $scope == wizardCtrl.getCurrentStep();
};
$scope.$on("$destroy", function() {
wizardCtrl.unregisterStep($scope);
});
}
};
});
angular.module("templates", [ "src/wizardTemplate.html" ]);
angular.module("src/wizardTemplate.html", []).run([ "$templateCache", function($templateCache) {
$templateCache.put("src/wizardTemplate.html", '<div class="row wizard-container">\n' + ' <div class="col-md-3 col-xs-12">\n' + ' <ul class="nav nav-pills nav-stacked wizard-sidebar">\n' + ' <li tooltip="{{getProgressPercentage() | number : 2}}%">\n' + " <progressbar value=\"getProgressPercentage()\" type=\"{{getProgressPercentage() == 100 ? 'success' : 'default'}}\"></progressbar>\n" + " </li>\n" + ' <li ng-repeat="step in steps" ng-class="{disabled: getStepState(step) == stepStatesEnum.disabled, active: getCurrentStep() == step}"\n' + ' ng-click="goToStepByReference(step)" ng-disabled="getStepState(step) == stepStatesEnum.disabled">\n' + " <a>\n" + ' {{step.title}} <i class="fa fa-check" ng-show="getStepState(step) == stepStatesEnum.complete"></i>\n' + " </a>\n" + " </li>\n" + " </ul>\n" + " </div>\n" + ' <div class="col-md-9 col-xs-12 wizard-main">\n' + ' <ul class="pager">\n' + ' <li class="previous" ng-class="{disabled: !hasPrevious()}"><a href="#" ng-click="goToPrevious()"><i class="fa fa-arrow-circle-left"></i> Previous</a></li>\n' + ' <li ng-repeat="step in steps">\n' + " <i class=\"fa\" ng-class=\"{'fa-circle-o disabled': getStepState(step) == stepStatesEnum.disabled, 'fa-circle': getStepState(step) == stepStatesEnum.complete, 'fa-circle-o': getStepState(step) == stepStatesEnum.ready, selected: getCurrentStep() == step}\"\n" + ' ng-click="goToStepByReference(step)" tooltip="{{step.title}}"></i>\n' + " </li>\n" + ' <li class="next" ng-class="{disabled: !hasNext()}"><a href="#" ng-click="goToNext()">Next <i class="fa fa-arrow-circle-right"></i></a></li>\n' + " </ul>\n" + ' <div class="wizard-step-container" ng-transclude></div>\n' + " </div>\n" + ' <div class="row">\n' + ' <div class="col-xs-12">\n' + " <!--Don't know why, but ng-hide doesn't work here, use ng-class instead-->\n" + ' <button class="btn btn-primary btn-block submit" ng-class="{\'ng-hide\': !isSubmittable()}" ng-click="onSubmitClicked()" ng-disabled="submitting">Submit <i class="fa fa-circle-o-notch fa-spin" ng-show="submitting"></i></button>\n' + " </div>\n" + " </div>\n" + "</div>");
} ]);
.animate.slide.ng-hide-remove-active,
.animate.slide.ng-hide-add-active {
-webkit-transition: 0.5s all ease;
position: relative;
}
.animate.slide.ng-hide-remove.ng-hide-remove-active {
transform: translateX(0);
-ms-transform: translateX(0);
-moz-transform: translateX(0);
-webkit-transform: translateX(0);
opacity: 1;
}
.animate.slide.ng-hide-add {
display: none !important;
}
.animate.slide.ng-hide-remove {
transform: translateX(20%);
-ms-transform: translateX(20%);
-moz-transform: translateX(20%);
-webkit-transform: translateX(20%);
opacity: 0.5;
}
.animate.fade-in.ng-hide-remove-active,
.animate.fade-in.ng-hide-add-active {
-webkit-transition: 0.5s all ease;
position: relative;
}
.animate.fade-in.ng-hide-remove.ng-hide-remove-active {
opacity: 1;
}
.animate.fade-in.ng-hide-add {
display: none !important;
}
.animate.fade-in.ng-hide-remove {
opacity: 0;
}
.animate.zoom.ng-hide-remove-active,
.animate.zoom.ng-hide-add-active {
-webkit-transition: 0.5s all ease;
position: relative;
}
.animate.zoom.ng-hide-remove.ng-hide-remove-active {
transform: translateZ(0);
-ms-transform: translateZ(0);
-moz-transform: translateZ(0);
-webkit-transform: translateZ(0);
-webkit-filter: opacity(100%);
-moz-filter: opacity(100%);
-o-filter: opacity(100%);
-ms-filter: opacity(100%);
}
.animate.zoom.ng-hide-add {
display: none !important;
}
.animate.zoom.ng-hide-remove {
transform: translateZ(-250px);
-ms-transform: translateZ(-250px);
-moz-transform: translateZ(-250px);
-webkit-transform: translateZ(-250px);
-webkit-filter: opacity(10%);
-moz-filter: opacity(10%);
-o-filter: opacity(10%);
-ms-filter: opacity(10%);
}
.wizard-container {
background-color: #e7e7e7;
}
.wizard-container .wizard-main {
padding: 20px;
}
.wizard-container .wizard-main .pager i {
-webkit-transition: all 0.3s ease;
}
.wizard-container .wizard-main .pager i.selected {
color: #38a7e2;
-webkit-transform: translateY(-3px);
}
.wizard-container .wizard-main .pager i.disabled {
color: gray;
cursor: not-allowed;
}
.wizard-container .wizard-main .wizard-step-container {
overflow-x: hidden;
-webkit-perspective: 800px;
-moz-perspective: 800px;
-ms-perspective: 800px;
perspective: 800px;
}
.wizard-container .wizard-main .wizard-step {
background-color: white;
padding: 15px;
box-shadow: gray 5px 10px 30px;
display: block;
}
.wizard-container .wizard-sidebar {
margin-top: 65px;
margin-bottom: 30px;
background-color: white;
}
.wizard-container .wizard-sidebar .progress {
margin: 0;
}
.wizard-container .wizard-sidebar li.active a {
margin-left: 15px;
}
.wizard-container .wizard-sidebar li a {
-ms-transition: margin 0.5s ease;
-moz-transition: margin 0.5s ease;
-webkit-transition: margin 0.5s ease;
transition: margin 0.5s ease;
}
.wizard-container .btn.submit {
margin-bottom: 5px;
}
.wizard-container .btn.submit i.ng-hide-add {
display: none;
}
angular.module("app", ["ngWizard"])
.controller('wizard', function ($scope){
$scope.submit = function (){
alert("Submitted Wizard!");
}
$scope.addNewStep = function (newStep){
$scope.dynamicSteps.push(newStep);
}
$scope.dynamicSteps = ["step 3"];
$scope.dynamicRequiredText = {};
});