Skip to content
This repository was archived by the owner on Sep 5, 2024. It is now read-only.

Commit 14fa477

Browse files
crisbetoThomasBurleson
authored andcommitted
feat(datepicker): add timezone support (#9410)
Adds the ability to specify the datepicker's timezone via the `ng-model-options` attribute, e.g. for UTC, the element would look like this: ``` <md-datepicker ng-model="date" ng-model-options="{ timezone: 'utc' }"></md-datepicker> ``` This works by re-using the logic from Angular's `input[type="date"]` directive. Fixes #8448. Fixes #8936.
1 parent 7e21118 commit 14fa477

File tree

2 files changed

+48
-12
lines changed

2 files changed

+48
-12
lines changed

src/components/datepicker/js/datepickerDirective.js

Lines changed: 44 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
// TODO(jelbourn): forward more attributes to the internal input (required, autofocus, etc.)
99
// TODO(jelbourn): something better for mobile (calendar panel takes up entire screen?)
1010
// TODO(jelbourn): input behavior (masking? auto-complete?)
11-
// TODO(jelbourn): UTC mode
1211

1312

1413
angular.module('material.components.datepicker')
@@ -20,6 +19,8 @@
2019
* @module material.components.datepicker
2120
*
2221
* @param {Date} ng-model The component's model. Expects a JavaScript Date object.
22+
* @param {Object=} ng-model-options Allows tuning of the way in which `ng-model` is being updated. Also allows
23+
* for a timezone to be specified. <a href="https://docs.angularjs.org/api/ng/directive/ngModelOptions#usage">Read more at the ngModelOptions docs.</a>
2324
* @param {expression=} ng-change Expression evaluated when the model value changes.
2425
* @param {expression=} ng-focus Expression evaluated when the input is focused or the calendar is opened.
2526
* @param {expression=} ng-blur Expression evaluated when focus is removed from the input or the calendar is closed.
@@ -57,7 +58,7 @@
5758
*
5859
*/
5960

60-
function datePickerDirective($$mdSvgRegistry, $mdUtil, $mdAria) {
61+
function datePickerDirective($$mdSvgRegistry, $mdUtil, $mdAria, inputDirective) {
6162
return {
6263
template: function(tElement, tAttrs) {
6364
// Buttons are not in the tab order because users can open the calendar via keyboard
@@ -129,7 +130,7 @@
129130
var parentForm = controllers[3];
130131
var mdNoAsterisk = $mdUtil.parseAttributeBoolean(attr.mdNoAsterisk);
131132

132-
mdDatePickerCtrl.configureNgModel(ngModelCtrl, mdInputContainer);
133+
mdDatePickerCtrl.configureNgModel(ngModelCtrl, mdInputContainer, inputDirective);
133134

134135
if (mdInputContainer) {
135136
// We need to move the spacer after the datepicker itself,
@@ -221,7 +222,7 @@
221222
* @ngInject @constructor
222223
*/
223224
function DatePickerCtrl($scope, $element, $attrs, $window, $mdConstant,
224-
$mdTheming, $mdUtil, $mdDateLocale, $$mdDateUtil, $$rAF, $mdGesture) {
225+
$mdTheming, $mdUtil, $mdDateLocale, $$mdDateUtil, $$rAF, $mdGesture, $filter) {
225226

226227
/** @final */
227228
this.$window = $window;
@@ -328,6 +329,9 @@
328329
/** Pre-bound handler for the window blur event. Allows for it to be removed later. */
329330
this.windowBlurHandler = angular.bind(this, this.handleWindowBlur);
330331

332+
/** The built-in Angular date filter. */
333+
this.ngDateFilter = $filter('date');
334+
331335
// Unless the user specifies so, the datepicker should not be a tab stop.
332336
// This is necessary because ngAria might add a tabindex to anything with an ng-model
333337
// (based on whether or not the user has turned that particular feature on/off).
@@ -365,17 +369,33 @@
365369
}
366370

367371
/**
368-
* Sets up the controller's reference to ngModelController.
369-
* @param {!angular.NgModelController} ngModelCtrl
372+
* Sets up the controller's reference to ngModelController and
373+
* applies Angular's `input[type="date"]` directive.
374+
* @param {!angular.NgModelController} ngModelCtrl Instance of the ngModel controller.
375+
* @param {Object} mdInputContainer Instance of the mdInputContainer controller.
376+
* @param {Object} inputDirective Config for Angular's `input` directive.
370377
*/
371-
DatePickerCtrl.prototype.configureNgModel = function(ngModelCtrl, mdInputContainer) {
378+
DatePickerCtrl.prototype.configureNgModel = function(ngModelCtrl, mdInputContainer, inputDirective) {
372379
this.ngModelCtrl = ngModelCtrl;
373380
this.mdInputContainer = mdInputContainer;
374381

382+
// The input needs to be [type="date"] in order to be picked up by Angular.
383+
this.$attrs.$set('type', 'date');
384+
385+
// Invoke the `input` directive link function, adding a stub for the element.
386+
// This allows us to re-use Angular's logic for setting the timezone via ng-model-options.
387+
// It works by calling the link function directly which then adds the proper `$parsers` and
388+
// `$formatters` to the ngModel controller.
389+
inputDirective[0].link.pre(this.$scope, {
390+
on: angular.noop,
391+
val: angular.noop,
392+
0: {}
393+
}, this.$attrs, [ngModelCtrl]);
394+
375395
var self = this;
376-
ngModelCtrl.$render = function() {
377-
var value = self.ngModelCtrl.$viewValue;
378396

397+
// Responds to external changes to the model value.
398+
self.ngModelCtrl.$formatters.push(function(value) {
379399
if (value && !(value instanceof Date)) {
380400
throw Error('The ng-model for md-datepicker must be a Date instance. ' +
381401
'Currently the model is a: ' + (typeof value));
@@ -386,7 +406,9 @@
386406
self.mdInputContainer && self.mdInputContainer.setHasValue(!!value);
387407
self.resizeInputElement();
388408
self.updateErrorState();
389-
};
409+
410+
return value;
411+
});
390412

391413
// Responds to external error state changes (e.g. ng-required based on another input).
392414
ngModelCtrl.$viewChangeListeners.unshift(angular.bind(this, this.updateErrorState));
@@ -401,7 +423,7 @@
401423
var self = this;
402424

403425
self.$scope.$on('md-calendar-change', function(event, date) {
404-
self.ngModelCtrl.$setViewValue(date);
426+
self.setModelValue(date);
405427
self.date = date;
406428
self.inputElement.value = self.dateLocale.formatDate(date);
407429
self.mdInputContainer && self.mdInputContainer.setHasValue(!!date);
@@ -564,7 +586,7 @@
564586

565587
// The datepicker's model is only updated when there is a valid input.
566588
if (isValidInput) {
567-
this.ngModelCtrl.$setViewValue(parsedDate);
589+
this.setModelValue(parsedDate);
568590
this.date = parsedDate;
569591
}
570592

@@ -806,4 +828,14 @@
806828
this.$scope.$parent.$eval(this.$attrs[attr]);
807829
}
808830
};
831+
832+
/**
833+
* Sets the ng-model value by first converting the date object into a strng. Converting it
834+
* is necessary, in order to pass Angular's `input[type="date"]` validations. Angular turns
835+
* the value into a Date object afterwards, before setting it on the model.
836+
* @param {Date=} value Date to be set as the model value.
837+
*/
838+
DatePickerCtrl.prototype.setModelValue = function(value) {
839+
this.ngModelCtrl.$setViewValue(this.ngDateFilter(value, 'yyyy-MM-dd'));
840+
};
809841
})();

src/components/datepicker/js/datepickerDirective.spec.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,10 @@ describe('md-datepicker', function() {
122122
}).not.toThrow();
123123
});
124124

125+
it('should set the element type as "date"', function() {
126+
expect(ngElement.attr('type')).toBe('date');
127+
});
128+
125129
describe('ngMessages support', function() {
126130
it('should set the `required` $error flag', function() {
127131
pageScope.isRequired = true;

0 commit comments

Comments
 (0)