horizon/horizon/static/framework/widgets/wizard/wizard.controller.js
Richard Jones f9b3bc7b2b Remove use of scope from action service
The scope being handed to (and retained on) the action
service was being used for two purposes:

1. handing data down to the underlying wizard steps, and
2. receiving data back from the underlying wizard steps.

This patch provides a mechanism for passing data down through
injection, and removes the need for event-based data return
by explicitly passing the captured wizard data to the submit()
resolution.

It also adds the controller scope to the allowed() and
perform() action service handlers to grant access to
contextual information without needing to attach state
to the service.

Change-Id: Ieb293d0a849cd84d15e7aae0a68558fde80fd2c2
Fixes-Bug: 1640049
2016-11-24 18:42:49 +11:00

225 lines
6.6 KiB
JavaScript

/*
* (c) Copyright 2015 Hewlett-Packard Development Company, L.P.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
(function() {
'use strict';
var extend = angular.extend;
var forEach = angular.forEach;
angular
.module('horizon.framework.widgets.wizard')
.controller('WizardController', WizardController);
WizardController.$inject = [
'$scope',
'$q',
'horizon.framework.widgets.wizard.labels',
'horizon.framework.widgets.wizard.events'
];
/**
* @ngdoc controller
* @name horizon.framework.widgets.wizard.controller:WizardController
* @description
* Controller used by 'wizard'
*/
function WizardController($scope, $q, wizardLabels, wizardEvents) {
var viewModel = $scope.viewModel = {};
var initTask = $q.defer();
/*eslint-disable angular/controller-as */
$scope.initPromise = initTask.promise;
$scope.currentIndex = -1;
$scope.workflow = $scope.workflow || {};
if ($scope.workflow.initControllers) {
$scope.workflow.initControllers($scope);
}
var steps = $scope.steps = $scope.workflow.steps || [];
$scope.wizardForm = {};
// a place to keep each step's captured data, named for their step.formName
$scope.stepModels = {};
$scope.switchTo = switchTo;
$scope.showError = showError;
/*eslint-enable angular/controller-as */
viewModel.btnText = extend({}, wizardLabels, $scope.workflow.btnText);
viewModel.btnIcon = $scope.workflow.btnIcon || {};
viewModel.showSpinner = false;
viewModel.hasError = false;
viewModel.onClickFinishBtn = onClickFinishBtn;
viewModel.isSubmitting = false;
$scope.initPromise.then(onInitSuccess, onInitError);
checkAllReadiness().then(always, always);
//////////
function switchTo(index) {
/**
* In each step's controller, $scope.$index can be used by the step
* to identify itself. For example:
*
* var comingToMe = (event.to === $scope.$index);
*/
$scope.$broadcast(wizardEvents.ON_SWITCH, {
from: $scope.currentIndex,
to: index
});
toggleHelpBtn(index);
/*eslint-disable angular/controller-as */
$scope.currentIndex = index;
$scope.openHelp = false;
/*eslint-enable angular/controller-as*/
}
function showError(errorMessage) {
viewModel.showSpinner = false;
viewModel.hasError = true;
if (errorMessage && angular.isString(errorMessage.data)) {
viewModel.errorMessage = errorMessage.data;
} else {
viewModel.errorMessage = errorMessage;
}
viewModel.isSubmitting = false;
}
function beforeSubmit() {
$scope.$broadcast(wizardEvents.BEFORE_SUBMIT);
}
function afterSubmit(args) {
$scope.$broadcast(wizardEvents.AFTER_SUBMIT);
/*eslint-disable angular/controller-as */
$scope.close(args);
/*eslint-enable angular/controller-as */
}
function onClickFinishBtn() {
// prevent the finish button from being clicked again
viewModel.isSubmitting = true;
beforeSubmit();
$scope.submit($scope.stepModels).then(afterSubmit, showError);
}
function onInitSuccess() {
$scope.$broadcast(wizardEvents.ON_INIT_SUCCESS);
if (steps.length > 0) {
toggleHelpBtn(0);
}
}
function onInitError() {
$scope.$broadcast(wizardEvents.ON_INIT_ERROR);
}
function toggleHelpBtn(index) {
// Toggle help icon button if a step's helpUrl is not defined
if (angular.isUndefined(steps[index].helpUrl)) {
$scope.hideHelpBtn = true;
} else {
$scope.hideHelpBtn = false;
}
}
/**
* Each step in the workflow can provide an optional `checkReadiness`
* method, which should return a promise. When this method is provided
* with a step, the `.ready` property of the step will be set to
* `false` until the promise gets resolved. If no `checkReadiness` method
* is provided, the `.ready` property of the step will be set to `true`
* by default.
*
* This is useful for workflows where some steps are optional and/or
* displayed to the UI conditionally, and the check for the condition
* is an asynchronous operation.
*
* @return {Promise} This promise gets resolved when all the checking
* for each step's promises are done.
*
* @example
```js
var launchInstanceWorkFlow = {
//...
steps: [
// ...
{
title: gettext('Network'),
templateUrl: path + 'launch-instance/network/network.html',
helpUrl: path + 'launch-instance/network/network.help.html',
formName: 'launchInstanceNetworkForm',
checkReadiness: function() {
var d = $q.defer();
setTimeout(function() {
d.resolve();
}, 500);
return d.promise;
}
}
//...
],
//...
};
```
*/
function checkAllReadiness() {
var stepReadyPromises = [];
forEach(steps, function(step, index) {
step.ready = !step.checkReadiness;
if (step.checkReadiness) {
var promise = step.checkReadiness();
stepReadyPromises.push(promise);
promise.then(function() {
step.ready = true;
},
function() {
$scope.steps.splice(index, 1);
}
);
}
});
viewModel.ready = stepReadyPromises.length === 0;
return $q.all(stepReadyPromises);
}
function switchToFirstReadyStep() {
forEach(steps, function (step, index) {
/*eslint-disable angular/controller-as */
if ($scope.currentIndex < 0 && step.ready) {
$scope.currentIndex = index;
return;
}
/*eslint-enable angular/controller-as */
});
}
// angular promise doesn't have #always method right now,
// this is a simple workaround.
function always() {
initTask.resolve();
viewModel.ready = true;
switchToFirstReadyStep();
}
}
})();