[Navigation] navigationService provides checking

Remove policy checking in navigation action and depend on navigation
service to provide those checks.

* Register checkFunctions with navigationService.checkBeforeNavigation
  which returns a function for unregistering them.
* navigationService.setNavigation will run checks before allowing
  navigation, unless a `force` argument is supplied.

https://github.com/nasa/openmct/issues/1360
This commit is contained in:
Pete Richards 2016-12-20 15:07:05 -08:00
parent f0b9292458
commit f2d61604f7
3 changed files with 108 additions and 41 deletions

View File

@ -198,7 +198,10 @@ define([
"services": [ "services": [
{ {
"key": "navigationService", "key": "navigationService",
"implementation": NavigationService "implementation": NavigationService,
"depends": [
"$window"
]
} }
], ],
"actions": [ "actions": [
@ -206,10 +209,7 @@ define([
"key": "navigate", "key": "navigate",
"implementation": NavigateAction, "implementation": NavigateAction,
"depends": [ "depends": [
"navigationService", "navigationService"
"$q",
"policyService",
"$window"
] ]
}, },
{ {

View File

@ -33,12 +33,9 @@ define(
* @constructor * @constructor
* @implements {Action} * @implements {Action}
*/ */
function NavigateAction(navigationService, $q, policyService, $window, context) { function NavigateAction(navigationService, context) {
this.domainObject = context.domainObject; this.domainObject = context.domainObject;
this.$q = $q;
this.navigationService = navigationService; this.navigationService = navigationService;
this.policyService = policyService;
this.$window = $window;
} }
/** /**
@ -51,32 +48,12 @@ define(
navigateTo = this.domainObject, navigateTo = this.domainObject,
currentObject = self.navigationService.getNavigation(); currentObject = self.navigationService.getNavigation();
function allow() { if (this.navigationService.shouldNavigate()) {
var navigationAllowed = true; this.navigationService.setNavigation(this.domainObject, true);
self.policyService.allow("navigation", currentObject, navigateTo, function (message) { return Promise.resolve({});
navigationAllowed = self.$window.confirm(message + "\r\n\r\n" +
" Are you sure you want to continue?");
});
return navigationAllowed;
}
function cancelIfEditing() {
var editing = currentObject.hasCapability('editor') &&
currentObject.getCapability('editor').isEditContextRoot();
return self.$q.when(editing && currentObject.getCapability("editor").finish());
}
function navigate() {
return self.navigationService.setNavigation(navigateTo);
}
if (allow()) {
return cancelIfEditing().then(navigate);
} else {
return this.$q.when(false);
} }
return Promise.reject('Navigation Prevented by User');
}; };
/** /**

View File

@ -30,16 +30,20 @@ define(
/** /**
* The navigation service maintains the application's current * The navigation service maintains the application's current
* navigation state, and allows listening for changes thereto. * navigation state, and allows listening for changes thereto.
*
* @memberof platform/commonUI/browse * @memberof platform/commonUI/browse
* @constructor * @constructor
*/ */
function NavigationService() { function NavigationService($window) {
this.navigated = undefined; this.navigated = undefined;
this.callbacks = []; this.callbacks = [];
this.checks = [];
this.$window = $window;
} }
/** /**
* Get the current navigation state. * Get the current navigation state.
*
* @returns {DomainObject} the object that is navigated-to * @returns {DomainObject} the object that is navigated-to
*/ */
NavigationService.prototype.getNavigation = function () { NavigationService.prototype.getNavigation = function () {
@ -47,16 +51,33 @@ define(
}; };
/** /**
* Set the current navigation state. This will invoke listeners. * Navigate to a specified object. If navigation checks exist and
* return reasons to prevent navigation, it will prompt the user before
* continuing. Trying to navigate to the currently navigated object will
* do nothing.
*
* If a truthy value is passed for `force`, it will skip navigation
* and will not prevent navigation to an already selected object.
*
* @param {DomainObject} domainObject the domain object to navigate to * @param {DomainObject} domainObject the domain object to navigate to
* @param {Boolean} force if true, force navigation to occur.
* @returns {Boolean} true if navigation occured, otherwise false.
*/ */
NavigationService.prototype.setNavigation = function (value) { NavigationService.prototype.setNavigation = function (domainObject, force) {
if (this.navigated !== value) { if (force) {
this.navigated = value; this.doNavigation(domainObject);
this.callbacks.forEach(function (callback) { return true;
callback(value);
});
} }
if (this.navigated === domainObject) {
return true;
}
var doNotNavigate = this.shouldWarnBeforeNavigate();
if (doNotNavigate && !this.$window.confirm(doNotNavigate)) {
return false;
}
this.doNavigation(domainObject);
return true; return true;
}; };
@ -64,6 +85,7 @@ define(
* Listen for changes in navigation. The passed callback will * Listen for changes in navigation. The passed callback will
* be invoked with the new domain object of navigation when * be invoked with the new domain object of navigation when
* this changes. * this changes.
*
* @param {function} callback the callback to invoke when * @param {function} callback the callback to invoke when
* navigation state changes * navigation state changes
*/ */
@ -73,6 +95,7 @@ define(
/** /**
* Stop listening for changes in navigation state. * Stop listening for changes in navigation state.
*
* @param {function} callback the callback which should * @param {function} callback the callback which should
* no longer be invoked when navigation state * no longer be invoked when navigation state
* changes * changes
@ -83,6 +106,73 @@ define(
}); });
}; };
/**
* Check if navigation should proceed. May prompt a user for input
* if any checkFns return messages. Returns true if the user wishes to
* navigate, otherwise false. If using this prior to calling
* `setNavigation`, you should call `setNavigation` with `force=true`
* to prevent duplicate dialogs being displayed to the user.
*
* @returns {Boolean} true if the user wishes to navigate, otherwise false.
*/
NavigationService.prototype.shouldNavigate = function () {
var doNotNavigate = this.shouldWarnBeforeNavigate();
return !doNotNavigate || this.$window.confirm(doNotNavigate);
};
/**
* Register a check function to be called before any navigation occurs.
* Check functions should return a human readable "message" if
* there are any reasons to prevent navigation. Otherwise, they should
* return falsy. Returns a function which can be called to remove the
* check function.
*
* @param {Function} checkFn a function to call before navigation occurs.
* @returns {Function} removeCheck call to remove check
*/
NavigationService.prototype.checkBeforeNavigation = function (checkFn) {
this.checks.push(checkFn);
return function removeCheck() {
this.checks = this.checks.filter(function (fn) {
return checkFn !== fn;
});
}.bind(this);
};
/**
* Private method to actually perform navigation.
*
* @private
*/
NavigationService.prototype.doNavigation = function (value) {
this.navigated = value;
this.callbacks.forEach(function (callback) {
callback(value);
});
};
/**
* Returns either a false value, or a string that should be displayed
* to the user before navigation is allowed.
*
* @private
*/
NavigationService.prototype.shouldWarnBeforeNavigate = function () {
var reasons = [];
this.checks.forEach(function (checkFn) {
var reason = checkFn();
if (reason) {
reasons.push(reason);
}
});
if (reasons.length) {
return reasons.join('\n');
}
return false;
};
return NavigationService; return NavigationService;
} }
); );