From 7e1cb4872eaff4c1020f17c1a15530b9b52a979d Mon Sep 17 00:00:00 2001 From: Dmytro Yarmak Date: Mon, 21 Jul 2014 12:27:49 +0300 Subject: [PATCH 01/30] Remove trailing whitespaces --- dist/unsavedChanges.js | 18 +++++++++--------- src/unsavedChanges.js | 18 +++++++++--------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/dist/unsavedChanges.js b/dist/unsavedChanges.js index 41882bd..8108280 100644 --- a/dist/unsavedChanges.js +++ b/dist/unsavedChanges.js @@ -141,7 +141,7 @@ angular.module('unsavedChanges', ['resettable']) reload: unsavedWarningsConfig.reloadMessage }; - // Check all registered forms + // Check all registered forms // if any one is dirty function will return true function allFormsClean() { @@ -167,7 +167,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); @@ -205,12 +205,12 @@ angular.module('unsavedChanges', ['resettable']) //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 + event.preventDefault(); // user clicks cancel, wants to stay on page } else { unsavedWarningsConfig.log("user doesn't care about loosing stuff"); $rootScope.$broadcast('resetResettables'); @@ -266,11 +266,11 @@ angular.module('unsavedChanges', ['resettable']) 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. + // 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 + // setting the form to valid - we need to set each // model value back to valid. scope.$apply($rootScope.$broadcast('resetResettables')); @@ -278,7 +278,7 @@ angular.module('unsavedChanges', ['resettable']) formCtrl.$setPristine(); }); - // @todo check destroy on clear button too? + // @todo check destroy on clear button too? scope.$on('$destroy', function() { unsavedWarningSharedService.removeForm(formCtrl); }); @@ -313,7 +313,7 @@ angular.module('resettable', []) 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; diff --git a/src/unsavedChanges.js b/src/unsavedChanges.js index 41882bd..8108280 100644 --- a/src/unsavedChanges.js +++ b/src/unsavedChanges.js @@ -141,7 +141,7 @@ angular.module('unsavedChanges', ['resettable']) reload: unsavedWarningsConfig.reloadMessage }; - // Check all registered forms + // Check all registered forms // if any one is dirty function will return true function allFormsClean() { @@ -167,7 +167,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); @@ -205,12 +205,12 @@ angular.module('unsavedChanges', ['resettable']) //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 + event.preventDefault(); // user clicks cancel, wants to stay on page } else { unsavedWarningsConfig.log("user doesn't care about loosing stuff"); $rootScope.$broadcast('resetResettables'); @@ -266,11 +266,11 @@ angular.module('unsavedChanges', ['resettable']) 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. + // 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 + // setting the form to valid - we need to set each // model value back to valid. scope.$apply($rootScope.$broadcast('resetResettables')); @@ -278,7 +278,7 @@ angular.module('unsavedChanges', ['resettable']) formCtrl.$setPristine(); }); - // @todo check destroy on clear button too? + // @todo check destroy on clear button too? scope.$on('$destroy', function() { unsavedWarningSharedService.removeForm(formCtrl); }); @@ -313,7 +313,7 @@ angular.module('resettable', []) 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; From ec5fa97abd69eefe826a96f2aec522a491b22713 Mon Sep 17 00:00:00 2001 From: Dmytro Yarmak Date: Mon, 21 Jul 2014 12:28:24 +0300 Subject: [PATCH 02/30] Fix problem with angular-translate v2 --- bower.json | 2 +- dist/unsavedChanges.js | 12 +++--------- dist/unsavedChanges.min.js | 2 +- src/unsavedChanges.js | 12 +++--------- 4 files changed, 8 insertions(+), 20 deletions(-) diff --git a/bower.json b/bower.json index b256877..1a3739d 100644 --- a/bower.json +++ b/bower.json @@ -32,7 +32,7 @@ "angular-mocks": "~1.2.2", "angular-scenario": "~1.2.2", "jquery": "~2.0.3", - "angular-translate": "latest" + "angular-translate": "^2.0.1" }, "resolutions": { "angular": "~1.2.5" diff --git a/dist/unsavedChanges.js b/dist/unsavedChanges.js index 8108280..3a6bb66 100644 --- a/dist/unsavedChanges.js +++ b/dist/unsavedChanges.js @@ -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; } @@ -135,12 +135,6 @@ angular.module('unsavedChanges', ['resettable']) 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 @@ -186,7 +180,7 @@ angular.module('unsavedChanges', ['resettable']) // 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(); }; @@ -208,7 +202,7 @@ angular.module('unsavedChanges', ['resettable']) // @todo this could be written a lot cleaner! if (!allFormsClean()) { unsavedWarningsConfig.log("a form is dirty"); - if (!confirm(messages.navigate)) { + if (!confirm(unsavedWarningsConfig.navigateMessage)) { unsavedWarningsConfig.log("user wants to cancel leaving"); event.preventDefault(); // user clicks cancel, wants to stay on page } else { diff --git a/dist/unsavedChanges.min.js b/dist/unsavedChanges.min.js index 29bc868..7534151 100644 --- a/dist/unsavedChanges.min.js +++ b/dist/unsavedChanges.min.js @@ -1 +1 @@ -"use strict";angular.module("unsavedChanges",["resettable"]).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}j.$broadcast("resetResettables");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");j.$broadcast("resetResettables")}}else{c.log("all forms are clean")}});d.push(n)})}}]).directive("unsavedWarningClear",["unsavedWarningSharedService",function(a){return{scope:{},require:"^form",priority:10,link:function(d,c,b,e){c.bind("click",function(f){e.$setPristine()})}}}]).directive("unsavedWarningForm",["unsavedWarningSharedService","$rootScope",function(b,a){return{scope:{},require:"form",link:function(e,d,c,f){b.init(f);d.bind("submit",function(g){if(f.$valid){f.$setPristine()}});d.bind("reset",function(g){g.preventDefault();e.$apply(a.$broadcast("resetResettables"));f.$setPristine()});e.$on("$destroy",function(){b.removeForm(f)})}}}]);angular.module("resettable",[]).directive("resettable",["$parse","$compile","$rootScope",function(d,c,b){return{scope:true,restrict:"A",link:function a(m,g,j,e){var h,k,l;j.$observe("ngModel",function(n){k=d(j.ngModel);h=k.assign;l=k(m)});var i=function(){h(m,l)};var f=m.$on("resetResettables",i);m.$on("$destroy",f)}}}]); \ No newline at end of file +"use strict";angular.module("unsavedChanges",["resettable"]).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").instant(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(i,c,j){var h=this;var a=[];var f=true;var d=[angular.noop];this.allForms=function(){return a};function g(){f=true;angular.forEach(a,function(l,k){c.log("Form : "+l.$name+" dirty : "+l.$dirty);if(l.$dirty){f=false}});return f}this.init=function(k){if(a.length===0){e()}c.log("Registering form",k);a.push(k)};this.removeForm=function(l){var k=a.indexOf(l);if(k===-1){return}a.splice(k,1);c.log("Removing form from watch list",l);if(a.length===0){b()}};function b(){c.log("No more forms, tearing down");angular.forEach(d,function(k){k()});window.onbeforeunload=null}this.confirmExit=function(){if(!g()){return c.reloadMessage}i.$broadcast("resetResettables");b()};function e(){c.log("Setting up");window.onbeforeunload=h.confirmExit;var k=c.routeEvent;angular.forEach(k,function(l){var m=i.$on(l,function(o,n,p){c.log("user is moving with "+l);if(!g()){c.log("a form is dirty");if(!confirm(c.navigateMessage)){c.log("user wants to cancel leaving");o.preventDefault()}else{c.log("user doesn't care about loosing stuff");i.$broadcast("resetResettables")}}else{c.log("all forms are clean")}});d.push(m)})}}]).directive("unsavedWarningClear",["unsavedWarningSharedService",function(a){return{scope:{},require:"^form",priority:10,link:function(d,c,b,e){c.bind("click",function(f){e.$setPristine()})}}}]).directive("unsavedWarningForm",["unsavedWarningSharedService","$rootScope",function(b,a){return{scope:{},require:"form",link:function(e,d,c,f){b.init(f);d.bind("submit",function(g){if(f.$valid){f.$setPristine()}});d.bind("reset",function(g){g.preventDefault();e.$apply(a.$broadcast("resetResettables"));f.$setPristine()});e.$on("$destroy",function(){b.removeForm(f)})}}}]);angular.module("resettable",[]).directive("resettable",["$parse","$compile","$rootScope",function(d,c,b){return{scope:true,restrict:"A",link:function a(m,g,j,e){var h,k,l;j.$observe("ngModel",function(n){k=d(j.ngModel);h=k.assign;l=k(m)});var i=function(){h(m,l)};var f=m.$on("resetResettables",i);m.$on("$destroy",f)}}}]); \ No newline at end of file diff --git a/src/unsavedChanges.js b/src/unsavedChanges.js index 8108280..3a6bb66 100644 --- a/src/unsavedChanges.js +++ b/src/unsavedChanges.js @@ -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; } @@ -135,12 +135,6 @@ angular.module('unsavedChanges', ['resettable']) 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 @@ -186,7 +180,7 @@ angular.module('unsavedChanges', ['resettable']) // 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(); }; @@ -208,7 +202,7 @@ angular.module('unsavedChanges', ['resettable']) // @todo this could be written a lot cleaner! if (!allFormsClean()) { unsavedWarningsConfig.log("a form is dirty"); - if (!confirm(messages.navigate)) { + if (!confirm(unsavedWarningsConfig.navigateMessage)) { unsavedWarningsConfig.log("user wants to cancel leaving"); event.preventDefault(); // user clicks cancel, wants to stay on page } else { From a5f555b69c50c13619e471398a5ba4cc34091ad0 Mon Sep 17 00:00:00 2001 From: Dmytro Yarmak Date: Tue, 2 Sep 2014 10:21:40 +0300 Subject: [PATCH 03/30] Update bower.json --- bower.json | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/bower.json b/bower.json index 1a3739d..3bd5077 100644 --- a/bower.json +++ b/bower.json @@ -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.2.5", + "angular-route": "~1.2.5", + "angular-mocks": "~1.2.5", + "angular-scenario": "~1.2.5", "jquery": "~2.0.3", "angular-translate": "^2.0.1" - }, - "resolutions": { - "angular": "~1.2.5" } } From 566431bfde5eec3c6181a48ba4e99adf0c715133 Mon Sep 17 00:00:00 2001 From: Dmytro Yarmak Date: Tue, 2 Sep 2014 10:30:50 +0300 Subject: [PATCH 04/30] Update bower.json --- bower.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bower.json b/bower.json index 3bd5077..c92de63 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "angular-unsavedChanges", - "version": "0.2.3-alpha.1", + "version": "0.2.4", "homepage": "https://github.com/facultymatt/angular-unsavedChanges", "authors": [ "Matt Miller " From 5955872a66f3b76a8ec68dcac6ff2bc18fb0d1c8 Mon Sep 17 00:00:00 2001 From: Dmytro Yarmak Date: Fri, 17 Oct 2014 17:51:37 +0300 Subject: [PATCH 05/30] Fix problem with don't cleaning removeFunctions after tear down --- dist/unsavedChanges.js | 21 +++++++++++---------- dist/unsavedChanges.min.js | 2 +- src/unsavedChanges.js | 21 +++++++++++---------- 3 files changed, 23 insertions(+), 21 deletions(-) diff --git a/dist/unsavedChanges.js b/dist/unsavedChanges.js index 41882bd..8d7bb64 100644 --- a/dist/unsavedChanges.js +++ b/dist/unsavedChanges.js @@ -128,7 +128,7 @@ angular.module('unsavedChanges', ['resettable']) var _this = this; var allForms = []; var areAllFormsClean = true; - var removeFunctions = [angular.noop]; + var removeFunctions = []; // @note only exposed for testing purposes. this.allForms = function() { @@ -141,7 +141,7 @@ angular.module('unsavedChanges', ['resettable']) reload: unsavedWarningsConfig.reloadMessage }; - // Check all registered forms + // Check all registered forms // if any one is dirty function will return true function allFormsClean() { @@ -167,7 +167,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); @@ -181,6 +181,7 @@ angular.module('unsavedChanges', ['resettable']) angular.forEach(removeFunctions, function(fn) { fn(); }); + removeFunctions = []; window.onbeforeunload = null; } @@ -205,12 +206,12 @@ angular.module('unsavedChanges', ['resettable']) //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 + event.preventDefault(); // user clicks cancel, wants to stay on page } else { unsavedWarningsConfig.log("user doesn't care about loosing stuff"); $rootScope.$broadcast('resetResettables'); @@ -266,11 +267,11 @@ angular.module('unsavedChanges', ['resettable']) 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. + // 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 + // setting the form to valid - we need to set each // model value back to valid. scope.$apply($rootScope.$broadcast('resetResettables')); @@ -278,7 +279,7 @@ angular.module('unsavedChanges', ['resettable']) formCtrl.$setPristine(); }); - // @todo check destroy on clear button too? + // @todo check destroy on clear button too? scope.$on('$destroy', function() { unsavedWarningSharedService.removeForm(formCtrl); }); @@ -313,7 +314,7 @@ angular.module('resettable', []) 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; diff --git a/dist/unsavedChanges.min.js b/dist/unsavedChanges.min.js index 29bc868..f45cd9b 100644 --- a/dist/unsavedChanges.min.js +++ b/dist/unsavedChanges.min.js @@ -1 +1 @@ -"use strict";angular.module("unsavedChanges",["resettable"]).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}j.$broadcast("resetResettables");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");j.$broadcast("resetResettables")}}else{c.log("all forms are clean")}});d.push(n)})}}]).directive("unsavedWarningClear",["unsavedWarningSharedService",function(a){return{scope:{},require:"^form",priority:10,link:function(d,c,b,e){c.bind("click",function(f){e.$setPristine()})}}}]).directive("unsavedWarningForm",["unsavedWarningSharedService","$rootScope",function(b,a){return{scope:{},require:"form",link:function(e,d,c,f){b.init(f);d.bind("submit",function(g){if(f.$valid){f.$setPristine()}});d.bind("reset",function(g){g.preventDefault();e.$apply(a.$broadcast("resetResettables"));f.$setPristine()});e.$on("$destroy",function(){b.removeForm(f)})}}}]);angular.module("resettable",[]).directive("resettable",["$parse","$compile","$rootScope",function(d,c,b){return{scope:true,restrict:"A",link:function a(m,g,j,e){var h,k,l;j.$observe("ngModel",function(n){k=d(j.ngModel);h=k.assign;l=k(m)});var i=function(){h(m,l)};var f=m.$on("resetResettables",i);m.$on("$destroy",f)}}}]); \ No newline at end of file +"use strict";angular.module("unsavedChanges",["resettable"]).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=[];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()});d=[];window.onbeforeunload=null}this.confirmExit=function(){if(!h()){return f.reload}j.$broadcast("resetResettables");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");j.$broadcast("resetResettables")}}else{c.log("all forms are clean")}});d.push(n)})}}]).directive("unsavedWarningClear",["unsavedWarningSharedService",function(a){return{scope:{},require:"^form",priority:10,link:function(d,c,b,e){c.bind("click",function(f){e.$setPristine()})}}}]).directive("unsavedWarningForm",["unsavedWarningSharedService","$rootScope",function(b,a){return{scope:{},require:"form",link:function(e,d,c,f){b.init(f);d.bind("submit",function(g){if(f.$valid){f.$setPristine()}});d.bind("reset",function(g){g.preventDefault();e.$apply(a.$broadcast("resetResettables"));f.$setPristine()});e.$on("$destroy",function(){b.removeForm(f)})}}}]);angular.module("resettable",[]).directive("resettable",["$parse","$compile","$rootScope",function(d,c,b){return{scope:true,restrict:"A",link:function a(m,g,j,e){var h,k,l;j.$observe("ngModel",function(n){k=d(j.ngModel);h=k.assign;l=k(m)});var i=function(){h(m,l)};var f=m.$on("resetResettables",i);m.$on("$destroy",f)}}}]); \ No newline at end of file diff --git a/src/unsavedChanges.js b/src/unsavedChanges.js index 41882bd..8d7bb64 100644 --- a/src/unsavedChanges.js +++ b/src/unsavedChanges.js @@ -128,7 +128,7 @@ angular.module('unsavedChanges', ['resettable']) var _this = this; var allForms = []; var areAllFormsClean = true; - var removeFunctions = [angular.noop]; + var removeFunctions = []; // @note only exposed for testing purposes. this.allForms = function() { @@ -141,7 +141,7 @@ angular.module('unsavedChanges', ['resettable']) reload: unsavedWarningsConfig.reloadMessage }; - // Check all registered forms + // Check all registered forms // if any one is dirty function will return true function allFormsClean() { @@ -167,7 +167,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); @@ -181,6 +181,7 @@ angular.module('unsavedChanges', ['resettable']) angular.forEach(removeFunctions, function(fn) { fn(); }); + removeFunctions = []; window.onbeforeunload = null; } @@ -205,12 +206,12 @@ angular.module('unsavedChanges', ['resettable']) //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 + event.preventDefault(); // user clicks cancel, wants to stay on page } else { unsavedWarningsConfig.log("user doesn't care about loosing stuff"); $rootScope.$broadcast('resetResettables'); @@ -266,11 +267,11 @@ angular.module('unsavedChanges', ['resettable']) 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. + // 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 + // setting the form to valid - we need to set each // model value back to valid. scope.$apply($rootScope.$broadcast('resetResettables')); @@ -278,7 +279,7 @@ angular.module('unsavedChanges', ['resettable']) formCtrl.$setPristine(); }); - // @todo check destroy on clear button too? + // @todo check destroy on clear button too? scope.$on('$destroy', function() { unsavedWarningSharedService.removeForm(formCtrl); }); @@ -313,7 +314,7 @@ angular.module('resettable', []) 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; From 7bd9745174ed4de686a118872da31d33594ad247 Mon Sep 17 00:00:00 2001 From: Dmytro Yarmak Date: Fri, 17 Oct 2014 17:58:00 +0300 Subject: [PATCH 06/30] Increase version to v0.2.5 --- bower.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bower.json b/bower.json index c92de63..f180c48 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "angular-unsavedChanges", - "version": "0.2.4", + "version": "0.2.5", "homepage": "https://github.com/facultymatt/angular-unsavedChanges", "authors": [ "Matt Miller " diff --git a/package.json b/package.json index e98ed42..a569280 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "angular-unsavedChanges", - "version": "0.2.3-alpha.1", + "version": "0.2.5", "description": "AngularJS directive to warn user of unsaved changes when navigating away from a form.", "main": "Gruntfile.js", "devDependencies": { From 4c4c2b56c6adebec71a9fb77b8a05db1ee2e8d2f Mon Sep 17 00:00:00 2001 From: Matt Miller Date: Mon, 3 Nov 2014 05:47:12 -0500 Subject: [PATCH 07/30] update to angular 1.3, closes #23 --- bower.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/bower.json b/bower.json index f180c48..a2c5abf 100644 --- a/bower.json +++ b/bower.json @@ -25,10 +25,10 @@ "tests" ], "devDependencies": { - "angular": "~1.2.5", - "angular-route": "~1.2.5", - "angular-mocks": "~1.2.5", - "angular-scenario": "~1.2.5", + "angular": "~1.3.x", + "angular-route": "~1.3.x", + "angular-mocks": "~1.3.x", + "angular-scenario": "~1.3.x", "jquery": "~2.0.3", "angular-translate": "^2.0.1" } From 0d855608a630543de81e4e1448bccffb60692e0a Mon Sep 17 00:00:00 2001 From: Matt Miller Date: Mon, 3 Nov 2014 05:47:57 -0500 Subject: [PATCH 08/30] user $window instead of window --- src/unsavedChanges.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/unsavedChanges.js b/src/unsavedChanges.js index d4b31f7..cf5c187 100644 --- a/src/unsavedChanges.js +++ b/src/unsavedChanges.js @@ -121,8 +121,8 @@ 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; @@ -176,7 +176,7 @@ angular.module('unsavedChanges', ['resettable']) fn(); }); removeFunctions = []; - window.onbeforeunload = null; + $window.onbeforeunload = null; } // Function called when user tries to close the window @@ -192,7 +192,7 @@ angular.module('unsavedChanges', ['resettable']) function setup() { unsavedWarningsConfig.log('Setting up'); - window.onbeforeunload = _this.confirmExit; + $window.onbeforeunload = _this.confirmExit; var eventsToWatchFor = unsavedWarningsConfig.routeEvent; From f7ebbe5df0c4e22d3a1ecd1970eff29238a4135d Mon Sep 17 00:00:00 2001 From: Matt Miller Date: Mon, 3 Nov 2014 05:48:39 -0500 Subject: [PATCH 09/30] require form on element or parent, fixes #22 --- src/unsavedChanges.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/unsavedChanges.js b/src/unsavedChanges.js index cf5c187..e536675 100644 --- a/src/unsavedChanges.js +++ b/src/unsavedChanges.js @@ -241,7 +241,7 @@ angular.module('unsavedChanges', ['resettable']) function(unsavedWarningSharedService, $rootScope) { return { scope: {}, - require: 'form', + require: '^form', link: function(scope, formElement, attrs, formCtrl) { // register this form From 34d61f5aa7d456a5f4650d01747a31b4b85edad0 Mon Sep 17 00:00:00 2001 From: Matt Miller Date: Mon, 3 Nov 2014 05:48:55 -0500 Subject: [PATCH 10/30] add test for isolate scope on form element --- test/unit/unsavedChanges.spec.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/unit/unsavedChanges.spec.js b/test/unit/unsavedChanges.spec.js index 265acaa..b4cb77e 100644 --- a/test/unit/unsavedChanges.spec.js +++ b/test/unit/unsavedChanges.spec.js @@ -135,7 +135,9 @@ describe('UnsavedChanges', function() { describe('Form', function() { - it('creates isolate scope', function() {}); + it('creates isolate scope', function() { + expect(controllerScope.$parent).toEqual($rootScope); + }); it('adds listener to onbeforeunload to detect page reload', function() { expect($window.onbeforeunload.toString()).toContain('allFormsClean()'); From 37fa8b63b326440fff14a233c133161379c2d6a6 Mon Sep 17 00:00:00 2001 From: Matt Miller Date: Mon, 3 Nov 2014 05:49:13 -0500 Subject: [PATCH 11/30] fix unit tests for jasmine 2.0 --- test/unit/unsavedChanges.spec.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/unit/unsavedChanges.spec.js b/test/unit/unsavedChanges.spec.js index b4cb77e..b18a3fd 100644 --- a/test/unit/unsavedChanges.spec.js +++ b/test/unit/unsavedChanges.spec.js @@ -103,9 +103,9 @@ describe('UnsavedChanges', function() { controllerScope.test = 'default value'; // @note logs will not occur if we are not calling through - spyOn(console, 'log').andCallThrough(); - spyOn(controllerScope.testForm, '$setPristine').andCallThrough(); - spyOn($rootScope, '$broadcast').andCallThrough(); + spyOn(console, 'log').and.callThrough(); + spyOn(controllerScope.testForm, '$setPristine').and.callThrough(); + spyOn($rootScope, '$broadcast').and.callThrough(); })); From 1bd5b8c0d37e0fa296079483cfb48cd83e9cce9a Mon Sep 17 00:00:00 2001 From: Matt Miller Date: Mon, 3 Nov 2014 05:53:59 -0500 Subject: [PATCH 12/30] build and release v0.2.3-alpha.2 --- changelog.md | 15 +++++++++++++++ dist/unsavedChanges.js | 10 +++++----- dist/unsavedChanges.min.js | 2 +- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/changelog.md b/changelog.md index 604e651..05470d2 100644 --- a/changelog.md +++ b/changelog.md @@ -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. diff --git a/dist/unsavedChanges.js b/dist/unsavedChanges.js index d4b31f7..e536675 100644 --- a/dist/unsavedChanges.js +++ b/dist/unsavedChanges.js @@ -121,8 +121,8 @@ 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; @@ -176,7 +176,7 @@ angular.module('unsavedChanges', ['resettable']) fn(); }); removeFunctions = []; - window.onbeforeunload = null; + $window.onbeforeunload = null; } // Function called when user tries to close the window @@ -192,7 +192,7 @@ angular.module('unsavedChanges', ['resettable']) function setup() { unsavedWarningsConfig.log('Setting up'); - window.onbeforeunload = _this.confirmExit; + $window.onbeforeunload = _this.confirmExit; var eventsToWatchFor = unsavedWarningsConfig.routeEvent; @@ -241,7 +241,7 @@ angular.module('unsavedChanges', ['resettable']) function(unsavedWarningSharedService, $rootScope) { return { scope: {}, - require: 'form', + require: '^form', link: function(scope, formElement, attrs, formCtrl) { // register this form diff --git a/dist/unsavedChanges.min.js b/dist/unsavedChanges.min.js index 9845054..ae35dff 100644 --- a/dist/unsavedChanges.min.js +++ b/dist/unsavedChanges.min.js @@ -1 +1 @@ -"use strict";angular.module("unsavedChanges",["resettable"]).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").instant(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(i,c,j){var h=this;var a=[];var f=true;var d=[];this.allForms=function(){return a};function g(){f=true;angular.forEach(a,function(l,k){c.log("Form : "+l.$name+" dirty : "+l.$dirty);if(l.$dirty){f=false}});return f}this.init=function(k){if(a.length===0){e()}c.log("Registering form",k);a.push(k)};this.removeForm=function(l){var k=a.indexOf(l);if(k===-1){return}a.splice(k,1);c.log("Removing form from watch list",l);if(a.length===0){b()}};function b(){c.log("No more forms, tearing down");angular.forEach(d,function(k){k()});d=[];window.onbeforeunload=null}this.confirmExit=function(){if(!g()){return c.reloadMessage}i.$broadcast("resetResettables");b()};function e(){c.log("Setting up");window.onbeforeunload=h.confirmExit;var k=c.routeEvent;angular.forEach(k,function(l){var m=i.$on(l,function(o,n,p){c.log("user is moving with "+l);if(!g()){c.log("a form is dirty");if(!confirm(c.navigateMessage)){c.log("user wants to cancel leaving");o.preventDefault()}else{c.log("user doesn't care about loosing stuff");i.$broadcast("resetResettables")}}else{c.log("all forms are clean")}});d.push(m)})}}]).directive("unsavedWarningClear",["unsavedWarningSharedService",function(a){return{scope:{},require:"^form",priority:10,link:function(d,c,b,e){c.bind("click",function(f){e.$setPristine()})}}}]).directive("unsavedWarningForm",["unsavedWarningSharedService","$rootScope",function(b,a){return{scope:{},require:"form",link:function(e,d,c,f){b.init(f);d.bind("submit",function(g){if(f.$valid){f.$setPristine()}});d.bind("reset",function(g){g.preventDefault();e.$apply(a.$broadcast("resetResettables"));f.$setPristine()});e.$on("$destroy",function(){b.removeForm(f)})}}}]);angular.module("resettable",[]).directive("resettable",["$parse","$compile","$rootScope",function(d,c,b){return{scope:true,restrict:"A",link:function a(m,g,j,e){var h,k,l;j.$observe("ngModel",function(n){k=d(j.ngModel);h=k.assign;l=k(m)});var i=function(){h(m,l)};var f=m.$on("resetResettables",i);m.$on("$destroy",f)}}}]); \ No newline at end of file +"use strict";angular.module("unsavedChanges",["resettable"]).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").instant(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","$window",function(j,c,k,d){var i=this;var a=[];var g=true;var e=[];this.allForms=function(){return a};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){f()}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(e,function(l){l()});e=[];d.onbeforeunload=null}this.confirmExit=function(){if(!h()){return c.reloadMessage}j.$broadcast("resetResettables");b()};function f(){c.log("Setting up");d.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(c.navigateMessage)){c.log("user wants to cancel leaving");p.preventDefault()}else{c.log("user doesn't care about loosing stuff");j.$broadcast("resetResettables")}}else{c.log("all forms are clean")}});e.push(n)})}}]).directive("unsavedWarningClear",["unsavedWarningSharedService",function(a){return{scope:{},require:"^form",priority:10,link:function(d,c,b,e){c.bind("click",function(f){e.$setPristine()})}}}]).directive("unsavedWarningForm",["unsavedWarningSharedService","$rootScope",function(b,a){return{scope:{},require:"^form",link:function(e,d,c,f){b.init(f);d.bind("submit",function(g){if(f.$valid){f.$setPristine()}});d.bind("reset",function(g){g.preventDefault();e.$apply(a.$broadcast("resetResettables"));f.$setPristine()});e.$on("$destroy",function(){b.removeForm(f)})}}}]);angular.module("resettable",[]).directive("resettable",["$parse","$compile","$rootScope",function(d,c,b){return{scope:true,restrict:"A",link:function a(m,g,j,e){var h,k,l;j.$observe("ngModel",function(n){k=d(j.ngModel);h=k.assign;l=k(m)});var i=function(){h(m,l)};var f=m.$on("resetResettables",i);m.$on("$destroy",f)}}}]); \ No newline at end of file From 7935051aaedc8090378d73a54f8fbd1d6e3d1046 Mon Sep 17 00:00:00 2001 From: Matt Miller Date: Mon, 3 Nov 2014 08:49:27 -0500 Subject: [PATCH 13/30] test to verify #19 --- test/unit/unsavedChanges.spec.js | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/test/unit/unsavedChanges.spec.js b/test/unit/unsavedChanges.spec.js index b18a3fd..8acaf65 100644 --- a/test/unit/unsavedChanges.spec.js +++ b/test/unit/unsavedChanges.spec.js @@ -302,6 +302,33 @@ describe('UnsavedChanges', function() { expect(controllerScope.testForm.test.$modelValue).toEqual(undefined); }); + it('exposes new value on ng-change', function() { + var didFire = false; + + var newScope = $rootScope.$new(); + newScope.test = 'a new default value'; + + newScope.didChange = function(newValue) { + didFire = newScope.test; + } + formTemplate = angular.element('
' + + '
' + + '' + + '' + + '
' + + '
'); + + $compile(formTemplate)(newScope); + + inputCtrl = formTemplate.find('#test').controller('ngModel'); + inputCtrl.$setViewValue('things are changing'); + + controllerScope.$digest(); + + expect(didFire).toEqual('things are changing'); + expect(newScope.newForm.test.$modelValue).toEqual('things are changing'); + }); + it('resets to original model value on form navigate', function() {}); it('observes ngModel and only sets original value when value is resolved', function() {}); From debabf3ffc214c00bb5b4de2ce0b84ad7dc1b62a Mon Sep 17 00:00:00 2001 From: Matt Miller Date: Mon, 3 Nov 2014 08:50:31 -0500 Subject: [PATCH 14/30] remove scope to allow access to value within controller scope This addresses #19 where when the ngChange event fired, the value was still the old value. --- src/unsavedChanges.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/unsavedChanges.js b/src/unsavedChanges.js index e536675..6a8ffc1 100644 --- a/src/unsavedChanges.js +++ b/src/unsavedChanges.js @@ -295,6 +295,10 @@ 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', []) @@ -302,7 +306,6 @@ angular.module('resettable', []) function($parse, $compile, $rootScope) { return { - scope: true, restrict: 'A', link: function postLink(scope, elem, attr, ngModelCtrl) { From 07f8ab39549cd488021fa0c83b30e2bc0a0e8070 Mon Sep 17 00:00:00 2001 From: Matt Miller Date: Tue, 4 Nov 2014 08:31:34 -0500 Subject: [PATCH 15/30] fix(resettable): switch from broadcast to trigger Fix issue where resettables across the page were cleared when user clicked the clear buttion. Closes #26 --- src/unsavedChanges.js | 16 +++++----- test/unit/unsavedChanges.spec.js | 51 +++++++++++++++++++++++++++++++- 2 files changed, 58 insertions(+), 9 deletions(-) diff --git a/src/unsavedChanges.js b/src/unsavedChanges.js index 6a8ffc1..602c493 100644 --- a/src/unsavedChanges.js +++ b/src/unsavedChanges.js @@ -260,14 +260,12 @@ 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) { + scope.$apply(resettables.triggerHandler('resetResettables')); + } // sets for back to valid and pristine states formCtrl.$setPristine(); @@ -323,6 +321,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); diff --git a/test/unit/unsavedChanges.spec.js b/test/unit/unsavedChanges.spec.js index 8acaf65..8a7972f 100644 --- a/test/unit/unsavedChanges.spec.js +++ b/test/unit/unsavedChanges.spec.js @@ -127,7 +127,7 @@ describe('UnsavedChanges', function() { expect(controllerScope.testForm.$setPristine).toHaveBeenCalled(); }); - it('calls $broadcast message when clicked', function() { + xit('calls $broadcast message when clicked', function() { expect($rootScope.$broadcast).toHaveBeenCalledWith('resetResettables'); }); @@ -302,6 +302,55 @@ describe('UnsavedChanges', function() { expect(controllerScope.testForm.test.$modelValue).toEqual(undefined); }); + it('only values in this form', function() { + + var newScope = $rootScope.$new(); + newScope.test1 = 'test1 default'; + newScope.test2 = 'test2 default'; + newScope.test3 = 'test3 default'; + + formTemplate = angular.element('
' + + '
' + + '' + + '' + + '
' + + + '
' + + '' + + '' + + '
' + + + '
' + + '' + + '' + + '
' + + + '
'); + + $compile(formTemplate)(newScope); + newScope.$digest(); + + // change values for all inputs + newScope.test1 = 'test1 changed'; + newScope.test2 = 'test2 changed'; + newScope.test3 = 'test3 changed'; + + // clear form one changes only + formTemplate.find('#clear1').click(); + newScope.$digest(); + + expect(newScope.test1).toEqual('test1 default'); + expect(newScope.test2).toEqual('test2 changed'); + expect(newScope.test3).toEqual('test3 changed'); + + // debugging scope + // expect(newScope.$parent).toEqual($rootScope); + // expect(formTemplate.find('#form1').scope()).toEqual(newScope); + // expect(formTemplate.find('#form2').scope()).toEqual(newScope); + // expect(formTemplate.find('#form3').scope()).toEqual(newScope); + + }); + it('exposes new value on ng-change', function() { var didFire = false; From 9d56c277024b814faa013547e76b9e41c412e19b Mon Sep 17 00:00:00 2001 From: Matt Miller Date: Tue, 4 Nov 2014 08:51:11 -0500 Subject: [PATCH 16/30] fix(form): added test and fixed issue with #22 --- src/unsavedChanges.js | 9 +++++++++ test/unit/unsavedChanges.spec.js | 33 +++++++++++++++++++++++++++++++- 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/src/unsavedChanges.js b/src/unsavedChanges.js index 602c493..f5b59b4 100644 --- a/src/unsavedChanges.js +++ b/src/unsavedChanges.js @@ -244,6 +244,15 @@ angular.module('unsavedChanges', ['resettable']) 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 + while(formElement.tagName !== 'form') { + formElement = formElement.parent(); + } + // register this form unsavedWarningSharedService.init(formCtrl); diff --git a/test/unit/unsavedChanges.spec.js b/test/unit/unsavedChanges.spec.js index 8a7972f..164ca44 100644 --- a/test/unit/unsavedChanges.spec.js +++ b/test/unit/unsavedChanges.spec.js @@ -302,7 +302,7 @@ describe('UnsavedChanges', function() { expect(controllerScope.testForm.test.$modelValue).toEqual(undefined); }); - it('only values in this form', function() { + it('only resets values within this form', function() { var newScope = $rootScope.$new(); newScope.test1 = 'test1 default'; @@ -351,6 +351,37 @@ describe('UnsavedChanges', function() { }); + it('can be used as child element of form', function() { + + var newScope = $rootScope.$new(); + newScope.test1 = 'test1 default'; + + formTemplate = angular.element('
' + + '
' + + '
' + + '
' + + '' + + '' + + '
' + + '
' + + '
' + + + '
'); + + $compile(formTemplate)(newScope); + newScope.$digest(); + + // change values for all inputs + newScope.test1 = 'test1 changed'; + + // clear form one changes only + formTemplate.find('#clear1').click(); + newScope.$digest(); + + expect(newScope.test1).toEqual('test1 default'); + + }); + it('exposes new value on ng-change', function() { var didFire = false; From e69045e03052127da1ad2b1f1a508fcb4219d9b7 Mon Sep 17 00:00:00 2001 From: Matt Miller Date: Tue, 4 Nov 2014 08:54:55 -0500 Subject: [PATCH 17/30] update bower, fixes #16 --- bower.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bower.json b/bower.json index a2c5abf..09607ea 100644 --- a/bower.json +++ b/bower.json @@ -3,10 +3,10 @@ "version": "0.2.5", "homepage": "https://github.com/facultymatt/angular-unsavedChanges", "authors": [ - "Matt Miller " + "Matt Miller " ], "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", From aaad30a5b8bc6dbfae8c4b771dfe0467f4726702 Mon Sep 17 00:00:00 2001 From: Matt Miller Date: Tue, 4 Nov 2014 11:11:53 -0500 Subject: [PATCH 18/30] fix(directive): Fix nasty race condition. --- src/unsavedChanges.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/unsavedChanges.js b/src/unsavedChanges.js index f5b59b4..f5d47c0 100644 --- a/src/unsavedChanges.js +++ b/src/unsavedChanges.js @@ -249,9 +249,14 @@ angular.module('unsavedChanges', ['resettable']) // 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 - while(formElement.tagName !== 'form') { + 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); From c357a1654589a21bd8897782eff7295ba8bca6f1 Mon Sep 17 00:00:00 2001 From: Matt Miller Date: Tue, 4 Nov 2014 11:12:02 -0500 Subject: [PATCH 19/30] update readme --- readme.md | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/readme.md b/readme.md index ae35068..dbbe2eb 100644 --- a/readme.md +++ b/readme.md @@ -44,8 +44,20 @@ Add to forms you want to register with directive. The module will only listen wh ``` +Optionally, you can add to an element within a form: + +``` +
+
+
+
+``` + +When used in this way, it must be no more then 3 levels nested within parent 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). +Add to button or link that will disregard changes, preventing the messaging when user tries to navigate. Note that button type should be `reset`. ```
@@ -62,6 +74,11 @@ Add to inputs that use `ng-model` to reset model values when user dismisses chan ``` +Note that if you have multiple forms on the page, only the model values inside the form which was reset will be effected. + +On page change or reload, all model values will be effected. + + ### 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. From f831050a08686545fb26c9e516bad5d7abba32eb Mon Sep 17 00:00:00 2001 From: Matt Miller Date: Tue, 4 Nov 2014 11:18:57 -0500 Subject: [PATCH 20/30] build v0.2.3-alpha.3 --- dist/unsavedChanges.js | 35 ++++++++++++++++++++++++++--------- dist/unsavedChanges.min.js | 2 +- 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/dist/unsavedChanges.js b/dist/unsavedChanges.js index e536675..f5d47c0 100644 --- a/dist/unsavedChanges.js +++ b/dist/unsavedChanges.js @@ -244,6 +244,20 @@ angular.module('unsavedChanges', ['resettable']) 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); @@ -260,14 +274,12 @@ 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) { + scope.$apply(resettables.triggerHandler('resetResettables')); + } // sets for back to valid and pristine states formCtrl.$setPristine(); @@ -295,6 +307,10 @@ 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', []) @@ -302,7 +318,6 @@ angular.module('resettable', []) function($parse, $compile, $rootScope) { return { - scope: true, restrict: 'A', link: function postLink(scope, elem, attr, ngModelCtrl) { @@ -320,6 +335,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); diff --git a/dist/unsavedChanges.min.js b/dist/unsavedChanges.min.js index ae35dff..ef6a715 100644 --- a/dist/unsavedChanges.min.js +++ b/dist/unsavedChanges.min.js @@ -1 +1 @@ -"use strict";angular.module("unsavedChanges",["resettable"]).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").instant(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","$window",function(j,c,k,d){var i=this;var a=[];var g=true;var e=[];this.allForms=function(){return a};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){f()}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(e,function(l){l()});e=[];d.onbeforeunload=null}this.confirmExit=function(){if(!h()){return c.reloadMessage}j.$broadcast("resetResettables");b()};function f(){c.log("Setting up");d.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(c.navigateMessage)){c.log("user wants to cancel leaving");p.preventDefault()}else{c.log("user doesn't care about loosing stuff");j.$broadcast("resetResettables")}}else{c.log("all forms are clean")}});e.push(n)})}}]).directive("unsavedWarningClear",["unsavedWarningSharedService",function(a){return{scope:{},require:"^form",priority:10,link:function(d,c,b,e){c.bind("click",function(f){e.$setPristine()})}}}]).directive("unsavedWarningForm",["unsavedWarningSharedService","$rootScope",function(b,a){return{scope:{},require:"^form",link:function(e,d,c,f){b.init(f);d.bind("submit",function(g){if(f.$valid){f.$setPristine()}});d.bind("reset",function(g){g.preventDefault();e.$apply(a.$broadcast("resetResettables"));f.$setPristine()});e.$on("$destroy",function(){b.removeForm(f)})}}}]);angular.module("resettable",[]).directive("resettable",["$parse","$compile","$rootScope",function(d,c,b){return{scope:true,restrict:"A",link:function a(m,g,j,e){var h,k,l;j.$observe("ngModel",function(n){k=d(j.ngModel);h=k.assign;l=k(m)});var i=function(){h(m,l)};var f=m.$on("resetResettables",i);m.$on("$destroy",f)}}}]); \ No newline at end of file +"use strict";angular.module("unsavedChanges",["resettable"]).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").instant(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","$window",function(j,c,k,d){var i=this;var a=[];var g=true;var e=[];this.allForms=function(){return a};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){f()}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(e,function(l){l()});e=[];d.onbeforeunload=null}this.confirmExit=function(){if(!h()){return c.reloadMessage}j.$broadcast("resetResettables");b()};function f(){c.log("Setting up");d.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(c.navigateMessage)){c.log("user wants to cancel leaving");p.preventDefault()}else{c.log("user doesn't care about loosing stuff");j.$broadcast("resetResettables")}}else{c.log("all forms are clean")}});e.push(n)})}}]).directive("unsavedWarningClear",["unsavedWarningSharedService",function(a){return{scope:{},require:"^form",priority:10,link:function(d,c,b,e){c.bind("click",function(f){e.$setPristine()})}}}]).directive("unsavedWarningForm",["unsavedWarningSharedService","$rootScope",function(b,a){return{scope:{},require:"^form",link:function(e,d,c,g){var f=0;while(d[0].tagName!=="FORM"&&f<3){f++;d=d.parent()}if(f>=3){throw ("unsavedWarningForm must be inside a form element")}b.init(g);d.bind("submit",function(h){if(g.$valid){g.$setPristine()}});d.bind("reset",function(h){h.preventDefault();var i=angular.element(d[0].querySelector("[resettable]"));if(i.length){e.$apply(i.triggerHandler("resetResettables"))}g.$setPristine()});e.$on("$destroy",function(){b.removeForm(g)})}}}]);angular.module("resettable",[]).directive("resettable",["$parse","$compile","$rootScope",function(d,c,b){return{restrict:"A",link:function a(m,g,j,e){var h,k,l;j.$observe("ngModel",function(n){k=d(j.ngModel);h=k.assign;l=k(m)});var i=function(){h(m,l)};g.on("resetResettables",i);var f=m.$on("resetResettables",i);m.$on("$destroy",f)}}}]); \ No newline at end of file From 36b7fbe929c4f5e207e321263212e069888d55f3 Mon Sep 17 00:00:00 2001 From: danielcrisp Date: Thu, 17 Sep 2015 19:08:33 +0100 Subject: [PATCH 21/30] Use safe method for triggering scope digest --- src/unsavedChanges.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/unsavedChanges.js b/src/unsavedChanges.js index f5d47c0..a124357 100644 --- a/src/unsavedChanges.js +++ b/src/unsavedChanges.js @@ -237,8 +237,8 @@ angular.module('unsavedChanges', ['resettable']) } ]) -.directive('unsavedWarningForm', ['unsavedWarningSharedService', '$rootScope', - function(unsavedWarningSharedService, $rootScope) { +.directive('unsavedWarningForm', ['unsavedWarningSharedService', '$rootScope', '$timeout', + function(unsavedWarningSharedService, $rootScope, $timeout) { return { scope: {}, require: '^form', @@ -278,7 +278,9 @@ angular.module('unsavedChanges', ['resettable']) // trigger resettables within this form or element var resettables = angular.element(formElement[0].querySelector('[resettable]')); if(resettables.length) { - scope.$apply(resettables.triggerHandler('resetResettables')); + $timeout(function () { + resettables.triggerHandler('resetResettables'); + }); } // sets for back to valid and pristine states From a287d8b491098b70a41205f6235c07a451e6d002 Mon Sep 17 00:00:00 2001 From: Daniel Crisp Date: Thu, 17 Sep 2015 19:35:51 +0100 Subject: [PATCH 22/30] Added safer methods than --- src/unsavedChanges.js | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/unsavedChanges.js b/src/unsavedChanges.js index a124357..f2b1262 100644 --- a/src/unsavedChanges.js +++ b/src/unsavedChanges.js @@ -203,13 +203,16 @@ angular.module('unsavedChanges', ['resettable']) // @todo this could be written a lot cleaner! if (!allFormsClean()) { unsavedWarningsConfig.log("a form is dirty"); - 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'); - } + // 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"); } @@ -278,6 +281,7 @@ angular.module('unsavedChanges', ['resettable']) // 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'); }); From 6438113a3d36e14b971228fce7ee9bce2a51e766 Mon Sep 17 00:00:00 2001 From: Daniel Crisp Date: Thu, 17 Sep 2015 19:36:32 +0100 Subject: [PATCH 23/30] Build --- dist/unsavedChanges.js | 26 ++++++++++++++++---------- dist/unsavedChanges.min.js | 2 +- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/dist/unsavedChanges.js b/dist/unsavedChanges.js index f5d47c0..f2b1262 100644 --- a/dist/unsavedChanges.js +++ b/dist/unsavedChanges.js @@ -203,13 +203,16 @@ angular.module('unsavedChanges', ['resettable']) // @todo this could be written a lot cleaner! if (!allFormsClean()) { unsavedWarningsConfig.log("a form is dirty"); - 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'); - } + // 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"); } @@ -237,8 +240,8 @@ angular.module('unsavedChanges', ['resettable']) } ]) -.directive('unsavedWarningForm', ['unsavedWarningSharedService', '$rootScope', - function(unsavedWarningSharedService, $rootScope) { +.directive('unsavedWarningForm', ['unsavedWarningSharedService', '$rootScope', '$timeout', + function(unsavedWarningSharedService, $rootScope, $timeout) { return { scope: {}, require: '^form', @@ -278,7 +281,10 @@ angular.module('unsavedChanges', ['resettable']) // trigger resettables within this form or element var resettables = angular.element(formElement[0].querySelector('[resettable]')); if(resettables.length) { - scope.$apply(resettables.triggerHandler('resetResettables')); + // use safer method than $apply + $timeout(function () { + resettables.triggerHandler('resetResettables'); + }); } // sets for back to valid and pristine states diff --git a/dist/unsavedChanges.min.js b/dist/unsavedChanges.min.js index ef6a715..d2d1053 100644 --- a/dist/unsavedChanges.min.js +++ b/dist/unsavedChanges.min.js @@ -1 +1 @@ -"use strict";angular.module("unsavedChanges",["resettable"]).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").instant(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","$window",function(j,c,k,d){var i=this;var a=[];var g=true;var e=[];this.allForms=function(){return a};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){f()}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(e,function(l){l()});e=[];d.onbeforeunload=null}this.confirmExit=function(){if(!h()){return c.reloadMessage}j.$broadcast("resetResettables");b()};function f(){c.log("Setting up");d.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(c.navigateMessage)){c.log("user wants to cancel leaving");p.preventDefault()}else{c.log("user doesn't care about loosing stuff");j.$broadcast("resetResettables")}}else{c.log("all forms are clean")}});e.push(n)})}}]).directive("unsavedWarningClear",["unsavedWarningSharedService",function(a){return{scope:{},require:"^form",priority:10,link:function(d,c,b,e){c.bind("click",function(f){e.$setPristine()})}}}]).directive("unsavedWarningForm",["unsavedWarningSharedService","$rootScope",function(b,a){return{scope:{},require:"^form",link:function(e,d,c,g){var f=0;while(d[0].tagName!=="FORM"&&f<3){f++;d=d.parent()}if(f>=3){throw ("unsavedWarningForm must be inside a form element")}b.init(g);d.bind("submit",function(h){if(g.$valid){g.$setPristine()}});d.bind("reset",function(h){h.preventDefault();var i=angular.element(d[0].querySelector("[resettable]"));if(i.length){e.$apply(i.triggerHandler("resetResettables"))}g.$setPristine()});e.$on("$destroy",function(){b.removeForm(g)})}}}]);angular.module("resettable",[]).directive("resettable",["$parse","$compile","$rootScope",function(d,c,b){return{restrict:"A",link:function a(m,g,j,e){var h,k,l;j.$observe("ngModel",function(n){k=d(j.ngModel);h=k.assign;l=k(m)});var i=function(){h(m,l)};g.on("resetResettables",i);var f=m.$on("resetResettables",i);m.$on("$destroy",f)}}}]); \ No newline at end of file +"use strict";angular.module("unsavedChanges",["resettable"]).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").instant(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","$window",function(j,c,k,d){var i=this;var a=[];var g=true;var e=[];this.allForms=function(){return a};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){f()}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(e,function(l){l()});e=[];d.onbeforeunload=null}this.confirmExit=function(){if(!h()){return c.reloadMessage}j.$broadcast("resetResettables");b()};function f(){c.log("Setting up");d.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");setTimeout(function(){if(!confirm(c.navigateMessage)){c.log("user wants to cancel leaving");p.preventDefault()}else{c.log("user doesn't care about loosing stuff");j.$broadcast("resetResettables")}})}else{c.log("all forms are clean")}});e.push(n)})}}]).directive("unsavedWarningClear",["unsavedWarningSharedService",function(a){return{scope:{},require:"^form",priority:10,link:function(d,c,b,e){c.bind("click",function(f){e.$setPristine()})}}}]).directive("unsavedWarningForm",["unsavedWarningSharedService","$rootScope","$timeout",function(c,a,b){return{scope:{},require:"^form",link:function(f,e,d,h){var g=0;while(e[0].tagName!=="FORM"&&g<3){g++;e=e.parent()}if(g>=3){throw ("unsavedWarningForm must be inside a form element")}c.init(h);e.bind("submit",function(i){if(h.$valid){h.$setPristine()}});e.bind("reset",function(i){i.preventDefault();var j=angular.element(e[0].querySelector("[resettable]"));if(j.length){b(function(){j.triggerHandler("resetResettables")})}h.$setPristine()});f.$on("$destroy",function(){c.removeForm(h)})}}}]);angular.module("resettable",[]).directive("resettable",["$parse","$compile","$rootScope",function(d,c,b){return{restrict:"A",link:function a(m,g,j,e){var h,k,l;j.$observe("ngModel",function(n){k=d(j.ngModel);h=k.assign;l=k(m)});var i=function(){h(m,l)};g.on("resetResettables",i);var f=m.$on("resetResettables",i);m.$on("$destroy",f)}}}]); \ No newline at end of file From a44192e177f17b246d8d209a69a06c8b9096757c Mon Sep 17 00:00:00 2001 From: "akira.takahashi" Date: Fri, 19 Aug 2016 14:52:47 +0900 Subject: [PATCH 24/30] update to angular 1.5.* --- bower.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/bower.json b/bower.json index 09607ea..9b4d4e2 100644 --- a/bower.json +++ b/bower.json @@ -25,10 +25,10 @@ "tests" ], "devDependencies": { - "angular": "~1.3.x", - "angular-route": "~1.3.x", - "angular-mocks": "~1.3.x", - "angular-scenario": "~1.3.x", + "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": "^2.0.1" } From 74bdc2ede988dc375c291d630263ea4b471947e8 Mon Sep 17 00:00:00 2001 From: "akira.takahashi" Date: Fri, 19 Aug 2016 14:53:25 +0900 Subject: [PATCH 25/30] Update dependencies in package.json --- package.json | 53 ++++++++++++++++++++++++++-------------------------- 1 file changed, 26 insertions(+), 27 deletions(-) diff --git a/package.json b/package.json index a569280..ebeb8fb 100644 --- a/package.json +++ b/package.json @@ -4,49 +4,48 @@ "description": "AngularJS directive to warn user of unsaved changes when navigating away from a form.", "main": "Gruntfile.js", "devDependencies": { - "grunt-strip": "~0.2.1", + "bower": "^1.7.9", "express": "latest", - "bower": "~1.2.6", - "grunt": "~0.4.1", - "grunt-shell": "~0.4.0", + "grunt": "^1.0.1", + "grunt-concurrent": "^2.3.1", + "grunt-contrib-clean": "^1.0.0", + "grunt-contrib-concat": "^1.0.1", + "grunt-contrib-connect": "^1.0.2", + "grunt-contrib-copy": "^1.0.0", + "grunt-contrib-cssmin": "^1.0.1", + "grunt-contrib-jshint": "^1.0.0", + "grunt-contrib-uglify": "^2.0.0", + "grunt-contrib-watch": "^1.0.0", + "grunt-jsbeautifier": "~0.2.3", + "grunt-karma": "latest", "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-protractor-runner": "^3.2.0", + "grunt-replace": "^1.0.1", "grunt-rev": "~0.1.0", - "grunt-concurrent": "~0.3.0", - "load-grunt-tasks": "~0.2.0", - "time-grunt": "~0.1.0", - "grunt-karma": "latest", - "karma-script-launcher": "latest", + "grunt-shell": "^1.3.1", + "grunt-strip": "~0.2.1", + "grunt-usemin": "^3.1.1", + "grunt-yui-compressor": "^0.4.0", + "karma": "latest", "karma-chrome-launcher": "latest", + "karma-coverage": "latest", "karma-firefox-launcher": "latest", "karma-html2js-preprocessor": "latest", "karma-jasmine": "latest", - "karma-requirejs": "latest", - "karma-coverage": "latest", "karma-osx-reporter": "latest", "karma-phantomjs-launcher": "latest", - "karma": "latest", + "karma-requirejs": "latest", + "karma-script-launcher": "latest", + "load-grunt-tasks": "^3.5.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" + "time-grunt": "^1.4.0" }, "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "repository": { "type": "git", - "url": "https://github.com/facultymatt/angular-unsavedChanges.git" + "url": "https://github.com/naviplus-asp/angular-unsavedChanges.git" }, "keywords": [ "form", From c3c37e1c0fafd28ec36db261c5149e24e3feb842 Mon Sep 17 00:00:00 2001 From: "akira.takahashi" Date: Mon, 22 Aug 2016 14:38:30 +0900 Subject: [PATCH 26/30] Fix broken e2e tests --- test/e2e/unsavedChanges.e2e.js | 34 ++++++++++++++++++++++++---------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/test/e2e/unsavedChanges.e2e.js b/test/e2e/unsavedChanges.e2e.js index 127e7da..d34bbfb 100644 --- a/test/e2e/unsavedChanges.e2e.js +++ b/test/e2e/unsavedChanges.e2e.js @@ -7,6 +7,7 @@ var alertDialog; var cleanUp = function() { browser.navigate().refresh(); + browser.ignoreSynchronization = true; alertDialog = browser.switchTo().alert(); alertDialog.accept(); }; @@ -23,9 +24,11 @@ describe('When single form is dirty', function() { describe('when user clicks a link', function() { - beforeEach(function() { + beforeEach(function(done) { + browser.ignoreSynchronization = true; element(by.id('page2')).click(); - alertDialog = browser.switchTo().alert(); + alertDialog = browser.switchTo().alert() + alertDialog.then(done); }); it('should alert user', function() { @@ -79,9 +82,11 @@ describe('When single form is dirty', function() { describe('when user refreshes page', function() { - beforeEach(function() { + beforeEach(function(done) { + browser.ignoreSynchronization = true; browser.navigate().refresh(); - alertDialog = browser.switchTo().alert(); + alertDialog = browser.switchTo().alert() + alertDialog.then(done); }); it('should alert user', function() { @@ -131,9 +136,13 @@ describe('When single form is dirty', function() { describe('when user clicks back button', function() { - beforeEach(function() { - browser.navigate().back(); - alertDialog = browser.switchTo().alert(); + beforeEach(function (done) { + browser.ignoreSynchronization = true; + browser.navigate().back().catch(function () { + browser.switchTo().alert().then(function (dialog) { + alertDialog = dialog; + }) + }).then(done) }); it('should alert user', function() { @@ -153,7 +162,7 @@ describe('When single form is dirty', function() { describe('when user rejects alert', function() { beforeEach(function() { - alertDialog.dismiss(); + alertDialog.dismiss() }); it('should stay on page', function() { @@ -187,8 +196,9 @@ describe('When single form is dirty', function() { afterEach(function() { browser.navigate().refresh(); + browser.ignoreSynchronization = true; alertDialog = browser.switchTo().alert(); - alertDialog.accept(); + alertDialog.then(alertDialog.accept) }); describe('user clicks link, dismisses, then clicks link again', function() { @@ -215,6 +225,7 @@ describe('When single form is dirty', function() { describe('user clicks link, dismisses, then refreshes page', function() { beforeEach(function() { + browser.ignoreSynchronization = true; element(by.id('page2')).click(); alertDialog = browser.switchTo().alert(); alertDialog.dismiss(); @@ -236,6 +247,7 @@ describe('When single form is dirty', function() { describe('user refreshes, dismisses, then clicks link', function() { beforeEach(function() { + browser.ignoreSynchronization = true; browser.navigate().refresh(); alertDialog = browser.switchTo().alert(); alertDialog.dismiss(); @@ -257,6 +269,7 @@ describe('When single form is dirty', function() { describe('user refreshes, dismisses, then refreshes', function() { beforeEach(function() { + browser.ignoreSynchronization = true; browser.navigate().refresh(); alertDialog = browser.switchTo().alert(); alertDialog.dismiss(); @@ -343,7 +356,7 @@ describe('When single form is dirty', function() { expect(browser.getCurrentUrl()).toContain('/page2'); }); }); - + }); }); @@ -361,6 +374,7 @@ describe('When multiple forms are dirty', function() { }); it('should only show one message (versus three)', function() { + browser.ignoreSynchronization = true; element(by.id('page1')).click(); alertDialog = browser.switchTo().alert(); alertDialog.accept(); From bd6dd7073446ccb9d2bc82b74fb6139b71acc4c1 Mon Sep 17 00:00:00 2001 From: "akira.takahashi" Date: Mon, 22 Aug 2016 14:41:32 +0900 Subject: [PATCH 27/30] Add webdriver-manager grunt task --- Gruntfile.js | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/Gruntfile.js b/Gruntfile.js index 8a7f3d1..31f7436 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -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: { @@ -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: { @@ -38,7 +52,7 @@ module.exports = function(grunt) { // our protractor server testserver: { options: { - port: 9999 + port: 9998 } }, travisServer: { @@ -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 ]); From 1d632daef75cf60436404c85460be3ced092a31d Mon Sep 17 00:00:00 2001 From: "akira.takahashi" Date: Mon, 22 Aug 2016 14:47:18 +0900 Subject: [PATCH 28/30] Add npm tasks --- package.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index ebeb8fb..a726195 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,10 @@ "time-grunt": "^1.4.0" }, "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "build": "grunt", + "dev": "grunt server", + "test": "grunt test", + "test:e2e": "grunt test:e2e" }, "repository": { "type": "git", From d5598eb566546eff1e919e5ddade44cdeabd3fa9 Mon Sep 17 00:00:00 2001 From: "akira.takahashi" Date: Mon, 22 Aug 2016 14:56:52 +0900 Subject: [PATCH 29/30] Fix grunt-connect port config --- protractor.conf.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/protractor.conf.js b/protractor.conf.js index 99997b8..d9e0b2d 100644 --- a/protractor.conf.js +++ b/protractor.conf.js @@ -48,7 +48,7 @@ exports.config = { // A base URL for your application under test. Calls to protractor.get() // with relative paths will be prepended with this. - baseUrl: 'http://localhost:9999', + baseUrl: 'http://localhost:9998', // Selector for the element housing the angular app - this defaults to // body, but is necessary if ng-app is on a descendant of From bacefb94427d1ad1d83613f43f90a0dd26d274db Mon Sep 17 00:00:00 2001 From: "akira.takahashi" Date: Mon, 22 Aug 2016 15:21:34 +0900 Subject: [PATCH 30/30] Use Array#every insted of Array#foreach in allFormsClean --- src/unsavedChanges.js | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/unsavedChanges.js b/src/unsavedChanges.js index f5d47c0..9ebafc3 100644 --- a/src/unsavedChanges.js +++ b/src/unsavedChanges.js @@ -139,14 +139,10 @@ angular.module('unsavedChanges', ['resettable']) // 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 allForms.every(function(form, idx) { + unsavedWarningsConfig.log('Form : ' + form.$name + ' dirty : ' + form.$dirty); + return form.$pristine; }); - return areAllFormsClean; // no dirty forms were found } // adds form controller to registered forms array @@ -194,6 +190,7 @@ angular.module('unsavedChanges', ['resettable']) $window.onbeforeunload = _this.confirmExit; + var eventsToWatchFor = unsavedWarningsConfig.routeEvent; angular.forEach(eventsToWatchFor, function(aEvent) {