Skip to content
This repository was archived by the owner on Aug 15, 2021. It is now read-only.

Commit a894793

Browse files
committed
Merge pull request #111 from Cosium/fix-110
#110 Add an option to automatically scroll to the first faulty input
2 parents 8bdba1e + 0be903c commit a894793

File tree

3 files changed

+108
-4
lines changed

3 files changed

+108
-4
lines changed

src/providers/validator.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ function ValidatorFn() {
22
var elementStateModifiers = {},
33
enableValidElementStyling = true,
44
enableInvalidElementStyling = true,
5+
enableFirstInvalidElementScrollingOnSubmit = false,
56
validationEnabled = true,
67

78
toBoolean = function (value) {
@@ -236,6 +237,25 @@ function ValidatorFn() {
236237
enableInvalidElementStyling = enabled;
237238
};
238239

240+
/**
241+
* @ngdoc function
242+
* @name validator#setFirstInvalidElementScrollingOnSubmit
243+
* @methodOf validator
244+
*
245+
* @description
246+
* Globally enables first invalid element scrolling on form submit. This is disabled by default.
247+
*
248+
* @param enabled {Boolean} enabled True to enable scrolling otherwise false.
249+
*/
250+
this.setFirstInvalidElementScrollingOnSubmit = function (enabled) {
251+
enableFirstInvalidElementScrollingOnSubmit = enabled;
252+
};
253+
254+
this.firstInvalidElementScrollingOnSubmitEnabled = function () {
255+
return enableFirstInvalidElementScrollingOnSubmit;
256+
};
257+
258+
239259
this.getDomModifier = function (el) {
240260
var modifierKey = (el !== undefined ? el.attr('element-modifier') : this.defaultElementModifier) ||
241261
(el !== undefined ? el.attr('data-element-modifier') : this.defaultElementModifier) ||

src/services/validationManager.js

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ function ElementUtilsFn() {
88
};
99
}
1010

11-
function ValidationManagerFn(validator, elementUtils) {
11+
function ValidationManagerFn(validator, elementUtils, $anchorScroll) {
1212
var elementTypesToValidate = ['input', 'textarea', 'select', 'form'],
1313

1414
elementIsVisible = function (el) {
@@ -162,6 +162,12 @@ function ValidationManagerFn(validator, elementUtils) {
162162
ctrlFormOptions.forceValidation = force;
163163
try {
164164
isValid = validateElement(controller, ctrlElement, ctrlFormOptions);
165+
if (validator.firstInvalidElementScrollingOnSubmitEnabled() && !isValid && frmValid) {
166+
var ctrlElementId = ctrlElement.attr('id');
167+
if (ctrlElementId) {
168+
$anchorScroll(ctrlElementId);
169+
}
170+
}
165171
frmValid = frmValid && isValid;
166172
} finally {
167173
ctrlFormOptions.forceValidation = originalForceValue;
@@ -223,7 +229,8 @@ function ValidationManagerFn(validator, elementUtils) {
223229

224230
ValidationManagerFn.$inject = [
225231
'validator',
226-
'jcs-elementUtils'
232+
'jcs-elementUtils',
233+
'$anchorScroll'
227234
];
228235

229236
angular.module('jcs-autoValidate').factory('jcs-elementUtils', ElementUtilsFn);

tests/services/validationManager.spec.js

Lines changed: 79 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
'use strict';
33

44
describe('jcs-autoValidate validationManager', function () {
5-
var sandbox, $rootScope, $compile, $q, validator, validationManager, modelCtrl, defer, elementUtils,
5+
var sandbox, $rootScope, $compile, $q, validator, validationManager, modelCtrl, defer, elementUtils, $anchorScroll,
66
setModelCtrl = function () {
77
modelCtrl = {
88
$parsers: [],
@@ -35,15 +35,23 @@
3535
beforeEach(module('jcs-autoValidate'));
3636

3737
describe('validationManager', function () {
38-
beforeEach(inject(function ($injector) {
38+
beforeEach(function () {
3939
sandbox = sinon.sandbox.create();
40+
});
41+
42+
beforeEach(module('jcs-autoValidate', function ($provide) {
43+
$provide.value('$anchorScroll', sandbox.stub());
44+
}));
45+
46+
beforeEach(inject(function ($injector) {
4047
$rootScope = $injector.get('$rootScope');
4148
$compile = $injector.get('$compile');
4249
$q = $injector.get('$q');
4350
defer = $q.defer();
4451
validator = $injector.get('validator');
4552
validationManager = $injector.get('validationManager');
4653
elementUtils = $injector.get('jcs-elementUtils');
54+
$anchorScroll = $injector.get('$anchorScroll');
4755

4856
sandbox.stub(validator, 'makeValid');
4957
sandbox.stub(validator, 'makeInvalid');
@@ -439,6 +447,75 @@
439447

440448
expect(validator.makeInvalid.calledOnce).to.equal(true);
441449
});
450+
451+
it('should call $anchorScroll to first invalid element when the option is disabled', function () {
452+
var frm = compileElement('<form name="frm1" ng-submit=""></form>', true),
453+
inpt = compileElement('<input type="text" ng-model="name" required="required" ng-minlength="2" />', true);
454+
455+
456+
sandbox.stub(elementUtils, 'isElementVisible').returns(true);
457+
sandbox.stub(validator, 'firstInvalidElementScrollingOnSubmitEnabled').returns(false);
458+
frm.append(inpt);
459+
$rootScope.$apply();
460+
461+
frm.on('submit', function (event) {
462+
event.preventDefault();
463+
});
464+
465+
defer.resolve('errorMsg');
466+
frm.trigger('submit');
467+
468+
$rootScope.$apply();
469+
470+
expect($anchorScroll.notCalled).to.equal(true);
471+
});
472+
473+
it('should call $anchorScroll with first invalid element id when the option is enabled', function () {
474+
var frm = compileElement('<form name="frm1" ng-submit=""></form>', true),
475+
firstInpt = compileElement('<input type="text" id="first" ng-model="name" required="required" ng-minlength="2" />', true),
476+
secondInpt = compileElement('<input type="text" id="second" ng-model="description" required="required" ng-minlength="2" />', true);
477+
478+
479+
sandbox.stub(elementUtils, 'isElementVisible').returns(true);
480+
sandbox.stub(validator, 'firstInvalidElementScrollingOnSubmitEnabled').returns(true);
481+
frm.append(firstInpt);
482+
frm.append(secondInpt);
483+
$rootScope.$apply();
484+
485+
frm.on('submit', function (event) {
486+
event.preventDefault();
487+
});
488+
489+
defer.resolve('errorMsg');
490+
frm.trigger('submit');
491+
492+
$rootScope.$apply();
493+
494+
expect($anchorScroll.calledOnce).to.equal(true);
495+
expect($anchorScroll.calledWith('first')).to.equal(true);
496+
});
497+
498+
it('should not call $anchorScroll with an undefined id', function () {
499+
var frm = compileElement('<form name="frm1" ng-submit=""></form>', true),
500+
firstInpt = compileElement('<input type="text" ng-model="name" required="required" ng-minlength="2" />', true);
501+
502+
503+
sandbox.stub(elementUtils, 'isElementVisible').returns(true);
504+
sandbox.stub(validator, 'firstInvalidElementScrollingOnSubmitEnabled').returns(true);
505+
frm.append(firstInpt);
506+
$rootScope.$apply();
507+
508+
frm.on('submit', function (event) {
509+
event.preventDefault();
510+
});
511+
512+
defer.resolve('errorMsg');
513+
frm.trigger('submit');
514+
515+
$rootScope.$apply();
516+
517+
expect($anchorScroll.notCalled).to.equal(true);
518+
});
442519
});
443520

444521
describe('resetForm', function () {

0 commit comments

Comments
 (0)