feat(input[number]): support step
input[number] will now set the step error if the input value (ngModel $viewValue) does not fit the step constraint set in the step / ngStep attribute. Fixes #10597
This commit is contained in:
+2
-1
@@ -576,7 +576,8 @@ var ALIASED_ATTR = {
|
||||
'ngMaxlength': 'maxlength',
|
||||
'ngMin': 'min',
|
||||
'ngMax': 'max',
|
||||
'ngPattern': 'pattern'
|
||||
'ngPattern': 'pattern',
|
||||
'ngStep': 'step'
|
||||
};
|
||||
|
||||
function getBooleanAttrName(element, name) {
|
||||
|
||||
@@ -679,7 +679,17 @@ var inputType = {
|
||||
* @param {string} ngModel Assignable angular expression to data-bind to.
|
||||
* @param {string=} name Property name of the form under which the control is published.
|
||||
* @param {string=} min Sets the `min` validation error key if the value entered is less than `min`.
|
||||
* Can be interpolated.
|
||||
* @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`.
|
||||
* Can be interpolated.
|
||||
* @param {string=} ngMin Like `min`, sets the `min` validation error key if the value entered is less than `ngMin`,
|
||||
* but does not trigger HTML5 native validation. Takes an expression.
|
||||
* @param {string=} ngMax Like `max`, sets the `max` validation error key if the value entered is greater than `ngMax`,
|
||||
* but does not trigger HTML5 native validation. Takes an expression.
|
||||
* @param {string=} step Sets the `step` validation error key if the value entered does not fit the `step` constraint.
|
||||
* Can be interpolated.
|
||||
* @param {string=} ngStep Like `step`, sets the `max` validation error key if the value entered does not fit the `ngStep` constraint,
|
||||
* but does not trigger HTML5 native validation. Takes an expression.
|
||||
* @param {string=} required Sets `required` validation error key if the value is not entered.
|
||||
* @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
|
||||
* the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
|
||||
@@ -1549,6 +1559,19 @@ function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) {
|
||||
ctrl.$validate();
|
||||
});
|
||||
}
|
||||
|
||||
if (isDefined(attr.step) || attr.ngStep) {
|
||||
var stepVal;
|
||||
ctrl.$validators.step = function(modelValue, viewValue) {
|
||||
return ctrl.$isEmpty(viewValue) || isUndefined(stepVal) || viewValue % stepVal === 0;
|
||||
};
|
||||
|
||||
attr.$observe('step', function(val) {
|
||||
stepVal = parseNumberAttrVal(val);
|
||||
// TODO(matsko): implement validateLater to reduce number of validations
|
||||
ctrl.$validate();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function rangeInputType(scope, element, attr, ctrl, $sniffer, $browser) {
|
||||
|
||||
@@ -2621,6 +2621,157 @@ describe('input', function() {
|
||||
});
|
||||
});
|
||||
|
||||
describe('step', function() {
|
||||
it('should validate', function() {
|
||||
$rootScope.step = 10;
|
||||
$rootScope.value = 20;
|
||||
var inputElm = helper.compileInput('<input type="number" ng-model="value" name="alias" step="{{step}}" />');
|
||||
|
||||
expect(inputElm.val()).toBe('20');
|
||||
expect(inputElm).toBeValid();
|
||||
expect($rootScope.value).toBe(20);
|
||||
expect($rootScope.form.alias.$error.step).toBeFalsy();
|
||||
|
||||
helper.changeInputValueTo('18');
|
||||
expect(inputElm).toBeInvalid();
|
||||
expect(inputElm.val()).toBe('18');
|
||||
expect($rootScope.value).toBeUndefined();
|
||||
expect($rootScope.form.alias.$error.step).toBeTruthy();
|
||||
|
||||
helper.changeInputValueTo('10');
|
||||
expect(inputElm).toBeValid();
|
||||
expect(inputElm.val()).toBe('10');
|
||||
expect($rootScope.value).toBe(10);
|
||||
expect($rootScope.form.alias.$error.step).toBeFalsy();
|
||||
|
||||
$rootScope.$apply('value = 12');
|
||||
expect(inputElm).toBeInvalid();
|
||||
expect(inputElm.val()).toBe('12');
|
||||
expect($rootScope.value).toBe(12);
|
||||
expect($rootScope.form.alias.$error.step).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should validate even if the step value changes on-the-fly', function() {
|
||||
$rootScope.step = 10;
|
||||
var inputElm = helper.compileInput('<input type="number" ng-model="value" name="alias" step="{{step}}" />');
|
||||
|
||||
helper.changeInputValueTo('10');
|
||||
expect(inputElm).toBeValid();
|
||||
expect($rootScope.value).toBe(10);
|
||||
|
||||
// Step changes, but value matches
|
||||
$rootScope.$apply('step = 5');
|
||||
expect(inputElm.val()).toBe('10');
|
||||
expect(inputElm).toBeValid();
|
||||
expect($rootScope.value).toBe(10);
|
||||
expect($rootScope.form.alias.$error.step).toBeFalsy();
|
||||
|
||||
// Step changes, value does not match
|
||||
$rootScope.$apply('step = 6');
|
||||
expect(inputElm).toBeInvalid();
|
||||
expect($rootScope.value).toBeUndefined();
|
||||
expect(inputElm.val()).toBe('10');
|
||||
expect($rootScope.form.alias.$error.step).toBeTruthy();
|
||||
|
||||
// null = valid
|
||||
$rootScope.$apply('step = null');
|
||||
expect(inputElm).toBeValid();
|
||||
expect($rootScope.value).toBe(10);
|
||||
expect(inputElm.val()).toBe('10');
|
||||
expect($rootScope.form.alias.$error.step).toBeFalsy();
|
||||
|
||||
// Step val as string
|
||||
$rootScope.$apply('step = "7"');
|
||||
expect(inputElm).toBeInvalid();
|
||||
expect($rootScope.value).toBeUndefined();
|
||||
expect(inputElm.val()).toBe('10');
|
||||
expect($rootScope.form.alias.$error.step).toBeTruthy();
|
||||
|
||||
// unparsable string is ignored
|
||||
$rootScope.$apply('step = "abc"');
|
||||
expect(inputElm).toBeValid();
|
||||
expect($rootScope.value).toBe(10);
|
||||
expect(inputElm.val()).toBe('10');
|
||||
expect($rootScope.form.alias.$error.step).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('ngStep', function() {
|
||||
it('should validate', function() {
|
||||
$rootScope.step = 10;
|
||||
$rootScope.value = 20;
|
||||
var inputElm = helper.compileInput('<input type="number" ng-model="value" name="alias" ng-step="step" />');
|
||||
|
||||
expect(inputElm.val()).toBe('20');
|
||||
expect(inputElm).toBeValid();
|
||||
expect($rootScope.value).toBe(20);
|
||||
expect($rootScope.form.alias.$error.step).toBeFalsy();
|
||||
|
||||
helper.changeInputValueTo('18');
|
||||
expect(inputElm).toBeInvalid();
|
||||
expect(inputElm.val()).toBe('18');
|
||||
expect($rootScope.value).toBeUndefined();
|
||||
expect($rootScope.form.alias.$error.step).toBeTruthy();
|
||||
|
||||
helper.changeInputValueTo('10');
|
||||
expect(inputElm).toBeValid();
|
||||
expect(inputElm.val()).toBe('10');
|
||||
expect($rootScope.value).toBe(10);
|
||||
expect($rootScope.form.alias.$error.step).toBeFalsy();
|
||||
|
||||
$rootScope.$apply('value = 12');
|
||||
expect(inputElm).toBeInvalid();
|
||||
expect(inputElm.val()).toBe('12');
|
||||
expect($rootScope.value).toBe(12);
|
||||
expect($rootScope.form.alias.$error.step).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should validate even if the step value changes on-the-fly', function() {
|
||||
$rootScope.step = 10;
|
||||
var inputElm = helper.compileInput('<input type="number" ng-model="value" name="alias" ng-step="step" />');
|
||||
|
||||
helper.changeInputValueTo('10');
|
||||
expect(inputElm).toBeValid();
|
||||
expect($rootScope.value).toBe(10);
|
||||
|
||||
// Step changes, but value matches
|
||||
$rootScope.$apply('step = 5');
|
||||
expect(inputElm.val()).toBe('10');
|
||||
expect(inputElm).toBeValid();
|
||||
expect($rootScope.value).toBe(10);
|
||||
expect($rootScope.form.alias.$error.step).toBeFalsy();
|
||||
|
||||
// Step changes, value does not match
|
||||
$rootScope.$apply('step = 6');
|
||||
expect(inputElm).toBeInvalid();
|
||||
expect($rootScope.value).toBeUndefined();
|
||||
expect(inputElm.val()).toBe('10');
|
||||
expect($rootScope.form.alias.$error.step).toBeTruthy();
|
||||
|
||||
// null = valid
|
||||
$rootScope.$apply('step = null');
|
||||
expect(inputElm).toBeValid();
|
||||
expect($rootScope.value).toBe(10);
|
||||
expect(inputElm.val()).toBe('10');
|
||||
expect($rootScope.form.alias.$error.step).toBeFalsy();
|
||||
|
||||
// Step val as string
|
||||
$rootScope.$apply('step = "7"');
|
||||
expect(inputElm).toBeInvalid();
|
||||
expect($rootScope.value).toBeUndefined();
|
||||
expect(inputElm.val()).toBe('10');
|
||||
expect($rootScope.form.alias.$error.step).toBeTruthy();
|
||||
|
||||
// unparsable string is ignored
|
||||
$rootScope.$apply('step = "abc"');
|
||||
expect(inputElm).toBeValid();
|
||||
expect($rootScope.value).toBe(10);
|
||||
expect(inputElm.val()).toBe('10');
|
||||
expect($rootScope.form.alias.$error.step).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('required', function() {
|
||||
|
||||
|
||||
Reference in New Issue
Block a user