First commit
This commit is contained in:
commit
c6e2478c40
13918 changed files with 2303184 additions and 0 deletions
49
sites/all/modules/civicrm/bower_components/angular-unsavedChanges/.bower.json
vendored
Normal file
49
sites/all/modules/civicrm/bower_components/angular-unsavedChanges/.bower.json
vendored
Normal file
|
@ -0,0 +1,49 @@
|
|||
{
|
||||
"name": "angular-unsavedChanges",
|
||||
"version": "0.1.1",
|
||||
"homepage": "https://github.com/facultymatt/angular-unsavedChanges",
|
||||
"authors": [
|
||||
"Matt Miller <matt@facultycreative.com>"
|
||||
],
|
||||
"description": "AngularJS directive to warn user of unsaved changes when navigating away from a form.",
|
||||
"main": "unsavedChanges.js",
|
||||
"keywords": [
|
||||
"form",
|
||||
"angularjs",
|
||||
"unsaved",
|
||||
"changes",
|
||||
"warning",
|
||||
"dirty",
|
||||
"reload"
|
||||
],
|
||||
"license": "MIT",
|
||||
"ignore": [
|
||||
"**/.*",
|
||||
"node_modules",
|
||||
"bower_components",
|
||||
"test",
|
||||
"tests"
|
||||
],
|
||||
"dependencies": {
|
||||
"angular": "~1.2.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"angular-route": "~1.2.2",
|
||||
"angular-mocks": "~1.2.2",
|
||||
"angular-scenario": "~1.2.2",
|
||||
"jquery": "~2.0.3",
|
||||
"angular-translate": "latest"
|
||||
},
|
||||
"resolutions": {
|
||||
"angular": "~1.2.5"
|
||||
},
|
||||
"_release": "0.1.1",
|
||||
"_resolution": {
|
||||
"type": "version",
|
||||
"tag": "v0.1.1",
|
||||
"commit": "9bba1eba672e0e6169ff41ba02db1ce2c1c6acc7"
|
||||
},
|
||||
"_source": "https://github.com/facultymatt/angular-unsavedChanges.git",
|
||||
"_target": "~0.1.1",
|
||||
"_originalSource": "angular-unsavedChanges"
|
||||
}
|
139
sites/all/modules/civicrm/bower_components/angular-unsavedChanges/Gruntfile.js
vendored
Normal file
139
sites/all/modules/civicrm/bower_components/angular-unsavedChanges/Gruntfile.js
vendored
Normal file
|
@ -0,0 +1,139 @@
|
|||
// @todo configure grunt default stuff to run on every save so we know that
|
||||
// dist is always up to date and jsLinted
|
||||
|
||||
module.exports = function(grunt) {
|
||||
|
||||
require('load-grunt-tasks')(grunt, {
|
||||
scope: ['dependencies', 'devDependencies']
|
||||
});
|
||||
|
||||
grunt.initConfig({
|
||||
// end 2 end testing with protractor
|
||||
protractor: {
|
||||
options: {
|
||||
keepAlive: false,
|
||||
configFile: './protractor.conf.js'
|
||||
},
|
||||
singlerun: {},
|
||||
travis: {
|
||||
configFile: './protractor_travis.conf.js'
|
||||
},
|
||||
auto: {
|
||||
keepAlive: true,
|
||||
options: {
|
||||
args: {
|
||||
seleniumPort: 4444
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
connect: {
|
||||
server: {
|
||||
options: {
|
||||
port: 9001,
|
||||
open: 'http://localhost:9001/demo',
|
||||
keepalive: true
|
||||
}
|
||||
},
|
||||
// our protractor server
|
||||
testserver: {
|
||||
options: {
|
||||
port: 9999
|
||||
}
|
||||
},
|
||||
travisServer: {
|
||||
options: {
|
||||
port: 9999
|
||||
}
|
||||
},
|
||||
},
|
||||
// watch tasks
|
||||
// Watch specified files for changes and execute tasks on change
|
||||
watch: {
|
||||
livereload: {
|
||||
options: {
|
||||
livereload: true
|
||||
},
|
||||
files: [
|
||||
'src/*.js',
|
||||
'demo/*.js'
|
||||
],
|
||||
tasks: ['jshint']
|
||||
},
|
||||
},
|
||||
karma: {
|
||||
plugins: [
|
||||
'karma-osx-reporter'
|
||||
],
|
||||
unit: {
|
||||
configFile: 'karma-unit.conf.js',
|
||||
autoWatch: false,
|
||||
singleRun: true
|
||||
},
|
||||
unitAuto: {
|
||||
configFile: 'karma-unit.conf.js',
|
||||
autoWatch: true,
|
||||
singleRun: false
|
||||
}
|
||||
},
|
||||
'min': {
|
||||
'dist': {
|
||||
'src': ['dist/unsavedChanges.js'],
|
||||
'dest': 'dist/unsavedChanges.min.js'
|
||||
}
|
||||
},
|
||||
jshint: {
|
||||
all: ['src/*.js']
|
||||
},
|
||||
strip: {
|
||||
main: {
|
||||
src: 'src/unsavedChanges.js',
|
||||
dest: 'dist/unsavedChanges.js'
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
grunt.registerTask('test', [
|
||||
'test:unit'
|
||||
]);
|
||||
|
||||
grunt.registerTask('server', [
|
||||
'connect:server'
|
||||
]);
|
||||
|
||||
grunt.registerTask('test:unit', [
|
||||
'karma:unit'
|
||||
]);
|
||||
|
||||
grunt.registerTask('autotest', [
|
||||
'autotest:unit'
|
||||
]);
|
||||
|
||||
grunt.registerTask('autotest:unit', [
|
||||
'karma:unitAuto'
|
||||
]);
|
||||
|
||||
grunt.registerTask('default', [
|
||||
'jshint',
|
||||
'strip:main',
|
||||
'min'
|
||||
]);
|
||||
|
||||
grunt.registerTask('autotest:e2e', [
|
||||
'connect:testserver', // - starts the app so the test runner can visit the app
|
||||
'shell:selenium', // - starts selenium server in watch mode
|
||||
'watch:protractor' // - watches scripts and e2e specs, and starts tests on file change
|
||||
]);
|
||||
|
||||
grunt.registerTask('test:e2e', [
|
||||
'connect:testserver', // - run concurrent tests
|
||||
'protractor:singlerun' // - single run protractor
|
||||
]);
|
||||
|
||||
grunt.registerTask('test:travis', [
|
||||
'connect:travisServer', // - run concurrent tests
|
||||
'karma:unit' // - single run karma unit
|
||||
]);
|
||||
|
||||
};
|
40
sites/all/modules/civicrm/bower_components/angular-unsavedChanges/bower.json
vendored
Normal file
40
sites/all/modules/civicrm/bower_components/angular-unsavedChanges/bower.json
vendored
Normal file
|
@ -0,0 +1,40 @@
|
|||
{
|
||||
"name": "angular-unsavedChanges",
|
||||
"version": "0.1.1",
|
||||
"homepage": "https://github.com/facultymatt/angular-unsavedChanges",
|
||||
"authors": [
|
||||
"Matt Miller <matt@facultycreative.com>"
|
||||
],
|
||||
"description": "AngularJS directive to warn user of unsaved changes when navigating away from a form.",
|
||||
"main": "unsavedChanges.js",
|
||||
"keywords": [
|
||||
"form",
|
||||
"angularjs",
|
||||
"unsaved",
|
||||
"changes",
|
||||
"warning",
|
||||
"dirty",
|
||||
"reload"
|
||||
],
|
||||
"license": "MIT",
|
||||
"ignore": [
|
||||
"**/.*",
|
||||
"node_modules",
|
||||
"bower_components",
|
||||
"test",
|
||||
"tests"
|
||||
],
|
||||
"dependencies": {
|
||||
"angular": "~1.2.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"angular-route": "~1.2.2",
|
||||
"angular-mocks": "~1.2.2",
|
||||
"angular-scenario": "~1.2.2",
|
||||
"jquery": "~2.0.3",
|
||||
"angular-translate": "latest"
|
||||
},
|
||||
"resolutions": {
|
||||
"angular": "~1.2.5"
|
||||
}
|
||||
}
|
41
sites/all/modules/civicrm/bower_components/angular-unsavedChanges/changelog.md
vendored
Normal file
41
sites/all/modules/civicrm/bower_components/angular-unsavedChanges/changelog.md
vendored
Normal file
|
@ -0,0 +1,41 @@
|
|||
# Changelog
|
||||
|
||||
Versioning follows [http://semver.org/](http://semver.org/), ie: MAJOR.MINOR.PATCH. Major version 0 is initial development. Minor versions may be backwards incompatible.
|
||||
|
||||
### 0.1.0
|
||||
|
||||
**Features**
|
||||
|
||||
- Add `lazy-model` directive, and change `clear` buttons to `type="reset"` which allows for resetting the model to original values. Furthermore values are only persisted to model if user submits valid form.
|
||||
- Only set pristine when clearing changes if form is valid. (https://github.com/facultymatt/angular-unsavedChanges/commit/26cd981397f3e1e637280e3778aa80708821dab4). The lazy-model form reset hook handles resetting the value.
|
||||
- Directive now removes onbeforeunload and route change listeners if no registered forms exist on the page. (https://github.com/facultymatt/angular-unsavedChanges/commit/58cad5401656bb806183d0a42c8b81bf1fbeeac6)
|
||||
|
||||
**Breaking Changes**
|
||||
|
||||
- Change getters and setters to user NJO (native javascript objects). This means that insated of setting `provider.setUseTranslateService(true)` you can natively set `provider.useTranslateService = true`. This may seem like semantics but if follows one of angulars core principals.
|
||||
|
||||
### 0.0.3
|
||||
|
||||
**Tests**
|
||||
|
||||
- Add full set of unit and e2e tests
|
||||
|
||||
**Features**
|
||||
|
||||
- Add config option for custom messages
|
||||
- Add support for uiRouter state change event via. config
|
||||
- Add support for Angular Translate
|
||||
- Add custom logging method for development
|
||||
|
||||
**Chores**
|
||||
|
||||
- Add module to bower.
|
||||
|
||||
**Breaking Changes**
|
||||
|
||||
- Changed name from `mm.unsavedChanges` to `unsavedChanges`
|
||||
|
||||
|
||||
### 0.0.2 and below
|
||||
|
||||
Offical changelog was not maintained for these versions.
|
43
sites/all/modules/civicrm/bower_components/angular-unsavedChanges/demo/app.js
vendored
Normal file
43
sites/all/modules/civicrm/bower_components/angular-unsavedChanges/demo/app.js
vendored
Normal file
|
@ -0,0 +1,43 @@
|
|||
angular
|
||||
.module('app', ['unsavedChanges', 'ngRoute'])
|
||||
.config(['$routeProvider', 'unsavedWarningsConfigProvider',
|
||||
function($routeProvider, unsavedWarningsConfigProvider) {
|
||||
|
||||
$routeProvider
|
||||
.when('/page1', {
|
||||
templateUrl: 'page1.html'
|
||||
})
|
||||
.when('/page2', {
|
||||
templateUrl: 'page2.html'
|
||||
})
|
||||
.otherwise({
|
||||
redirectTo: '/page1'
|
||||
});
|
||||
|
||||
// We can turn on logging through this provider method
|
||||
|
||||
// We uncomment out the below line in order to watch for angular-ui router events
|
||||
// rather than standard Angular router events. The default event is $locationChangeStart
|
||||
//unsavedWarningsConfigProvider.setRouteEventToWatchFor('$stateChangeStart');
|
||||
|
||||
// We uncomment out the below line in order to change the navigate message
|
||||
//unsavedWarningsConfigProvider.setNavigateMessage('Leaving now will lose your unsaved work');
|
||||
|
||||
// We uncomment out the below line in order to change the refresh message
|
||||
//unsavedWarningsConfigProvider.setReloadMessage('Refreshing now will lose your unsaved work');
|
||||
|
||||
// We use the below line in order to override the default and tell unsavedWarning to NOT
|
||||
// use the awesome angular-translate library for some reason
|
||||
unsavedWarningsConfigProvider.useTranslateService = false;
|
||||
}
|
||||
])
|
||||
.controller('demoCtrl', function($scope) {
|
||||
$scope.user = {};
|
||||
$scope.demoFormSubmit = function() {
|
||||
$scope.message = 'Form Saved';
|
||||
//$scope.user = {};
|
||||
}
|
||||
$scope.clearChanges = function() {
|
||||
$scope.user = {};
|
||||
}
|
||||
});
|
20
sites/all/modules/civicrm/bower_components/angular-unsavedChanges/demo/index.html
vendored
Normal file
20
sites/all/modules/civicrm/bower_components/angular-unsavedChanges/demo/index.html
vendored
Normal file
|
@ -0,0 +1,20 @@
|
|||
<!DOCTYPE html>
|
||||
<html class="no-js" ng-app="app">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<title>Angular Unsaved Changes</title>
|
||||
<meta name="description" content="">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<script src="bower_components/angular/angular.js"></script>
|
||||
<script src="bower_components/angular-route/angular-route.js"></script>
|
||||
<script src="../src/unsavedChanges.js"></script>
|
||||
<script src="app.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<a id="page1" href="#/page1">Page 1</a>
|
||||
<a id="page2" href="#/page2">Page 2</a>
|
||||
<div ng-view></div>
|
||||
</body>
|
||||
</html>
|
17
sites/all/modules/civicrm/bower_components/angular-unsavedChanges/demo/page1.html
vendored
Normal file
17
sites/all/modules/civicrm/bower_components/angular-unsavedChanges/demo/page1.html
vendored
Normal file
|
@ -0,0 +1,17 @@
|
|||
<h1>Page 1</h1>
|
||||
<div ng-controller="demoCtrl">
|
||||
<form name="demoForm" ng-submit="demoFormSubmit()" novalidate unsaved-warning-form>
|
||||
<input id="userName" type="text" name="name" lazy-model="user.name" required>
|
||||
<input type="text" name="email" lazy-model="user.email">
|
||||
<button id="submitForm" type="submit">Submit Form</button>
|
||||
<button id="clear" type="reset" unsaved-warning-clear ng-disabled="demoForm.$pristine">Disregard Changes</button>
|
||||
</form>
|
||||
|
||||
<pre>
|
||||
{{user | json}}
|
||||
</pre>
|
||||
|
||||
<p>Is Form Dirty?: {{demoForm.$dirty}}</p>
|
||||
<p ng-show="message">Message: {{message}}</p>
|
||||
</div>
|
||||
<input ng-model="outsideForm">
|
33
sites/all/modules/civicrm/bower_components/angular-unsavedChanges/demo/page2.html
vendored
Normal file
33
sites/all/modules/civicrm/bower_components/angular-unsavedChanges/demo/page2.html
vendored
Normal file
|
@ -0,0 +1,33 @@
|
|||
<div>
|
||||
<h1>Page 2</h1>
|
||||
<div ng-controller="demoCtrl">
|
||||
<form name="demoForm1" ng-submit="demoFormSubmit()" novalidate unsaved-warning-form>
|
||||
<legend>Form 1</legend>
|
||||
<input type="text" name="name" ng-model="user1.name">
|
||||
<button type="submit">Submit Form</button>
|
||||
<button id="clear" type="button" unsaved-warning-clear>Disregard Form 1 Changes</button>
|
||||
</form>
|
||||
<p>Is Form 1 Dirty?: {{demoForm1.$dirty}}</p>
|
||||
<p ng-show="message">Message: {{message}}</p>
|
||||
</div>
|
||||
<div ng-controller="demoCtrl">
|
||||
<form name="demoForm2" ng-submit="demoFormSubmit()" novalidate unsaved-warning-form>
|
||||
<legend>Form 2</legend>
|
||||
<input type="text" name="name" ng-model="user2.name">
|
||||
<button type="submit">Submit Form</button>
|
||||
</form>
|
||||
<p>Is Form 2 Dirty?: {{demoForm2.$dirty}}</p>
|
||||
<p ng-show="message">Message: {{message}}</p>
|
||||
</div>
|
||||
<div ng-controller="demoCtrl">
|
||||
<form name="demoForm3" ng-submit="demoFormSubmit()" novalidate unsaved-warning-form>
|
||||
<legend>Form 3</legend>
|
||||
<input type="text" name="name" ng-model="user3.name">
|
||||
<button type="submit">Submit Form</button>
|
||||
<button type="button" unsaved-warning-clear>Disregard Form 3 Changes</button>
|
||||
</form>
|
||||
<p>Is Form 3 Dirty?: {{demoForm3.$dirty}}</p>
|
||||
<p ng-show="message">Message: {{message}}</p>
|
||||
</div>
|
||||
|
||||
</div>
|
331
sites/all/modules/civicrm/bower_components/angular-unsavedChanges/dist/unsavedChanges.js
vendored
Normal file
331
sites/all/modules/civicrm/bower_components/angular-unsavedChanges/dist/unsavedChanges.js
vendored
Normal file
|
@ -0,0 +1,331 @@
|
|||
'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);
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
]);
|
1
sites/all/modules/civicrm/bower_components/angular-unsavedChanges/dist/unsavedChanges.min.js
vendored
Normal file
1
sites/all/modules/civicrm/bower_components/angular-unsavedChanges/dist/unsavedChanges.min.js
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
"use strict";angular.module("unsavedChanges",["lazyModel"]).provider("unsavedWarningsConfig",function(){var f=this;var e=false;var b=true;var d=["$locationChangeStart","$stateChangeStart"];var c="You will lose unsaved changes if you leave this page";var a="You will lose unsaved changes if you reload this page";Object.defineProperty(f,"navigateMessage",{get:function(){return c},set:function(g){c=g}});Object.defineProperty(f,"reloadMessage",{get:function(){return a},set:function(g){a=g}});Object.defineProperty(f,"useTranslateService",{get:function(){return b},set:function(g){b=!!(g)}});Object.defineProperty(f,"routeEvent",{get:function(){return d},set:function(g){if(typeof g==="string"){g=[g]}d=g}});Object.defineProperty(f,"logEnabled",{get:function(){return e},set:function(g){e=!!(g)}});this.$get=["$injector",function(h){function i(j){if(h.has("$translate")&&b){return h.get("$translate")(j)}else{return false}}var g={log:function(){if(console.log&&e&&arguments.length){var j=[].slice.call(arguments);if(typeof console.log==="object"){log.apply.call(console.log,console,j)}else{console.log.apply(console,j)}}}};Object.defineProperty(g,"useTranslateService",{get:function(){return b}});Object.defineProperty(g,"reloadMessage",{get:function(){return i(a)||a}});Object.defineProperty(g,"navigateMessage",{get:function(){return i(c)||c}});Object.defineProperty(g,"routeEvent",{get:function(){return d}});Object.defineProperty(g,"logEnabled",{get:function(){return e}});return g}]}).service("unsavedWarningSharedService",["$rootScope","unsavedWarningsConfig","$injector",function(j,c,k){var i=this;var a=[];var g=true;var d=[angular.noop];this.allForms=function(){return a};var f={navigate:c.navigateMessage,reload:c.reloadMessage};function h(){g=true;angular.forEach(a,function(m,l){c.log("Form : "+m.$name+" dirty : "+m.$dirty);if(m.$dirty){g=false}});return g}this.init=function(l){if(a.length===0){e()}c.log("Registering form",l);a.push(l)};this.removeForm=function(m){var l=a.indexOf(m);if(l===-1){return}a.splice(l,1);c.log("Removing form from watch list",m);if(a.length===0){b()}};function b(){c.log("No more forms, tearing down");angular.forEach(d,function(l){l()});window.onbeforeunload=null}this.confirmExit=function(){if(!h()){return f.reload}b()};function e(){c.log("Setting up");window.onbeforeunload=i.confirmExit;var l=c.routeEvent;angular.forEach(l,function(m){var n=j.$on(m,function(p,o,q){c.log("user is moving with "+m);if(!h()){c.log("a form is dirty");if(!confirm(f.navigate)){c.log("user wants to cancel leaving");p.preventDefault()}else{c.log("user doesn't care about loosing stuff")}}else{c.log("all forms are clean")}});d.push(n)})}}]).directive("unsavedWarningClear",["unsavedWarningSharedService",function(a){return{scope:true,require:"^form",priority:3000,link:function(d,c,b,e){c.bind("click",function(f){e.$setPristine()})}}}]).directive("unsavedWarningForm",["unsavedWarningSharedService",function(a){return{require:"form",link:function(d,c,b,e){a.init(e);c.bind("submit",function(f){if(e.$valid){e.$setPristine()}});d.$on("$destroy",function(){a.removeForm(e)})}}}]);angular.module("lazyModel",[]).directive("lazyModel",["$parse","$compile",function(b,a){return{restrict:"A",priority:500,terminal:true,require:"^form",scope:true,compile:function c(g,e){var f=b(e.lazyModel);var h=f.assign;g.attr("ng-model","buffer");g.removeAttr("lazy-model");return{pre:function(i,j){i.buffer=f(i.$parent);a(j)(i)},post:function d(j,l,i,m){var k=l.parent();while(k[0].tagName!=="FORM"){k=k.parent()}k.bind("submit",function(){if(m.$valid){j.$apply(function(){h(j.$parent,j.buffer)})}});k.bind("reset",function(n){n.preventDefault();j.$apply(function(){j.buffer=f(j.$parent)})})}}}}}]);
|
75
sites/all/modules/civicrm/bower_components/angular-unsavedChanges/karma-unit.conf.js
vendored
Normal file
75
sites/all/modules/civicrm/bower_components/angular-unsavedChanges/karma-unit.conf.js
vendored
Normal file
|
@ -0,0 +1,75 @@
|
|||
// Karma configuration
|
||||
// http://karma-runner.github.io/0.10/config/configuration-file.html
|
||||
|
||||
module.exports = function(config) {
|
||||
config.set({
|
||||
// base path, that will be used to resolve files and exclude
|
||||
basePath: '',
|
||||
|
||||
// testing framework to use (jasmine/mocha/qunit/...)
|
||||
frameworks: ['jasmine'],
|
||||
|
||||
files: [
|
||||
// ------------------------------
|
||||
// DEPENDENCIES
|
||||
// ------------------------------
|
||||
'demo/bower_components/jquery/jquery.js',
|
||||
'demo/bower_components/angular/angular.js',
|
||||
'demo/bower_components/angular-mocks/angular-mocks.js',
|
||||
'demo/bower_components/angular-route/angular-route.js',
|
||||
'demo/bower_components/angular-translate/angular-translate.js',
|
||||
//'demo/bower_components/angular-scenario/angular-scenario.js',
|
||||
//'node_modules/karma-ng-scenario/lib/adapter.js',
|
||||
'src/unsavedChanges.js',
|
||||
'test/unit/**/*.spec.js'
|
||||
],
|
||||
// preprocessors: {
|
||||
// 'app/scripts/components/*/views/*.html': ['ng-html2js']
|
||||
// },
|
||||
|
||||
ngHtml2JsPreprocessor: {
|
||||
// strip this from the file path
|
||||
stripPrefix: 'app/',
|
||||
|
||||
// make templates accessible in tests
|
||||
moduleName: 'templates'
|
||||
},
|
||||
|
||||
// list of files / patterns to exclude
|
||||
exclude: [],
|
||||
|
||||
// web server port
|
||||
port: 8080,
|
||||
|
||||
// level of logging
|
||||
// possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG
|
||||
logLevel: config.LOG_INFO,
|
||||
|
||||
// enable / disable watching file and executing tests whenever any file changes
|
||||
// @note our grunt tasks can over ride these settings. They are just default here.
|
||||
// we do this with out auto_test grunt task.
|
||||
autoWatch: false,
|
||||
|
||||
// Start these browsers, currently available:
|
||||
// @note you must have the browser on the computer doing the testing
|
||||
// - Chrome
|
||||
// - ChromeCanary
|
||||
// - Firefox
|
||||
// - Opera
|
||||
// - Safari (only Mac)
|
||||
// - PhantomJS
|
||||
// - IE (only Windows)
|
||||
browsers: ['PhantomJS'],
|
||||
|
||||
// Continuous Integration mode
|
||||
// if true, it capture browsers, run tests and exit
|
||||
singleRun: true,
|
||||
|
||||
// reporters?
|
||||
reporters: ['progress', 'osx'],
|
||||
|
||||
// provide green / red for apss / fail.
|
||||
colors: true
|
||||
|
||||
});
|
||||
};
|
63
sites/all/modules/civicrm/bower_components/angular-unsavedChanges/package.json
vendored
Normal file
63
sites/all/modules/civicrm/bower_components/angular-unsavedChanges/package.json
vendored
Normal file
|
@ -0,0 +1,63 @@
|
|||
{
|
||||
"name": "angular-unsavedChanges",
|
||||
"version": "0.1.1",
|
||||
"description": "AngularJS directive to warn user of unsaved changes when navigating away from a form.",
|
||||
"main": "Gruntfile.js",
|
||||
"devDependencies": {
|
||||
"grunt-strip": "~0.2.1",
|
||||
"express": "latest",
|
||||
"bower": "~1.2.6",
|
||||
"grunt": "~0.4.1",
|
||||
"grunt-shell": "~0.4.0",
|
||||
"grunt-open": "~0.2.2",
|
||||
"grunt-contrib-copy": "~0.4.1",
|
||||
"grunt-contrib-concat": "~0.3.0",
|
||||
"grunt-contrib-uglify": "~0.2.0",
|
||||
"grunt-contrib-jshint": "~0.6.0",
|
||||
"grunt-contrib-cssmin": "~0.6.0",
|
||||
"grunt-contrib-connect": "~0.5.0",
|
||||
"grunt-contrib-clean": "~0.5.0",
|
||||
"grunt-contrib-watch": "~0.5.3",
|
||||
"grunt-usemin": "~0.1.11",
|
||||
"grunt-rev": "~0.1.0",
|
||||
"grunt-concurrent": "~0.3.0",
|
||||
"load-grunt-tasks": "~0.2.0",
|
||||
"time-grunt": "~0.1.0",
|
||||
"grunt-karma": "~0.6.2",
|
||||
"karma-script-launcher": "~0.1.0",
|
||||
"karma-chrome-launcher": "~0.1.0",
|
||||
"karma-firefox-launcher": "~0.1.0",
|
||||
"karma-html2js-preprocessor": "~0.1.0",
|
||||
"karma-jasmine": "~0.1.3",
|
||||
"karma-requirejs": "~0.1.0",
|
||||
"karma-coverage": "0.1.0",
|
||||
"karma-osx-reporter": "*",
|
||||
"karma-phantomjs-launcher": "~0.1.0",
|
||||
"karma": "~0.10.2",
|
||||
"protractor": "latest",
|
||||
"grunt-protractor-runner": "latest",
|
||||
"grunt-jsbeautifier": "~0.2.3",
|
||||
"grunt-replace": "~0.5.1",
|
||||
"grunt-contrib-jshint": "~0.6.3",
|
||||
"grunt-yui-compressor": "~0.3.0"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/facultymatt/angular-unsavedChanges.git"
|
||||
},
|
||||
"keywords": [
|
||||
"form",
|
||||
"angularjs"
|
||||
],
|
||||
"authors": [
|
||||
"Matt Miller <matt@facultycreative.com>"
|
||||
],
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/facultymatt/angular-unsavedChanges/issues"
|
||||
},
|
||||
"homepage": "https://github.com/facultymatt/angular-unsavedChanges"
|
||||
}
|
70
sites/all/modules/civicrm/bower_components/angular-unsavedChanges/protractor.conf.js
vendored
Normal file
70
sites/all/modules/civicrm/bower_components/angular-unsavedChanges/protractor.conf.js
vendored
Normal file
|
@ -0,0 +1,70 @@
|
|||
// A reference configuration file.
|
||||
exports.config = {
|
||||
// ----- How to setup Selenium -----
|
||||
//
|
||||
// There are three ways to specify how to use Selenium. Specify one of the
|
||||
// following:
|
||||
//
|
||||
// 1. seleniumServerJar - to start Selenium Standalone locally.
|
||||
// 2. seleniumAddress - to connect to a Selenium server which is already
|
||||
// running.
|
||||
// 3. sauceUser/sauceKey - to use remote Selenium servers via SauceLabs.
|
||||
|
||||
// The location of the selenium standalone server .jar file.
|
||||
//seleniumServerJar: './selenium/selenium-server-standalone-2.35.0.jar',
|
||||
|
||||
seleniumAddress: 'http://127.0.0.1:4444/wd/hub',
|
||||
// The port to start the selenium server on, or null if the server should
|
||||
// find its own unused port.
|
||||
seleniumPort: null,
|
||||
// Chromedriver location is used to help the selenium standalone server
|
||||
// find chromedriver. This will be passed to the selenium jar as
|
||||
// the system property webdriver.chrome.driver. If null, selenium will
|
||||
// attempt to find chromedriver using PATH.
|
||||
//chromeDriver: './selenium/chromedriver',
|
||||
// Additional command line options to pass to selenium. For example,
|
||||
// if you need to change the browser timeout, use
|
||||
// seleniumArgs: ['-browserTimeout=60'],
|
||||
seleniumArgs: [],
|
||||
|
||||
// ----- What tests to run -----
|
||||
//
|
||||
// Spec patterns are relative to the location of this config.
|
||||
specs: [
|
||||
'test/e2e/*.js'
|
||||
],
|
||||
|
||||
// ----- Capabilities to be passed to the webdriver instance ----
|
||||
//
|
||||
// For a full list of available capabilities, see
|
||||
// https://code.google.com/p/selenium/wiki/DesiredCapabilities
|
||||
// and
|
||||
// https://code.google.com/p/selenium/source/browse/javascript/webdriver/capabilities.js
|
||||
capabilities: {
|
||||
'browserName': 'chrome',
|
||||
//'version': '7',
|
||||
//'platform': 'XP'
|
||||
},
|
||||
|
||||
// A base URL for your application under test. Calls to protractor.get()
|
||||
// with relative paths will be prepended with this.
|
||||
baseUrl: 'http://localhost:9999',
|
||||
|
||||
// Selector for the element housing the angular app - this defaults to
|
||||
// body, but is necessary if ng-app is on a descendant of <body>
|
||||
rootElement: 'body',
|
||||
|
||||
// ----- Options to be passed to minijasminenode -----
|
||||
jasmineNodeOpts: {
|
||||
// onComplete will be called just before the driver quits.
|
||||
onComplete: null,
|
||||
// If true, display spec names.
|
||||
isVerbose: false,
|
||||
// If true, print colors to the terminal.
|
||||
showColors: true,
|
||||
// If true, include stack traces in failures.
|
||||
includeStackTrace: true,
|
||||
// Default time to wait in ms before a test fails.
|
||||
defaultTimeoutInterval: 1000000
|
||||
}
|
||||
};
|
75
sites/all/modules/civicrm/bower_components/angular-unsavedChanges/protractor_travis.conf.js
vendored
Normal file
75
sites/all/modules/civicrm/bower_components/angular-unsavedChanges/protractor_travis.conf.js
vendored
Normal file
|
@ -0,0 +1,75 @@
|
|||
// A reference configuration file.
|
||||
exports.config = {
|
||||
// ----- How to setup Selenium -----
|
||||
//
|
||||
// There are three ways to specify how to use Selenium. Specify one of the
|
||||
// following:
|
||||
//
|
||||
// 1. seleniumServerJar - to start Selenium Standalone locally.
|
||||
// 2. seleniumAddress - to connect to a Selenium server which is already
|
||||
// running.
|
||||
// 3. sauceUser/sauceKey - to use remote Selenium servers via SauceLabs.
|
||||
|
||||
seleniumAddress: 'http://facultymatt:b280b942-1965-446d-90bf-e069b5cd2cf9@localhost:4445/wd/hub',
|
||||
// The port to start the selenium server on, or null if the server should
|
||||
// find its own unused port.
|
||||
seleniumPort: null,
|
||||
// Chromedriver location is used to help the selenium standalone server
|
||||
// find chromedriver. This will be passed to the selenium jar as
|
||||
// the system property webdriver.chrome.driver. If null, selenium will
|
||||
// attempt to find chromedriver using PATH.
|
||||
//chromeDriver: './selenium/chromedriver',
|
||||
// Additional command line options to pass to selenium. For example,
|
||||
// if you need to change the browser timeout, use
|
||||
// seleniumArgs: ['-browserTimeout=60'],
|
||||
seleniumArgs: [],
|
||||
|
||||
// If sauceUser and sauceKey are specified, seleniumServerJar will be ignored.
|
||||
// The tests will be run remotely using SauceLabs.
|
||||
sauceUser: 'facultymatt',
|
||||
sauceKey: 'b280b942-1965-446d-90bf-e069b5cd2cf9',
|
||||
|
||||
// ----- What tests to run -----
|
||||
//
|
||||
// Spec patterns are relative to the location of this config.
|
||||
specs: [
|
||||
'e2e/*.js'
|
||||
],
|
||||
|
||||
// ----- Capabilities to be passed to the webdriver instance ----
|
||||
//
|
||||
// For a full list of available capabilities, see
|
||||
// https://code.google.com/p/selenium/wiki/DesiredCapabilities
|
||||
// and
|
||||
// https://code.google.com/p/selenium/source/browse/javascript/webdriver/capabilities.js
|
||||
capabilities: {
|
||||
'username': 'facultymatt',
|
||||
'accessKey': 'b280b942-1965-446d-90bf-e069b5cd2cf9',
|
||||
'browserName': 'chrome',
|
||||
'tunnelIdentifier': process.env.TRAVIS_JOB_NUMBER
|
||||
//'version': '7',
|
||||
//'platform': 'XP'
|
||||
},
|
||||
|
||||
// A base URL for your application under test. Calls to protractor.get()
|
||||
// with relative paths will be prepended with this.
|
||||
baseUrl: 'http://localhost:9999',
|
||||
|
||||
// Selector for the element housing the angular app - this defaults to
|
||||
// body, but is necessary if ng-app is on a descendant of <body>
|
||||
rootElement: 'body',
|
||||
|
||||
// ----- Options to be passed to minijasminenode -----
|
||||
jasmineNodeOpts: {
|
||||
// onComplete will be called just before the driver quits.
|
||||
onComplete: null,
|
||||
// If true, display spec names.
|
||||
isVerbose: false,
|
||||
// If true, print colors to the terminal.
|
||||
showColors: true,
|
||||
// If true, include stack traces in failures.
|
||||
includeStackTrace: true,
|
||||
// Default time to wait in ms before a test fails.
|
||||
defaultTimeoutInterval: 1000000
|
||||
}
|
||||
};
|
136
sites/all/modules/civicrm/bower_components/angular-unsavedChanges/readme.md
vendored
Normal file
136
sites/all/modules/civicrm/bower_components/angular-unsavedChanges/readme.md
vendored
Normal file
|
@ -0,0 +1,136 @@
|
|||
# An AngularJS directive for forms that alerts user of unsaved changes.
|
||||
|
||||
_Dev Note: This module is still in development. However it's used in many of my production projects so it can be considered stable and battle tested._
|
||||
|
||||
This directive will alert users when they navigate away from a page where a form has unsaved changes. It will be triggered in all situations where form data would be lost:
|
||||
|
||||
- when user clicks a link
|
||||
- when user navigates with forward / back button
|
||||
- when user swipes (iOS)
|
||||
- when user refreshes the page
|
||||
|
||||
In addition this module:
|
||||
|
||||
- Works with multiple forms on the same page
|
||||
- Provides a button to disregard unsaved changes
|
||||
- Works with Angular Translate module
|
||||
- Has configurable reload and navigate messages
|
||||
- Works with uiRouter by default by listeneing for `$locationChangeStart` and `$stateChangeStart`
|
||||
- Can be configured to listen for any event
|
||||
|
||||
## How it Works
|
||||
|
||||
The directive binds to `locationChangeStart` and `window.onbeforeunload`. When these events happen all registered froms are checked if they are dirty. The module defers to the forms `$dirty` property as a single source of truth. If dirty, the user is alerted. Disregarding changes resets the form and sets pristine.
|
||||
|
||||
## Basic Usage
|
||||
|
||||
- Install from bower using `$ bower install angular-unsavedChanges --save`.
|
||||
- Include the JS, for example `<script src="bower_components/angular-unsavedChanges/dist/unsavedChanges.js"></script>`.
|
||||
- Include in your app, for example: `angular.module('app', ['unsavedChanges', 'anotherDirective'])`
|
||||
- Add attribute to your form, `unsaved-changes-warning`
|
||||
- That's it!
|
||||
|
||||
|
||||
## API
|
||||
|
||||
### Directives
|
||||
The module provides two directives for use.
|
||||
|
||||
#### unsaved-warning-form
|
||||
Add to forms you want to register with directive. The module will only listen when forms are registered.
|
||||
|
||||
```
|
||||
<form name="testForm" unsaved-warning-form>
|
||||
</form>
|
||||
```
|
||||
|
||||
#### unsaved-warning-clear
|
||||
Add to button or link that will disregard changes, preventing the messaging when user tries to navigate. Note that button type should be `reset` to work with `lazy-model` directive (outlined below).
|
||||
|
||||
```
|
||||
<form name="testForm" unsaved-warning-form>
|
||||
<input name="test" type="text" ng-model="test"/>
|
||||
<button type="submit"></button>
|
||||
<button type="reset" unsaved-warning-clear></button>
|
||||
</form>
|
||||
```
|
||||
|
||||
### Provider Configuration
|
||||
A number of options can be configured. The module uses the `Object.defineProperty` pattern. This avoids the need for custom getters and setters and allows us to treat configuration as pure JS objects.
|
||||
|
||||
#### useTranslateService
|
||||
Defaults to `true`. Will use translate service if available. It's safe to leave this set to `true`, even when not using the translate service, because the module still checks that the service exists.
|
||||
|
||||
```
|
||||
unsavedWarningsConfigProvider.useTranslateService = true;
|
||||
```
|
||||
|
||||
#### logEnabled
|
||||
Defaults to `false`. Uses the services internal logging method for debugging.
|
||||
|
||||
```
|
||||
unsavedWarningsConfigProvider.logEnabled = true;
|
||||
```
|
||||
|
||||
#### routeEvent
|
||||
Defaults to `['$locationChangeStart' ,'$stateChangeStart']` which supports ui router by default.
|
||||
|
||||
```
|
||||
unsavedWarningsConfigProvider.routeEvent = '$stateChangeStart';
|
||||
```
|
||||
|
||||
#### navigateMessage
|
||||
Set custom message displayed when user navigates. If using translate this will be the key to translate.
|
||||
```
|
||||
unsavedWarningsConfigProvider.navigateMessage = "Custom Navigate Message";
|
||||
```
|
||||
|
||||
#### reloadMessage
|
||||
Set custom message displayed when user refreshes the page. If using translate this will be the key to translate.
|
||||
```
|
||||
unsavedWarningsConfigProvider.reloadMessage = "Custom Reload Message";
|
||||
```
|
||||
|
||||
## Integration with Lazy Model Directive
|
||||
|
||||
This module includes a customized version of [Lazy Model](https://github.com/vitalets/lazy-model). Lazy model ensures that model changes are only persisted when user submits valid form. It also resets model values to their original value when form is reset.
|
||||
|
||||
To use this simply add `lazy-model` to your inputs instead of `ng-model`. Submitting the form will update your model, while clicking "clear changes" will reset the model values to their original state.
|
||||
|
||||
```
|
||||
<input name="test" type="text" lazy-model="test"/>
|
||||
```
|
||||
|
||||
|
||||
## Gotchas / Known Bugs
|
||||
|
||||
*** Known issue: sometimes the form is removed from expected scope. Ie: in your controller `$scope.formName` no longer works. You might need to access `$scope.$$childTail.formName`. This will be fixed in furture versions.
|
||||
|
||||
|
||||
## Demo / Dev
|
||||
|
||||
To try the demo run `npm install` && `bower install` && `grunt connect`. The browser should open [http://127.0.0.1:9001/demo](http://127.0.0.1:9001/demo).
|
||||
|
||||
|
||||
## Test
|
||||
|
||||
Note you need to manually change the paths in `index.html` and `karam-unit.conf` to point to the `dist` version for final testing. Make sure to run `$ grunt` first.
|
||||
|
||||
__End 2 End Testing__
|
||||
Because of the alert / event driven nature of this module it made the most sense to rely on e2e tests. (also its hard to interact with alerts via unit tests).
|
||||
|
||||
To run the e2e tests do the following:
|
||||
|
||||
- Install Protractor as per directions here: [https://github.com/angular/protractor](https://github.com/angular/protractor)
|
||||
- Start selenium server: `webdriver-manager start` (or use other selenium methods as per Protractor documentation.)
|
||||
- Run `$ grunt test:e2e`
|
||||
|
||||
|
||||
__Unit Tests__
|
||||
|
||||
- Run `$ grunt test:unit` OR `$ grunt test`
|
||||
|
||||
|
||||
## Build
|
||||
|
||||
Run `$ grunt` to lint and minify the code. Also strips console logs.
|
331
sites/all/modules/civicrm/bower_components/angular-unsavedChanges/src/unsavedChanges.js
vendored
Normal file
331
sites/all/modules/civicrm/bower_components/angular-unsavedChanges/src/unsavedChanges.js
vendored
Normal file
|
@ -0,0 +1,331 @@
|
|||
'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);
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
]);
|
5
sites/all/modules/civicrm/bower_components/angular-unsavedChanges/travis_test.sh
vendored
Executable file
5
sites/all/modules/civicrm/bower_components/angular-unsavedChanges/travis_test.sh
vendored
Executable file
|
@ -0,0 +1,5 @@
|
|||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
grunt test:travis
|
Loading…
Add table
Add a link
Reference in a new issue