mirror of
https://github.com/zhigang1992/angular.js.git
synced 2026-01-12 22:45:52 +08:00
feat(input): add $touched and $untouched states
Sets the ngModel controller property $touched to True and $untouched to False whenever a 'blur' event is triggered over a control with the ngModel directive. Also adds the $setTouched and $setUntouched methods to the NgModelController. References #583
This commit is contained in:
committed by
Matias Niemelä
parent
94bcc03f3e
commit
adcc5a00bf
@@ -5,7 +5,9 @@
|
||||
-VALID_CLASS,
|
||||
-INVALID_CLASS,
|
||||
-PRISTINE_CLASS,
|
||||
-DIRTY_CLASS
|
||||
-DIRTY_CLASS,
|
||||
-UNTOUCHED_CLASS,
|
||||
-TOUCHED_CLASS
|
||||
*/
|
||||
|
||||
var URL_REGEXP = /^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/;
|
||||
@@ -1407,7 +1409,9 @@ var inputDirective = ['$browser', '$sniffer', '$filter', function($browser, $sni
|
||||
var VALID_CLASS = 'ng-valid',
|
||||
INVALID_CLASS = 'ng-invalid',
|
||||
PRISTINE_CLASS = 'ng-pristine',
|
||||
DIRTY_CLASS = 'ng-dirty';
|
||||
DIRTY_CLASS = 'ng-dirty',
|
||||
UNTOUCHED_CLASS = 'ng-untouched',
|
||||
TOUCHED_CLASS = 'ng-touched';
|
||||
|
||||
/**
|
||||
* @ngdoc type
|
||||
@@ -1442,6 +1446,8 @@ var VALID_CLASS = 'ng-valid',
|
||||
*
|
||||
* @property {Object} $error An object hash with all errors as keys.
|
||||
*
|
||||
* @property {boolean} $untouched True if control has not lost focus yet.
|
||||
* @property {boolean} $touched True if control has lost focus.
|
||||
* @property {boolean} $pristine True if user has not interacted with the control yet.
|
||||
* @property {boolean} $dirty True if user has already interacted with the control.
|
||||
* @property {boolean} $valid True if there is no error.
|
||||
@@ -1555,6 +1561,8 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
|
||||
this.$parsers = [];
|
||||
this.$formatters = [];
|
||||
this.$viewChangeListeners = [];
|
||||
this.$untouched = true;
|
||||
this.$touched = false;
|
||||
this.$pristine = true;
|
||||
this.$dirty = false;
|
||||
this.$valid = true;
|
||||
@@ -1609,7 +1617,9 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
|
||||
|
||||
|
||||
// Setup initial state of the control
|
||||
$element.addClass(PRISTINE_CLASS);
|
||||
$element
|
||||
.addClass(PRISTINE_CLASS)
|
||||
.addClass(UNTOUCHED_CLASS);
|
||||
toggleValidCss(true);
|
||||
|
||||
// convenience method for easy toggling of classes
|
||||
@@ -1679,6 +1689,38 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
|
||||
$animate.addClass($element, PRISTINE_CLASS);
|
||||
};
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name ngModel.NgModelController#$setUntouched
|
||||
*
|
||||
* @description
|
||||
* Sets the control to its untouched state.
|
||||
*
|
||||
* This method can be called to remove the 'ng-touched' class and set the control to its
|
||||
* untouched state (ng-untouched class).
|
||||
*/
|
||||
this.$setUntouched = function() {
|
||||
ctrl.$touched = false;
|
||||
ctrl.$untouched = true;
|
||||
$animate.setClass($element, UNTOUCHED_CLASS, TOUCHED_CLASS);
|
||||
};
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name ngModel.NgModelController#$setTouched
|
||||
*
|
||||
* @description
|
||||
* Sets the control to its touched state.
|
||||
*
|
||||
* This method can be called to remove the 'ng-untouched' class and set the control to its
|
||||
* touched state (ng-touched class).
|
||||
*/
|
||||
this.$setTouched = function() {
|
||||
ctrl.$touched = true;
|
||||
ctrl.$untouched = false;
|
||||
$animate.setClass($element, TOUCHED_CLASS, UNTOUCHED_CLASS);
|
||||
};
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name ngModel.NgModelController#$rollbackViewValue
|
||||
@@ -2014,6 +2056,12 @@ var ngModelDirective = function() {
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
element.on('blur', function(ev) {
|
||||
scope.$apply(function() {
|
||||
modelCtrl.$setTouched();
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -48,6 +48,8 @@ beforeEach(function() {
|
||||
toBeValid: cssMatcher('ng-valid', 'ng-invalid'),
|
||||
toBeDirty: cssMatcher('ng-dirty', 'ng-pristine'),
|
||||
toBePristine: cssMatcher('ng-pristine', 'ng-dirty'),
|
||||
toBeUntouched: cssMatcher('ng-untouched', 'ng-touched'),
|
||||
toBeTouched: cssMatcher('ng-touched', 'ng-untouched'),
|
||||
toBeShown: function() {
|
||||
this.message = valueFn(
|
||||
"Expected element " + (this.isNot ? "": "not ") + "to have 'ng-hide' class");
|
||||
|
||||
@@ -51,6 +51,8 @@ describe('NgModelController', function() {
|
||||
|
||||
|
||||
it('should init the properties', function() {
|
||||
expect(ctrl.$untouched).toBe(true);
|
||||
expect(ctrl.$touched).toBe(false);
|
||||
expect(ctrl.$dirty).toBe(false);
|
||||
expect(ctrl.$pristine).toBe(true);
|
||||
expect(ctrl.$valid).toBe(true);
|
||||
@@ -133,6 +135,28 @@ describe('NgModelController', function() {
|
||||
});
|
||||
});
|
||||
|
||||
describe('setUntouched', function() {
|
||||
|
||||
it('should set control to its untouched state', function() {
|
||||
ctrl.$setTouched();
|
||||
|
||||
ctrl.$setUntouched();
|
||||
expect(ctrl.$touched).toBe(false);
|
||||
expect(ctrl.$untouched).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('setTouched', function() {
|
||||
|
||||
it('should set control to its touched state', function() {
|
||||
ctrl.$setUntouched();
|
||||
|
||||
ctrl.$setTouched();
|
||||
expect(ctrl.$touched).toBe(true);
|
||||
expect(ctrl.$untouched).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('view -> model', function() {
|
||||
|
||||
it('should set the value to $viewValue', function() {
|
||||
@@ -265,13 +289,14 @@ describe('NgModelController', function() {
|
||||
|
||||
describe('ngModel', function() {
|
||||
|
||||
it('should set css classes (ng-valid, ng-invalid, ng-pristine, ng-dirty)',
|
||||
it('should set css classes (ng-valid, ng-invalid, ng-pristine, ng-dirty, ng-untouched, ng-touched)',
|
||||
inject(function($compile, $rootScope, $sniffer) {
|
||||
var element = $compile('<input type="email" ng-model="value" />')($rootScope);
|
||||
|
||||
$rootScope.$digest();
|
||||
expect(element).toBeValid();
|
||||
expect(element).toBePristine();
|
||||
expect(element).toBeUntouched();
|
||||
expect(element.hasClass('ng-valid-email')).toBe(true);
|
||||
expect(element.hasClass('ng-invalid-email')).toBe(false);
|
||||
|
||||
@@ -297,6 +322,9 @@ describe('ngModel', function() {
|
||||
expect(element.hasClass('ng-valid-email')).toBe(true);
|
||||
expect(element.hasClass('ng-invalid-email')).toBe(false);
|
||||
|
||||
browserTrigger(element, 'blur');
|
||||
expect(element).toBeTouched();
|
||||
|
||||
dealoc(element);
|
||||
}));
|
||||
|
||||
@@ -309,6 +337,23 @@ describe('ngModel', function() {
|
||||
expect(element).toHaveClass('ng-invalid-required');
|
||||
}));
|
||||
|
||||
it('should set the control touched state on "blur" event', inject(function($compile, $rootScope) {
|
||||
var element = $compile('<form name="myForm">' +
|
||||
'<input name="myControl" ng-model="value" >' +
|
||||
'</form>')($rootScope);
|
||||
var inputElm = element.find('input');
|
||||
var control = $rootScope.myForm.myControl;
|
||||
|
||||
expect(control.$touched).toBe(false);
|
||||
expect(control.$untouched).toBe(true);
|
||||
|
||||
browserTrigger(inputElm, 'blur');
|
||||
expect(control.$touched).toBe(true);
|
||||
expect(control.$untouched).toBe(false);
|
||||
|
||||
dealoc(element);
|
||||
}));
|
||||
|
||||
|
||||
it('should register/deregister a nested ngModel with parent form when entering or leaving DOM',
|
||||
inject(function($compile, $rootScope) {
|
||||
@@ -2687,6 +2732,22 @@ describe('NgModel animations', function() {
|
||||
assertValidAnimation(animations[1], 'addClass', 'ng-pristine');
|
||||
}));
|
||||
|
||||
it('should trigger an animation when untouched', inject(function($animate) {
|
||||
model.$setUntouched();
|
||||
|
||||
var animations = findElementAnimations(input, $animate.queue);
|
||||
assertValidAnimation(animations[0], 'setClass', 'ng-untouched');
|
||||
expect(animations[0].args[2]).toBe('ng-touched');
|
||||
}));
|
||||
|
||||
it('should trigger an animation when touched', inject(function($animate) {
|
||||
model.$setTouched();
|
||||
|
||||
var animations = findElementAnimations(input, $animate.queue);
|
||||
assertValidAnimation(animations[0], 'setClass', 'ng-touched', 'ng-untouched');
|
||||
expect(animations[0].args[2]).toBe('ng-untouched');
|
||||
}));
|
||||
|
||||
it('should trigger custom errors as addClass/removeClass when invalid/valid', inject(function($animate) {
|
||||
model.$setValidity('custom-error', false);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user