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;
}
};
}