drupal-civicrm/sites/all/modules/civicrm/bower_components/angular-unsavedChanges/dist/unsavedChanges.js
2018-01-14 13:10:16 +00:00

332 lines
11 KiB
JavaScript

'use strict';
/*jshint globalstrict: true*/
/*jshint undef:false */
// @todo NOTE We should investigate changing default to
// $routeChangeStart see https://github.com/angular-ui/ui-router/blob/3898270241d4e32c53e63554034d106363205e0e/src/compat.js#L126
angular.module('unsavedChanges', ['lazyModel'])
.provider('unsavedWarningsConfig', function() {
var _this = this;
// defaults
var logEnabled = false;
var useTranslateService = true;
var routeEvent = ['$locationChangeStart', '$stateChangeStart'];
var navigateMessage = 'You will lose unsaved changes if you leave this page';
var reloadMessage = 'You will lose unsaved changes if you reload this page';
Object.defineProperty(_this, 'navigateMessage', {
get: function() {
return navigateMessage;
},
set: function(value) {
navigateMessage = value;
}
});
Object.defineProperty(_this, 'reloadMessage', {
get: function() {
return reloadMessage;
},
set: function(value) {
reloadMessage = value;
}
});
Object.defineProperty(_this, 'useTranslateService', {
get: function() {
return useTranslateService;
},
set: function(value) {
useTranslateService = !! (value);
}
});
Object.defineProperty(_this, 'routeEvent', {
get: function() {
return routeEvent;
},
set: function(value) {
if (typeof value === 'string') value = [value];
routeEvent = value;
}
});
Object.defineProperty(_this, 'logEnabled', {
get: function() {
return logEnabled;
},
set: function(value) {
logEnabled = !! (value);
}
});
this.$get = ['$injector',
function($injector) {
function translateIfAble(message) {
if ($injector.has('$translate') && useTranslateService) {
return $injector.get('$translate')(message);
} else {
return false;
}
}
var publicInterface = {
// log function that accepts any number of arguments
// @see http://stackoverflow.com/a/7942355/1738217
log: function() {
if (console.log && logEnabled && arguments.length) {
var newarr = [].slice.call(arguments);
if (typeof console.log === 'object') {
log.apply.call(console.log, console, newarr);
} else {
console.log.apply(console, newarr);
}
}
}
};
Object.defineProperty(publicInterface, 'useTranslateService', {
get: function() {
return useTranslateService;
}
});
Object.defineProperty(publicInterface, 'reloadMessage', {
get: function() {
return translateIfAble(reloadMessage) || reloadMessage;
}
});
Object.defineProperty(publicInterface, 'navigateMessage', {
get: function() {
return translateIfAble(navigateMessage) || navigateMessage;
}
});
Object.defineProperty(publicInterface, 'routeEvent', {
get: function() {
return routeEvent;
}
});
Object.defineProperty(publicInterface, 'logEnabled', {
get: function() {
return logEnabled;
}
});
return publicInterface;
}
];
})
.service('unsavedWarningSharedService', ['$rootScope', 'unsavedWarningsConfig', '$injector',
function($rootScope, unsavedWarningsConfig, $injector) {
// Controller scopped variables
var _this = this;
var allForms = [];
var areAllFormsClean = true;
var removeFunctions = [angular.noop];
// @note only exposed for testing purposes.
this.allForms = function() {
return allForms;
};
// save shorthand reference to messages
var messages = {
navigate: unsavedWarningsConfig.navigateMessage,
reload: unsavedWarningsConfig.reloadMessage
};
// Check all registered forms
// if any one is dirty function will return true
function allFormsClean() {
areAllFormsClean = true;
angular.forEach(allForms, function(item, idx) {
unsavedWarningsConfig.log('Form : ' + item.$name + ' dirty : ' + item.$dirty);
if (item.$dirty) {
areAllFormsClean = false;
}
});
return areAllFormsClean; // no dirty forms were found
}
// adds form controller to registered forms array
// this array will be checked when user navigates away from page
this.init = function(form) {
if (allForms.length === 0) setup();
unsavedWarningsConfig.log("Registering form", form);
allForms.push(form);
};
this.removeForm = function(form) {
var idx = allForms.indexOf(form);
// this form is not present array
// @todo needs test coverage
if (idx === -1) return;
allForms.splice(idx, 1);
unsavedWarningsConfig.log("Removing form from watch list", form);
if (allForms.length === 0) tearDown();
};
function tearDown() {
unsavedWarningsConfig.log('No more forms, tearing down');
angular.forEach(removeFunctions, function(fn) {
fn();
});
window.onbeforeunload = null;
}
// Function called when user tries to close the window
this.confirmExit = function() {
// @todo this could be written a lot cleaner!
if (!allFormsClean()) return messages.reload;
tearDown();
};
// bind to window close
// @todo investigate new method for listening as discovered in previous tests
function setup() {
unsavedWarningsConfig.log('Setting up');
window.onbeforeunload = _this.confirmExit;
var eventsToWatchFor = unsavedWarningsConfig.routeEvent;
angular.forEach(eventsToWatchFor, function(aEvent) {
// calling this function later will unbind this, acting as $off()
var removeFn = $rootScope.$on(aEvent, function(event, next, current) {
unsavedWarningsConfig.log("user is moving with " + aEvent);
// @todo this could be written a lot cleaner!
if (!allFormsClean()) {
unsavedWarningsConfig.log("a form is dirty");
if (!confirm(messages.navigate)) {
unsavedWarningsConfig.log("user wants to cancel leaving");
event.preventDefault(); // user clicks cancel, wants to stay on page
} else {
unsavedWarningsConfig.log("user doesn't care about loosing stuff");
}
} else {
unsavedWarningsConfig.log("all forms are clean");
}
});
removeFunctions.push(removeFn);
});
}
}
])
.directive('unsavedWarningClear', ['unsavedWarningSharedService',
function(unsavedWarningSharedService) {
return {
scope: true,
require: '^form',
priority: 3000,
link: function(scope, element, attrs, formCtrl) {
element.bind('click', function(event) {
formCtrl.$setPristine();
});
}
};
}
])
.directive('unsavedWarningForm', ['unsavedWarningSharedService',
function(unsavedWarningSharedService) {
return {
require: 'form',
link: function(scope, formElement, attrs, formCtrl) {
// register this form
unsavedWarningSharedService.init(formCtrl);
// bind to form submit, this makes the typical submit button work
// in addition to the ability to bind to a seperate button which clears warning
formElement.bind('submit', function(event) {
if (formCtrl.$valid) {
formCtrl.$setPristine();
}
});
// @todo check destroy on clear button too?
scope.$on('$destroy', function() {
unsavedWarningSharedService.removeForm(formCtrl);
});
}
};
}
]);
/**
* --------------------------------------------
* Lazy model adapted from vitalets
* @see https://github.com/vitalets/lazy-model/
* --------------------------------------------
*
*/
angular.module('lazyModel', [])
.directive('lazyModel', ['$parse', '$compile',
function($parse, $compile) {
return {
restrict: 'A',
priority: 500,
terminal: true,
require: '^form',
scope: true,
compile: function compile(elem, attr) {
// getter and setter for original model
var ngModelGet = $parse(attr.lazyModel);
var ngModelSet = ngModelGet.assign;
// set ng-model to buffer in isolate scope
elem.attr('ng-model', 'buffer');
// remove lazy-model attribute to exclude recursion
elem.removeAttr("lazy-model");
return {
pre: function(scope, elem) {
// initialize buffer value as copy of original model
scope.buffer = ngModelGet(scope.$parent);
// compile element with ng-model directive pointing to buffer value
$compile(elem)(scope);
},
post: function postLink(scope, elem, attr, formCtrl) {
// bind form submit to write back final value from buffer
var form = elem.parent();
while (form[0].tagName !== 'FORM') {
form = form.parent();
}
form.bind('submit', function() {
// form valid - save new value
if (formCtrl.$valid) {
scope.$apply(function() {
ngModelSet(scope.$parent, scope.buffer);
});
}
});
form.bind('reset', function(e) {
e.preventDefault();
scope.$apply(function() {
scope.buffer = ngModelGet(scope.$parent);
});
});
}
};
}
};
}
]);