|
8 | 8 | // TODO(jelbourn): forward more attributes to the internal input (required, autofocus, etc.)
|
9 | 9 | // TODO(jelbourn): something better for mobile (calendar panel takes up entire screen?)
|
10 | 10 | // TODO(jelbourn): input behavior (masking? auto-complete?)
|
11 |
| - // TODO(jelbourn): UTC mode |
12 | 11 |
|
13 | 12 |
|
14 | 13 | angular.module('material.components.datepicker')
|
|
20 | 19 | * @module material.components.datepicker
|
21 | 20 | *
|
22 | 21 | * @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> |
23 | 24 | * @param {expression=} ng-change Expression evaluated when the model value changes.
|
24 | 25 | * @param {expression=} ng-focus Expression evaluated when the input is focused or the calendar is opened.
|
25 | 26 | * @param {expression=} ng-blur Expression evaluated when focus is removed from the input or the calendar is closed.
|
|
57 | 58 | *
|
58 | 59 | */
|
59 | 60 |
|
60 |
| - function datePickerDirective($$mdSvgRegistry, $mdUtil, $mdAria) { |
| 61 | + function datePickerDirective($$mdSvgRegistry, $mdUtil, $mdAria, inputDirective) { |
61 | 62 | return {
|
62 | 63 | template: function(tElement, tAttrs) {
|
63 | 64 | // Buttons are not in the tab order because users can open the calendar via keyboard
|
|
129 | 130 | var parentForm = controllers[3];
|
130 | 131 | var mdNoAsterisk = $mdUtil.parseAttributeBoolean(attr.mdNoAsterisk);
|
131 | 132 |
|
132 |
| - mdDatePickerCtrl.configureNgModel(ngModelCtrl, mdInputContainer); |
| 133 | + mdDatePickerCtrl.configureNgModel(ngModelCtrl, mdInputContainer, inputDirective); |
133 | 134 |
|
134 | 135 | if (mdInputContainer) {
|
135 | 136 | // We need to move the spacer after the datepicker itself,
|
|
221 | 222 | * @ngInject @constructor
|
222 | 223 | */
|
223 | 224 | function DatePickerCtrl($scope, $element, $attrs, $window, $mdConstant,
|
224 |
| - $mdTheming, $mdUtil, $mdDateLocale, $$mdDateUtil, $$rAF, $mdGesture) { |
| 225 | + $mdTheming, $mdUtil, $mdDateLocale, $$mdDateUtil, $$rAF, $mdGesture, $filter) { |
225 | 226 |
|
226 | 227 | /** @final */
|
227 | 228 | this.$window = $window;
|
|
328 | 329 | /** Pre-bound handler for the window blur event. Allows for it to be removed later. */
|
329 | 330 | this.windowBlurHandler = angular.bind(this, this.handleWindowBlur);
|
330 | 331 |
|
| 332 | + /** The built-in Angular date filter. */ |
| 333 | + this.ngDateFilter = $filter('date'); |
| 334 | + |
331 | 335 | // Unless the user specifies so, the datepicker should not be a tab stop.
|
332 | 336 | // This is necessary because ngAria might add a tabindex to anything with an ng-model
|
333 | 337 | // (based on whether or not the user has turned that particular feature on/off).
|
|
365 | 369 | }
|
366 | 370 |
|
367 | 371 | /**
|
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. |
370 | 377 | */
|
371 |
| - DatePickerCtrl.prototype.configureNgModel = function(ngModelCtrl, mdInputContainer) { |
| 378 | + DatePickerCtrl.prototype.configureNgModel = function(ngModelCtrl, mdInputContainer, inputDirective) { |
372 | 379 | this.ngModelCtrl = ngModelCtrl;
|
373 | 380 | this.mdInputContainer = mdInputContainer;
|
374 | 381 |
|
| 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 | + |
375 | 395 | var self = this;
|
376 |
| - ngModelCtrl.$render = function() { |
377 |
| - var value = self.ngModelCtrl.$viewValue; |
378 | 396 |
|
| 397 | + // Responds to external changes to the model value. |
| 398 | + self.ngModelCtrl.$formatters.push(function(value) { |
379 | 399 | if (value && !(value instanceof Date)) {
|
380 | 400 | throw Error('The ng-model for md-datepicker must be a Date instance. ' +
|
381 | 401 | 'Currently the model is a: ' + (typeof value));
|
|
386 | 406 | self.mdInputContainer && self.mdInputContainer.setHasValue(!!value);
|
387 | 407 | self.resizeInputElement();
|
388 | 408 | self.updateErrorState();
|
389 |
| - }; |
| 409 | + |
| 410 | + return value; |
| 411 | + }); |
390 | 412 |
|
391 | 413 | // Responds to external error state changes (e.g. ng-required based on another input).
|
392 | 414 | ngModelCtrl.$viewChangeListeners.unshift(angular.bind(this, this.updateErrorState));
|
|
401 | 423 | var self = this;
|
402 | 424 |
|
403 | 425 | self.$scope.$on('md-calendar-change', function(event, date) {
|
404 |
| - self.ngModelCtrl.$setViewValue(date); |
| 426 | + self.setModelValue(date); |
405 | 427 | self.date = date;
|
406 | 428 | self.inputElement.value = self.dateLocale.formatDate(date);
|
407 | 429 | self.mdInputContainer && self.mdInputContainer.setHasValue(!!date);
|
|
564 | 586 |
|
565 | 587 | // The datepicker's model is only updated when there is a valid input.
|
566 | 588 | if (isValidInput) {
|
567 |
| - this.ngModelCtrl.$setViewValue(parsedDate); |
| 589 | + this.setModelValue(parsedDate); |
568 | 590 | this.date = parsedDate;
|
569 | 591 | }
|
570 | 592 |
|
|
806 | 828 | this.$scope.$parent.$eval(this.$attrs[attr]);
|
807 | 829 | }
|
808 | 830 | };
|
| 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 | + }; |
809 | 841 | })();
|
0 commit comments