Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

feat(ngModelOptions): add validateOn option #7414

Closed
wants to merge 1 commit into from
Closed
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
70 changes: 50 additions & 20 deletions src/ng/directive/input.js
Original file line number Diff line number Diff line change
Expand Up @@ -1757,7 +1757,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
* Runs each of the registered validations set on the $validators object.
*/
this.$validate = function() {
this.$$runValidators(ctrl.$modelValue, ctrl.$viewValue);
this.$$runValidators(ctrl.$$validateValue, ctrl.$viewValue);
};

this.$$runValidators = function(modelValue, viewValue) {
Expand All @@ -1766,26 +1766,9 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
});
};

/**
* @ngdoc method
* @name ngModel.NgModelController#$commitViewValue
*
* @description
* Commit a pending update to the `$modelValue`.
*
* Updates may be pending by a debounced event or because the input is waiting for a some future
* event defined in `ng-model-options`. this method is rarely needed as `NgModelController`
* usually handles calling this in response to input events.
*/
this.$commitViewValue = function() {
this.$$validateViewValue = function() {
var viewValue = ctrl.$viewValue;

$timeout.cancel(pendingDebounce);
if (ctrl.$$lastCommittedViewValue === viewValue) {
return;
}
ctrl.$$lastCommittedViewValue = viewValue;

// change to dirty
if (ctrl.$pristine) {
ctrl.$dirty = true;
Expand All @@ -1804,9 +1787,38 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
(isUndefined(ctrl.$$invalidModelValue) || ctrl.$$invalidModelValue != modelValue)) {

ctrl.$$runValidators(modelValue, viewValue);
ctrl.$modelValue = ctrl.$valid ? modelValue : undefined;
ctrl.$$validateValue = ctrl.$valid ? modelValue : undefined;
ctrl.$$invalidModelValue = ctrl.$valid ? undefined : modelValue;

return ctrl.$valid ? modelValue : undefined;
}

return ctrl.$modelValue;
};

/**
* @ngdoc method
* @name ngModel.NgModelController#$commitViewValue
*
* @description
* Commit a pending update to the `$modelValue`.
*
* Updates may be pending by a debounced event or because the input is waiting for a some future
* event defined in `ng-model-options`. this method is rarely needed as `NgModelController`
* usually handles calling this in response to input events.
*/
this.$commitViewValue = function() {
var viewValue = ctrl.$viewValue;

$timeout.cancel(pendingDebounce);
if (ctrl.$$lastCommittedViewValue === viewValue) {
return;
}
ctrl.$$lastCommittedViewValue = viewValue;

var modelValue = ctrl.$$validateViewValue();
if (ctrl.$modelValue !== modelValue) {
ctrl.$modelValue = modelValue;
ngModelSet($scope, ctrl.$modelValue);
forEach(ctrl.$viewChangeListeners, function(listener) {
try {
Expand Down Expand Up @@ -1848,6 +1860,9 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
*/
this.$setViewValue = function(value, trigger) {
ctrl.$viewValue = value;
if (ctrl.$options && ctrl.$options.validateOnDefault) {
ctrl.$$validateViewValue();
}
if (!ctrl.$options || ctrl.$options.updateOnDefault) {
ctrl.$$debounceViewValueCommit(trigger);
}
Expand Down Expand Up @@ -1897,6 +1912,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$

ctrl.$$runValidators(modelValue, viewValue);
ctrl.$modelValue = ctrl.$valid ? modelValue : undefined;
ctrl.$$validateValue = ctrl.$modelValue;
ctrl.$$invalidModelValue = ctrl.$valid ? undefined : modelValue;

if (ctrl.$viewValue !== viewValue) {
Expand Down Expand Up @@ -2048,6 +2064,13 @@ var ngModelDirective = function() {
});
});
}
if (modelCtrl.$options && modelCtrl.$options.validateOn) {
element.on(modelCtrl.$options.validateOn, function(ev) {
scope.$apply(function() {
modelCtrl.$$validateViewValue();
});
});
}

element.on('blur', function(ev) {
scope.$apply(function() {
Expand Down Expand Up @@ -2504,6 +2527,13 @@ var ngModelOptionsDirective = function() {
that.$options.updateOnDefault = true;
return ' ';
}));

if (this.$options.validateOn !== undefined) {
this.$options.validateOn = trim(this.$options.validateOn.replace(DEFAULT_REGEXP, function() {
that.$options.validateOnDefault = true;
return ' ';
}));
}
} else {
this.$options.updateOnDefault = true;
}
Expand Down
38 changes: 34 additions & 4 deletions test/ng/directive/inputSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -293,12 +293,12 @@ describe('NgModelController', function() {
return (/^[A-Z]+$/).test(value);
};

ctrl.$modelValue = 'test';
ctrl.$$validateValue = 'test';
ctrl.$validate();

expect(ctrl.$valid).toBe(false);

ctrl.$modelValue = 'TEST';
ctrl.$$validateValue = 'TEST';
ctrl.$validate();

expect(ctrl.$valid).toBe(true);
Expand All @@ -309,12 +309,12 @@ describe('NgModelController', function() {
return (/^[A-Z]+$/).test(value);
};

ctrl.$modelValue = 'test';
ctrl.$$validateValue = 'test';
ctrl.$validate();

expect(ctrl.$valid).toBe(false);

ctrl.$modelValue = 'TEST';
ctrl.$$validateValue = 'TEST';
ctrl.$validate();

expect(ctrl.$valid).toBe(true);
Expand Down Expand Up @@ -846,6 +846,36 @@ describe('input', function() {
expect(scope.name).toEqual('a');
});

it('should allow validating before view value is committed', function() {
scope.value = 2;
compileInput(
'<input type="number" ng-model="value" name="alias" max="10" '+
'ng-model-options="{ updateOn: \'blur\', validateOn: \'default\' }"'+
'/>');
changeInputValueTo('20');
expect(scope.form.alias.$error.max).toBeTruthy();
expect(formElm).toBeDirty();
expect(scope.value).toEqual(2);
browserTrigger(inputElm, 'blur');
expect(scope.value).toBeUndefined();
});

it('should allow defining trigger for validation', function() {
scope.value = 2;
compileInput(
'<input type="number" ng-model="value" name="alias" max="10" '+
'ng-model-options="{ updateOn: \'\', validateOn: \'blur\' }"'+
'/>');
changeInputValueTo('20');
expect(scope.form.alias.$error.max).toBeFalsy();
browserTrigger(inputElm, 'blur');
expect(scope.form.alias.$error.max).toBeTruthy();
expect(formElm).toBeDirty();
expect(scope.value).toEqual(2);
browserTrigger(formElm, 'submit');
expect(scope.value).toBeUndefined();
});

it('should not dirty the input if nothing was changed before updateOn trigger', function() {
compileInput(
'<input type="text" ng-model="name" name="alias" '+
Expand Down