feat(ngModelOptions): add timeStripZeroSeconds and timeSecondsFormat
Closes #10721 Closes #16510 Closes #16584
This commit is contained in:
@@ -255,6 +255,10 @@ var inputType = {
|
||||
* The timezone to be used to read/write the `Date` instance in the model can be defined using
|
||||
* {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser.
|
||||
*
|
||||
* The format of the displayed time can be adjusted with the
|
||||
* {@link ng.directive:ngModelOptions#ngModelOptions-arguments ngModelOptions} `timeSecondsFormat`
|
||||
* and `timeStripZeroSeconds`.
|
||||
*
|
||||
* @param {string} ngModel Assignable AngularJS 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`.
|
||||
@@ -356,7 +360,12 @@ var inputType = {
|
||||
* Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string.
|
||||
*
|
||||
* The timezone to be used to read/write the `Date` instance in the model can be defined using
|
||||
* {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser.
|
||||
* {@link ng.directive:ngModelOptions#ngModelOptions-arguments ngModelOptions}. By default,
|
||||
* this is the timezone of the browser.
|
||||
*
|
||||
* The format of the displayed time can be adjusted with the
|
||||
* {@link ng.directive:ngModelOptions#ngModelOptions-arguments ngModelOptions} `timeSecondsFormat`
|
||||
* and `timeStripZeroSeconds`.
|
||||
*
|
||||
* @param {string} ngModel Assignable AngularJS expression to data-bind to.
|
||||
* @param {string=} name Property name of the form under which the control is published.
|
||||
@@ -1491,6 +1500,8 @@ function createDateInputType(type, regexp, parseDate, format) {
|
||||
return function dynamicDateInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter) {
|
||||
badInputChecker(scope, element, attr, ctrl, type);
|
||||
baseInputType(scope, element, attr, ctrl, $sniffer, $browser);
|
||||
|
||||
var isTimeType = type === 'time' || type === 'datetimelocal';
|
||||
var previousDate;
|
||||
var previousTimezone;
|
||||
|
||||
@@ -1514,11 +1525,13 @@ function createDateInputType(type, regexp, parseDate, format) {
|
||||
if (isValidDate(value)) {
|
||||
previousDate = value;
|
||||
var timezone = ctrl.$options.getOption('timezone');
|
||||
|
||||
if (timezone) {
|
||||
previousTimezone = timezone;
|
||||
previousDate = convertTimezoneToLocal(previousDate, timezone, true);
|
||||
}
|
||||
return $filter('date')(value, format, timezone);
|
||||
|
||||
return formatter(value, timezone);
|
||||
} else {
|
||||
previousDate = null;
|
||||
previousTimezone = null;
|
||||
@@ -1573,6 +1586,24 @@ function createDateInputType(type, regexp, parseDate, format) {
|
||||
}
|
||||
return parsedDate;
|
||||
}
|
||||
|
||||
function formatter(value, timezone) {
|
||||
var targetFormat = format;
|
||||
|
||||
if (isTimeType && isString(ctrl.$options.getOption('timeSecondsFormat'))) {
|
||||
targetFormat = format
|
||||
.replace('ss.sss', ctrl.$options.getOption('timeSecondsFormat'))
|
||||
.replace(/:$/, '');
|
||||
}
|
||||
|
||||
var formatted = $filter('date')(value, targetFormat, timezone);
|
||||
|
||||
if (isTimeType && ctrl.$options.getOption('timeStripZeroSeconds')) {
|
||||
formatted = formatted.replace(/(?::00)?(?:\.000)?$/, '');
|
||||
}
|
||||
|
||||
return formatted;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -406,12 +406,6 @@ defaultModelOptions = new ModelOptions({
|
||||
* </example>
|
||||
*
|
||||
*
|
||||
* ## Specifying timezones
|
||||
*
|
||||
* You can specify the timezone that date/time input directives expect by providing its name in the
|
||||
* `timezone` property.
|
||||
*
|
||||
*
|
||||
* ## Programmatically changing options
|
||||
*
|
||||
* The `ngModelOptions` expression is only evaluated once when the directive is linked; it is not
|
||||
@@ -423,8 +417,70 @@ defaultModelOptions = new ModelOptions({
|
||||
* Default events, extra triggers, and catch-all debounce values}.
|
||||
*
|
||||
*
|
||||
* ## Specifying timezones
|
||||
*
|
||||
* You can specify the timezone that date/time input directives expect by providing its name in the
|
||||
* `timezone` property.
|
||||
*
|
||||
*
|
||||
* ## Formatting the value of `time` and `datetime-local`
|
||||
*
|
||||
* With the options `timeSecondsFormat` and `timeStripZeroSeconds` it is possible to adjust the value
|
||||
* that is displayed in the control. Note that browsers may apply their own formatting
|
||||
* in the user interface.
|
||||
*
|
||||
<example name="ngModelOptions-time-format" module="timeExample">
|
||||
<file name="index.html">
|
||||
<time-example></time-example>
|
||||
</file>
|
||||
<file name="script.js">
|
||||
angular.module('timeExample', [])
|
||||
.component('timeExample', {
|
||||
templateUrl: 'timeExample.html',
|
||||
controller: function() {
|
||||
this.time = new Date(1970, 0, 1, 14, 57, 0);
|
||||
|
||||
this.options = {
|
||||
timeSecondsFormat: 'ss',
|
||||
timeStripZeroSeconds: true
|
||||
};
|
||||
|
||||
this.optionChange = function() {
|
||||
this.timeForm.timeFormatted.$overrideModelOptions(this.options);
|
||||
this.time = new Date(this.time);
|
||||
};
|
||||
}
|
||||
});
|
||||
</file>
|
||||
<file name="timeExample.html">
|
||||
<form name="$ctrl.timeForm">
|
||||
<strong>Default</strong>:
|
||||
<input type="time" ng-model="$ctrl.time" step="any" /><br>
|
||||
<strong>With options</strong>:
|
||||
<input type="time" name="timeFormatted" ng-model="$ctrl.time" step="any" ng-model-options="$ctrl.options" />
|
||||
<br>
|
||||
|
||||
Options:<br>
|
||||
<code>timeSecondsFormat</code>:
|
||||
<input
|
||||
type="text"
|
||||
ng-model="$ctrl.options.timeSecondsFormat"
|
||||
ng-change="$ctrl.optionChange()">
|
||||
<br>
|
||||
<code>timeStripZeroSeconds</code>:
|
||||
<input
|
||||
type="checkbox"
|
||||
ng-model="$ctrl.options.timeStripZeroSeconds"
|
||||
ng-change="$ctrl.optionChange()">
|
||||
</form>
|
||||
</file>
|
||||
* </example>
|
||||
*
|
||||
* @param {Object} ngModelOptions options to apply to {@link ngModel} directives on this element and
|
||||
* and its descendents. Valid keys are:
|
||||
* and its descendents.
|
||||
*
|
||||
* **General options**:
|
||||
*
|
||||
* - `updateOn`: string specifying which event should the input be bound to. You can set several
|
||||
* events using an space delimited list. There is a special event called `default` that
|
||||
* matches the default events belonging to the control. These are the events that are bound to
|
||||
@@ -457,6 +513,10 @@ defaultModelOptions = new ModelOptions({
|
||||
* not validate correctly instead of the default behavior of setting the model to undefined.
|
||||
* - `getterSetter`: boolean value which determines whether or not to treat functions bound to
|
||||
* `ngModel` as getters/setters.
|
||||
*
|
||||
*
|
||||
* **Input-type specific options**:
|
||||
*
|
||||
* - `timezone`: Defines the timezone to be used to read/write the `Date` instance in the model for
|
||||
* `<input type="date" />`, `<input type="time" />`, ... . It understands UTC/GMT and the
|
||||
* continental US time zone abbreviations, but for general use, use a time zone offset, for
|
||||
@@ -465,6 +525,24 @@ defaultModelOptions = new ModelOptions({
|
||||
* Note that changing the timezone will have no effect on the current date, and is only applied after
|
||||
* the next input / model change.
|
||||
*
|
||||
* - `timeSecondsFormat`: Defines if the `time` and `datetime-local` types should show seconds and
|
||||
* milliseconds. The option follows the format string of {@link date date filter}.
|
||||
* By default, the options is `undefined` which is equal to `'ss.sss'` (seconds and milliseconds).
|
||||
* The other options are `'ss'` (strips milliseconds), and `''` (empty string), which strips both
|
||||
* seconds and milliseconds.
|
||||
* Note that browsers that support `time` and `datetime-local` require the hour and minutes
|
||||
* part of the time string, and may show the value differently in the user interface.
|
||||
* {@link ngModelOptions#formatting-the-value-of-time-and-datetime-local- See the example}.
|
||||
*
|
||||
* - `timeStripZeroSeconds`: Defines if the `time` and `datetime-local` types should strip the
|
||||
* seconds and milliseconds from the formatted value if they are zero. This option is applied
|
||||
* after `timeSecondsFormat`.
|
||||
* This option can be used to make the formatting consistent over different browsers, as some
|
||||
* browsers with support for `time` will natively hide the milliseconds and
|
||||
* seconds if they are zero, but others won't, and browsers that don't implement these input
|
||||
* types will always show the full string.
|
||||
* {@link ngModelOptions#formatting-the-value-of-time-and-datetime-local- See the example}.
|
||||
*
|
||||
*/
|
||||
var ngModelOptionsDirective = function() {
|
||||
NgModelOptionsController.$inject = ['$attrs', '$scope'];
|
||||
|
||||
@@ -1384,6 +1384,88 @@ describe('input', function() {
|
||||
expect($rootScope.form.alias.$error.datetimelocal).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should use the timeSecondsFormat specified in ngModelOptions', function() {
|
||||
var inputElm = helper.compileInput(
|
||||
'<input type="datetime-local" ng-model-options="{timeSecondsFormat: \'\'}" ng-model="time"/>'
|
||||
);
|
||||
|
||||
var ctrl = inputElm.controller('ngModel');
|
||||
|
||||
$rootScope.$apply(function() {
|
||||
$rootScope.time = new Date(1970, 0, 1, 15, 41, 0, 500);
|
||||
});
|
||||
expect(inputElm.val()).toBe('1970-01-01T15:41');
|
||||
|
||||
$rootScope.$apply(function() {
|
||||
$rootScope.time = new Date(1970, 0, 1, 15, 41, 50, 500);
|
||||
});
|
||||
expect(inputElm.val()).toBe('1970-01-01T15:41');
|
||||
|
||||
ctrl.$overrideModelOptions({timeSecondsFormat: 'ss'});
|
||||
|
||||
$rootScope.$apply(function() {
|
||||
$rootScope.time = new Date(1970, 0, 1, 15, 41, 5, 500);
|
||||
});
|
||||
expect(inputElm.val()).toBe('1970-01-01T15:41:05');
|
||||
|
||||
ctrl.$overrideModelOptions({timeSecondsFormat: 'ss.sss'});
|
||||
|
||||
$rootScope.$apply(function() {
|
||||
$rootScope.time = new Date(1970, 0, 1, 15, 41, 50, 50);
|
||||
});
|
||||
expect(inputElm.val()).toBe('1970-01-01T15:41:50.050');
|
||||
});
|
||||
|
||||
|
||||
it('should strip empty milliseconds and seconds if specified in ngModelOptions', function() {
|
||||
var inputElm = helper.compileInput(
|
||||
'<input type="datetime-local" ng-model-options="{timeStripZeroSeconds: true}" ng-model="threeFortyOnePm"/>'
|
||||
);
|
||||
|
||||
$rootScope.$apply(function() {
|
||||
$rootScope.threeFortyOnePm = new Date(1970, 0, 1, 15, 41, 50, 500);
|
||||
});
|
||||
|
||||
expect(inputElm.val()).toBe('1970-01-01T15:41:50.500');
|
||||
|
||||
$rootScope.$apply(function() {
|
||||
$rootScope.threeFortyOnePm = new Date(1970, 0, 1, 15, 41, 0, 500);
|
||||
});
|
||||
|
||||
expect(inputElm.val()).toBe('1970-01-01T15:41:00.500');
|
||||
|
||||
$rootScope.$apply(function() {
|
||||
$rootScope.threeFortyOnePm = new Date(1970, 0, 1, 15, 41, 50, 0);
|
||||
});
|
||||
|
||||
expect(inputElm.val()).toBe('1970-01-01T15:41:50');
|
||||
|
||||
$rootScope.$apply(function() {
|
||||
$rootScope.threeFortyOnePm = new Date(1970, 0, 1, 15, 41, 0, 0);
|
||||
});
|
||||
|
||||
expect(inputElm.val()).toBe('1970-01-01T15:41');
|
||||
});
|
||||
|
||||
|
||||
it('should apply timeStripZeroSeconds after timeSecondsFormat', function() {
|
||||
var inputElm = helper.compileInput('<input type="datetime-local"' +
|
||||
' ng-model-options="{timeSecondsFormat: \'ss\', timeStripZeroSeconds: true}"' +
|
||||
' ng-model="threeFortyOnePm"/>');
|
||||
|
||||
$rootScope.$apply(function() {
|
||||
$rootScope.threeFortyOnePm = new Date(1970, 0, 1, 15, 41, 50, 500);
|
||||
});
|
||||
|
||||
expect(inputElm.val()).toBe('1970-01-01T15:41:50');
|
||||
|
||||
$rootScope.$apply(function() {
|
||||
$rootScope.threeFortyOnePm = new Date(1970, 0, 1, 15, 41, 0, 500);
|
||||
});
|
||||
|
||||
expect(inputElm.val()).toBe('1970-01-01T15:41');
|
||||
});
|
||||
|
||||
describe('min', function() {
|
||||
var inputElm;
|
||||
beforeEach(function() {
|
||||
@@ -1593,7 +1675,7 @@ describe('input', function() {
|
||||
});
|
||||
|
||||
|
||||
it('should set the view if the model if a valid Date object.', function() {
|
||||
it('should set the view if the model is a valid Date object.', function() {
|
||||
var inputElm = helper.compileInput('<input type="time" ng-model="threeFortyOnePm"/>');
|
||||
|
||||
$rootScope.$apply(function() {
|
||||
@@ -1604,7 +1686,7 @@ describe('input', function() {
|
||||
});
|
||||
|
||||
|
||||
it('should set the model undefined if the view is invalid', function() {
|
||||
it('should set the model to undefined if the view is invalid', function() {
|
||||
var inputElm = helper.compileInput('<input type="time" ng-model="breakMe"/>');
|
||||
|
||||
$rootScope.$apply(function() {
|
||||
@@ -1623,7 +1705,7 @@ describe('input', function() {
|
||||
});
|
||||
|
||||
|
||||
it('should render as blank if null', function() {
|
||||
it('should set blank if null', function() {
|
||||
var inputElm = helper.compileInput('<input type="time" ng-model="test" />');
|
||||
|
||||
$rootScope.$apply('test = null');
|
||||
@@ -1633,7 +1715,7 @@ describe('input', function() {
|
||||
});
|
||||
|
||||
|
||||
it('should come up blank when no value specified', function() {
|
||||
it('should set blank when no value specified', function() {
|
||||
var inputElm = helper.compileInput('<input type="time" ng-model="test" />');
|
||||
|
||||
expect(inputElm.val()).toBe('');
|
||||
@@ -1644,6 +1726,88 @@ describe('input', function() {
|
||||
expect(inputElm.val()).toBe('');
|
||||
});
|
||||
|
||||
it('should use the timeSecondsFormat specified in ngModelOptions', function() {
|
||||
var inputElm = helper.compileInput(
|
||||
'<input type="time" ng-model-options="{timeSecondsFormat: \'\'}" ng-model="time"/>'
|
||||
);
|
||||
|
||||
var ctrl = inputElm.controller('ngModel');
|
||||
|
||||
$rootScope.$apply(function() {
|
||||
$rootScope.time = new Date(1970, 0, 1, 15, 41, 0, 500);
|
||||
});
|
||||
expect(inputElm.val()).toBe('15:41');
|
||||
|
||||
$rootScope.$apply(function() {
|
||||
$rootScope.time = new Date(1970, 0, 1, 15, 41, 50, 500);
|
||||
});
|
||||
expect(inputElm.val()).toBe('15:41');
|
||||
|
||||
ctrl.$overrideModelOptions({timeSecondsFormat: 'ss'});
|
||||
|
||||
$rootScope.$apply(function() {
|
||||
$rootScope.time = new Date(1970, 0, 1, 15, 41, 5, 500);
|
||||
});
|
||||
expect(inputElm.val()).toBe('15:41:05');
|
||||
|
||||
ctrl.$overrideModelOptions({timeSecondsFormat: 'ss.sss'});
|
||||
|
||||
$rootScope.$apply(function() {
|
||||
$rootScope.time = new Date(1970, 0, 1, 15, 41, 50, 50);
|
||||
});
|
||||
expect(inputElm.val()).toBe('15:41:50.050');
|
||||
});
|
||||
|
||||
|
||||
it('should strip empty milliseconds and seconds if specified in ngModelOptions', function() {
|
||||
var inputElm = helper.compileInput(
|
||||
'<input type="time" ng-model-options="{timeStripZeroSeconds: true}" ng-model="threeFortyOnePm"/>'
|
||||
);
|
||||
|
||||
$rootScope.$apply(function() {
|
||||
$rootScope.threeFortyOnePm = new Date(1970, 0, 1, 15, 41, 50, 500);
|
||||
});
|
||||
|
||||
expect(inputElm.val()).toBe('15:41:50.500');
|
||||
|
||||
$rootScope.$apply(function() {
|
||||
$rootScope.threeFortyOnePm = new Date(1970, 0, 1, 15, 41, 0, 500);
|
||||
});
|
||||
|
||||
expect(inputElm.val()).toBe('15:41:00.500');
|
||||
|
||||
$rootScope.$apply(function() {
|
||||
$rootScope.threeFortyOnePm = new Date(1970, 0, 1, 15, 41, 50, 0);
|
||||
});
|
||||
|
||||
expect(inputElm.val()).toBe('15:41:50');
|
||||
|
||||
$rootScope.$apply(function() {
|
||||
$rootScope.threeFortyOnePm = new Date(1970, 0, 1, 15, 41, 0, 0);
|
||||
});
|
||||
|
||||
expect(inputElm.val()).toBe('15:41');
|
||||
});
|
||||
|
||||
|
||||
it('should apply timeStripZeroSeconds after timeSecondsFormat', function() {
|
||||
var inputElm = helper.compileInput('<input type="time"' +
|
||||
' ng-model-options="{timeSecondsFormat: \'ss\', timeStripZeroSeconds: true}"' +
|
||||
' ng-model="threeFortyOnePm"/>');
|
||||
|
||||
$rootScope.$apply(function() {
|
||||
$rootScope.threeFortyOnePm = new Date(1970, 0, 1, 15, 41, 50, 500);
|
||||
});
|
||||
|
||||
expect(inputElm.val()).toBe('15:41:50');
|
||||
|
||||
$rootScope.$apply(function() {
|
||||
$rootScope.threeFortyOnePm = new Date(1970, 0, 1, 15, 41, 0, 500);
|
||||
});
|
||||
|
||||
expect(inputElm.val()).toBe('15:41');
|
||||
});
|
||||
|
||||
|
||||
it('should parse empty string to null', function() {
|
||||
var inputElm = helper.compileInput('<input type="time" ng-model="test" />');
|
||||
|
||||
Reference in New Issue
Block a user