Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
7e1cb48
Remove trailing whitespaces
dmytroyarmak Jul 21, 2014
ec5fa97
Fix problem with angular-translate v2
dmytroyarmak Jul 21, 2014
a5f555b
Update bower.json
dmytroyarmak Sep 2, 2014
566431b
Update bower.json
dmytroyarmak Sep 2, 2014
5955872
Fix problem with don't cleaning removeFunctions after tear down
dmytroyarmak Oct 17, 2014
9d8b5a1
Merge branch 'clean-remove-functions-array-after-tear-down' into develop
dmytroyarmak Oct 17, 2014
7bd9745
Increase version to v0.2.5
dmytroyarmak Oct 17, 2014
ef2bf30
Merge pull request #14 from dmytroyarmak/develop
Nov 1, 2014
4c4c2b5
update to angular 1.3, closes #23
Nov 3, 2014
0d85560
user $window instead of window
Nov 3, 2014
f7ebbe5
require form on element or parent, fixes #22
Nov 3, 2014
34d61f5
add test for isolate scope on form element
Nov 3, 2014
37fa8b6
fix unit tests for jasmine 2.0
Nov 3, 2014
1bd5b8c
build and release v0.2.3-alpha.2
Nov 3, 2014
7935051
test to verify #19
Nov 3, 2014
debabf3
remove scope to allow access to value within controller scope
Nov 3, 2014
07f8ab3
fix(resettable): switch from broadcast to trigger
Nov 4, 2014
9d56c27
fix(form): added test and fixed issue with #22
Nov 4, 2014
e69045e
update bower, fixes #16
Nov 4, 2014
b56d852
Merge pull request #27 from facultymatt/fix-resettables
Nov 4, 2014
12dbe89
Merge pull request #28 from facultymatt/fix-bower
Nov 4, 2014
aaad30a
fix(directive): Fix nasty race condition.
Nov 4, 2014
c357a16
update readme
Nov 4, 2014
f831050
build v0.2.3-alpha.3
Nov 4, 2014
36b7fbe
Use safe method for triggering scope digest
danielcrisp Sep 17, 2015
a287d8b
Added safer methods than
danielcrisp Sep 17, 2015
6438113
Build
danielcrisp Sep 17, 2015
a44192e
update to angular 1.5.*
rike422 Aug 19, 2016
74bdc2e
Update dependencies in package.json
rike422 Aug 19, 2016
c3c37e1
Fix broken e2e tests
rike422 Aug 22, 2016
bd6dd70
Add webdriver-manager grunt task
rike422 Aug 22, 2016
1d632da
Add npm tasks
rike422 Aug 22, 2016
d5598eb
Fix grunt-connect port config
rike422 Aug 22, 2016
bacefb9
Use Array#every insted of Array#foreach in allFormsClean
rike422 Aug 22, 2016
13658e0
Merge pull request #52 from danielcrisp/develop
rike422 Aug 22, 2016
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion Gruntfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ module.exports = function(grunt) {
scope: ['dependencies', 'devDependencies']
});

grunt.loadNpmTasks('grunt-protractor-runner');
grunt.loadNpmTasks('grunt-protractor-webdriver');

grunt.initConfig({
// end 2 end testing with protractor
protractor: {
Expand All @@ -27,6 +30,17 @@ module.exports = function(grunt) {
}
}
},
protractor_webdriver: {
options: {
keepAlive : true
},
e2eStart: {
options: {
path: './node_modules/.bin/',
command: 'webdriver-manager start --standalone'
},
},
},
connect: {
server: {
options: {
Expand All @@ -38,7 +52,7 @@ module.exports = function(grunt) {
// our protractor server
testserver: {
options: {
port: 9999
port: 9998
}
},
travisServer: {
Expand Down Expand Up @@ -121,12 +135,14 @@ module.exports = function(grunt) {
]);

grunt.registerTask('autotest:e2e', [
'protractor_webdriver:e2eStart',
'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', [
'protractor_webdriver:e2eStart',
'connect:testserver', // - run concurrent tests
'protractor:singlerun' // - single run protractor
]);
Expand Down
21 changes: 8 additions & 13 deletions bower.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
{
"name": "angular-unsavedChanges",
"version": "0.2.3-alpha.1",
"version": "0.2.5",
"homepage": "https://github.com/facultymatt/angular-unsavedChanges",
"authors": [
"Matt Miller <matt@facultycreative.com>"
"Matt Miller <mattmillerart@gmail.com>"
],
"description": "AngularJS directive to warn user of unsaved changes when navigating away from a form.",
"main": "unsavedChanges.js",
"main": "dist/unsavedChanges.js",
"keywords": [
"form",
"angularjs",
Expand All @@ -24,17 +24,12 @@
"test",
"tests"
],
"dependencies": {
"angular": "~1.2.5"
},
"devDependencies": {
"angular-route": "~1.2.2",
"angular-mocks": "~1.2.2",
"angular-scenario": "~1.2.2",
"angular": "~1.5.x",
"angular-route": "~1.5.x",
"angular-mocks": "~1.5.x",
"angular-scenario": "~1.5.x",
"jquery": "~2.0.3",
"angular-translate": "latest"
},
"resolutions": {
"angular": "~1.2.5"
"angular-translate": "^2.0.1"
}
}
15 changes: 15 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,21 @@

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.2.3-alpha.2

__angular-unsavedChanges will remain in alpha until the e2e tests pass, as per https://github.com/facultymatt/angular-unsavedChanges/issues/25__


- Fixed support for angular translate > 2.0.0 [#14](https://github.com/facultymatt/angular-unsavedChanges/pull/14), thanks to @dmytroyarmak
- Fixed issue where removeFunctions were not being cleared properly [#21](https://github.com/facultymatt/angular-unsavedChanges/pull/21), thanks to @dmytroyarmak
- fix unit tests for jasmine 2.0
- add test for isolate scope on form element
- require form on element or parent, fixes #22
- use $window instead of window
- update to angular 1.3, closes #23



### 0.2.3-alpha.1

- Removed form and model dependencies and code from `resettable` directive. We weren't using the get form functionality anyhow.
Expand Down
92 changes: 55 additions & 37 deletions dist/unsavedChanges.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ angular.module('unsavedChanges', ['resettable'])

function translateIfAble(message) {
if ($injector.has('$translate') && useTranslateService) {
return $injector.get('$translate')(message);
return $injector.get('$translate').instant(message);
} else {
return false;
}
Expand Down Expand Up @@ -121,27 +121,21 @@ angular.module('unsavedChanges', ['resettable'])
];
})

.service('unsavedWarningSharedService', ['$rootScope', 'unsavedWarningsConfig', '$injector',
function($rootScope, unsavedWarningsConfig, $injector) {
.service('unsavedWarningSharedService', ['$rootScope', 'unsavedWarningsConfig', '$injector', '$window',
function($rootScope, unsavedWarningsConfig, $injector, $window) {

// Controller scopped variables
var _this = this;
var allForms = [];
var areAllFormsClean = true;
var removeFunctions = [angular.noop];
var removeFunctions = [];

// @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
// Check all registered forms
// if any one is dirty function will return true

function allFormsClean() {
Expand All @@ -167,7 +161,7 @@ angular.module('unsavedChanges', ['resettable'])
var idx = allForms.indexOf(form);

// this form is not present array
// @todo needs test coverage
// @todo needs test coverage
if (idx === -1) return;

allForms.splice(idx, 1);
Expand All @@ -181,12 +175,13 @@ angular.module('unsavedChanges', ['resettable'])
angular.forEach(removeFunctions, function(fn) {
fn();
});
window.onbeforeunload = null;
removeFunctions = [];
$window.onbeforeunload = null;
}

// Function called when user tries to close the window
this.confirmExit = function() {
if (!allFormsClean()) return messages.reload;
if (!allFormsClean()) return unsavedWarningsConfig.reloadMessage;
$rootScope.$broadcast('resetResettables');
tearDown();
};
Expand All @@ -197,24 +192,27 @@ angular.module('unsavedChanges', ['resettable'])
function setup() {
unsavedWarningsConfig.log('Setting up');

window.onbeforeunload = _this.confirmExit;
$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!
// @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");
$rootScope.$broadcast('resetResettables');
}
// allow any existing scope digest to complete
setTimeout(function () {
if (!confirm(unsavedWarningsConfig.navigateMessage)) {
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");
$rootScope.$broadcast('resetResettables');
}
});
} else {
unsavedWarningsConfig.log("all forms are clean");
}
Expand Down Expand Up @@ -242,13 +240,27 @@ angular.module('unsavedChanges', ['resettable'])
}
])

.directive('unsavedWarningForm', ['unsavedWarningSharedService', '$rootScope',
function(unsavedWarningSharedService, $rootScope) {
.directive('unsavedWarningForm', ['unsavedWarningSharedService', '$rootScope', '$timeout',
function(unsavedWarningSharedService, $rootScope, $timeout) {
return {
scope: {},
require: 'form',
require: '^form',
link: function(scope, formElement, attrs, formCtrl) {

// @todo refactor, temp fix for issue #22
// where user might use form on element inside a form
// we shouldnt need isolate scope on this, but it causes the tests to fail
// traverse up parent elements to find the form.
// we need a form element since we bind to form events: submit, reset
var count = 0;
while(formElement[0].tagName !== 'FORM' && count < 3) {
count++;
formElement = formElement.parent();
}
if(count >= 3) {
throw('unsavedWarningForm must be inside a form element');
}

// register this form
unsavedWarningSharedService.init(formCtrl);

Expand All @@ -265,20 +277,21 @@ angular.module('unsavedChanges', ['resettable'])
// do things like reset validation, present messages, etc.
formElement.bind('reset', function(event) {
event.preventDefault();
// because we bind to `resetResettables` also when
// dismissing alerts, we need to apply() in this
// instance to ensure the model view updates.
// @note for ngActiveResoruce, where the models
// themselves do validation, we can't rely on just
// setting the form to valid - we need to set each
// model value back to valid.
scope.$apply($rootScope.$broadcast('resetResettables'));

// trigger resettables within this form or element
var resettables = angular.element(formElement[0].querySelector('[resettable]'));
if(resettables.length) {
// use safer method than $apply
$timeout(function () {
resettables.triggerHandler('resetResettables');
});
}

// sets for back to valid and pristine states
formCtrl.$setPristine();
});

// @todo check destroy on clear button too?
// @todo check destroy on clear button too?
scope.$on('$destroy', function() {
unsavedWarningSharedService.removeForm(formCtrl);
});
Expand All @@ -300,20 +313,23 @@ angular.module('unsavedChanges', ['resettable'])
* to original value.
* --------------------------------------------
*
* @note we don't create a seperate scope so the model value
* is still available onChange within the controller scope.
* This fixes https://github.com/facultymatt/angular-unsavedChanges/issues/19
*
*/
angular.module('resettable', [])

.directive('resettable', ['$parse', '$compile', '$rootScope',
function($parse, $compile, $rootScope) {

return {
scope: true,
restrict: 'A',
link: function postLink(scope, elem, attr, ngModelCtrl) {

var setter, getter, originalValue;

// save getters and setters and store the original value.
// save getters and setters and store the original value.
attr.$observe('ngModel', function(newValue) {
getter = $parse(attr.ngModel);
setter = getter.assign;
Expand All @@ -325,6 +341,8 @@ angular.module('resettable', [])
setter(scope, originalValue);
};

elem.on('resetResettables', resetFn);

// @note this doesn't work if called using
// $rootScope.on() and $rootScope.$emit() pattern
var removeListenerFn = scope.$on('resetResettables', resetFn);
Expand Down
Loading