diff --git a/README.md b/README.md index 5dd10ec..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 @@ -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 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..e5314b5 100644 --- a/src/showErrors.coffee +++ b/src/showErrors.coffee @@ -15,19 +15,27 @@ 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]' + 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 blurred = true toggleClasses formCtrl[inputName].$invalid @@ -37,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 -> @@ -67,6 +76,7 @@ showErrorsModule.directive 'showErrors', showErrorsModule.provider 'showErrorsConfig', -> _showSuccess = false _trigger = 'blur' + _ignorePristine = false @showSuccess = (showSuccess) -> _showSuccess = showSuccess @@ -74,8 +84,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..a1abd63 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,19 +22,31 @@ } 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]'); + 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) { + return; + } blurred = true; return toggleClasses(formCtrl[inputName].$invalid); }); @@ -46,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() { @@ -79,19 +93,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..3a2adb2 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("[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;