@@ -1031,6 +1031,113 @@ var inputType = {
1031
1031
*/
1032
1032
'radio' : radioInputType ,
1033
1033
1034
+ /**
1035
+ * @ngdoc input
1036
+ * @name input[range]
1037
+ *
1038
+ * @description
1039
+ * Native range input with validation and transformation.
1040
+ *
1041
+ * The model for the range input must always be a `Number`.
1042
+ *
1043
+ * IE9 and other browsers that do not support the `range` type fall back
1044
+ * to a text input. Model binding, validation and number parsing are nevertheless supported.
1045
+ *
1046
+ * Browsers that support range (latest Chrome, Safari, Firefox, Edge) treat `input[range]`
1047
+ * in a way that never allows the input to hold an invalid value. That means:
1048
+ * - any non-numerical value is set to `(max + min) / 2`.
1049
+ * - any numerical value that is less than the current min val, or greater than the current max val
1050
+ * is set to the min / max val respectively.
1051
+ *
1052
+ * This has the following consequences for Angular:
1053
+ *
1054
+ * Since the element value should always reflect the current model value, a range input
1055
+ * will set the bound ngModel expression to the value that the browser has set for the
1056
+ * input element. For example, in the following input `<input type="range" ng-model="model.value">`,
1057
+ * if the application sets `model.value = null`, the browser will set the input to `'50'`.
1058
+ * Angular will then set the model to `50`, to prevent input and model value being out of sync.
1059
+ *
1060
+ * That means the model for range will immediately be set to `50` after `ngModel` has been
1061
+ * initialized. It also means a range input can never have the required error.
1062
+ *
1063
+ * This does not only affect changes to the model value, but also to the values of the `min` and
1064
+ * `max` attributes. When these change in a way that will cause the browser to modify the input value,
1065
+ * Angular will also update the model value.
1066
+ *
1067
+ * Automatic value adjustment also means that a range input element can never have the `required`,
1068
+ * `min`, or `max` errors, except when using `ngMax` and `ngMin`, which are not affected by automatic
1069
+ * value adjustment, because they do not set the `min` and `max` attributes.
1070
+ *
1071
+ * @param {string } ngModel Assignable angular expression to data-bind to.
1072
+ * @param {string= } name Property name of the form under which the control is published.
1073
+ * @param {string= } min Sets the `min` validation to ensure that the value entered is greater
1074
+ * than `min`. Can be interpolated.
1075
+ * @param {string= } max Sets the `max` validation to ensure that the value entered is less than `max`.
1076
+ * Can be interpolated.
1077
+ * @param {string= } ngMin Takes an expression. Sets the `min` validation to ensure that the value
1078
+ * entered is greater than `min`. Does not set the `min` attribute and therefore
1079
+ * adds no native HTML5 validation. It also means the browser won't adjust the
1080
+ * element value in case `min` is greater than the current value.
1081
+ * @param {string= } ngMax Takes an expression. Sets the `max` validation to ensure that the value
1082
+ * entered is less than `max`. Does not set the `max` attribute and therefore
1083
+ * adds no native HTML5 validation. It also means the browser won't adjust the
1084
+ * element value in case `max` is less than the current value.
1085
+ * @param {string= } ngChange Angular expression to be executed when the ngModel value changes due
1086
+ * to user interaction with the input element.
1087
+ *
1088
+ * @example
1089
+ <example name="range-input-directive" module="rangeExample">
1090
+ <file name="index.html">
1091
+ <script>
1092
+ angular.module('rangeExample', [])
1093
+ .controller('ExampleController', ['$scope', function($scope) {
1094
+ $scope.value = 75;
1095
+ $scope.min = 10;
1096
+ $scope.max = 90;
1097
+ }]);
1098
+ </script>
1099
+ <form name="myForm" ng-controller="ExampleController">
1100
+
1101
+ Model as range: <input type="range" name="range" ng-model="value" min="{{min}}" max="{{max}}">
1102
+ <hr>
1103
+ Model as number: <input type="number" ng-model="value"><br>
1104
+ Min: <input type="number" ng-model="min"><br>
1105
+ Max: <input type="number" ng-model="min"><br>
1106
+ value = <code>{{value}}</code><br/>
1107
+ myForm.range.$valid = <code>{{myForm.range.$valid}}</code><br/>
1108
+ myForm.range.$error = <code>{{myForm.range.$error}}</code>
1109
+ </form>
1110
+ </file>
1111
+ </example>
1112
+
1113
+ * ## Range Input with ngMin & ngMax attributes
1114
+
1115
+ * @example
1116
+ <example name="range-input-directive-ng" module="rangeExample">
1117
+ <file name="index.html">
1118
+ <script>
1119
+ angular.module('rangeExample', [])
1120
+ .controller('ExampleController', ['$scope', function($scope) {
1121
+ $scope.value = 75;
1122
+ $scope.min = 10;
1123
+ $scope.max = 90;
1124
+ }]);
1125
+ </script>
1126
+ <form name="myForm" ng-controller="ExampleController">
1127
+ Model as range: <input type="range" name="range" ng-model="value" ng-min="min" ng-max="max">
1128
+ <hr>
1129
+ Model as number: <input type="number" ng-model="value"><br>
1130
+ Min: <input type="number" ng-model="min"><br>
1131
+ Max: <input type="number" ng-model="min"><br>
1132
+ value = <code>{{value}}</code><br/>
1133
+ myForm.range.$valid = <code>{{myForm.range.$valid}}</code><br/>
1134
+ myForm.range.$error = <code>{{myForm.range.$error}}</code>
1135
+ </form>
1136
+ </file>
1137
+ </example>
1138
+
1139
+ */
1140
+ 'range' : rangeInputType ,
1034
1141
1035
1142
/**
1036
1143
* @ngdoc input
@@ -1382,10 +1489,7 @@ function badInputChecker(scope, element, attr, ctrl) {
1382
1489
}
1383
1490
}
1384
1491
1385
- function numberInputType ( scope , element , attr , ctrl , $sniffer , $browser ) {
1386
- badInputChecker ( scope , element , attr , ctrl ) ;
1387
- baseInputType ( scope , element , attr , ctrl , $sniffer , $browser ) ;
1388
-
1492
+ function numberFormatterParser ( ctrl ) {
1389
1493
ctrl . $$parserName = 'number' ;
1390
1494
ctrl . $parsers . push ( function ( value ) {
1391
1495
if ( ctrl . $isEmpty ( value ) ) return null ;
@@ -1402,6 +1506,12 @@ function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) {
1402
1506
}
1403
1507
return value ;
1404
1508
} ) ;
1509
+ }
1510
+
1511
+ function numberInputType ( scope , element , attr , ctrl , $sniffer , $browser ) {
1512
+ badInputChecker ( scope , element , attr , ctrl ) ;
1513
+ numberFormatterParser ( ctrl ) ;
1514
+ baseInputType ( scope , element , attr , ctrl , $sniffer , $browser ) ;
1405
1515
1406
1516
if ( isDefined ( attr . min ) || attr . ngMin ) {
1407
1517
var minVal ;
@@ -1436,6 +1546,110 @@ function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) {
1436
1546
}
1437
1547
}
1438
1548
1549
+ function rangeInputType ( scope , element , attr , ctrl , $sniffer , $browser ) {
1550
+ badInputChecker ( scope , element , attr , ctrl ) ;
1551
+ numberFormatterParser ( ctrl ) ;
1552
+ baseInputType ( scope , element , attr , ctrl , $sniffer , $browser ) ;
1553
+
1554
+ var minVal = 0 ,
1555
+ maxVal = 100 ,
1556
+ supportsRange = ctrl . $$hasNativeValidators && element [ 0 ] . type === 'range' ,
1557
+ validity = element [ 0 ] . validity ;
1558
+
1559
+ var originalRender = ctrl . $render ;
1560
+
1561
+ ctrl . $render = supportsRange && isDefined ( validity . rangeUnderflow ) && isDefined ( validity . rangeOverflow ) ?
1562
+ //Browsers that implement range will set these values automatically, but reading the adjusted values after
1563
+ //$render would cause the min / max validators to be applied with the wrong value
1564
+ function rangeRender ( ) {
1565
+ originalRender ( ) ;
1566
+ ctrl . $setViewValue ( element . val ( ) ) ;
1567
+ } :
1568
+ originalRender ;
1569
+
1570
+ function minChange ( val ) {
1571
+ if ( isDefined ( val ) && ! isNumber ( val ) ) {
1572
+ val = parseFloat ( val ) ;
1573
+ }
1574
+ minVal = isNumber ( val ) && ! isNaN ( val ) ? val : undefined ;
1575
+ // ignore changes before model is initialized
1576
+ if ( isNumber ( ctrl . $modelValue ) && isNaN ( ctrl . $modelValue ) ) {
1577
+ return ;
1578
+ }
1579
+
1580
+ if ( supportsRange && minAttrType === 'min' ) {
1581
+ var elVal = element . val ( ) ;
1582
+ // IE11 doesn't set the el val correctly if the minVal is greater than the element value
1583
+ if ( minVal > elVal ) {
1584
+ element . val ( minVal ) ;
1585
+ elVal = minVal ;
1586
+ }
1587
+ ctrl . $setViewValue ( elVal ) ;
1588
+ } else {
1589
+ // TODO(matsko): implement validateLater to reduce number of validations
1590
+ ctrl . $validate ( ) ;
1591
+ }
1592
+ }
1593
+
1594
+ var minAttrType = isDefined ( attr . ngMin ) ? 'ngMin' : isDefined ( attr . min ) ? 'min' : false ;
1595
+ if ( minAttrType ) {
1596
+ ctrl . $validators . min = isDefined ( attr . min ) && supportsRange ?
1597
+ function noopMinValidator ( value ) {
1598
+ // Since all browsers set the input to a valid value, we don't need to check validity
1599
+ return true ;
1600
+ } :
1601
+ // ngMin doesn't set the min attr, so the browser doesn't adjust the input value as setting min would
1602
+ function minValidator ( modelValue , viewValue ) {
1603
+ return ctrl . $isEmpty ( viewValue ) || isUndefined ( minVal ) || viewValue >= minVal ;
1604
+ } ;
1605
+
1606
+ // Assign minVal when the directive is linked. This won't run the validators as the model isn't ready yet
1607
+ minChange ( attr . min ) ;
1608
+ attr . $observe ( 'min' , minChange ) ;
1609
+ }
1610
+
1611
+ function maxChange ( val ) {
1612
+ if ( isDefined ( val ) && ! isNumber ( val ) ) {
1613
+ val = parseFloat ( val ) ;
1614
+ }
1615
+ maxVal = isNumber ( val ) && ! isNaN ( val ) ? val : undefined ;
1616
+ // ignore changes before model is initialized
1617
+ if ( isNumber ( ctrl . $modelValue ) && isNaN ( ctrl . $modelValue ) ) {
1618
+ return ;
1619
+ }
1620
+
1621
+ if ( supportsRange && maxAttrType === 'max' ) {
1622
+ var elVal = element . val ( ) ;
1623
+ // IE11 doesn't set the el val correctly if the maxVal is less than the element value
1624
+ if ( maxVal < elVal ) {
1625
+ element . val ( maxVal ) ;
1626
+ elVal = minVal ;
1627
+ }
1628
+ ctrl . $setViewValue ( elVal ) ;
1629
+ } else {
1630
+ // TODO(matsko): implement validateLater to reduce number of validations
1631
+ ctrl . $validate ( ) ;
1632
+ }
1633
+ }
1634
+ var maxAttrType = isDefined ( attr . max ) ? 'max' : attr . ngMax ? 'ngMax' : false ;
1635
+ if ( maxAttrType ) {
1636
+ ctrl . $validators . max = isDefined ( attr . max ) && supportsRange ?
1637
+ function noopMaxValidator ( ) {
1638
+ // Since all browsers set the input to a valid value, we don't need to check validity
1639
+ return true ;
1640
+ } :
1641
+ // ngMax doesn't set the max attr, so the browser doesn't adjust the input value as setting max would
1642
+ function maxValidator ( modelValue , viewValue ) {
1643
+ return ctrl . $isEmpty ( viewValue ) || isUndefined ( maxVal ) || viewValue <= maxVal ;
1644
+ } ;
1645
+
1646
+ // Assign maxVal when the directive is linked. This won't run the validators as the model isn't ready yet
1647
+ maxChange ( attr . max ) ;
1648
+ attr . $observe ( 'max' , maxChange ) ;
1649
+ }
1650
+
1651
+ }
1652
+
1439
1653
function urlInputType ( scope , element , attr , ctrl , $sniffer , $browser ) {
1440
1654
// Note: no badInputChecker here by purpose as `url` is only a validation
1441
1655
// in browsers, i.e. we can always read out input.value even if it is not valid!
0 commit comments