src/wizard/wizard-step.component.js
/**
* @ngdoc directive
* @name patternfly.wizard.directive:pfWizardStep
* @restrict E
*
* @description
* Component for rendering a Wizard step. Each step can stand alone or have substeps. This directive can only be used as a child of pf-wizard.
*
* @param {string} stepTitle The step title displayed in the header and used for the review screen when displayed
* @param {string} stepId Sets the text identifier of the step
* @param {number} stepPriority This sets the priority of this wizard step relative to other wizard steps. They should be numbered sequentially in the order they should be viewed.
* @param {boolean} substeps Sets whether this step has substeps
* @param {boolean=} nextEnabled Sets whether the next button should be enabled when this step is first displayed
* @param {boolean=} prevEnabled Sets whether the back button should be enabled when this step is first displayed
* @param {string=} nextTooltip The text to display as a tooltip on the next button
* @param {string=} prevTooltip The text to display as a tooltip on the back button
* @param {boolean=} wzDisabled Hides the step when set to True
* @param {boolean} okToNavAway Sets whether or not it's ok for the user to leave this page
* @param {boolean} allowClickNav Sets whether the user can click on the numeric step indicators to navigate directly to this step
* @param {string=} description The step description (optional)
* @param {object} wizardData Data passed to the step that is shared by the entire wizard
* @param {function()=} onShow The function called when the wizard shows this step
* @param {object=} focusSelectors Array of selectors to be used (in the order given) to find the initial focus component for the page
* @param {boolean=} showReview Indicates whether review information should be displayed for this step when the review step is reached
* @param {boolean=} showReviewDetails Indicators whether the review information should be expanded by default when the review step is reached
* @param {string=} reviewTemplate The template that should be used for the review details screen
*/
angular.module('patternfly.wizard').component('pfWizardStep', {
transclude: true,
bindings: {
stepTitle: '@',
stepId: '@',
stepPriority: '@',
substeps: '=?',
nextEnabled: '<?',
prevEnabled: '<?',
nextTooltip: '<?',
prevTooltip: '<?',
disabled: '@?wzDisabled',
okToNavAway: '<?',
allowClickNav: '<?',
description: '@',
wizardData: '=',
onShow: '=?',
focusSelectors: '<?',
showReview: '@?',
showReviewDetails: '@?',
reviewTemplate: '@?'
},
templateUrl: 'wizard/wizard-step.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.stepTitle === titleToFind) {
foundStep = step;
}
});
return foundStep;
};
var findWizard = function (scope) {
var wizard;
if (scope) {
if (angular.isDefined(scope.wizard)) {
wizard = scope.wizard;
} else {
wizard = findWizard(scope.$parent);
}
}
return wizard;
};
ctrl.$onInit = function () {
firstRun = true;
ctrl.steps = [];
ctrl.context = {};
ctrl.title = ctrl.stepTitle;
ctrl.wizard = findWizard($scope.$parent);
ctrl.contentStyle = ctrl.wizard.contentStyle;
// Provide wizard step controls to sub-steps
$scope.wizardStep = this;
ctrl.wizard.addStep(ctrl);
ctrl.pageNumber = ctrl.wizard.getStepNumber(ctrl);
if (angular.isUndefined(ctrl.nextEnabled)) {
ctrl.nextEnabled = true;
}
if (angular.isUndefined(ctrl.prevEnabled)) {
ctrl.prevEnabled = true;
}
if (angular.isUndefined(ctrl.showReview)) {
ctrl.showReview = false;
}
if (angular.isUndefined(ctrl.showReviewDetails)) {
ctrl.showReviewDetails = false;
}
if (angular.isUndefined(ctrl.stepPriority)) {
ctrl.stepPriority = 999;
} else {
ctrl.stepPriority = parseInt(ctrl.stepPriority);
}
if (angular.isUndefined(ctrl.okToNavAway)) {
ctrl.okToNavAway = true;
}
if (angular.isUndefined(ctrl.allowClickNav)) {
ctrl.allowClickNav = true;
}
if (ctrl.substeps && !ctrl.onShow) {
ctrl.onShow = function () {
$timeout(function () {
if (!ctrl.selectedStep) {
ctrl.goTo(ctrl.getEnabledSteps()[0]);
}
}, 10);
};
}
};
ctrl.$onChanges = function (changesObj) {
if (changesObj.nextTooltip) {
if (_.get(ctrl.wizard, 'selectedStep') === ctrl) {
ctrl.wizard.nextTooltip = changesObj.nextTooltip.currentValue;
}
}
if (changesObj.prevTooltip) {
if (_.get(ctrl.wizard, 'selectedStep') === ctrl) {
ctrl.wizard.prevTooltip = changesObj.prevTooltip.currentValue;
}
}
};
ctrl.getEnabledSteps = function () {
return ctrl.steps.filter(function (step) {
return step.disabled !== 'true';
});
};
ctrl.getReviewSteps = function () {
var reviewSteps = ctrl.getEnabledSteps().filter(function (step) {
return !angular.isUndefined(step.reviewTemplate);
});
return reviewSteps;
};
ctrl.resetNav = function () {
ctrl.goTo(ctrl.getEnabledSteps()[0]);
};
ctrl.currentStepNumber = function () {
//retrieve current step number
return stepIdx(ctrl.selectedStep) + 1;
};
ctrl.getStepNumber = function (step) {
return stepIdx(step) + 1;
};
ctrl.isNextEnabled = function () {
var enabled = angular.isUndefined(ctrl.nextEnabled) || ctrl.nextEnabled;
if (ctrl.substeps && ctrl.selectedStep) {
enabled = enabled && ctrl.selectedStep.isNextEnabled();
}
return enabled;
};
ctrl.isPrevEnabled = function () {
var enabled = angular.isUndefined(ctrl.prevEnabled) || ctrl.prevEnabled;
if (ctrl.substeps && ctrl.selectedStep) {
enabled = enabled && ctrl.selectedStep.isPrevEnabled();
}
return enabled;
};
ctrl.getStepDisplayNumber = function (step) {
return ctrl.pageNumber + String.fromCharCode(65 + stepIdx(step)) + ".";
};
ctrl.prevStepsComplete = function (nextStep) {
var nextIdx = stepIdx(nextStep);
var complete = true;
angular.forEach(ctrl.getEnabledSteps(), function (step, stepIndex) {
if (stepIndex < nextIdx) {
complete = complete && step.nextEnabled;
}
});
return complete;
};
ctrl.goTo = function (step) {
var focusElement = null;
if (ctrl.wizard.isWizardDone() || !step.okToNavAway || step === ctrl.selectedStep) {
return;
}
if (firstRun || (ctrl.getStepNumber(step) < ctrl.currentStepNumber() && ctrl.selectedStep.prevEnabled) || ctrl.prevStepsComplete(step)) {
unselectAll();
ctrl.selectedStep = step;
if (step) {
step.selected = true;
ctrl.wizard.setPageSelected(step);
if (angular.isFunction (ctrl.selectedStep.onShow)) {
ctrl.selectedStep.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);
ctrl.currentStep = step.stepTitle;
firstRun = false;
}
ctrl.wizard.updateSubStepNumber (stepIdx(ctrl.selectedStep));
}
};
ctrl.stepClick = function (step) {
if (step.allowClickNav) {
ctrl.goTo(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);
}
};
ctrl.currentStepTitle = function () {
return ctrl.selectedStep.stepTitle;
};
ctrl.currentStepDescription = function () {
return ctrl.selectedStep.description;
};
ctrl.currentStep = function () {
return ctrl.selectedStep;
};
ctrl.totalStepCount = function () {
return ctrl.getEnabledSteps().length;
};
// 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);
// Check if callback is a function
if (angular.isFunction (callback)) {
if (callback(ctrl.selectedStep)) {
if (index === enabledSteps.length - 1) {
return false;
}
// Go to the next step
ctrl.goTo(enabledSteps[index + 1]);
return true;
}
return true;
}
// Completed property set on scope 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) {
return false;
}
// Go to the next step
ctrl.goTo(enabledSteps[index + 1]);
return true;
};
ctrl.previous = function (callback) {
var index = stepIdx(ctrl.selectedStep);
var goPrev = false;
// Check if callback is a function
if (!angular.isFunction (callback) || callback(ctrl.selectedStep)) {
if (index !== 0) {
ctrl.goTo(ctrl.getEnabledSteps()[index - 1]);
goPrev = true;
}
}
return goPrev;
};
}
});