Skip to content

Option to ignore pristine fields #57

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 23 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
<form name="userForm">
<div class="form-group" show-errors="{ ignorePristine: 'true' }">
<input ng-model="firstName" ng-required name="firstName" class="form-control" type="text" autofocus/>
</div>
</form>
```

##### Globally
```javascript
app = angular.module('yourApp', ['ui.bootstrap.showErrors']);
app.config(['showErrorsConfigProvider', function(showErrorsConfigProvider) {
showErrorsConfigProvider.ignorePristine(true);
}]);
```

## Development

Expand Down
7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
22 changes: 18 additions & 4 deletions src/showErrors.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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 ->
Expand Down Expand Up @@ -67,15 +76,20 @@ showErrorsModule.directive 'showErrors',
showErrorsModule.provider 'showErrorsConfig', ->
_showSuccess = false
_trigger = 'blur'
_ignorePristine = false

@showSuccess = (showSuccess) ->
_showSuccess = showSuccess

@trigger = (trigger) ->
_trigger = trigger

@ignorePristine = (ignorePristine) ->
_ignorePristine = ignorePristine

@$get = ->
showSuccess: _showSuccess
trigger: _trigger
ignorePristine: _ignorePristine

return
35 changes: 27 additions & 8 deletions src/showErrors.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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);
});
Expand All @@ -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() {
Expand Down Expand Up @@ -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
};
};
});
Expand Down
4 changes: 2 additions & 2 deletions src/showErrors.min.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 16 additions & 3 deletions test/showErrors.spec.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -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('<form name="userForm"><div class="form-group" show-errors><input type="text" name="firstName"></input></div></form>')($scope)
).toThrow "show-errors element has no child input elements with a 'name' attribute and a 'form-control' class"
$compile('<form name="userForm"><div class="form-group" show-errors><input type="text"></input></div></form>')($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( ->
Expand Down Expand Up @@ -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', ->
Expand Down
22 changes: 19 additions & 3 deletions test/showErrors.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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('<form name="userForm"><div class="form-group" show-errors><input type="text" name="firstName"></input></div></form>')($scope);
}).toThrow("show-errors element has no child input elements with a 'name' attribute and a 'form-control' class");
return $compile('<form name="userForm"><div class="form-group" show-errors><input type="text"></input></div></form>')($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() {
Expand Down Expand Up @@ -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;
Expand Down