|
12 | 12 | function datePickerDirective() {
|
13 | 13 | return {
|
14 | 14 | template:
|
15 |
| - '<div class="md-date-picker">' + |
16 |
| - '<input> <br>' + |
17 |
| - '<md-calendar ng-model="ctrl.date"></md-calendar>' + |
18 |
| - '</div>', |
| 15 | + '<input><button type="button" ng-click="ctrl.openCalendarPane()">📅</button>' + |
| 16 | + '<div class="md-date-calendar-pane">' + |
| 17 | + '<md-calendar ng-model="ctrl.date" ng-if="ctrl.isCalendarOpen"></md-calendar>' + |
| 18 | + '</div>', |
| 19 | + // <md-calendar ng-model="ctrl.date"></md-calendar> |
19 | 20 | require: ['ngModel', 'mdDatePicker'],
|
20 | 21 | scope: {},
|
21 | 22 | controller: DatePickerCtrl,
|
22 | 23 | controllerAs: 'ctrl',
|
23 | 24 | link: function(scope, element, attr, controllers) {
|
24 | 25 | var ngModelCtrl = controllers[0];
|
25 | 26 | var mdDatePickerCtrl = controllers[1];
|
| 27 | + |
26 | 28 | mdDatePickerCtrl.configureNgModel(ngModelCtrl);
|
27 | 29 | }
|
28 | 30 | };
|
29 | 31 | }
|
30 | 32 |
|
31 |
| - function DatePickerCtrl($scope, $element, $$mdDateLocale, $$mdDateUtil) { |
| 33 | + /** |
| 34 | + * Controller for md-date-picker. |
| 35 | + * |
| 36 | + * @ngInject @constructor |
| 37 | + */ |
| 38 | + function DatePickerCtrl($scope, $element, $compile, $timeout, $mdConstant, $mdUtil, |
| 39 | + $$mdDateLocale, $$mdDateUtil) { |
| 40 | + /** @final */ |
| 41 | + this.$compile = $compile; |
| 42 | + |
| 43 | + /** @final */ |
| 44 | + this.$timeout = $timeout; |
| 45 | + |
32 | 46 | /** @final */
|
33 | 47 | this.dateLocale = $$mdDateLocale;
|
34 | 48 |
|
35 | 49 | /** @final */
|
36 | 50 | this.dateUtil = $$mdDateUtil;
|
37 | 51 |
|
| 52 | + /** @final */ |
| 53 | + this.$mdConstant = $mdConstant; |
| 54 | + |
| 55 | + /* @final */ |
| 56 | + this.$mdUtil = $mdUtil; |
| 57 | + |
38 | 58 | /** @type {!angular.NgModelController} */
|
39 | 59 | this.ngModelCtrl = null;
|
40 | 60 |
|
41 | 61 | /** @type {HTMLInputElement} */
|
42 | 62 | this.inputElement = $element[0].querySelector('input');
|
43 | 63 |
|
| 64 | + /** @type {HTMLElement} Floating calendar pane (instantiated lazily) */ |
| 65 | + this.calendarPane = $element[0].querySelector('.md-date-calendar-pane'); |
| 66 | + |
44 | 67 | /** @type {Date} */
|
45 | 68 | this.date = null;
|
46 | 69 |
|
|
50 | 73 | /** @final {!angular.Scope} */
|
51 | 74 | this.$scope = $scope;
|
52 | 75 |
|
| 76 | + /** @type {boolean} Whether the date-picker's calendar pane is open. */ |
| 77 | + this.isCalendarOpen = false; |
| 78 | + |
| 79 | + /** Pre-bound click handler is saved so that the event listener can be removed. */ |
| 80 | + this.bodyClickHandler = this.handleBodyClick.bind(this); |
| 81 | + |
53 | 82 | this.attachChangeListeners();
|
| 83 | + this.attachInterationListeners(); |
| 84 | + |
| 85 | + var self = this; |
| 86 | + $scope.$on('$destroy', function() { |
| 87 | + self.detachCalendarPane(); |
| 88 | + }); |
54 | 89 | }
|
55 | 90 |
|
56 | 91 | /**
|
|
78 | 113 | self.$scope.$on('md-calendar-change', function(event, date) {
|
79 | 114 | self.ngModelCtrl.$setViewValue(date);
|
80 | 115 | self.inputElement.value = self.dateLocale.formatDate(date);
|
| 116 | + self.closeCalendarPane(); |
81 | 117 | });
|
82 | 118 |
|
83 | 119 | // TODO(jelbourn): debounce
|
|
89 | 125 | }
|
90 | 126 | });
|
91 | 127 | };
|
| 128 | + |
| 129 | + /** Attach event listeners for user interaction. */ |
| 130 | + DatePickerCtrl.prototype.attachInterationListeners = function() { |
| 131 | + var self = this; |
| 132 | + var $scope = this.$scope; |
| 133 | + var keyCodes = this.$mdConstant.KEY_CODE; |
| 134 | + |
| 135 | + self.inputElement.addEventListener('keydown', function(event) { |
| 136 | + $scope.$apply(function() { |
| 137 | + if (event.altKey && event.keyCode == keyCodes.DOWN_ARROW) { |
| 138 | + self.openCalendarPane(); |
| 139 | + } |
| 140 | + }); |
| 141 | + }); |
| 142 | + |
| 143 | + self.$scope.$on('md-calendar-escape', function() { |
| 144 | + self.closeCalendarPane(); |
| 145 | + }); |
| 146 | + }; |
| 147 | + |
| 148 | + /** Position and attach the floating calendar to the document. */ |
| 149 | + DatePickerCtrl.prototype.attachCalendarPane = function() { |
| 150 | + var elementRect = this.$element[0].getBoundingClientRect(); |
| 151 | + |
| 152 | + this.calendarPane.style.left = elementRect.left + 'px'; |
| 153 | + this.calendarPane.style.top = elementRect.bottom + 'px'; |
| 154 | + document.body.appendChild(this.calendarPane); |
| 155 | + }; |
| 156 | + |
| 157 | + /** Detach the floating calendar pane from the document. */ |
| 158 | + DatePickerCtrl.prototype.detachCalendarPane = function() { |
| 159 | + // Use native DOM removal because we do not want any of the angular state of this element |
| 160 | + // to be disposed. |
| 161 | + this.calendarPane.parentNode.removeChild(this.calendarPane); |
| 162 | + }; |
| 163 | + |
| 164 | + /** Open the floating calendar pane. */ |
| 165 | + DatePickerCtrl.prototype.openCalendarPane = function() { |
| 166 | + if (!this.isCalendarOpen) { |
| 167 | + this.isCalendarOpen = true; |
| 168 | + this.attachCalendarPane(); |
| 169 | + // TODO(jelbourn): dispatch to tell other date pickers to close. |
| 170 | + this.focusCalendar(); |
| 171 | + |
| 172 | + // Attach click listener inside of a timeout because, if this open call was triggered by a |
| 173 | + // click, we don't want it to be immediately propogated up to the body and handled. |
| 174 | + var self = this; |
| 175 | + this.$timeout(function() { |
| 176 | + document.body.addEventListener('click', self.bodyClickHandler); |
| 177 | + }, 0, false); |
| 178 | + } |
| 179 | + }; |
| 180 | + |
| 181 | + /** Close the floating calendar pane. */ |
| 182 | + DatePickerCtrl.prototype.closeCalendarPane = function() { |
| 183 | + this.isCalendarOpen = false; |
| 184 | + this.detachCalendarPane(); |
| 185 | + this.inputElement.focus(); |
| 186 | + document.body.removeEventListener('click', this.bodyClickHandler); |
| 187 | + }; |
| 188 | + |
| 189 | + /** Gets the controller instance for the calendar in the floating pane. */ |
| 190 | + DatePickerCtrl.prototype.getCalendarCtrl = function() { |
| 191 | + return angular.element(this.calendarPane.querySelector('md-calendar')).controller('mdCalendar'); |
| 192 | + }; |
| 193 | + |
| 194 | + /** Focus the calendar in the floating pane. */ |
| 195 | + DatePickerCtrl.prototype.focusCalendar = function() { |
| 196 | + // Use a timeout in order to allow the calendar to be rendered, as it is gated behind an ng-if. |
| 197 | + var self = this; |
| 198 | + this.$timeout(function() { |
| 199 | + self.getCalendarCtrl().focus(); |
| 200 | + }, 0, false); |
| 201 | + }; |
| 202 | + |
| 203 | + /** |
| 204 | + * Handles a click on the document body when the floating calendar pane is open. |
| 205 | + * Closes the floating calendar pane if the click is not inside of it. |
| 206 | + * @param {MouseEvent} event |
| 207 | + */ |
| 208 | + DatePickerCtrl.prototype.handleBodyClick = function(event) { |
| 209 | + if (this.isCalendarOpen) { |
| 210 | + var isInCalendar = this.$mdUtil.getClosest(event.target, 'md-calendar'); |
| 211 | + if (!isInCalendar) { |
| 212 | + this.closeCalendarPane(); |
| 213 | + } |
| 214 | + } |
| 215 | + }; |
92 | 216 | })();
|
0 commit comments