diff --git a/dist/angular-wizard.js b/dist/angular-wizard.js index 6380ed4..df76c27 100644 --- a/dist/angular-wizard.js +++ b/dist/angular-wizard.js @@ -1,6 +1,6 @@ /** * Easy to use Wizard library for Angular JS - * @version v1.1.1 - 2017-06-07 * @link https://github.com/mgonto/angular-wizard + * @version v1.1.2 - 2018-05-02 * @link https://github.com/mgonto/angular-wizard * @author Martin Gontovnikas * @license MIT License, http://www.opensource.org/licenses/MIT */ @@ -30,479 +30,495 @@ angular.module("wizard.html", []).run(["$templateCache", function($templateCache angular.module('mgo-angular-wizard', ['templates-angularwizard']); -angular.module('mgo-angular-wizard').directive('wzStep', function() { - return { - restrict: 'EA', - replace: true, - transclude: true, - scope: { - wzTitle: '@', - wzHeadingTitle: '@', - canenter : '=', - canexit : '=', - disabled: '@?wzDisabled', - description: '@', - wzData: '=', - wzOrder: '@?' - }, - require: '^wizard', - templateUrl: function(element, attributes) { - return attributes.template || "step.html"; - }, - link: function ($scope, $element, $attrs, wizard) { - $attrs.$observe('wzTitle', function (value) { - $scope.title = $scope.wzTitle; - }); - $scope.title = $scope.wzTitle; - wizard.addStep($scope); - $scope.$on('$destroy', function(){ - wizard.removeStep($scope); - }); - } - }; -}); - -//wizard directive -angular.module('mgo-angular-wizard').directive('wizard', function() { - return { - restrict: 'EA', - replace: true, - transclude: true, - scope: { - currentStep: '=', - onCancel: '&', - onFinish: '&', - hideIndicators: '=', - editMode: '=', - name: '@', - indicatorsPosition: '@?' - }, - templateUrl: function(element, attributes) { - return attributes.template || "wizard.html"; - }, - - //controller for wizard directive, treat this just like an angular controller - controller: ['$scope', '$element', '$log', 'WizardHandler', '$q', '$timeout', function ($scope, $element, $log, WizardHandler, $q, $timeout) { - //setting default step position if none declared. - if ($scope.indicatorsPosition == undefined) { - $scope.indicatorsPosition = 'bottom'; - } - //this variable allows directive to load without having to pass any step validation - var firstRun = true; - //creating instance of wizard, passing this as second argument allows access to functions attached to this via Service - WizardHandler.addWizard($scope.name || WizardHandler.defaultName, this); - - $scope.$on('$destroy', function() { - WizardHandler.removeWizard($scope.name || WizardHandler.defaultName); - }); - - //steps array where all the scopes of each step are added - $scope.steps = []; - - var stepIdx = function(step) { - var idx = 0; - var res = -1; - angular.forEach($scope.getEnabledSteps(), function(currStep) { - if (currStep === step) { - res = idx; - } - idx++; - }); - return res; - }; - - var stepByTitle = function(titleToFind) { - var foundStep = null; - angular.forEach($scope.getEnabledSteps(), function(step) { - if (step.wzTitle === titleToFind) { - foundStep = step; - } - }); - return foundStep; - }; - - - //update completed state for each step based on the editMode and current step number - var handleEditModeChange = function() { - var editMode = $scope.editMode; - if (angular.isUndefined(editMode) || (editMode === null)) return; - - //Set completed for all steps to the value of editMode - angular.forEach($scope.steps, function (step) { - step.completed = editMode; - }); - - //If editMode is false, set ONLY ENABLED steps with index lower then completedIndex to completed - if (!editMode) { - var completedStepsIndex = $scope.currentStepNumber() - 1; - angular.forEach($scope.getEnabledSteps(), function(step, stepIndex) { - if(stepIndex < completedStepsIndex) { - step.completed = true; - } - }); - } - }; - - //access to context object for step validation - $scope.context = {}; - - //watching changes to currentStep - $scope.$watch('currentStep', function(step) { - //checking to make sure currentStep is truthy value - if (!step) return; - //setting stepTitle equal to current step title or default title - var stepTitle = $scope.selectedStep.wzTitle; - if ($scope.selectedStep && stepTitle !== $scope.currentStep) { - //invoking goTo() with step title as argument - $scope.goTo(stepByTitle($scope.currentStep)); - } - }); - - //watching steps array length and editMode value, if edit module is undefined or null the nothing is done - //if edit mode is truthy, then all steps are marked as completed - $scope.$watch('[editMode, steps.length]', function() { - handleEditModeChange(); - }, true); - - //called each time step directive is loaded - this.addStep = function(step) { - var wzOrder = (step.wzOrder >= 0 && !$scope.steps[step.wzOrder]) ? step.wzOrder : $scope.steps.length; - //adding the scope of directive onto step array - $scope.steps[wzOrder] = step; - //if this step is the new first then goTo it - if ($scope.getEnabledSteps()[0] === step) { - //goTo first step - $scope.goTo($scope.getEnabledSteps()[0]); - } - }; - - //called each time step directive is destroyed - this.removeStep = function (step) { - var index = $scope.steps.indexOf(step); - if (index > 0) { - $scope.steps.splice(index, 1); - } - }; - - this.context = $scope.context; - - $scope.getStepNumber = function(step) { - return stepIdx(step) + 1; - }; - - $scope.goTo = function(step) { - //if this is the first time the wizard is loading it bi-passes step validation - if(firstRun){ - //deselect all steps so you can set fresh below - unselectAll(); - $scope.selectedStep = step; - //making sure current step is not undefined - if (!angular.isUndefined($scope.currentStep)) { - $scope.currentStep = step.wzTitle; - } - //setting selected step to argument passed into goTo() - step.selected = true; - //emit event upwards with data on goTo() invoktion - $scope.$emit('wizard:stepChanged', {step: step, index: stepIdx(step)}); - //setting variable to false so all other step changes must pass validation - firstRun = false; - } else { - //createing variables to capture current state that goTo() was invoked from and allow booleans - var thisStep; - //getting data for step you are transitioning out of - if($scope.currentStepNumber() > 0){ - thisStep = $scope.currentStepNumber() - 1; - } else if ($scope.currentStepNumber() === 0){ - thisStep = 0; - } - //$log.log('steps[thisStep] Data: ', $scope.getEnabledSteps()[thisStep].canexit); - $q.all([canExitStep($scope.getEnabledSteps()[thisStep], step), canEnterStep(step)]).then(function(data) { - if(data[0] && data[1]){ - //deselect all steps so you can set fresh below - unselectAll(); - - //$log.log('value for canExit argument: ', $scope.currentStep.canexit); - $scope.selectedStep = step; - //making sure current step is not undefined - if(!angular.isUndefined($scope.currentStep)){ - $scope.currentStep = step.wzTitle; - } - //setting selected step to argument passed into goTo() - step.selected = true; - //emit event upwards with data on goTo() invoktion - $scope.$emit('wizard:stepChanged', {step: step, index: stepIdx(step)}); - //$log.log('current step number: ', $scope.currentStepNumber()); - } else { - $scope.$emit('wizard:stepChangeFailed', {step: step, index: _.indexOf($scope.getEnabledSteps(), step)}); - } - }); - } - }; - - function canEnterStep(step) { - var defer; - var canEnter; - //If no validation function is provided, allow the user to enter the step - if(step.canenter === undefined){ - return true; - } - //If canenter is a boolean value instead of a function, return the value - if(typeof step.canenter === 'boolean'){ - return step.canenter; - } - //If canenter is a string instead of a function, evaluate the function - if(typeof step.canenter === 'string'){ - var splitFunction = step.canenter.split('('); - canEnter = eval('$scope.$parent.' + splitFunction[0] + '($scope.context' + splitFunction[1]) - } else { - canEnter = step.canenter($scope.context); - } - //Check to see if the canenter function is a promise which needs to be returned - if(angular.isFunction(canEnter.then)){ - defer = $q.defer(); - canEnter.then(function(response){ - defer.resolve(response); - }); - return defer.promise; - } else { - return canEnter === true; - } - } - - function canExitStep(step, stepTo) { - var defer; - var canExit; - //Exiting the step should be allowed if no validation function was provided or if the user is moving backwards - if(typeof(step.canexit) === 'undefined' || $scope.getStepNumber(stepTo) < $scope.currentStepNumber()){ - return true; - } - //If canexit is a boolean value instead of a function, return the value - if(typeof step.canexit === 'boolean'){ - return step.canexit; - } - //If canenter is a string instead of a function, evaluate the function - if(typeof step.canexit === 'string'){ - var splitFunction = step.canexit.split('('); - canExit = eval('$scope.$parent.' + splitFunction[0] + '($scope.context' + splitFunction[1]) - } else { - canExit = step.canexit($scope.context); - } - //Check to see if the canexit function is a promise which needs to be returned - if(angular.isFunction(canExit.then)){ - defer = $q.defer(); - canExit.then(function(response){ - defer.resolve(response); - }); - return defer.promise; - } else { - return canExit === true; - } - } - - $scope.currentStepNumber = function() { - //retreive current step number - return stepIdx($scope.selectedStep) + 1; - }; - - $scope.getEnabledSteps = function() { - return $scope.steps.filter(function(step){ - return step && step.disabled !== 'true'; - }); - }; - - //unSelect All Steps - function unselectAll() { - //traverse steps array and set each "selected" property to false - angular.forEach($scope.getEnabledSteps(), function (step) { - step.selected = false; - }); - //set selectedStep variable to null - $scope.selectedStep = null; - } - - //ALL METHODS ATTACHED TO this ARE ACCESSIBLE VIA WizardHandler.wizard().methodName() - - this.currentStepTitle = function(){ - return $scope.selectedStep.wzTitle; - }; - - this.currentStepDescription = function(){ - return $scope.selectedStep.description; - }; - - this.currentStep = function(){ - return $scope.selectedStep; - }; - - this.totalStepCount = function() { - return $scope.getEnabledSteps().length; - }; - - //Access to enabled steps from outside - this.getEnabledSteps = function(){ - return $scope.getEnabledSteps(); - }; - - //Access to current step number from outside - this.currentStepNumber = function(){ - return $scope.currentStepNumber(); - }; - //method used for next button within step - this.next = function(callback) { - var enabledSteps = $scope.getEnabledSteps(); - //setting variable equal to step you were on when next() was invoked - var index = stepIdx($scope.selectedStep); - //checking to see if callback is a function - if(angular.isFunction(callback)){ - if(callback()){ - if (index === enabledSteps.length - 1) { - this.finish(); - } else { - //invoking goTo() with step number next in line - $scope.goTo(enabledSteps[index + 1]); - } - } else { - return; - } - } - if (!callback) { - //completed property set on scope which is used to add class/remove class from progress bar - $scope.selectedStep.completed = true; - } - //checking to see if this is the last step. If it is next behaves the same as finish() - if (index === enabledSteps.length - 1) { - this.finish(); - } else { - //invoking goTo() with step number next in line - $scope.goTo(enabledSteps[index + 1]); - } - - }; - - //used to traverse to any step, step number placed as argument - this.goTo = function(step) { - //wrapped inside $timeout so newly enabled steps are included. - $timeout(function() { - var enabledSteps = $scope.getEnabledSteps(); - var stepTo; - //checking that step is a Number - if (angular.isNumber(step)) { - stepTo = enabledSteps[step]; - } else { - //finding the step associated with the title entered as goTo argument - stepTo = stepByTitle(step); - } - //going to step - $scope.goTo(stepTo); - }); - }; - - //calls finish() which calls onFinish() which is declared on an attribute and linked to controller via wizard directive. - this.finish = function() { - if ($scope.onFinish) { - $scope.onFinish(); - } - }; - - this.previous = function() { - //getting index of current step - var index = stepIdx($scope.selectedStep); - //ensuring you aren't trying to go back from the first step - if (index === 0) { - throw new Error("Can't go back. It's already in step 0"); - } else { - //go back one step from current step - $scope.goTo($scope.getEnabledSteps()[index - 1]); - } - }; - - //cancel is alias for previous. - this.cancel = function() { - if ($scope.onCancel) { - //onCancel is linked to controller via wizard directive: - $scope.onCancel(); - } else { - //getting index of current step - var index = stepIdx($scope.selectedStep); - //ensuring you aren't trying to go back from the first step - if (index === 0) { - throw new Error("Can't go back. It's already in step 0"); - } else { - //go back one step from current step - $scope.goTo($scope.getEnabledSteps()[0]); - } - } - }; - - //reset - this.reset = function(){ - //traverse steps array and set each "completed" property to false - angular.forEach($scope.getEnabledSteps(), function (step) { - step.completed = false; - }); - //go to first step - this.goTo(0); - }; - - //change edit mode - this.setEditMode = function(mode) { - $scope.editMode = mode; - handleEditModeChange(); - }; - }] - }; -}); - -function wizardButtonDirective(action) { - angular.module('mgo-angular-wizard') - .directive(action, function() { - return { - restrict: 'A', - replace: false, - require: '^wizard', - link: function($scope, $element, $attrs, wizard) { - - $element.on("click", function(e) { - e.preventDefault(); - $scope.$apply(function() { - $scope.$eval($attrs[action]); - wizard[action.replace("wz", "").toLowerCase()](); - }); - }); - } - }; - }); -} - -wizardButtonDirective('wzNext'); -wizardButtonDirective('wzPrevious'); -wizardButtonDirective('wzFinish'); -wizardButtonDirective('wzCancel'); -wizardButtonDirective('wzReset'); - -angular.module('mgo-angular-wizard').factory('WizardHandler', function() { - var service = {}; - - var wizards = {}; - - service.defaultName = "defaultWizard"; - - service.addWizard = function(name, wizard) { - wizards[name] = wizard; - }; - - service.removeWizard = function(name) { - delete wizards[name]; - }; - - service.wizard = function(name) { - var nameToUse = name; - if (!name) { - nameToUse = service.defaultName; - } - - return wizards[nameToUse]; - }; - - return service; -}); +angular.module('mgo-angular-wizard').directive('wzStep', function() { + return { + restrict: 'EA', + replace: true, + transclude: true, + scope: { + wzTitle: '@', + wzHeadingTitle: '@', + canenter : '=', + canexit : '=', + disabled: '@?wzDisabled', + description: '@', + wzData: '=', + wzOrder: '@?' + }, + require: '^wizard', + templateUrl: function(element, attributes) { + return attributes.template || "step.html"; + }, + link: function ($scope, $element, $attrs, wizard) { + $attrs.$observe('wzTitle', function (value) { + $scope.title = $scope.wzTitle; + }); + $scope.title = $scope.wzTitle; + wizard.addStep($scope); + $scope.$on('$destroy', function(){ + wizard.removeStep($scope); + }); + } + }; +}); + +//wizard directive +angular.module('mgo-angular-wizard').directive('wizard', function () { + return { + restrict: 'EA', + replace: true, + transclude: true, + scope: { + currentStep: '=', + onCancel: '&', + onFinish: '&', + hideIndicators: '=', + editMode: '=', + name: '@', + indicatorsPosition: '@?' + }, + templateUrl: function (element, attributes) { + return attributes.template || "wizard.html"; + }, + + //controller for wizard directive, treat this just like an angular controller + controller: ['$scope', '$element', '$log', 'WizardHandler', '$q', '$timeout', function ($scope, $element, $log, WizardHandler, $q, $timeout) { + //setting default step position if none declared. + if ($scope.indicatorsPosition == undefined) { + $scope.indicatorsPosition = 'bottom'; + } + //this variable allows directive to load without having to pass any step validation + var firstRun = true; + //creating instance of wizard, passing this as second argument allows access to functions attached to this via Service + WizardHandler.addWizard($scope.name || WizardHandler.defaultName, this); + + $scope.$on('$destroy', function () { + WizardHandler.removeWizard($scope.name || WizardHandler.defaultName); + }); + + //steps array where all the scopes of each step are added + $scope.steps = []; + + var stepIdx = function (step) { + var idx = 0; + var res = -1; + angular.forEach($scope.getEnabledSteps(), function (currStep) { + if (currStep === step) { + res = idx; + } + idx++; + }); + return res; + }; + + var stepByTitle = function (titleToFind) { + var foundStep = null; + angular.forEach($scope.getEnabledSteps(), function (step) { + if (step.wzTitle === titleToFind) { + foundStep = step; + } + }); + return foundStep; + }; + + + //update completed state for each step based on the editMode and current step number + var handleEditModeChange = function () { + var editMode = $scope.editMode; + if (angular.isUndefined(editMode) || (editMode === null)) return; + + //Set completed for all steps to the value of editMode + angular.forEach($scope.steps, function (step) { + step.completed = editMode; + }); + + //If editMode is false, set ONLY ENABLED steps with index lower then completedIndex to completed + if (!editMode) { + var completedStepsIndex = $scope.currentStepNumber() - 1; + angular.forEach($scope.getEnabledSteps(), function (step, stepIndex) { + if (stepIndex < completedStepsIndex) { + step.completed = true; + } + }); + } + }; + + //access to context object for step validation + $scope.context = {}; + + //watching changes to currentStep + $scope.$watch('currentStep', function (step) { + //checking to make sure currentStep is truthy value + if (!step) return; + //setting stepTitle equal to current step title or default title + var stepTitle = $scope.selectedStep.wzTitle; + if ($scope.selectedStep && stepTitle !== $scope.currentStep) { + //invoking goTo() with step title as argument + $scope.goTo(stepByTitle($scope.currentStep)); + } + }); + + //watching steps array length and editMode value, if edit module is undefined or null the nothing is done + //if edit mode is truthy, then all steps are marked as completed + $scope.$watch('[editMode, steps.length]', function () { + handleEditModeChange(); + }, true); + + //called each time step directive is loaded + this.addStep = function (step) { + var wzOrder = (step.wzOrder >= 0 && !$scope.steps[step.wzOrder]) ? step.wzOrder : $scope.steps.length; + //adding the scope of directive onto step array + $scope.steps[wzOrder] = step; + //if this step is the new first then goTo it + if ($scope.getEnabledSteps()[0] === step) { + //goTo first step + $scope.goTo($scope.getEnabledSteps()[0]); + } + }; + + //called each time step directive is destroyed + this.removeStep = function (step) { + var index = $scope.steps.indexOf(step); + if (index > 0) { + $scope.steps.splice(index, 1); + } + }; + + this.context = $scope.context; + + $scope.getStepNumber = function (step) { + return stepIdx(step) + 1; + }; + + $scope.goTo = function (step) { + //if this is the first time the wizard is loading it bi-passes step validation + if (firstRun) { + //deselect all steps so you can set fresh below + unselectAll(); + $scope.selectedStep = step; + //making sure current step is not undefined + if (!angular.isUndefined($scope.currentStep)) { + $scope.currentStep = step.wzTitle; + } + //setting selected step to argument passed into goTo() + step.selected = true; + //emit event upwards with data on goTo() invoktion + $scope.$emit('wizard:stepChanged', { step: step, index: stepIdx(step) }); + //setting variable to false so all other step changes must pass validation + firstRun = false; + } else { + //createing variables to capture current state that goTo() was invoked from and allow booleans + var thisStep; + //getting data for step you are transitioning out of + if ($scope.currentStepNumber() > 0) { + thisStep = $scope.currentStepNumber() - 1; + } else if ($scope.currentStepNumber() === 0) { + thisStep = 0; + } + //$log.log('steps[thisStep] Data: ', $scope.getEnabledSteps()[thisStep].canexit); + $q.all([canExitStep($scope.getEnabledSteps()[thisStep], step), canEnterStep(step)]).then(function (data) { + if (data[0] && data[1]) { + //deselect all steps so you can set fresh below + unselectAll(); + + //$log.log('value for canExit argument: ', $scope.currentStep.canexit); + $scope.selectedStep = step; + //making sure current step is not undefined + if (!angular.isUndefined($scope.currentStep)) { + $scope.currentStep = step.wzTitle; + } + //setting selected step to argument passed into goTo() + step.selected = true; + //emit event upwards with data on goTo() invoktion + $scope.$emit('wizard:stepChanged', { step: step, index: stepIdx(step) }); + //$log.log('current step number: ', $scope.currentStepNumber()); + } else { + $scope.$emit('wizard:stepChangeFailed', { step: step, index: _.indexOf($scope.getEnabledSteps(), step) }); + } + }); + } + }; + + function canEnterStep(step) { + var defer; + var canEnter; + //If no validation function is provided, allow the user to enter the step + if (step.canenter === undefined) { + return true; + } + //If canenter is a boolean value instead of a function, return the value + if (typeof step.canenter === 'boolean') { + return step.canenter; + } + //If canenter is a string instead of a function, evaluate the function + if (typeof step.canenter === 'string') { + var splitFunction = step.canenter.split('('); + canEnter = eval('$scope.$parent.' + splitFunction[0] + '($scope.context' + splitFunction[1]) + } else { + canEnter = step.canenter($scope.context); + } + //Check to see if the canenter function is a promise which needs to be returned + if (angular.isFunction(canEnter.then)) { + defer = $q.defer(); + canEnter.then(function (response) { + defer.resolve(response); + }); + return defer.promise; + } else { + return canEnter === true; + } + } + + function canExitStep(step, stepTo) { + var defer; + var canExit; + //Exiting the step should be allowed if no validation function was provided or if the user is moving backwards + if (typeof (step.canexit) === 'undefined' || $scope.getStepNumber(stepTo) < $scope.currentStepNumber()) { + return true; + } + //If canexit is a boolean value instead of a function, return the value + if (typeof step.canexit === 'boolean') { + return step.canexit; + } + //If canenter is a string instead of a function, evaluate the function + if (typeof step.canexit === 'string') { + var splitFunction = step.canexit.split('('); + canExit = eval('$scope.$parent.' + splitFunction[0] + '($scope.context' + splitFunction[1]) + } else { + canExit = step.canexit($scope.context); + } + //Check to see if the canexit function is a promise which needs to be returned + if (angular.isFunction(canExit.then)) { + defer = $q.defer(); + canExit.then(function (response) { + defer.resolve(response); + }); + return defer.promise; + } else { + return canExit === true; + } + } + + $scope.currentStepNumber = function () { + //retreive current step number + return stepIdx($scope.selectedStep) + 1; + }; + + $scope.getEnabledSteps = function () { + return $scope.steps.filter(function (step) { + return step && step.disabled !== 'true'; + }); + }; + + //unSelect All Steps + function unselectAll() { + //traverse steps array and set each "selected" property to false + angular.forEach($scope.getEnabledSteps(), function (step) { + step.selected = false; + }); + //set selectedStep variable to null + $scope.selectedStep = null; + } + + //ALL METHODS ATTACHED TO this ARE ACCESSIBLE VIA WizardHandler.wizard().methodName() + + this.currentStepTitle = function () { + return $scope.selectedStep.wzTitle; + }; + + this.currentStepDescription = function () { + return $scope.selectedStep.description; + }; + + this.currentStep = function () { + return $scope.selectedStep; + }; + + this.totalStepCount = function () { + return $scope.getEnabledSteps().length; + }; + + //Access to enabled steps from outside + this.getEnabledSteps = function () { + return $scope.getEnabledSteps(); + }; + + //Access to current step number from outside + this.currentStepNumber = function () { + return $scope.currentStepNumber(); + }; + //method used for next button within step + this.next = function (callback) { + var enabledSteps = $scope.getEnabledSteps(); + //setting variable equal to step you were on when next() was invoked + var index = stepIdx($scope.selectedStep); + //checking to see if callback is a function + if (angular.isFunction(callback)) { + if (callback()) { + if (index === enabledSteps.length - 1) { + this.finish(); + } else { + //invoking goTo() with step number next in line + $scope.goTo(enabledSteps[index + 1]); + } + } else { + return; + } + } + if (!callback) { + //completed property set on scope which is used to add class/remove class from progress bar + $scope.selectedStep.completed = true; + } + //checking to see if this is the last step. If it is next behaves the same as finish() + if (index === enabledSteps.length - 1) { + this.finish(); + } else { + //invoking goTo() with step number next in line + $scope.goTo(enabledSteps[index + 1]); + } + + }; + + //used to traverse to any step, step number placed as argument + this.goTo = function (step) { + //wrapped inside $timeout so newly enabled steps are included. + $timeout(function () { + var enabledSteps = $scope.getEnabledSteps(); + var stepTo; + //checking that step is a Number + if (angular.isNumber(step)) { + stepTo = enabledSteps[step]; + } else { + //finding the step associated with the title entered as goTo argument + stepTo = stepByTitle(step); + } + //going to step + $scope.goTo(stepTo); + }); + }; + + //calls finish() which calls onFinish() which is declared on an attribute and linked to controller via wizard directive. + this.finish = function () { + var thisStepNumber = $scope.currentStepNumber() - 1; + thisStepNumber = thisStepNumber < 0 ? 0 : thisStepNumber; + var thisStep = $scope.getEnabledSteps()[thisStepNumber]; + + $q.when(canExitStep(thisStep, thisStep)).then(function (result) { + if (typeof result === 'boolean') { + if (result === true) { + if ($scope.onFinish) { + $scope.onFinish(); + } + } + } else { + if ($scope.onFinish) { + $scope.onFinish(); + } + } + }, function (error) { + $scope.$emit('wizard:finishFailed', { step: thisStep, index: _.indexOf($scope.getEnabledSteps(), thisStep) }); + }); + }; + + this.previous = function () { + //getting index of current step + var index = stepIdx($scope.selectedStep); + //ensuring you aren't trying to go back from the first step + if (index === 0) { + throw new Error("Can't go back. It's already in step 0"); + } else { + //go back one step from current step + $scope.goTo($scope.getEnabledSteps()[index - 1]); + } + }; + + //cancel is alias for previous. + this.cancel = function () { + if ($scope.onCancel) { + //onCancel is linked to controller via wizard directive: + $scope.onCancel(); + } else { + //getting index of current step + var index = stepIdx($scope.selectedStep); + //ensuring you aren't trying to go back from the first step + if (index === 0) { + throw new Error("Can't go back. It's already in step 0"); + } else { + //go back one step from current step + $scope.goTo($scope.getEnabledSteps()[0]); + } + } + }; + + //reset + this.reset = function () { + //traverse steps array and set each "completed" property to false + angular.forEach($scope.getEnabledSteps(), function (step) { + step.completed = false; + }); + //go to first step + this.goTo(0); + }; + + //change edit mode + this.setEditMode = function (mode) { + $scope.editMode = mode; + handleEditModeChange(); + }; + }] + }; +}); + +function wizardButtonDirective(action) { + angular.module('mgo-angular-wizard') + .directive(action, function() { + return { + restrict: 'A', + replace: false, + require: '^wizard', + link: function($scope, $element, $attrs, wizard) { + + $element.on("click", function(e) { + e.preventDefault(); + $scope.$apply(function() { + $scope.$eval($attrs[action]); + wizard[action.replace("wz", "").toLowerCase()](); + }); + }); + } + }; + }); +} + +wizardButtonDirective('wzNext'); +wizardButtonDirective('wzPrevious'); +wizardButtonDirective('wzFinish'); +wizardButtonDirective('wzCancel'); +wizardButtonDirective('wzReset'); + +angular.module('mgo-angular-wizard').factory('WizardHandler', function() { + var service = {}; + + var wizards = {}; + + service.defaultName = "defaultWizard"; + + service.addWizard = function(name, wizard) { + wizards[name] = wizard; + }; + + service.removeWizard = function(name) { + delete wizards[name]; + }; + + service.wizard = function(name) { + var nameToUse = name; + if (!name) { + nameToUse = service.defaultName; + } + + return wizards[nameToUse]; + }; + + return service; +}); diff --git a/dist/angular-wizard.min.js b/dist/angular-wizard.min.js index e3c7e7e..4eb2ee8 100644 --- a/dist/angular-wizard.min.js +++ b/dist/angular-wizard.min.js @@ -1,7 +1,7 @@ /** * Easy to use Wizard library for Angular JS - * @version v1.1.1 - 2017-06-07 * @link https://github.com/mgonto/angular-wizard + * @version v1.1.2 - 2018-05-02 * @link https://github.com/mgonto/angular-wizard * @author Martin Gontovnikas * @license MIT License, http://www.opensource.org/licenses/MIT */ -function wizardButtonDirective(a){angular.module("mgo-angular-wizard").directive(a,function(){return{restrict:"A",replace:!1,require:"^wizard",link:function(b,c,d,e){c.on("click",function(c){c.preventDefault(),b.$apply(function(){b.$eval(d[a]),e[a.replace("wz","").toLowerCase()]()})})}}})}angular.module("templates-angularwizard",["step.html","wizard.html"]),angular.module("step.html",[]).run(["$templateCache",function(a){a.put("step.html",'
\n
')}]),angular.module("wizard.html",[]).run(["$templateCache",function(a){a.put("wizard.html",'
\n

{{ selectedStep.wzHeadingTitle }}

\n\n
\n \n
\n
\n')}]),angular.module("mgo-angular-wizard",["templates-angularwizard"]),angular.module("mgo-angular-wizard").directive("wzStep",function(){return{restrict:"EA",replace:!0,transclude:!0,scope:{wzTitle:"@",wzHeadingTitle:"@",canenter:"=",canexit:"=",disabled:"@?wzDisabled",description:"@",wzData:"=",wzOrder:"@?"},require:"^wizard",templateUrl:function(a,b){return b.template||"step.html"},link:function(a,b,c,d){c.$observe("wzTitle",function(b){a.title=a.wzTitle}),a.title=a.wzTitle,d.addStep(a),a.$on("$destroy",function(){d.removeStep(a)})}}}),angular.module("mgo-angular-wizard").directive("wizard",function(){return{restrict:"EA",replace:!0,transclude:!0,scope:{currentStep:"=",onCancel:"&",onFinish:"&",hideIndicators:"=",editMode:"=",name:"@",indicatorsPosition:"@?"},templateUrl:function(a,b){return b.template||"wizard.html"},controller:["$scope","$element","$log","WizardHandler","$q","$timeout",function($scope,$element,$log,WizardHandler,$q,$timeout){function canEnterStep(step){var defer,canEnter;if(void 0===step.canenter)return!0;if("boolean"==typeof step.canenter)return step.canenter;if("string"==typeof step.canenter){var splitFunction=step.canenter.split("(");canEnter=eval("$scope.$parent."+splitFunction[0]+"($scope.context"+splitFunction[1])}else canEnter=step.canenter($scope.context);return angular.isFunction(canEnter.then)?(defer=$q.defer(),canEnter.then(function(a){defer.resolve(a)}),defer.promise):canEnter===!0}function canExitStep(step,stepTo){var defer,canExit;if("undefined"==typeof step.canexit||$scope.getStepNumber(stepTo)<$scope.currentStepNumber())return!0;if("boolean"==typeof step.canexit)return step.canexit;if("string"==typeof step.canexit){var splitFunction=step.canexit.split("(");canExit=eval("$scope.$parent."+splitFunction[0]+"($scope.context"+splitFunction[1])}else canExit=step.canexit($scope.context);return angular.isFunction(canExit.then)?(defer=$q.defer(),canExit.then(function(a){defer.resolve(a)}),defer.promise):canExit===!0}function unselectAll(){angular.forEach($scope.getEnabledSteps(),function(a){a.selected=!1}),$scope.selectedStep=null}void 0==$scope.indicatorsPosition&&($scope.indicatorsPosition="bottom");var firstRun=!0;WizardHandler.addWizard($scope.name||WizardHandler.defaultName,this),$scope.$on("$destroy",function(){WizardHandler.removeWizard($scope.name||WizardHandler.defaultName)}),$scope.steps=[];var stepIdx=function(a){var b=0,c=-1;return angular.forEach($scope.getEnabledSteps(),function(d){d===a&&(c=b),b++}),c},stepByTitle=function(a){var b=null;return angular.forEach($scope.getEnabledSteps(),function(c){c.wzTitle===a&&(b=c)}),b},handleEditModeChange=function(){var a=$scope.editMode;if(!angular.isUndefined(a)&&null!==a&&(angular.forEach($scope.steps,function(b){b.completed=a}),!a)){var b=$scope.currentStepNumber()-1;angular.forEach($scope.getEnabledSteps(),function(a,c){b>c&&(a.completed=!0)})}};$scope.context={},$scope.$watch("currentStep",function(a){if(a){var b=$scope.selectedStep.wzTitle;$scope.selectedStep&&b!==$scope.currentStep&&$scope.goTo(stepByTitle($scope.currentStep))}}),$scope.$watch("[editMode, steps.length]",function(){handleEditModeChange()},!0),this.addStep=function(a){var b=a.wzOrder>=0&&!$scope.steps[a.wzOrder]?a.wzOrder:$scope.steps.length;$scope.steps[b]=a,$scope.getEnabledSteps()[0]===a&&$scope.goTo($scope.getEnabledSteps()[0])},this.removeStep=function(a){var b=$scope.steps.indexOf(a);b>0&&$scope.steps.splice(b,1)},this.context=$scope.context,$scope.getStepNumber=function(a){return stepIdx(a)+1},$scope.goTo=function(a){if(firstRun)unselectAll(),$scope.selectedStep=a,angular.isUndefined($scope.currentStep)||($scope.currentStep=a.wzTitle),a.selected=!0,$scope.$emit("wizard:stepChanged",{step:a,index:stepIdx(a)}),firstRun=!1;else{var b;$scope.currentStepNumber()>0?b=$scope.currentStepNumber()-1:0===$scope.currentStepNumber()&&(b=0),$q.all([canExitStep($scope.getEnabledSteps()[b],a),canEnterStep(a)]).then(function(b){b[0]&&b[1]?(unselectAll(),$scope.selectedStep=a,angular.isUndefined($scope.currentStep)||($scope.currentStep=a.wzTitle),a.selected=!0,$scope.$emit("wizard:stepChanged",{step:a,index:stepIdx(a)})):$scope.$emit("wizard:stepChangeFailed",{step:a,index:_.indexOf($scope.getEnabledSteps(),a)})})}},$scope.currentStepNumber=function(){return stepIdx($scope.selectedStep)+1},$scope.getEnabledSteps=function(){return $scope.steps.filter(function(a){return a&&"true"!==a.disabled})},this.currentStepTitle=function(){return $scope.selectedStep.wzTitle},this.currentStepDescription=function(){return $scope.selectedStep.description},this.currentStep=function(){return $scope.selectedStep},this.totalStepCount=function(){return $scope.getEnabledSteps().length},this.getEnabledSteps=function(){return $scope.getEnabledSteps()},this.currentStepNumber=function(){return $scope.currentStepNumber()},this.next=function(a){var b=$scope.getEnabledSteps(),c=stepIdx($scope.selectedStep);if(angular.isFunction(a)){if(!a())return;c===b.length-1?this.finish():$scope.goTo(b[c+1])}a||($scope.selectedStep.completed=!0),c===b.length-1?this.finish():$scope.goTo(b[c+1])},this.goTo=function(a){$timeout(function(){var b,c=$scope.getEnabledSteps();b=angular.isNumber(a)?c[a]:stepByTitle(a),$scope.goTo(b)})},this.finish=function(){$scope.onFinish&&$scope.onFinish()},this.previous=function(){var a=stepIdx($scope.selectedStep);if(0===a)throw new Error("Can't go back. It's already in step 0");$scope.goTo($scope.getEnabledSteps()[a-1])},this.cancel=function(){if($scope.onCancel)$scope.onCancel();else{var a=stepIdx($scope.selectedStep);if(0===a)throw new Error("Can't go back. It's already in step 0");$scope.goTo($scope.getEnabledSteps()[0])}},this.reset=function(){angular.forEach($scope.getEnabledSteps(),function(a){a.completed=!1}),this.goTo(0)},this.setEditMode=function(a){$scope.editMode=a,handleEditModeChange()}}]}}),wizardButtonDirective("wzNext"),wizardButtonDirective("wzPrevious"),wizardButtonDirective("wzFinish"),wizardButtonDirective("wzCancel"),wizardButtonDirective("wzReset"),angular.module("mgo-angular-wizard").factory("WizardHandler",function(){var a={},b={};return a.defaultName="defaultWizard",a.addWizard=function(a,c){b[a]=c},a.removeWizard=function(a){delete b[a]},a.wizard=function(c){var d=c;return c||(d=a.defaultName),b[d]},a}); \ No newline at end of file +function wizardButtonDirective(a){angular.module("mgo-angular-wizard").directive(a,function(){return{restrict:"A",replace:!1,require:"^wizard",link:function(b,c,d,e){c.on("click",function(c){c.preventDefault(),b.$apply(function(){b.$eval(d[a]),e[a.replace("wz","").toLowerCase()]()})})}}})}angular.module("templates-angularwizard",["step.html","wizard.html"]),angular.module("step.html",[]).run(["$templateCache",function(a){a.put("step.html",'
\n
')}]),angular.module("wizard.html",[]).run(["$templateCache",function(a){a.put("wizard.html",'
\n

{{ selectedStep.wzHeadingTitle }}

\n\n
\n \n
\n
\n')}]),angular.module("mgo-angular-wizard",["templates-angularwizard"]),angular.module("mgo-angular-wizard").directive("wzStep",function(){return{restrict:"EA",replace:!0,transclude:!0,scope:{wzTitle:"@",wzHeadingTitle:"@",canenter:"=",canexit:"=",disabled:"@?wzDisabled",description:"@",wzData:"=",wzOrder:"@?"},require:"^wizard",templateUrl:function(a,b){return b.template||"step.html"},link:function(a,b,c,d){c.$observe("wzTitle",function(b){a.title=a.wzTitle}),a.title=a.wzTitle,d.addStep(a),a.$on("$destroy",function(){d.removeStep(a)})}}}),angular.module("mgo-angular-wizard").directive("wizard",function(){return{restrict:"EA",replace:!0,transclude:!0,scope:{currentStep:"=",onCancel:"&",onFinish:"&",hideIndicators:"=",editMode:"=",name:"@",indicatorsPosition:"@?"},templateUrl:function(a,b){return b.template||"wizard.html"},controller:["$scope","$element","$log","WizardHandler","$q","$timeout",function($scope,$element,$log,WizardHandler,$q,$timeout){function canEnterStep(step){var defer,canEnter;if(void 0===step.canenter)return!0;if("boolean"==typeof step.canenter)return step.canenter;if("string"==typeof step.canenter){var splitFunction=step.canenter.split("(");canEnter=eval("$scope.$parent."+splitFunction[0]+"($scope.context"+splitFunction[1])}else canEnter=step.canenter($scope.context);return angular.isFunction(canEnter.then)?(defer=$q.defer(),canEnter.then(function(a){defer.resolve(a)}),defer.promise):canEnter===!0}function canExitStep(step,stepTo){var defer,canExit;if("undefined"==typeof step.canexit||$scope.getStepNumber(stepTo)<$scope.currentStepNumber())return!0;if("boolean"==typeof step.canexit)return step.canexit;if("string"==typeof step.canexit){var splitFunction=step.canexit.split("(");canExit=eval("$scope.$parent."+splitFunction[0]+"($scope.context"+splitFunction[1])}else canExit=step.canexit($scope.context);return angular.isFunction(canExit.then)?(defer=$q.defer(),canExit.then(function(a){defer.resolve(a)}),defer.promise):canExit===!0}function unselectAll(){angular.forEach($scope.getEnabledSteps(),function(a){a.selected=!1}),$scope.selectedStep=null}void 0==$scope.indicatorsPosition&&($scope.indicatorsPosition="bottom");var firstRun=!0;WizardHandler.addWizard($scope.name||WizardHandler.defaultName,this),$scope.$on("$destroy",function(){WizardHandler.removeWizard($scope.name||WizardHandler.defaultName)}),$scope.steps=[];var stepIdx=function(a){var b=0,c=-1;return angular.forEach($scope.getEnabledSteps(),function(d){d===a&&(c=b),b++}),c},stepByTitle=function(a){var b=null;return angular.forEach($scope.getEnabledSteps(),function(c){c.wzTitle===a&&(b=c)}),b},handleEditModeChange=function(){var a=$scope.editMode;if(!angular.isUndefined(a)&&null!==a&&(angular.forEach($scope.steps,function(b){b.completed=a}),!a)){var b=$scope.currentStepNumber()-1;angular.forEach($scope.getEnabledSteps(),function(a,c){b>c&&(a.completed=!0)})}};$scope.context={},$scope.$watch("currentStep",function(a){if(a){var b=$scope.selectedStep.wzTitle;$scope.selectedStep&&b!==$scope.currentStep&&$scope.goTo(stepByTitle($scope.currentStep))}}),$scope.$watch("[editMode, steps.length]",function(){handleEditModeChange()},!0),this.addStep=function(a){var b=a.wzOrder>=0&&!$scope.steps[a.wzOrder]?a.wzOrder:$scope.steps.length;$scope.steps[b]=a,$scope.getEnabledSteps()[0]===a&&$scope.goTo($scope.getEnabledSteps()[0])},this.removeStep=function(a){var b=$scope.steps.indexOf(a);b>0&&$scope.steps.splice(b,1)},this.context=$scope.context,$scope.getStepNumber=function(a){return stepIdx(a)+1},$scope.goTo=function(a){if(firstRun)unselectAll(),$scope.selectedStep=a,angular.isUndefined($scope.currentStep)||($scope.currentStep=a.wzTitle),a.selected=!0,$scope.$emit("wizard:stepChanged",{step:a,index:stepIdx(a)}),firstRun=!1;else{var b;$scope.currentStepNumber()>0?b=$scope.currentStepNumber()-1:0===$scope.currentStepNumber()&&(b=0),$q.all([canExitStep($scope.getEnabledSteps()[b],a),canEnterStep(a)]).then(function(b){b[0]&&b[1]?(unselectAll(),$scope.selectedStep=a,angular.isUndefined($scope.currentStep)||($scope.currentStep=a.wzTitle),a.selected=!0,$scope.$emit("wizard:stepChanged",{step:a,index:stepIdx(a)})):$scope.$emit("wizard:stepChangeFailed",{step:a,index:_.indexOf($scope.getEnabledSteps(),a)})})}},$scope.currentStepNumber=function(){return stepIdx($scope.selectedStep)+1},$scope.getEnabledSteps=function(){return $scope.steps.filter(function(a){return a&&"true"!==a.disabled})},this.currentStepTitle=function(){return $scope.selectedStep.wzTitle},this.currentStepDescription=function(){return $scope.selectedStep.description},this.currentStep=function(){return $scope.selectedStep},this.totalStepCount=function(){return $scope.getEnabledSteps().length},this.getEnabledSteps=function(){return $scope.getEnabledSteps()},this.currentStepNumber=function(){return $scope.currentStepNumber()},this.next=function(a){var b=$scope.getEnabledSteps(),c=stepIdx($scope.selectedStep);if(angular.isFunction(a)){if(!a())return;c===b.length-1?this.finish():$scope.goTo(b[c+1])}a||($scope.selectedStep.completed=!0),c===b.length-1?this.finish():$scope.goTo(b[c+1])},this.goTo=function(a){$timeout(function(){var b,c=$scope.getEnabledSteps();b=angular.isNumber(a)?c[a]:stepByTitle(a),$scope.goTo(b)})},this.finish=function(){var a=$scope.currentStepNumber()-1;a=0>a?0:a;var b=$scope.getEnabledSteps()[a];$q.when(canExitStep(b,b)).then(function(a){"boolean"==typeof a?a===!0&&$scope.onFinish&&$scope.onFinish():$scope.onFinish&&$scope.onFinish()},function(a){$scope.$emit("wizard:finishFailed",{step:b,index:_.indexOf($scope.getEnabledSteps(),b)})})},this.previous=function(){var a=stepIdx($scope.selectedStep);if(0===a)throw new Error("Can't go back. It's already in step 0");$scope.goTo($scope.getEnabledSteps()[a-1])},this.cancel=function(){if($scope.onCancel)$scope.onCancel();else{var a=stepIdx($scope.selectedStep);if(0===a)throw new Error("Can't go back. It's already in step 0");$scope.goTo($scope.getEnabledSteps()[0])}},this.reset=function(){angular.forEach($scope.getEnabledSteps(),function(a){a.completed=!1}),this.goTo(0)},this.setEditMode=function(a){$scope.editMode=a,handleEditModeChange()}}]}}),wizardButtonDirective("wzNext"),wizardButtonDirective("wzPrevious"),wizardButtonDirective("wzFinish"),wizardButtonDirective("wzCancel"),wizardButtonDirective("wzReset"),angular.module("mgo-angular-wizard").factory("WizardHandler",function(){var a={},b={};return a.defaultName="defaultWizard",a.addWizard=function(a,c){b[a]=c},a.removeWizard=function(a){delete b[a]},a.wizard=function(c){var d=c;return c||(d=a.defaultName),b[d]},a}); \ No newline at end of file diff --git a/karma.conf.js b/karma.conf.js index 1ab4bb1..1032c7d 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -20,25 +20,26 @@ module.exports = function(config) { plugins: [ "karma-phantomjs-launcher", + "karma-chrome-launcher", "karma-jasmine", "karma-coverage" ], - preprocessors: { - "src/*.js": ["coverage"] - }, + // preprocessors: { + // "src/*.js": ["coverage"] + // }, - // optionally, configure the reporter - coverageReporter: { - dir : "coverage/", - reporters: [ - {type: "lcov", subdir: "lcov"} - ] - }, + // // optionally, configure the reporter + // coverageReporter: { + // dir : "coverage/", + // reporters: [ + // {type: "lcov", subdir: "lcov"} + // ] + // }, - // test results reporter to use - // possible values: "dots", "progress", "junit" - reporters: ["dots", "coverage"], + // // test results reporter to use + // // possible values: "dots", "progress", "junit" + // reporters: ["dots", "coverage"], // list of files to exclude exclude: [ @@ -74,7 +75,14 @@ module.exports = function(config) { // - Safari (only Mac) // - PhantomJS // - IE (only Windows) - browsers: ['PhantomJS'], + browsers: ['ChromeDebugging'], + + customLaunchers: { + ChromeDebugging: { + base: 'Chrome', + flags: [ '--remote-debugging-port=9333' ] + } + }, // If browser does not capture in given timeout [ms], kill it diff --git a/package.json b/package.json index c64fae3..286cfb2 100644 --- a/package.json +++ b/package.json @@ -47,16 +47,16 @@ "grunt-contrib-uglify": "~0.2.0", "grunt-conventional-changelog": "~0.1.1", "grunt-html2js": "~0.1.3", - "grunt-karma": "~0.9.x", - "karma": "^0.12.0", + "grunt-karma": "~0.12.x", + "karma": "^1.6.0", "karma-coverage": "^1.1.1", "grunt-ng-annotate": "^0.6.0", "grunt-zip": "*", "jasmine-core": "^2.1.3", - "karma-chrome-launcher": "^0.1.7", + "karma-chrome-launcher": "^2.0.0", "karma-firefox-launcher": "~0.1.x", - "karma-jasmine": "~0.3.x", + "karma-jasmine": "~1.1.0", "karma-phantomjs-launcher": "~1.0.x" }, "license": "MIT" -} \ No newline at end of file +} diff --git a/src/wizard.js b/src/wizard.js index d0c0b94..bf7ea78 100644 --- a/src/wizard.js +++ b/src/wizard.js @@ -1,5 +1,5 @@ //wizard directive -angular.module('mgo-angular-wizard').directive('wizard', function() { +angular.module('mgo-angular-wizard').directive('wizard', function () { return { restrict: 'EA', replace: true, @@ -13,7 +13,7 @@ angular.module('mgo-angular-wizard').directive('wizard', function() { name: '@', indicatorsPosition: '@?' }, - templateUrl: function(element, attributes) { + templateUrl: function (element, attributes) { return attributes.template || "wizard.html"; }, @@ -28,38 +28,38 @@ angular.module('mgo-angular-wizard').directive('wizard', function() { //creating instance of wizard, passing this as second argument allows access to functions attached to this via Service WizardHandler.addWizard($scope.name || WizardHandler.defaultName, this); - $scope.$on('$destroy', function() { + $scope.$on('$destroy', function () { WizardHandler.removeWizard($scope.name || WizardHandler.defaultName); }); //steps array where all the scopes of each step are added $scope.steps = []; - var stepIdx = function(step) { + var stepIdx = function (step) { var idx = 0; var res = -1; - angular.forEach($scope.getEnabledSteps(), function(currStep) { - if (currStep === step) { - res = idx; - } - idx++; + angular.forEach($scope.getEnabledSteps(), function (currStep) { + if (currStep === step) { + res = idx; + } + idx++; }); return res; }; - var stepByTitle = function(titleToFind) { - var foundStep = null; - angular.forEach($scope.getEnabledSteps(), function(step) { - if (step.wzTitle === titleToFind) { - foundStep = step; - } - }); - return foundStep; + var stepByTitle = function (titleToFind) { + var foundStep = null; + angular.forEach($scope.getEnabledSteps(), function (step) { + if (step.wzTitle === titleToFind) { + foundStep = step; + } + }); + return foundStep; }; //update completed state for each step based on the editMode and current step number - var handleEditModeChange = function() { + var handleEditModeChange = function () { var editMode = $scope.editMode; if (angular.isUndefined(editMode) || (editMode === null)) return; @@ -71,8 +71,8 @@ angular.module('mgo-angular-wizard').directive('wizard', function() { //If editMode is false, set ONLY ENABLED steps with index lower then completedIndex to completed if (!editMode) { var completedStepsIndex = $scope.currentStepNumber() - 1; - angular.forEach($scope.getEnabledSteps(), function(step, stepIndex) { - if(stepIndex < completedStepsIndex) { + angular.forEach($scope.getEnabledSteps(), function (step, stepIndex) { + if (stepIndex < completedStepsIndex) { step.completed = true; } }); @@ -83,7 +83,7 @@ angular.module('mgo-angular-wizard').directive('wizard', function() { $scope.context = {}; //watching changes to currentStep - $scope.$watch('currentStep', function(step) { + $scope.$watch('currentStep', function (step) { //checking to make sure currentStep is truthy value if (!step) return; //setting stepTitle equal to current step title or default title @@ -96,12 +96,12 @@ angular.module('mgo-angular-wizard').directive('wizard', function() { //watching steps array length and editMode value, if edit module is undefined or null the nothing is done //if edit mode is truthy, then all steps are marked as completed - $scope.$watch('[editMode, steps.length]', function() { + $scope.$watch('[editMode, steps.length]', function () { handleEditModeChange(); }, true); //called each time step directive is loaded - this.addStep = function(step) { + this.addStep = function (step) { var wzOrder = (step.wzOrder >= 0 && !$scope.steps[step.wzOrder]) ? step.wzOrder : $scope.steps.length; //adding the scope of directive onto step array $scope.steps[wzOrder] = step; @@ -122,13 +122,13 @@ angular.module('mgo-angular-wizard').directive('wizard', function() { this.context = $scope.context; - $scope.getStepNumber = function(step) { + $scope.getStepNumber = function (step) { return stepIdx(step) + 1; }; - $scope.goTo = function(step) { + $scope.goTo = function (step) { //if this is the first time the wizard is loading it bi-passes step validation - if(firstRun){ + if (firstRun) { //deselect all steps so you can set fresh below unselectAll(); $scope.selectedStep = step; @@ -139,37 +139,37 @@ angular.module('mgo-angular-wizard').directive('wizard', function() { //setting selected step to argument passed into goTo() step.selected = true; //emit event upwards with data on goTo() invoktion - $scope.$emit('wizard:stepChanged', {step: step, index: stepIdx(step)}); + $scope.$emit('wizard:stepChanged', { step: step, index: stepIdx(step) }); //setting variable to false so all other step changes must pass validation firstRun = false; } else { //createing variables to capture current state that goTo() was invoked from and allow booleans var thisStep; //getting data for step you are transitioning out of - if($scope.currentStepNumber() > 0){ + if ($scope.currentStepNumber() > 0) { thisStep = $scope.currentStepNumber() - 1; - } else if ($scope.currentStepNumber() === 0){ + } else if ($scope.currentStepNumber() === 0) { thisStep = 0; } //$log.log('steps[thisStep] Data: ', $scope.getEnabledSteps()[thisStep].canexit); - $q.all([canExitStep($scope.getEnabledSteps()[thisStep], step), canEnterStep(step)]).then(function(data) { - if(data[0] && data[1]){ + $q.all([canExitStep($scope.getEnabledSteps()[thisStep], step), canEnterStep(step)]).then(function (data) { + if (data[0] && data[1]) { //deselect all steps so you can set fresh below unselectAll(); //$log.log('value for canExit argument: ', $scope.currentStep.canexit); $scope.selectedStep = step; //making sure current step is not undefined - if(!angular.isUndefined($scope.currentStep)){ + if (!angular.isUndefined($scope.currentStep)) { $scope.currentStep = step.wzTitle; } //setting selected step to argument passed into goTo() step.selected = true; //emit event upwards with data on goTo() invoktion - $scope.$emit('wizard:stepChanged', {step: step, index: stepIdx(step)}); + $scope.$emit('wizard:stepChanged', { step: step, index: stepIdx(step) }); //$log.log('current step number: ', $scope.currentStepNumber()); } else { - $scope.$emit('wizard:stepChangeFailed', {step: step, index: _.indexOf($scope.getEnabledSteps(), step)}); + $scope.$emit('wizard:stepChangeFailed', { step: step, index: $scope.getEnabledSteps().indexOf(step) }); } }); } @@ -179,24 +179,24 @@ angular.module('mgo-angular-wizard').directive('wizard', function() { var defer; var canEnter; //If no validation function is provided, allow the user to enter the step - if(step.canenter === undefined){ + if (step.canenter === undefined) { return true; } //If canenter is a boolean value instead of a function, return the value - if(typeof step.canenter === 'boolean'){ + if (typeof step.canenter === 'boolean') { return step.canenter; } //If canenter is a string instead of a function, evaluate the function - if(typeof step.canenter === 'string'){ + if (typeof step.canenter === 'string') { var splitFunction = step.canenter.split('('); canEnter = eval('$scope.$parent.' + splitFunction[0] + '($scope.context' + splitFunction[1]) } else { canEnter = step.canenter($scope.context); } //Check to see if the canenter function is a promise which needs to be returned - if(angular.isFunction(canEnter.then)){ + if (angular.isFunction(canEnter.then)) { defer = $q.defer(); - canEnter.then(function(response){ + canEnter.then(function (response) { defer.resolve(response); }); return defer.promise; @@ -209,24 +209,24 @@ angular.module('mgo-angular-wizard').directive('wizard', function() { var defer; var canExit; //Exiting the step should be allowed if no validation function was provided or if the user is moving backwards - if(typeof(step.canexit) === 'undefined' || $scope.getStepNumber(stepTo) < $scope.currentStepNumber()){ + if (typeof (step.canexit) === 'undefined' || $scope.getStepNumber(stepTo) < $scope.currentStepNumber()) { return true; } //If canexit is a boolean value instead of a function, return the value - if(typeof step.canexit === 'boolean'){ + if (typeof step.canexit === 'boolean') { return step.canexit; } //If canenter is a string instead of a function, evaluate the function - if(typeof step.canexit === 'string'){ + if (typeof step.canexit === 'string') { var splitFunction = step.canexit.split('('); canExit = eval('$scope.$parent.' + splitFunction[0] + '($scope.context' + splitFunction[1]) } else { canExit = step.canexit($scope.context); } //Check to see if the canexit function is a promise which needs to be returned - if(angular.isFunction(canExit.then)){ + if (angular.isFunction(canExit.then)) { defer = $q.defer(); - canExit.then(function(response){ + canExit.then(function (response) { defer.resolve(response); }); return defer.promise; @@ -235,13 +235,13 @@ angular.module('mgo-angular-wizard').directive('wizard', function() { } } - $scope.currentStepNumber = function() { + $scope.currentStepNumber = function () { //retreive current step number return stepIdx($scope.selectedStep) + 1; }; - $scope.getEnabledSteps = function() { - return $scope.steps.filter(function(step){ + $scope.getEnabledSteps = function () { + return $scope.steps.filter(function (step) { return step && step.disabled !== 'true'; }); }; @@ -258,48 +258,48 @@ angular.module('mgo-angular-wizard').directive('wizard', function() { //ALL METHODS ATTACHED TO this ARE ACCESSIBLE VIA WizardHandler.wizard().methodName() - this.currentStepTitle = function(){ + this.currentStepTitle = function () { return $scope.selectedStep.wzTitle; }; - this.currentStepDescription = function(){ + this.currentStepDescription = function () { return $scope.selectedStep.description; }; - this.currentStep = function(){ + this.currentStep = function () { return $scope.selectedStep; }; - this.totalStepCount = function() { + this.totalStepCount = function () { return $scope.getEnabledSteps().length; }; //Access to enabled steps from outside - this.getEnabledSteps = function(){ + this.getEnabledSteps = function () { return $scope.getEnabledSteps(); }; //Access to current step number from outside - this.currentStepNumber = function(){ + this.currentStepNumber = function () { return $scope.currentStepNumber(); }; //method used for next button within step - this.next = function(callback) { + this.next = function (callback) { var enabledSteps = $scope.getEnabledSteps(); //setting variable equal to step you were on when next() was invoked var index = stepIdx($scope.selectedStep); //checking to see if callback is a function - if(angular.isFunction(callback)){ - if(callback()){ + if (angular.isFunction(callback)) { + if (callback()) { if (index === enabledSteps.length - 1) { this.finish(); } else { //invoking goTo() with step number next in line $scope.goTo(enabledSteps[index + 1]); } - } else { + } else { return; - } + } } if (!callback) { //completed property set on scope which is used to add class/remove class from progress bar @@ -316,9 +316,9 @@ angular.module('mgo-angular-wizard').directive('wizard', function() { }; //used to traverse to any step, step number placed as argument - this.goTo = function(step) { + this.goTo = function (step) { //wrapped inside $timeout so newly enabled steps are included. - $timeout(function() { + $timeout(function () { var enabledSteps = $scope.getEnabledSteps(); var stepTo; //checking that step is a Number @@ -334,13 +334,29 @@ angular.module('mgo-angular-wizard').directive('wizard', function() { }; //calls finish() which calls onFinish() which is declared on an attribute and linked to controller via wizard directive. - this.finish = function() { - if ($scope.onFinish) { - $scope.onFinish(); - } + this.finish = function () { + var thisStepNumber = $scope.currentStepNumber() - 1; + thisStepNumber = thisStepNumber < 0 ? 0 : thisStepNumber; + var thisStep = $scope.getEnabledSteps()[thisStepNumber]; + + $q.when(canExitStep(thisStep, thisStep)).then(function (result) { + if (typeof result === 'boolean') { + if (result === true) { + if ($scope.onFinish) { + $scope.onFinish(); + } + } + } else { + if ($scope.onFinish) { + $scope.onFinish(); + } + } + }, function (error) { + $scope.$emit('wizard:finishFailed', { step: thisStep, index: $scope.getEnabledSteps().indexOf(thisStep) }); + }); }; - this.previous = function() { + this.previous = function () { //getting index of current step var index = stepIdx($scope.selectedStep); //ensuring you aren't trying to go back from the first step @@ -353,8 +369,8 @@ angular.module('mgo-angular-wizard').directive('wizard', function() { }; //cancel is alias for previous. - this.cancel = function() { - if ($scope.onCancel) { + this.cancel = function () { + if ($scope.onCancel) { //onCancel is linked to controller via wizard directive: $scope.onCancel(); } else { @@ -366,12 +382,12 @@ angular.module('mgo-angular-wizard').directive('wizard', function() { } else { //go back one step from current step $scope.goTo($scope.getEnabledSteps()[0]); - } + } } }; //reset - this.reset = function(){ + this.reset = function () { //traverse steps array and set each "completed" property to false angular.forEach($scope.getEnabledSteps(), function (step) { step.completed = false; @@ -381,7 +397,7 @@ angular.module('mgo-angular-wizard').directive('wizard', function() { }; //change edit mode - this.setEditMode = function(mode) { + this.setEditMode = function (mode) { $scope.editMode = mode; handleEditModeChange(); }; diff --git a/test/angularWizardSpec.js b/test/angularWizardSpec.js index 708230a..4d2e2b9 100644 --- a/test/angularWizardSpec.js +++ b/test/angularWizardSpec.js @@ -320,8 +320,8 @@ describe( 'AngularWizard', function() { var view = createGenericView(scope); expect(scope.referenceCurrentStep).toEqual('Starting'); WizardHandler.wizard().finish(); - expect(flag).toBeTruthy(); $rootScope.$digest(); + expect(flag).toBeTruthy(); }); it( "should go to first step when reset is called", function() { var scope = $rootScope.$new();