diff --git a/src/ng/directive/input.js b/src/ng/directive/input.js
index 1658dc5580e6..ee506a589efe 100644
--- a/src/ng/directive/input.js
+++ b/src/ng/directive/input.js
@@ -1061,8 +1061,11 @@ var inputType = {
* Angular will also update the model value.
*
* Automatic value adjustment also means that a range input element can never have the `required`,
- * `min`, or `max` errors, except when using `ngMax` and `ngMin`, which are not affected by automatic
- * value adjustment, because they do not set the `min` and `max` attributes.
+ * `min`, or `max` errors.
+ *
+ * Note that `input[range]` is not compatible with`ngMax` and `ngMin`, because they do not set the
+ * `min` and `max` attributes, which means that the browser won't automatically adjust the input
+ * value based on their values, and will always assume min = 0 and max = 100.
*
* @param {string} ngModel Assignable angular expression to data-bind to.
* @param {string=} name Property name of the form under which the control is published.
@@ -1070,14 +1073,6 @@ var inputType = {
* than `min`. Can be interpolated.
* @param {string=} max Sets the `max` validation to ensure that the value entered is less than `max`.
* Can be interpolated.
- * @param {string=} ngMin Takes an expression. Sets the `min` validation to ensure that the value
- * entered is greater than `min`. Does not set the `min` attribute and therefore
- * adds no native HTML5 validation. It also means the browser won't adjust the
- * element value in case `min` is greater than the current value.
- * @param {string=} ngMax Takes an expression. Sets the `max` validation to ensure that the value
- * entered is less than `max`. Does not set the `max` attribute and therefore
- * adds no native HTML5 validation. It also means the browser won't adjust the
- * element value in case `max` is less than the current value.
* @param {string=} ngChange Angular expression to be executed when the ngModel value changes due
* to user interaction with the input element.
*
@@ -1547,10 +1542,12 @@ function rangeInputType(scope, element, attr, ctrl, $sniffer, $browser) {
numberFormatterParser(ctrl);
baseInputType(scope, element, attr, ctrl, $sniffer, $browser);
- var minVal = 0,
- maxVal = 100,
- supportsRange = ctrl.$$hasNativeValidators && element[0].type === 'range',
- validity = element[0].validity;
+ var supportsRange = ctrl.$$hasNativeValidators && element[0].type === 'range',
+ minVal = supportsRange ? 0 : undefined,
+ maxVal = supportsRange ? 100 : undefined,
+ validity = element[0].validity,
+ hasMinAttr = isDefined(attr.min),
+ hasMaxAttr = isDefined(attr.max);
var originalRender = ctrl.$render;
@@ -1563,6 +1560,39 @@ function rangeInputType(scope, element, attr, ctrl, $sniffer, $browser) {
} :
originalRender;
+ if (hasMinAttr) {
+ ctrl.$validators.min = supportsRange ?
+ // Since all browsers set the input to a valid value, we don't need to check validity
+ function noopMinValidator() { return true; } :
+ // non-support browsers validate the range
+ function minValidator(modelValue, viewValue) {
+ return ctrl.$isEmpty(viewValue) || isUndefined(minVal) || viewValue >= minVal;
+ };
+
+ setInitialValueAndObserver('min', minChange);
+ }
+
+ if (hasMaxAttr) {
+ ctrl.$validators.max = supportsRange ?
+ // Since all browsers set the input to a valid value, we don't need to check validity
+ function noopMaxValidator() { return true; } :
+ // ngMax doesn't set the max attr, so the browser doesn't adjust the input value as setting max would
+ function maxValidator(modelValue, viewValue) {
+ return ctrl.$isEmpty(viewValue) || isUndefined(maxVal) || viewValue <= maxVal;
+ };
+
+ setInitialValueAndObserver('max', maxChange);
+ }
+
+ function setInitialValueAndObserver(htmlAttrName, changeFn) {
+ // interpolated attributes set the attribute value only after a digest, but we need the
+ // attribute value when the input is first rendered, so that the browser can adjust the
+ // input value based on the min/max value
+ element.attr(htmlAttrName, attr[htmlAttrName]);
+
+ attr.$observe(htmlAttrName, changeFn);
+ }
+
function minChange(val) {
if (isDefined(val) && !isNumber(val)) {
val = parseFloat(val);
@@ -1573,12 +1603,12 @@ function rangeInputType(scope, element, attr, ctrl, $sniffer, $browser) {
return;
}
- if (supportsRange && minAttrType === 'min') {
+ if (supportsRange) {
var elVal = element.val();
// IE11 doesn't set the el val correctly if the minVal is greater than the element value
if (minVal > elVal) {
- element.val(minVal);
elVal = minVal;
+ element.val(elVal);
}
ctrl.$setViewValue(elVal);
} else {
@@ -1587,23 +1617,6 @@ function rangeInputType(scope, element, attr, ctrl, $sniffer, $browser) {
}
}
- var minAttrType = isDefined(attr.ngMin) ? 'ngMin' : isDefined(attr.min) ? 'min' : false;
- if (minAttrType) {
- ctrl.$validators.min = isDefined(attr.min) && supportsRange ?
- function noopMinValidator(value) {
- // Since all browsers set the input to a valid value, we don't need to check validity
- return true;
- } :
- // ngMin doesn't set the min attr, so the browser doesn't adjust the input value as setting min would
- function minValidator(modelValue, viewValue) {
- return ctrl.$isEmpty(viewValue) || isUndefined(minVal) || viewValue >= minVal;
- };
-
- // Assign minVal when the directive is linked. This won't run the validators as the model isn't ready yet
- minChange(attr.min);
- attr.$observe('min', minChange);
- }
-
function maxChange(val) {
if (isDefined(val) && !isNumber(val)) {
val = parseFloat(val);
@@ -1614,12 +1627,13 @@ function rangeInputType(scope, element, attr, ctrl, $sniffer, $browser) {
return;
}
- if (supportsRange && maxAttrType === 'max') {
+ if (supportsRange) {
var elVal = element.val();
// IE11 doesn't set the el val correctly if the maxVal is less than the element value
if (maxVal < elVal) {
element.val(maxVal);
- elVal = minVal;
+ // IE11 and Chrome don't set the value to the minVal when max < min
+ elVal = maxVal < minVal ? minVal : maxVal;
}
ctrl.$setViewValue(elVal);
} else {
@@ -1627,22 +1641,6 @@ function rangeInputType(scope, element, attr, ctrl, $sniffer, $browser) {
ctrl.$validate();
}
}
- var maxAttrType = isDefined(attr.max) ? 'max' : attr.ngMax ? 'ngMax' : false;
- if (maxAttrType) {
- ctrl.$validators.max = isDefined(attr.max) && supportsRange ?
- function noopMaxValidator() {
- // Since all browsers set the input to a valid value, we don't need to check validity
- return true;
- } :
- // ngMax doesn't set the max attr, so the browser doesn't adjust the input value as setting max would
- function maxValidator(modelValue, viewValue) {
- return ctrl.$isEmpty(viewValue) || isUndefined(maxVal) || viewValue <= maxVal;
- };
-
- // Assign maxVal when the directive is linked. This won't run the validators as the model isn't ready yet
- maxChange(attr.max);
- attr.$observe('max', maxChange);
- }
}
diff --git a/test/ng/directive/inputSpec.js b/test/ng/directive/inputSpec.js
index 7b177f33740d..e3c71c4c1b27 100644
--- a/test/ng/directive/inputSpec.js
+++ b/test/ng/directive/inputSpec.js
@@ -2877,7 +2877,7 @@ describe('input', function() {
expect(inputElm.val()).toEqual('50');
});
- it('should set model to 50 when no value specified', function() {
+ it('should set model to 50 when no value specified and default min/max', function() {
var inputElm = helper.compileInput('');
expect(inputElm.val()).toBe('50');
@@ -2887,7 +2887,7 @@ describe('input', function() {
expect(scope.age).toBe(50);
});
- it('should parse non-number values to 50', function() {
+ it('should parse non-number values to 50 when default min/max', function() {
var inputElm = helper.compileInput('');
scope.$apply('age = 10');
@@ -2949,8 +2949,20 @@ describe('input', function() {
describe('min', function() {
if (supportsRange) {
+
+ it('should initialize correctly with non-default model and min value', function() {
+ scope.value = -3;
+ scope.min = -5;
+ var inputElm = helper.compileInput('');
+
+ expect(inputElm).toBeValid();
+ expect(inputElm.val()).toBe('-3');
+ expect(scope.value).toBe(-3);
+ expect(scope.form.alias.$error.min).toBeFalsy();
+ });
+
// Browsers that implement range will never allow you to set the value < min values
- it('should validate', function() {
+ it('should adjust invalid input values', function() {
var inputElm = helper.compileInput('');
helper.changeInputValueTo('5');
@@ -2964,6 +2976,22 @@ describe('input', function() {
expect(scope.form.alias.$error.min).toBeFalsy();
});
+ it('should set the model to the min val if it is less than the min val', function() {
+ scope.value = -10;
+ // Default min is 0
+ var inputElm = helper.compileInput('');
+
+ expect(inputElm).toBeValid();
+ expect(inputElm.val()).toBe('0');
+ expect(scope.value).toBe(0);
+
+ scope.$apply('value = 5; min = 10');
+
+ expect(inputElm).toBeValid();
+ expect(inputElm.val()).toBe('10');
+ expect(scope.value).toBe(10);
+ });
+
it('should adjust the element and model value when the min value changes on-the-fly', function() {
scope.min = 10;
var inputElm = helper.compileInput('');
@@ -2997,8 +3025,9 @@ describe('input', function() {
});
} else {
+ // input[type=range] will become type=text in browsers that don't support it
+
it('should validate if "range" is not implemented', function() {
- // This will become type=text in browsers that don't support it
var inputElm = helper.compileInput('');
helper.changeInputValueTo('5');
@@ -3012,6 +3041,34 @@ describe('input', function() {
expect(scope.form.alias.$error.min).toBeFalsy();
});
+ it('should not assume a min val of 0 if the min interpolates to a non-number', function() {
+ scope.value = -10;
+ var inputElm = helper.compileInput('');
+
+ expect(inputElm).toBeValid();
+ expect(inputElm.val()).toBe('-10');
+ expect(scope.value).toBe(-10);
+ expect(scope.form.alias.$error.min).toBeFalsy();
+
+ helper.changeInputValueTo('-5');
+ expect(inputElm).toBeValid();
+ expect(inputElm.val()).toBe('-5');
+ expect(scope.value).toBe(-5);
+ expect(scope.form.alias.$error.min).toBeFalsy();
+
+ scope.$apply('max = "null"');
+ expect(inputElm).toBeValid();
+ expect(inputElm.val()).toBe('-5');
+ expect(scope.value).toBe(-5);
+ expect(scope.form.alias.$error.max).toBeFalsy();
+
+ scope.$apply('max = "asdf"');
+ expect(inputElm).toBeValid();
+ expect(inputElm.val()).toBe('-5');
+ expect(scope.value).toBe(-5);
+ expect(scope.form.alias.$error.max).toBeFalsy();
+ });
+
it('should validate even if the min value changes on-the-fly', function() {
scope.min = 10;
var inputElm = helper.compileInput('');
@@ -3048,52 +3105,21 @@ describe('input', function() {
}
});
- describe('ngMin', function() {
-
- it('should validate', function() {
- var inputElm = helper.compileInput('');
-
- helper.changeInputValueTo('1');
- expect(inputElm).toBeInvalid();
- expect(scope.value).toBeFalsy();
- expect(scope.form.alias.$error.min).toBeTruthy();
-
- helper.changeInputValueTo('100');
- expect(inputElm).toBeValid();
- expect(scope.value).toBe(100);
- expect(scope.form.alias.$error.min).toBeFalsy();
- });
-
- it('should validate even if the ngMin value changes on-the-fly', function() {
- scope.min = 10;
- var inputElm = helper.compileInput('');
-
- helper.changeInputValueTo('15');
- expect(inputElm).toBeValid();
-
- scope.min = 20;
- scope.$digest();
- expect(inputElm).toBeInvalid();
-
- scope.min = null;
- scope.$digest();
- expect(inputElm).toBeValid();
-
- scope.min = '20';
- scope.$digest();
- expect(inputElm).toBeInvalid();
-
- scope.min = 'abc';
- scope.$digest();
- expect(inputElm).toBeValid();
- });
- });
-
-
describe('max', function() {
if (supportsRange) {
// Browsers that implement range will never allow you to set the value > max value
+ it('should initialize correctly with non-default model and max value', function() {
+ scope.value = 130;
+ scope.max = 150;
+ var inputElm = helper.compileInput('');
+
+ expect(inputElm).toBeValid();
+ expect(inputElm.val()).toBe('130');
+ expect(scope.value).toBe(130);
+ expect(scope.form.alias.$error.max).toBeFalsy();
+ });
+
it('should validate', function() {
var inputElm = helper.compileInput('');
@@ -3108,9 +3134,16 @@ describe('input', function() {
expect(scope.form.alias.$error.max).toBeFalsy();
});
- it('should set the model to the max val if it is more than the max val', function() {
- scope.value = 90;
- var inputElm = helper.compileInput('');
+ it('should set the model to the max val if it is greater than the max val', function() {
+ scope.value = 110;
+ // Default max is 100
+ var inputElm = helper.compileInput('');
+
+ expect(inputElm).toBeValid();
+ expect(inputElm.val()).toBe('100');
+ expect(scope.value).toBe(100);
+
+ scope.$apply('value = 90; max = 10');
expect(inputElm).toBeValid();
expect(inputElm.val()).toBe('10');
@@ -3164,6 +3197,34 @@ describe('input', function() {
expect(scope.form.alias.$error.max).toBeFalsy();
});
+ it('should not assume a max val of 100 if the max attribute interpolates to a non-number', function() {
+ scope.value = 120;
+ var inputElm = helper.compileInput('');
+
+ expect(inputElm).toBeValid();
+ expect(inputElm.val()).toBe('120');
+ expect(scope.value).toBe(120);
+ expect(scope.form.alias.$error.max).toBeFalsy();
+
+ helper.changeInputValueTo('140');
+ expect(inputElm).toBeValid();
+ expect(inputElm.val()).toBe('140');
+ expect(scope.value).toBe(140);
+ expect(scope.form.alias.$error.max).toBeFalsy();
+
+ scope.$apply('max = null');
+ expect(inputElm).toBeValid();
+ expect(inputElm.val()).toBe('140');
+ expect(scope.value).toBe(140);
+ expect(scope.form.alias.$error.max).toBeFalsy();
+
+ scope.$apply('max = "asdf"');
+ expect(inputElm).toBeValid();
+ expect(inputElm.val()).toBe('140');
+ expect(scope.value).toBe(140);
+ expect(scope.form.alias.$error.max).toBeFalsy();
+ });
+
it('should validate even if the max value changes on-the-fly', function() {
scope.max = 10;
var inputElm = helper.compileInput('');
@@ -3199,68 +3260,25 @@ describe('input', function() {
}
});
- describe('ngMax', function() {
-
- it('should validate', function() {
- var inputElm = helper.compileInput('');
-
- helper.changeInputValueTo('20');
- expect(inputElm).toBeInvalid();
- expect(scope.value).toBeUndefined();
- expect(scope.form.alias.$error.max).toBeTruthy();
-
- helper.changeInputValueTo('0');
- expect(inputElm).toBeValid();
- expect(scope.value).toBe(0);
- expect(scope.form.alias.$error.max).toBeFalsy();
- });
-
- it('should validate even if the ngMax value changes on-the-fly', function() {
- scope.max = 10;
- var inputElm = helper.compileInput('');
-
- helper.changeInputValueTo('5');
- expect(inputElm).toBeValid();
-
- scope.max = 0;
- scope.$digest();
- expect(inputElm).toBeInvalid();
-
- scope.max = null;
- scope.$digest();
- expect(inputElm).toBeValid();
-
- scope.max = '4';
- scope.$digest();
- expect(inputElm).toBeInvalid();
-
- scope.max = 'abc';
- scope.$digest();
- expect(inputElm).toBeValid();
- });
-
- });
-
if (supportsRange) {
describe('min and max', function() {
- it('should keep the initial default value when min and max are specified', function() {
+ it('should set the correct initial value when min and max are specified', function() {
scope.max = 80;
scope.min = 40;
var inputElm = helper.compileInput('');
- expect(inputElm.val()).toBe('50');
- expect(scope.value).toBe(50);
+ expect(inputElm.val()).toBe('60');
+ expect(scope.value).toBe(60);
});
-
it('should set element and model value to min if max is less than min', function() {
scope.min = 40;
var inputElm = helper.compileInput('');
- expect(inputElm.val()).toBe('50');
- expect(scope.value).toBe(50);
+ expect(inputElm.val()).toBe('70');
+ expect(scope.value).toBe(70);
scope.max = 20;
scope.$digest();
diff --git a/test/ng/directive/validatorsSpec.js b/test/ng/directive/validatorsSpec.js
index e7a0a13198d6..ca6992d4ea78 100644
--- a/test/ng/directive/validatorsSpec.js
+++ b/test/ng/directive/validatorsSpec.js
@@ -351,7 +351,7 @@ describe('validators', function() {
it('should accept values of any length when maxlength is non-numeric', function() {
- var inputElm = helper.compileInput('');
+ var inputElm = helper.compileInput('');
helper.changeInputValueTo('aaaaaaaaaa');
$rootScope.$apply('maxlength = "5"');