@@ -1027,6 +1027,121 @@ var inputType = {
1027
1027
*/
1028
1028
'radio' : radioInputType ,
1029
1029
1030
+ /**
1031
+ * @ngdoc input
1032
+ * @name input[range]
1033
+ *
1034
+ * @description
1035
+ * Native range input with validation and transformation.
1036
+ *
1037
+ * The model for the range input must always be a `Number`.
1038
+ *
1039
+ * IE9 and other browsers that do not support the `range` type fall back
1040
+ * to a text input without any default values for `min`, `max` and `step`. Model binding,
1041
+ * validation and number parsing are nevertheless supported.
1042
+ *
1043
+ * Browsers that support range (latest Chrome, Safari, Firefox, Edge) treat `input[range]`
1044
+ * in a way that never allows the input to hold an invalid value. That means:
1045
+ * - any non-numerical value is set to `(max + min) / 2`.
1046
+ * - any numerical value that is less than the current min val, or greater than the current max val
1047
+ * is set to the min / max val respectively.
1048
+ * - additionally, the current `step` is respected, so the nearest value that satisfies a step
1049
+ * is used.
1050
+ *
1051
+ * See the [HTML Spec on input[type=range]](https://www.w3.org/TR/html5/forms.html#range-state-(type=range))
1052
+ * for more info.
1053
+ *
1054
+ * This has the following consequences for Angular:
1055
+ *
1056
+ * Since the element value should always reflect the current model value, a range input
1057
+ * will set the bound ngModel expression to the value that the browser has set for the
1058
+ * input element. For example, in the following input `<input type="range" ng-model="model.value">`,
1059
+ * if the application sets `model.value = null`, the browser will set the input to `'50'`.
1060
+ * Angular will then set the model to `50`, to prevent input and model value being out of sync.
1061
+ *
1062
+ * That means the model for range will immediately be set to `50` after `ngModel` has been
1063
+ * initialized. It also means a range input can never have the required error.
1064
+ *
1065
+ * This does not only affect changes to the model value, but also to the values of the `min`,
1066
+ * `max`, and `step` attributes. When these change in a way that will cause the browser to modify
1067
+ * the input value, Angular will also update the model value.
1068
+ *
1069
+ * Automatic value adjustment also means that a range input element can never have the `required`,
1070
+ * `min`, or `max` errors.
1071
+ *
1072
+ * However, `step` is currently only fully implemented by Firefox. Other browsers have problems
1073
+ * when the step value changes dynamically - they do not adjust the element value correctly, but
1074
+ * instead may set the `stepMismatch` error. If that's the case, the Angular will set the `step`
1075
+ * error on the input, and set the model to `undefined`.
1076
+ *
1077
+ * Note that `input[range]` is not compatible with`ngMax`, `ngMin`, and `ngStep`, because they do
1078
+ * not set the `min` and `max` attributes, which means that the browser won't automatically adjust
1079
+ * the input value based on their values, and will always assume min = 0, max = 100, and step = 1.
1080
+ *
1081
+ * @param {string } ngModel Assignable angular expression to data-bind to.
1082
+ * @param {string= } name Property name of the form under which the control is published.
1083
+ * @param {string= } min Sets the `min` validation to ensure that the value entered is greater
1084
+ * than `min`. Can be interpolated.
1085
+ * @param {string= } max Sets the `max` validation to ensure that the value entered is less than `max`.
1086
+ * Can be interpolated.
1087
+ * @param {string= } step Sets the `step` validation to ensure that the value entered matches the `step`
1088
+ * Can be interpolated.
1089
+ * @param {string= } ngChange Angular expression to be executed when the ngModel value changes due
1090
+ * to user interaction with the input element.
1091
+ *
1092
+ * @example
1093
+ <example name="range-input-directive" module="rangeExample">
1094
+ <file name="index.html">
1095
+ <script>
1096
+ angular.module('rangeExample', [])
1097
+ .controller('ExampleController', ['$scope', function($scope) {
1098
+ $scope.value = 75;
1099
+ $scope.min = 10;
1100
+ $scope.max = 90;
1101
+ }]);
1102
+ </script>
1103
+ <form name="myForm" ng-controller="ExampleController">
1104
+
1105
+ Model as range: <input type="range" name="range" ng-model="value" min="{{min}}" max="{{max}}">
1106
+ <hr>
1107
+ Model as number: <input type="number" ng-model="value"><br>
1108
+ Min: <input type="number" ng-model="min"><br>
1109
+ Max: <input type="number" ng-model="max"><br>
1110
+ value = <code>{{value}}</code><br/>
1111
+ myForm.range.$valid = <code>{{myForm.range.$valid}}</code><br/>
1112
+ myForm.range.$error = <code>{{myForm.range.$error}}</code>
1113
+ </form>
1114
+ </file>
1115
+ </example>
1116
+
1117
+ * ## Range Input with ngMin & ngMax attributes
1118
+
1119
+ * @example
1120
+ <example name="range-input-directive-ng" module="rangeExample">
1121
+ <file name="index.html">
1122
+ <script>
1123
+ angular.module('rangeExample', [])
1124
+ .controller('ExampleController', ['$scope', function($scope) {
1125
+ $scope.value = 75;
1126
+ $scope.min = 10;
1127
+ $scope.max = 90;
1128
+ }]);
1129
+ </script>
1130
+ <form name="myForm" ng-controller="ExampleController">
1131
+ Model as range: <input type="range" name="range" ng-model="value" ng-min="min" ng-max="max">
1132
+ <hr>
1133
+ Model as number: <input type="number" ng-model="value"><br>
1134
+ Min: <input type="number" ng-model="min"><br>
1135
+ Max: <input type="number" ng-model="max"><br>
1136
+ value = <code>{{value}}</code><br/>
1137
+ myForm.range.$valid = <code>{{myForm.range.$valid}}</code><br/>
1138
+ myForm.range.$error = <code>{{myForm.range.$error}}</code>
1139
+ </form>
1140
+ </file>
1141
+ </example>
1142
+
1143
+ */
1144
+ 'range' : rangeInputType ,
1030
1145
1031
1146
/**
1032
1147
* @ngdoc input
@@ -1378,10 +1493,7 @@ function badInputChecker(scope, element, attr, ctrl) {
1378
1493
}
1379
1494
}
1380
1495
1381
- function numberInputType ( scope , element , attr , ctrl , $sniffer , $browser ) {
1382
- badInputChecker ( scope , element , attr , ctrl ) ;
1383
- baseInputType ( scope , element , attr , ctrl , $sniffer , $browser ) ;
1384
-
1496
+ function numberFormatterParser ( ctrl ) {
1385
1497
ctrl . $$parserName = 'number' ;
1386
1498
ctrl . $parsers . push ( function ( value ) {
1387
1499
if ( ctrl . $isEmpty ( value ) ) return null ;
@@ -1398,6 +1510,19 @@ function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) {
1398
1510
}
1399
1511
return value ;
1400
1512
} ) ;
1513
+ }
1514
+
1515
+ function parseNumberAttrVal ( val ) {
1516
+ if ( isDefined ( val ) && ! isNumber ( val ) ) {
1517
+ val = parseFloat ( val ) ;
1518
+ }
1519
+ return ! isNumberNaN ( val ) ? val : undefined ;
1520
+ }
1521
+
1522
+ function numberInputType ( scope , element , attr , ctrl , $sniffer , $browser ) {
1523
+ badInputChecker ( scope , element , attr , ctrl ) ;
1524
+ numberFormatterParser ( ctrl ) ;
1525
+ baseInputType ( scope , element , attr , ctrl , $sniffer , $browser ) ;
1401
1526
1402
1527
if ( isDefined ( attr . min ) || attr . ngMin ) {
1403
1528
var minVal ;
@@ -1406,10 +1531,7 @@ function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) {
1406
1531
} ;
1407
1532
1408
1533
attr . $observe ( 'min' , function ( val ) {
1409
- if ( isDefined ( val ) && ! isNumber ( val ) ) {
1410
- val = parseFloat ( val ) ;
1411
- }
1412
- minVal = isNumber ( val ) && ! isNaN ( val ) ? val : undefined ;
1534
+ minVal = parseNumberAttrVal ( val ) ;
1413
1535
// TODO(matsko): implement validateLater to reduce number of validations
1414
1536
ctrl . $validate ( ) ;
1415
1537
} ) ;
@@ -1422,16 +1544,146 @@ function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) {
1422
1544
} ;
1423
1545
1424
1546
attr . $observe ( 'max' , function ( val ) {
1425
- if ( isDefined ( val ) && ! isNumber ( val ) ) {
1426
- val = parseFloat ( val ) ;
1427
- }
1428
- maxVal = isNumber ( val ) && ! isNaN ( val ) ? val : undefined ;
1547
+ maxVal = parseNumberAttrVal ( val ) ;
1429
1548
// TODO(matsko): implement validateLater to reduce number of validations
1430
1549
ctrl . $validate ( ) ;
1431
1550
} ) ;
1432
1551
}
1433
1552
}
1434
1553
1554
+ function rangeInputType ( scope , element , attr , ctrl , $sniffer , $browser ) {
1555
+ badInputChecker ( scope , element , attr , ctrl ) ;
1556
+ numberFormatterParser ( ctrl ) ;
1557
+ baseInputType ( scope , element , attr , ctrl , $sniffer , $browser ) ;
1558
+
1559
+ var supportsRange = ctrl . $$hasNativeValidators && element [ 0 ] . type === 'range' ,
1560
+ minVal = supportsRange ? 0 : undefined ,
1561
+ maxVal = supportsRange ? 100 : undefined ,
1562
+ stepVal = supportsRange ? 1 : undefined ,
1563
+ validity = element [ 0 ] . validity ,
1564
+ hasMinAttr = isDefined ( attr . min ) ,
1565
+ hasMaxAttr = isDefined ( attr . max ) ,
1566
+ hasStepAttr = isDefined ( attr . step ) ;
1567
+
1568
+ var originalRender = ctrl . $render ;
1569
+
1570
+ ctrl . $render = supportsRange && isDefined ( validity . rangeUnderflow ) && isDefined ( validity . rangeOverflow ) ?
1571
+ //Browsers that implement range will set these values automatically, but reading the adjusted values after
1572
+ //$render would cause the min / max validators to be applied with the wrong value
1573
+ function rangeRender ( ) {
1574
+ originalRender ( ) ;
1575
+ ctrl . $setViewValue ( element . val ( ) ) ;
1576
+ } :
1577
+ originalRender ;
1578
+
1579
+ if ( hasMinAttr ) {
1580
+ ctrl . $validators . min = supportsRange ?
1581
+ // Since all browsers set the input to a valid value, we don't need to check validity
1582
+ function noopMinValidator ( ) { return true ; } :
1583
+ // non-support browsers validate the min val
1584
+ function minValidator ( modelValue , viewValue ) {
1585
+ return ctrl . $isEmpty ( viewValue ) || isUndefined ( minVal ) || viewValue >= minVal ;
1586
+ } ;
1587
+
1588
+ setInitialValueAndObserver ( 'min' , minChange ) ;
1589
+ }
1590
+
1591
+ if ( hasMaxAttr ) {
1592
+ ctrl . $validators . max = supportsRange ?
1593
+ // Since all browsers set the input to a valid value, we don't need to check validity
1594
+ function noopMaxValidator ( ) { return true ; } :
1595
+ // non-support browsers validate the max val
1596
+ function maxValidator ( modelValue , viewValue ) {
1597
+ return ctrl . $isEmpty ( viewValue ) || isUndefined ( maxVal ) || viewValue <= maxVal ;
1598
+ } ;
1599
+
1600
+ setInitialValueAndObserver ( 'max' , maxChange ) ;
1601
+ }
1602
+
1603
+ if ( hasStepAttr ) {
1604
+ ctrl . $validators . step = supportsRange ?
1605
+ function nativeStepValidator ( ) {
1606
+ // Currently, only FF implements the spec on step change correctly (i.e. adjusting the
1607
+ // input element value to a valid value). It's possible that other browsers set the stepMismatch
1608
+ // validity error instead, so we can at least report an error in that case.
1609
+ return ! validity . stepMismatch ;
1610
+ } :
1611
+ // ngStep doesn't set the setp attr, so the browser doesn't adjust the input value as setting step would
1612
+ function stepValidator ( modelValue , viewValue ) {
1613
+ return ctrl . $isEmpty ( viewValue ) || isUndefined ( stepVal ) || viewValue % stepVal === 0 ;
1614
+ } ;
1615
+
1616
+ setInitialValueAndObserver ( 'step' , stepChange ) ;
1617
+ }
1618
+
1619
+ function setInitialValueAndObserver ( htmlAttrName , changeFn ) {
1620
+ // interpolated attributes set the attribute value only after a digest, but we need the
1621
+ // attribute value when the input is first rendered, so that the browser can adjust the
1622
+ // input value based on the min/max value
1623
+ element . attr ( htmlAttrName , attr [ htmlAttrName ] ) ;
1624
+ attr . $observe ( htmlAttrName , changeFn ) ;
1625
+ }
1626
+
1627
+ function minChange ( val ) {
1628
+ minVal = parseNumberAttrVal ( val ) ;
1629
+ // ignore changes before model is initialized
1630
+ if ( isNumberNaN ( ctrl . $modelValue ) ) {
1631
+ return ;
1632
+ }
1633
+
1634
+ if ( supportsRange ) {
1635
+ var elVal = element . val ( ) ;
1636
+ // IE11 doesn't set the el val correctly if the minVal is greater than the element value
1637
+ if ( minVal > elVal ) {
1638
+ elVal = minVal ;
1639
+ element . val ( elVal ) ;
1640
+ }
1641
+ ctrl . $setViewValue ( elVal ) ;
1642
+ } else {
1643
+ // TODO(matsko): implement validateLater to reduce number of validations
1644
+ ctrl . $validate ( ) ;
1645
+ }
1646
+ }
1647
+
1648
+ function maxChange ( val ) {
1649
+ maxVal = parseNumberAttrVal ( val ) ;
1650
+ // ignore changes before model is initialized
1651
+ if ( isNumberNaN ( ctrl . $modelValue ) ) {
1652
+ return ;
1653
+ }
1654
+
1655
+ if ( supportsRange ) {
1656
+ var elVal = element . val ( ) ;
1657
+ // IE11 doesn't set the el val correctly if the maxVal is less than the element value
1658
+ if ( maxVal < elVal ) {
1659
+ element . val ( maxVal ) ;
1660
+ // IE11 and Chrome don't set the value to the minVal when max < min
1661
+ elVal = maxVal < minVal ? minVal : maxVal ;
1662
+ }
1663
+ ctrl . $setViewValue ( elVal ) ;
1664
+ } else {
1665
+ // TODO(matsko): implement validateLater to reduce number of validations
1666
+ ctrl . $validate ( ) ;
1667
+ }
1668
+ }
1669
+
1670
+ function stepChange ( val ) {
1671
+ stepVal = parseNumberAttrVal ( val ) ;
1672
+ // ignore changes before model is initialized
1673
+ if ( isNumberNaN ( ctrl . $modelValue ) ) {
1674
+ return ;
1675
+ }
1676
+
1677
+ // Some browsers don't adjust the input value correctly, but set the stepMismatch error
1678
+ if ( supportsRange && ctrl . $viewValue !== element . val ( ) ) {
1679
+ ctrl . $setViewValue ( element . val ( ) ) ;
1680
+ } else {
1681
+ // TODO(matsko): implement validateLater to reduce number of validations
1682
+ ctrl . $validate ( ) ;
1683
+ }
1684
+ }
1685
+ }
1686
+
1435
1687
function urlInputType ( scope , element , attr , ctrl , $sniffer , $browser ) {
1436
1688
// Note: no badInputChecker here by purpose as `url` is only a validation
1437
1689
// in browsers, i.e. we can always read out input.value even if it is not valid!
0 commit comments