diff --git a/platform/commonUI/edit/bundle.json b/platform/commonUI/edit/bundle.json index eccaeaf787..f7c13b8887 100644 --- a/platform/commonUI/edit/bundle.json +++ b/platform/commonUI/edit/bundle.json @@ -23,6 +23,13 @@ "depends": [ "$scope" ] } ], + "directives": [ + { + "key": "mctBeforeUnload", + "implementation": "directives/MCTBeforeUnload.js", + "depends": [ "$window" ] + } + ], "actions": [ { "key": "edit", diff --git a/platform/commonUI/edit/res/templates/edit.html b/platform/commonUI/edit/res/templates/edit.html index ed42b2dcf2..56f8a0b30e 100644 --- a/platform/commonUI/edit/res/templates/edit.html +++ b/platform/commonUI/edit/res/templates/edit.html @@ -1,8 +1,9 @@
+ ng-controller="EditController as editMode" + mct-before-unload="editMode.getUnloadWarning()"> - + diff --git a/platform/commonUI/edit/src/capabilities/EditorCapability.js b/platform/commonUI/edit/src/capabilities/EditorCapability.js index 5ac88d0b68..e982a3f90e 100644 --- a/platform/commonUI/edit/src/capabilities/EditorCapability.js +++ b/platform/commonUI/edit/src/capabilities/EditorCapability.js @@ -88,6 +88,13 @@ define( */ cancel: function () { return resolvePromise(undefined); + }, + /** + * Check if there are any unsaved changes. + * @returns {boolean} true if there are unsaved changes + */ + dirty: function () { + return cache.dirty(); } }; }; diff --git a/platform/commonUI/edit/src/controllers/EditController.js b/platform/commonUI/edit/src/controllers/EditController.js index cf07797429..d491c448fa 100644 --- a/platform/commonUI/edit/src/controllers/EditController.js +++ b/platform/commonUI/edit/src/controllers/EditController.js @@ -15,10 +15,12 @@ define( * @constructor */ function EditController($scope, navigationService) { + var navigatedObject; + function setNavigation(domainObject) { // Wrap the domain object such that all mutation is // confined to edit mode (until Save) - $scope.navigatedObject = + navigatedObject = domainObject && new EditableDomainObject(domainObject); } @@ -27,6 +29,21 @@ define( $scope.$on("$destroy", function () { navigationService.removeListener(setNavigation); }); + + return { + navigatedObject: function () { + return navigatedObject; + }, + getUnloadWarning: function () { + var editorCapability = navigatedObject && + navigatedObject.getCapability("editor"), + hasChanges = editorCapability && editorCapability.dirty(); + + return hasChanges ? + "Unsaved changes will be lost if you leave this page." : + undefined; + } + }; } return EditController; diff --git a/platform/commonUI/edit/src/directives/MCTBeforeUnload.js b/platform/commonUI/edit/src/directives/MCTBeforeUnload.js new file mode 100644 index 0000000000..10ced76db3 --- /dev/null +++ b/platform/commonUI/edit/src/directives/MCTBeforeUnload.js @@ -0,0 +1,68 @@ +/*global define*/ + +define( + [], + function () { + "use strict"; + + /** + * Defines the `mct-before-unload` directive. The expression bound + * to this attribute will be evaluated during page navigation events + * and, if it returns a truthy value, will be used to populate a + * prompt to the user to confirm this navigation. + * @constructor + * @param $window the window + */ + function MCTBeforeUnload($window) { + var unloads = [], + oldBeforeUnload = $window.onbeforeunload; + + // Run all unload functions, returning the first returns truthily. + function checkUnloads() { + var result; + unloads.forEach(function (unload) { + result = result || unload(); + }); + return result; + } + + // Link function for an mct-before-unload directive usage + function link(scope, element, attrs) { + // Invoke the + function unload() { + return scope.$eval(attrs.mctBeforeUnload); + } + + // Stop using this unload expression + function removeUnload() { + unloads = unloads.filter(function (callback) { + return callback !== unload; + }); + if (unloads.length === 0) { + $window.onbeforeunload = oldBeforeUnload; + } + } + + // Include this instance of the directive's unload function + unloads.push(unload); + + // If this is the first active instance of this directive, + // register as the window's beforeunload handler + $window.onbeforeunload = checkUnloads; + + // Remove it when the scope is destroyed + scope.$on("$destroy", removeUnload); + } + + return { + // Applicable as an attribute + restrict: "A", + // Link with the provided function + link: link + }; + } + + return MCTBeforeUnload; + + } +); \ No newline at end of file diff --git a/platform/commonUI/edit/src/objects/EditableDomainObjectCache.js b/platform/commonUI/edit/src/objects/EditableDomainObjectCache.js index 6673e31998..c13f6a72e7 100644 --- a/platform/commonUI/edit/src/objects/EditableDomainObjectCache.js +++ b/platform/commonUI/edit/src/objects/EditableDomainObjectCache.js @@ -91,6 +91,13 @@ define( // Invoke its save behavior object.getCapability('editor').save(); } + }, + /** + * Check if any objects have been marked dirty in this cache. + * @returns {boolean} true if objects are dirty + */ + dirty: function () { + return Object.keys(dirty).length > 0; } }; }