src/wizard/wizard.component.js
angular.module('patternfly.wizard').component('pfWizard', {
transclude: true,
bindings: {
title: '@',
wizardTitle: '@',
hideIndicators: '<?',
activeStepTitleOnly: '<?',
hideSidebar: '@',
hideHeader: '@',
hideBackButton: '@',
sidebarClass: '@',
stepClass: '@',
contentHeight: '<?',
currentStep: '<?',
cancelTitle: '<?',
backTitle: '<?',
nextTitle: '<?',
backCallback: '<?',
nextCallback: '<?',
onFinish: '&',
onCancel: '&',
wizardReady: '<?',
wizardDone: '<?',
loadingWizardTitle: '<?',
loadingSecondaryInformation: '<?',
embedInPage: '<?',
onStepChanged: '&?'
},
templateUrl: 'wizard/wizard.html',
controller: function ($timeout, $scope) {
'use strict';
var ctrl = this,
firstRun;
var stepIdx = function (step) {
var idx = 0;
var res = -1;
angular.forEach(ctrl.getEnabledSteps(), function (currStep) {
if (currStep === step) {
res = idx;
}
idx++;
});
return res;
};
var unselectAll = function () {
//traverse steps array and set each "selected" property to false
angular.forEach(ctrl.getEnabledSteps(), function (step) {
step.selected = false;
});
//set selectedStep variable to null
ctrl.selectedStep = null;
};
var stepByTitle = function (titleToFind) {
var foundStep = null;
angular.forEach(ctrl.getEnabledSteps(), function (step) {
if (step.title === titleToFind) {
foundStep = step;
}
});
return foundStep;
};
ctrl.$onInit = function () {
firstRun = true;
ctrl.steps = [];
ctrl.context = {};
ctrl.hideHeader = ctrl.hideHeader === 'true';
ctrl.hideSidebar = ctrl.hideSidebar === 'true';
ctrl.hideBackButton = ctrl.hideBackButton === 'true';
ctrl.activeStepTitleOnly = ctrl.activeStepTitleOnly === true;
// Use the wizardTitle first, if non-existent use the deprecated title parameter
ctrl.wizardTitle = ctrl.wizardTitle || ctrl.title;
// If a step class is given use it for all steps
if (angular.isDefined(ctrl.stepClass)) {
// If a sidebarClass is given, us it for sidebar panel, if not, apply the stepsClass to the sidebar panel
if (angular.isUndefined(ctrl.sidebarClass)) {
ctrl.sidebarClass = ctrl.stepClass;
}
} else {
// No step class give, setup the content style to allow scrolling and a fixed height
if (angular.isUndefined(ctrl.contentHeight)) {
ctrl.contentHeight = '300px';
}
ctrl.contentStyle = {
'height': ctrl.contentHeight,
'max-height': ctrl.contentHeight,
'overflow-y': 'auto'
};
}
if (angular.isUndefined(ctrl.wizardReady)) {
ctrl.wizardReady = true;
}
if (!ctrl.cancelTitle) {
ctrl.cancelTitle = "Cancel";
}
if (!ctrl.backTitle) {
ctrl.backTitle = "< Back";
}
if (!ctrl.nextTitle) {
ctrl.nextTitle = "Next >";
}
};
ctrl.$onChanges = function (changesObj) {
var step;
if (changesObj.hideHeader) {
ctrl.hideHeader = ctrl.hideHeader === 'true';
}
if (changesObj.hideSidebar) {
ctrl.hideSidebar = ctrl.hideSidebar === 'true';
}
if (changesObj.hideBackButton) {
ctrl.hideBackButton = ctrl.hideBackButton === 'true';
}
if (changesObj.wizardReady && changesObj.wizardReady.currentValue) {
ctrl.goTo(ctrl.getEnabledSteps()[0]);
}
if (changesObj.currentStep) {
//checking to make sure currentStep is truthy value
step = changesObj.currentStep.currentValue;
if (!step) {
return;
}
//setting stepTitle equal to current step title or default title
if (ctrl.selectedStep && ctrl.selectedStep.title !== step) {
ctrl.goTo(stepByTitle(step));
}
}
};
ctrl.getEnabledSteps = function () {
return ctrl.steps.filter(function (step) {
return step.disabled !== 'true';
});
};
ctrl.getReviewSteps = function () {
return ctrl.steps.filter(function (step) {
return !step.disabled &&
(!angular.isUndefined(step.reviewTemplate) || step.getReviewSteps().length > 0);
});
};
ctrl.currentStepNumber = function () {
//retrieve current step number
return stepIdx(ctrl.selectedStep) + 1;
};
ctrl.getStepNumber = function (step) {
return stepIdx(step) + 1;
};
ctrl.goTo = function (step, resetStepNav) {
var focusElement = null;
if (ctrl.wizardDone || (ctrl.selectedStep && !ctrl.selectedStep.okToNavAway) || step === ctrl.selectedStep) {
return;
}
if (firstRun || (ctrl.getStepNumber(step) < ctrl.currentStepNumber() && ctrl.selectedStep.isPrevEnabled()) || ctrl.selectedStep.isNextEnabled()) {
unselectAll();
if (!firstRun && resetStepNav && step.substeps) {
step.resetNav();
}
ctrl.selectedStep = step;
step.selected = true;
$timeout(function () {
if (angular.isFunction(step.onShow)) {
step.onShow();
}
// Give time for onShow to do its thing (maybe update the selectors), then time to display the elements
$timeout(function () {
if (step.focusSelectors) {
_.find(step.focusSelectors, function (selector) {
return focusElement = document.querySelector(selector);
});
}
// Default to next button if it is enabled
if (!focusElement && step.nextEnabled) {
focusElement = document.querySelector('.wizard-pf-next');
}
// Use cancel button if we haven't found anything else to set focus on
if (!focusElement) {
focusElement = document.querySelector('.wizard-pf-cancel');
}
if (focusElement) {
focusElement.focus();
}
}, 300);
}, 100);
// Make sure current step is not undefined
ctrl.currentStep = step.title;
ctrl.nextTooltip = step.nextTooltip;
ctrl.prevTooltip = step.prevTooltip;
//emit event upwards with data on goTo() invocation
if (!step.substeps) {
ctrl.setPageSelected(step);
}
firstRun = false;
}
if (!ctrl.selectedStep.substeps) {
ctrl.firstStep = stepIdx(ctrl.selectedStep) === 0;
} else {
ctrl.firstStep = stepIdx(ctrl.selectedStep) === 0 && ctrl.selectedStep.currentStepNumber() === 1;
}
};
ctrl.allowStepIndicatorClick = function (step) {
return step.allowClickNav &&
!ctrl.wizardDone &&
ctrl.selectedStep &&
ctrl.selectedStep.okToNavAway &&
(ctrl.selectedStep.nextEnabled || (step.stepPriority < ctrl.selectedStep.stepPriority)) &&
(ctrl.selectedStep.prevEnabled || (step.stepPriority > ctrl.selectedStep.stepPriority));
};
ctrl.stepClick = function (step) {
if (step.allowClickNav &&
ctrl.selectedStep &&
!ctrl.wizardDone &&
ctrl.selectedStep.okToNavAway &&
(ctrl.selectedStep.nextEnabled || (step.stepPriority < ctrl.selectedStep.stepPriority)) &&
(ctrl.selectedStep.prevEnabled || (step.stepPriority > ctrl.selectedStep.stepPriority))) {
ctrl.goTo(step, true);
}
};
ctrl.setPageSelected = function (step) {
if (angular.isFunction(ctrl.onStepChanged)) {
ctrl.onStepChanged({step: step, index: stepIdx(step)});
}
};
ctrl.addStep = function (step) {
// Insert the step into step array
var insertBefore = _.find(ctrl.steps, function (nextStep) {
return nextStep.stepPriority > step.stepPriority;
});
if (insertBefore) {
ctrl.steps.splice(ctrl.steps.indexOf(insertBefore), 0, step);
} else {
ctrl.steps.push(step);
}
if (ctrl.wizardReady && (ctrl.getEnabledSteps().length > 0) && (step === ctrl.getEnabledSteps()[0])) {
ctrl.goTo(ctrl.getEnabledSteps()[0]);
}
};
ctrl.isWizardDone = function () {
return ctrl.wizardDone;
};
ctrl.updateSubStepNumber = function (value) {
ctrl.firstStep = stepIdx(ctrl.selectedStep) === 0 && value === 0;
};
ctrl.currentStepTitle = function () {
return ctrl.selectedStep.title;
};
ctrl.currentStepDescription = function () {
return ctrl.selectedStep.description;
};
ctrl.currentStep = function () {
return ctrl.selectedStep;
};
ctrl.totalStepCount = function () {
return ctrl.getEnabledSteps().length;
};
// Allow access to any step
ctrl.goToStep = function (step, resetStepNav) {
var enabledSteps = ctrl.getEnabledSteps();
var stepTo;
if (angular.isNumber(step)) {
stepTo = enabledSteps[step];
} else {
stepTo = stepByTitle(step);
}
ctrl.goTo(stepTo, resetStepNav);
};
// Method used for next button within step
ctrl.next = function (callback) {
var enabledSteps = ctrl.getEnabledSteps();
// Save the step you were on when next() was invoked
var index = stepIdx(ctrl.selectedStep);
callback = callback || ctrl.nextCallback;
if (ctrl.selectedStep.substeps) {
if (ctrl.selectedStep.next(callback)) {
return;
}
}
// Check if callback is a function
if (angular.isFunction(callback)) {
if (callback(ctrl.selectedStep)) {
if (index < enabledSteps.length - 1) {
// Go to the next step
if (enabledSteps[index + 1].substeps) {
enabledSteps[index + 1].resetNav();
}
} else {
ctrl.finish();
}
} else {
return;
}
}
// Completed property set on ctrl which is used to add class/remove class from progress bar
ctrl.selectedStep.completed = true;
// Check to see if this is the last step. If it is next behaves the same as finish()
if (index === enabledSteps.length - 1) {
ctrl.finish();
} else {
// Go to the next step
ctrl.goTo(enabledSteps[index + 1]);
}
};
ctrl.previous = function (callback) {
var index = stepIdx(ctrl.selectedStep);
callback = callback || ctrl.backCallback;
if (ctrl.selectedStep.substeps) {
if (ctrl.selectedStep.previous(callback)) {
return;
}
}
// Check if callback is a function
if (!angular.isFunction(callback) || callback(ctrl.selectedStep)) {
if (index === 0) {
throw new Error("Can't go back. It's already in step 0");
} else {
ctrl.goTo(ctrl.getEnabledSteps()[index - 1]);
}
}
};
ctrl.finish = function () {
if (ctrl.onFinish) {
if (ctrl.onFinish() !== false) {
ctrl.reset();
}
}
};
ctrl.cancel = function () {
if (ctrl.onCancel) {
if (ctrl.onCancel() !== false) {
ctrl.reset();
}
}
};
//reset
ctrl.reset = function () {
//traverse steps array and set each "completed" property to false
angular.forEach(ctrl.getEnabledSteps(), function (step) {
step.completed = false;
});
//go to first step
ctrl.goToStep(0);
};
// Provide wizard controls to steps and sub-steps
$scope.wizard = this;
}
});