From 835e96a48eb53a1ffe7861b436c162f3a62e8ac2 Mon Sep 17 00:00:00 2001 From: Aaron Schinkowitch Date: Thu, 23 Jun 2016 10:39:13 +0200 Subject: [PATCH 1/5] Add option to ignore showing errors on pristine fields (for example if a user tabs through a required field). --- package.json | 7 ++++--- src/showErrors.coffee | 13 +++++++++++++ src/showErrors.js | 25 +++++++++++++++++++++---- src/showErrors.min.js | 4 ++-- 4 files changed, 40 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index f1f6094..f8b0db9 100644 --- a/package.json +++ b/package.json @@ -14,10 +14,11 @@ "grunt-contrib-uglify": "~0.2.5", "grunt-contrib-watch": "~0.5.3", "grunt-karma": "0.9.0", - "matchdep": "0.3.0", - "phantomjs": "~1.9.10", + "karma": "^0.13.22", + "karma-jasmine": "~0.1.5", "karma-phantomjs-launcher": "~0.1.4", - "karma-jasmine": "~0.1.5" + "matchdep": "0.3.0", + "phantomjs": "~1.9.10" }, "scripts": { "test": "grunt --verbose" diff --git a/src/showErrors.coffee b/src/showErrors.coffee index 4d66b4d..16072ed 100644 --- a/src/showErrors.coffee +++ b/src/showErrors.coffee @@ -15,10 +15,17 @@ showErrorsModule.directive 'showErrors', showSuccess = options.showSuccess showSuccess + getIgnorePristine = (options) -> + ignorePristine = showErrorsConfig.ignorePristine + if options && options.ignorePristine? + ignorePristine = options.ignorePristine + ignorePristine + linkFn = (scope, el, attrs, formCtrl) -> blurred = false options = scope.$eval attrs.showErrors showSuccess = getShowSuccess options + ignorePristine = getIgnorePristine options trigger = getTrigger options inputEl = el[0].querySelector '.form-control[name]' @@ -28,6 +35,7 @@ showErrorsModule.directive 'showErrors', throw "show-errors element has no child input elements with a 'name' attribute and a 'form-control' class" inputNgEl.bind trigger, -> + return if ignorePristine && formCtrl[inputName].$pristine blurred = true toggleClasses formCtrl[inputName].$invalid @@ -67,6 +75,7 @@ showErrorsModule.directive 'showErrors', showErrorsModule.provider 'showErrorsConfig', -> _showSuccess = false _trigger = 'blur' + _ignorePristine = false @showSuccess = (showSuccess) -> _showSuccess = showSuccess @@ -74,8 +83,12 @@ showErrorsModule.provider 'showErrorsConfig', -> @trigger = (trigger) -> _trigger = trigger + @ignorePristine = (ignorePristine) -> + _ignorePristine = ignorePristine + @$get = -> showSuccess: _showSuccess trigger: _trigger + ignorePristine: _ignorePristine return diff --git a/src/showErrors.js b/src/showErrors.js index 934f59b..dba923b 100644 --- a/src/showErrors.js +++ b/src/showErrors.js @@ -5,7 +5,7 @@ showErrorsModule.directive('showErrors', [ '$timeout', 'showErrorsConfig', '$interpolate', function($timeout, showErrorsConfig, $interpolate) { - var getShowSuccess, getTrigger, linkFn; + var getIgnorePristine, getShowSuccess, getTrigger, linkFn; getTrigger = function(options) { var trigger; trigger = showErrorsConfig.trigger; @@ -22,11 +22,20 @@ } return showSuccess; }; + getIgnorePristine = function(options) { + var ignorePristine; + ignorePristine = showErrorsConfig.ignorePristine; + if (options && (options.ignorePristine != null)) { + ignorePristine = options.ignorePristine; + } + return ignorePristine; + }; linkFn = function(scope, el, attrs, formCtrl) { - var blurred, inputEl, inputName, inputNgEl, options, showSuccess, toggleClasses, trigger; + var blurred, ignorePristine, inputEl, inputName, inputNgEl, options, showSuccess, toggleClasses, trigger; blurred = false; options = scope.$eval(attrs.showErrors); showSuccess = getShowSuccess(options); + ignorePristine = getIgnorePristine(options); trigger = getTrigger(options); inputEl = el[0].querySelector('.form-control[name]'); inputNgEl = angular.element(inputEl); @@ -35,6 +44,9 @@ throw "show-errors element has no child input elements with a 'name' attribute and a 'form-control' class"; } inputNgEl.bind(trigger, function() { + if (ignorePristine && formCtrl[inputName].$pristine) { + return; + } blurred = true; return toggleClasses(formCtrl[inputName].$invalid); }); @@ -79,19 +91,24 @@ ]); showErrorsModule.provider('showErrorsConfig', function() { - var _showSuccess, _trigger; + var _ignorePristine, _showSuccess, _trigger; _showSuccess = false; _trigger = 'blur'; + _ignorePristine = false; this.showSuccess = function(showSuccess) { return _showSuccess = showSuccess; }; this.trigger = function(trigger) { return _trigger = trigger; }; + this.ignorePristine = function(ignorePristine) { + return _ignorePristine = ignorePristine; + }; this.$get = function() { return { showSuccess: _showSuccess, - trigger: _trigger + trigger: _trigger, + ignorePristine: _ignorePristine }; }; }); diff --git a/src/showErrors.min.js b/src/showErrors.min.js index 72c5343..40ac065 100644 --- a/src/showErrors.min.js +++ b/src/showErrors.min.js @@ -1,2 +1,2 @@ -/*! angular-bootstrap-show-errors (version 2.3.0) 2015-01-19 */ -(function(){var a;a=angular.module("ui.bootstrap.showErrors",[]),a.directive("showErrors",["$timeout","showErrorsConfig","$interpolate",function(a,b,c){var d,e,f;return e=function(a){var c;return c=b.trigger,a&&null!=a.trigger&&(c=a.trigger),c},d=function(a){var c;return c=b.showSuccess,a&&null!=a.showSuccess&&(c=a.showSuccess),c},f=function(b,f,g,h){var i,j,k,l,m,n,o,p;if(i=!1,m=b.$eval(g.showErrors),n=d(m),p=e(m),j=f[0].querySelector(".form-control[name]"),l=angular.element(j),k=c(l.attr("name")||"")(b),!k)throw"show-errors element has no child input elements with a 'name' attribute and a 'form-control' class";return l.bind(p,function(){return i=!0,o(h[k].$invalid)}),b.$watch(function(){return h[k]&&h[k].$invalid},function(a){return i?o(a):void 0}),b.$on("show-errors-check-validity",function(){return o(h[k].$invalid)}),b.$on("show-errors-reset",function(){return a(function(){return f.removeClass("has-error"),f.removeClass("has-success"),i=!1},0,!1)}),o=function(a){return f.toggleClass("has-error",a),n?f.toggleClass("has-success",!a):void 0}},{restrict:"A",require:"^form",compile:function(a,b){if(-1===b.showErrors.indexOf("skipFormGroupCheck")&&!a.hasClass("form-group")&&!a.hasClass("input-group"))throw"show-errors element does not have the 'form-group' or 'input-group' class";return f}}}]),a.provider("showErrorsConfig",function(){var a,b;a=!1,b="blur",this.showSuccess=function(b){return a=b},this.trigger=function(a){return b=a},this.$get=function(){return{showSuccess:a,trigger:b}}})}).call(this); \ No newline at end of file +/*! angular-bootstrap-show-errors (version 2.3.0) 2016-06-23 */ +(function(){var a;a=angular.module("ui.bootstrap.showErrors",[]),a.directive("showErrors",["$timeout","showErrorsConfig","$interpolate",function(a,b,c){var d,e,f,g;return f=function(a){var c;return c=b.trigger,a&&null!=a.trigger&&(c=a.trigger),c},e=function(a){var c;return c=b.showSuccess,a&&null!=a.showSuccess&&(c=a.showSuccess),c},d=function(a){var c;return c=b.ignorePristine,a&&null!=a.ignorePristine&&(c=a.ignorePristine),c},g=function(b,g,h,i){var j,k,l,m,n,o,p,q,r;if(j=!1,o=b.$eval(h.showErrors),p=e(o),k=d(o),r=f(o),l=g[0].querySelector(".form-control[name]"),n=angular.element(l),m=c(n.attr("name")||"")(b),!m)throw"show-errors element has no child input elements with a 'name' attribute and a 'form-control' class";return n.bind(r,function(){return k&&i[m].$pristine?void 0:(j=!0,q(i[m].$invalid))}),b.$watch(function(){return i[m]&&i[m].$invalid},function(a){return j?q(a):void 0}),b.$on("show-errors-check-validity",function(){return q(i[m].$invalid)}),b.$on("show-errors-reset",function(){return a(function(){return g.removeClass("has-error"),g.removeClass("has-success"),j=!1},0,!1)}),q=function(a){return g.toggleClass("has-error",a),p?g.toggleClass("has-success",!a):void 0}},{restrict:"A",require:"^form",compile:function(a,b){if(-1===b.showErrors.indexOf("skipFormGroupCheck")&&!a.hasClass("form-group")&&!a.hasClass("input-group"))throw"show-errors element does not have the 'form-group' or 'input-group' class";return g}}}]),a.provider("showErrorsConfig",function(){var a,b,c;b=!1,c="blur",a=!1,this.showSuccess=function(a){return b=a},this.trigger=function(a){return c=a},this.ignorePristine=function(b){return a=b},this.$get=function(){return{showSuccess:b,trigger:c,ignorePristine:a}}})}).call(this); \ No newline at end of file From 0dc435d1ae23cd73671ad67cfebd7347b47c2b8b Mon Sep 17 00:00:00 2001 From: Aaron Schinkowitch Date: Thu, 23 Jun 2016 11:20:48 +0200 Subject: [PATCH 2/5] Documenation for option to ignore showing errors on pristine fields (for example if a user tabs through a required field). --- README.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/README.md b/README.md index 5dd10ec..32ef537 100644 --- a/README.md +++ b/README.md @@ -149,6 +149,28 @@ app.config(['showErrorsConfigProvider', function(showErrorsConfigProvider) { showErrorsConfigProvider.trigger('keypress'); }]); ``` + +Ignore Pristine +--- +You may choose not to not show validation errors on pristine fields that have been blurred. This is useful if you +prefer to allow the user to tab past a required field or if you are setting focus onto a required field. + +##### By Input Element +```html +
+
+ +
+
+``` + +##### Globally +```javascript +app = angular.module('yourApp', ['ui.bootstrap.showErrors']); +app.config(['showErrorsConfigProvider', function(showErrorsConfigProvider) { + showErrorsConfigProvider.ignorePristine(true); +}]); +``` ## Development From b5ab52d4443e5ba8212000235797cef29b63a216 Mon Sep 17 00:00:00 2001 From: Aaron Schinkowitch Date: Thu, 23 Jun 2016 11:21:20 +0200 Subject: [PATCH 3/5] Documenation for option to ignore showing errors on pristine fields (for example if a user tabs through a required field). --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 32ef537..2296361 100644 --- a/README.md +++ b/README.md @@ -159,7 +159,7 @@ prefer to allow the user to tab past a required field or if you are setting focu ```html
- +
``` From ed5c6bbb1b35dbe42983962abfb5dfa02ccee203 Mon Sep 17 00:00:00 2001 From: Aaron Schinkowitch Date: Thu, 23 Jun 2016 12:00:05 +0200 Subject: [PATCH 4/5] Documenation for option to ignore showing errors on pristine fields (for example if a user tabs through a required field). --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2296361..43feb20 100644 --- a/README.md +++ b/README.md @@ -159,7 +159,7 @@ prefer to allow the user to tab past a required field or if you are setting focu ```html
- +
``` From 5c9871f3fa6c1cc4187c298404ee6fd70e7acce7 Mon Sep 17 00:00:00 2001 From: Aaron Schinkowitch Date: Thu, 23 Jun 2016 12:48:18 +0200 Subject: [PATCH 5/5] Merge changes from dgsmith2 repository --- README.md | 2 +- src/showErrors.coffee | 9 +++++---- src/showErrors.js | 10 ++++++---- src/showErrors.min.js | 2 +- test/showErrors.spec.coffee | 19 ++++++++++++++++--- test/showErrors.spec.js | 22 +++++++++++++++++++--- 6 files changed, 48 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 43feb20..9b4ec36 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ Force Validity Check --- By default this directive doesn't check the validity until the user tabs off the input element. However, there are times you want to show invalid form elements even if the user has not tabbed off. (e.g. before saving the form) -To force the validity check, broadcast the `show-errors-check-validity` event. +To force the validity check, broadcast the `show-errors-check-validity` event. In broadcasting `show-errors-check-validity` you can optionally specify a form name to limit which form is updated with messages. #### Example diff --git a/src/showErrors.coffee b/src/showErrors.coffee index 16072ed..e5314b5 100644 --- a/src/showErrors.coffee +++ b/src/showErrors.coffee @@ -28,11 +28,11 @@ showErrorsModule.directive 'showErrors', ignorePristine = getIgnorePristine options trigger = getTrigger options - inputEl = el[0].querySelector '.form-control[name]' + inputEl = el[0].querySelector '[name]' inputNgEl = angular.element inputEl inputName = $interpolate(inputNgEl.attr('name') || '')(scope) unless inputName - throw "show-errors element has no child input elements with a 'name' attribute and a 'form-control' class" + throw "show-errors element has no child input elements with a 'name' attribute" inputNgEl.bind trigger, -> return if ignorePristine && formCtrl[inputName].$pristine @@ -45,8 +45,9 @@ showErrorsModule.directive 'showErrors', return if !blurred toggleClasses invalid - scope.$on 'show-errors-check-validity', -> - toggleClasses formCtrl[inputName].$invalid + scope.$on 'show-errors-check-validity', (event, name) -> + if angular.isUndefined(name) || formCtrl['$name'] == name + toggleClasses formCtrl[inputName].$invalid scope.$on 'show-errors-reset', -> $timeout -> diff --git a/src/showErrors.js b/src/showErrors.js index dba923b..a1abd63 100644 --- a/src/showErrors.js +++ b/src/showErrors.js @@ -37,11 +37,11 @@ showSuccess = getShowSuccess(options); ignorePristine = getIgnorePristine(options); trigger = getTrigger(options); - inputEl = el[0].querySelector('.form-control[name]'); + inputEl = el[0].querySelector('[name]'); inputNgEl = angular.element(inputEl); inputName = $interpolate(inputNgEl.attr('name') || '')(scope); if (!inputName) { - throw "show-errors element has no child input elements with a 'name' attribute and a 'form-control' class"; + throw "show-errors element has no child input elements with a 'name' attribute"; } inputNgEl.bind(trigger, function() { if (ignorePristine && formCtrl[inputName].$pristine) { @@ -58,8 +58,10 @@ } return toggleClasses(invalid); }); - scope.$on('show-errors-check-validity', function() { - return toggleClasses(formCtrl[inputName].$invalid); + scope.$on('show-errors-check-validity', function(event, name) { + if (angular.isUndefined(name) || formCtrl['$name'] === name) { + return toggleClasses(formCtrl[inputName].$invalid); + } }); scope.$on('show-errors-reset', function() { return $timeout(function() { diff --git a/src/showErrors.min.js b/src/showErrors.min.js index 40ac065..3a2adb2 100644 --- a/src/showErrors.min.js +++ b/src/showErrors.min.js @@ -1,2 +1,2 @@ /*! angular-bootstrap-show-errors (version 2.3.0) 2016-06-23 */ -(function(){var a;a=angular.module("ui.bootstrap.showErrors",[]),a.directive("showErrors",["$timeout","showErrorsConfig","$interpolate",function(a,b,c){var d,e,f,g;return f=function(a){var c;return c=b.trigger,a&&null!=a.trigger&&(c=a.trigger),c},e=function(a){var c;return c=b.showSuccess,a&&null!=a.showSuccess&&(c=a.showSuccess),c},d=function(a){var c;return c=b.ignorePristine,a&&null!=a.ignorePristine&&(c=a.ignorePristine),c},g=function(b,g,h,i){var j,k,l,m,n,o,p,q,r;if(j=!1,o=b.$eval(h.showErrors),p=e(o),k=d(o),r=f(o),l=g[0].querySelector(".form-control[name]"),n=angular.element(l),m=c(n.attr("name")||"")(b),!m)throw"show-errors element has no child input elements with a 'name' attribute and a 'form-control' class";return n.bind(r,function(){return k&&i[m].$pristine?void 0:(j=!0,q(i[m].$invalid))}),b.$watch(function(){return i[m]&&i[m].$invalid},function(a){return j?q(a):void 0}),b.$on("show-errors-check-validity",function(){return q(i[m].$invalid)}),b.$on("show-errors-reset",function(){return a(function(){return g.removeClass("has-error"),g.removeClass("has-success"),j=!1},0,!1)}),q=function(a){return g.toggleClass("has-error",a),p?g.toggleClass("has-success",!a):void 0}},{restrict:"A",require:"^form",compile:function(a,b){if(-1===b.showErrors.indexOf("skipFormGroupCheck")&&!a.hasClass("form-group")&&!a.hasClass("input-group"))throw"show-errors element does not have the 'form-group' or 'input-group' class";return g}}}]),a.provider("showErrorsConfig",function(){var a,b,c;b=!1,c="blur",a=!1,this.showSuccess=function(a){return b=a},this.trigger=function(a){return c=a},this.ignorePristine=function(b){return a=b},this.$get=function(){return{showSuccess:b,trigger:c,ignorePristine:a}}})}).call(this); \ No newline at end of file +(function(){var a;a=angular.module("ui.bootstrap.showErrors",[]),a.directive("showErrors",["$timeout","showErrorsConfig","$interpolate",function(a,b,c){var d,e,f,g;return f=function(a){var c;return c=b.trigger,a&&null!=a.trigger&&(c=a.trigger),c},e=function(a){var c;return c=b.showSuccess,a&&null!=a.showSuccess&&(c=a.showSuccess),c},d=function(a){var c;return c=b.ignorePristine,a&&null!=a.ignorePristine&&(c=a.ignorePristine),c},g=function(b,g,h,i){var j,k,l,m,n,o,p,q,r;if(j=!1,o=b.$eval(h.showErrors),p=e(o),k=d(o),r=f(o),l=g[0].querySelector("[name]"),n=angular.element(l),m=c(n.attr("name")||"")(b),!m)throw"show-errors element has no child input elements with a 'name' attribute";return n.bind(r,function(){return k&&i[m].$pristine?void 0:(j=!0,q(i[m].$invalid))}),b.$watch(function(){return i[m]&&i[m].$invalid},function(a){return j?q(a):void 0}),b.$on("show-errors-check-validity",function(a,b){return angular.isUndefined(b)||i.$name===b?q(i[m].$invalid):void 0}),b.$on("show-errors-reset",function(){return a(function(){return g.removeClass("has-error"),g.removeClass("has-success"),j=!1},0,!1)}),q=function(a){return g.toggleClass("has-error",a),p?g.toggleClass("has-success",!a):void 0}},{restrict:"A",require:"^form",compile:function(a,b){if(-1===b.showErrors.indexOf("skipFormGroupCheck")&&!a.hasClass("form-group")&&!a.hasClass("input-group"))throw"show-errors element does not have the 'form-group' or 'input-group' class";return g}}}]),a.provider("showErrorsConfig",function(){var a,b,c;b=!1,c="blur",a=!1,this.showSuccess=function(a){return b=a},this.trigger=function(a){return c=a},this.ignorePristine=function(b){return a=b},this.$get=function(){return{showSuccess:b,trigger:c,ignorePristine:a}}})}).call(this); \ No newline at end of file diff --git a/test/showErrors.spec.coffee b/test/showErrors.spec.coffee index 5ead727..d618122 100644 --- a/test/showErrors.spec.coffee +++ b/test/showErrors.spec.coffee @@ -27,11 +27,11 @@ describe 'showErrors', -> $scope.$digest() el - describe 'directive does not contain an input element with a form-control class and name attribute', -> + describe 'directive does not contain an input element with a name attribute', -> it 'throws an exception', -> expect( -> - $compile('
')($scope) - ).toThrow "show-errors element has no child input elements with a 'name' attribute and a 'form-control' class" + $compile('
')($scope) + ).toThrow "show-errors element has no child input elements with a 'name' attribute" it "throws an exception if the element doesn't have the form-group or input-group class", -> expect( -> @@ -145,6 +145,19 @@ describe 'showErrors', -> $scope.$apply -> $scope.showErrorsCheckValidity = true expectFormGroupHasErrorClass(el).toBe true + + describe 'showErrorsCheckValidity with form name', -> + it 'correctly applies when form name matches', -> + el = compileEl() + $scope.userForm.firstName.$setViewValue(invalidName) + $scope.$broadcast('show-errors-check-validity', 'userForm') + expectFormGroupHasErrorClass(el).toBe true + + it 'correctly skips when form name differs', -> + el = compileEl() + $scope.userForm.firstName.$setViewValue(invalidName) + $scope.$broadcast('show-errors-check-validity', 'differentForm') + expectFormGroupHasErrorClass(el).toBe false describe 'showErrorsReset', -> it 'removes has-error', -> diff --git a/test/showErrors.spec.js b/test/showErrors.spec.js index 4186e2d..cf53b43 100644 --- a/test/showErrors.spec.js +++ b/test/showErrors.spec.js @@ -28,11 +28,11 @@ $scope.$digest(); return el; }; - describe('directive does not contain an input element with a form-control class and name attribute', function() { + describe('directive does not contain an input element with a name attribute', function() { return it('throws an exception', function() { return expect(function() { - return $compile('
')($scope); - }).toThrow("show-errors element has no child input elements with a 'name' attribute and a 'form-control' class"); + return $compile('
')($scope); + }).toThrow("show-errors element has no child input elements with a 'name' attribute"); }); }); it("throws an exception if the element doesn't have the form-group or input-group class", function() { @@ -174,6 +174,22 @@ return expectFormGroupHasErrorClass(el).toBe(true); }); }); + describe('showErrorsCheckValidity with form name', function() { + it('correctly applies when form name matches', function() { + var el; + el = compileEl(); + $scope.userForm.firstName.$setViewValue(invalidName); + $scope.$broadcast('show-errors-check-validity', 'userForm'); + return expectFormGroupHasErrorClass(el).toBe(true); + }); + return it('correctly skips when form name differs', function() { + var el; + el = compileEl(); + $scope.userForm.firstName.$setViewValue(invalidName); + $scope.$broadcast('show-errors-check-validity', 'differentForm'); + return expectFormGroupHasErrorClass(el).toBe(false); + }); + }); describe('showErrorsReset', function() { return it('removes has-error', function() { var el;