From 23e3ef5aeecebfe6b05b928aa713cf0be269164b Mon Sep 17 00:00:00 2001 From: crisbeto Date: Sat, 30 Jul 2016 11:43:34 +0300 Subject: [PATCH] feat(datepicker): add ngFocus and ngBlur support Adds support for the `ngFocus` and `ngBlur` expressions on the datepicker. Fixes #8779. --- .../datepicker/js/datepickerDirective.js | 21 ++++++++ .../datepicker/js/datepickerDirective.spec.js | 54 +++++++++++++++++++ 2 files changed, 75 insertions(+) diff --git a/src/components/datepicker/js/datepickerDirective.js b/src/components/datepicker/js/datepickerDirective.js index b9b6bfc3e4..5440521071 100644 --- a/src/components/datepicker/js/datepickerDirective.js +++ b/src/components/datepicker/js/datepickerDirective.js @@ -21,6 +21,8 @@ * * @param {Date} ng-model The component's model. Expects a JavaScript Date object. * @param {expression=} ng-change Expression evaluated when the model value changes. + * @param {expression=} ng-focus Expression evaluated when the input is focused or the calendar is opened. + * @param {expression=} ng-blur Expression evaluated when focus is removed from the input or the calendar is closed. * @param {Date=} md-min-date Expression representing a min date (inclusive). * @param {Date=} md-max-date Expression representing a max date (inclusive). * @param {(function(Date): boolean)=} md-date-filter Function expecting a date and returning a boolean whether it can be selected or not. @@ -676,6 +678,7 @@ this.attachCalendarPane(); this.focusCalendar(); + this.evalAttr('ngFocus'); // Attach click listener inside of a timeout because, if this open call was triggered by a // click, we don't want it to be immediately propogated up to the body and handled. @@ -698,6 +701,7 @@ self.detachCalendarPane(); self.ngModelCtrl.$setTouched(); + self.evalAttr('ngBlur'); self.documentElement.off('click touchstart', self.bodyClickHandler); window.removeEventListener('resize', self.windowResizeHandler); @@ -744,6 +748,13 @@ if (!isFocused) { this.ngModelCtrl.$setTouched(); } + + // The ng* expressions shouldn't be evaluated when mdOpenOnFocus is on, + // because they also get called when the calendar is opened/closed. + if (!this.openOnFocus) { + this.evalAttr(isFocused ? 'ngFocus' : 'ngBlur'); + } + this.isFocused = isFocused; }; @@ -772,4 +783,14 @@ DatePickerCtrl.prototype.handleWindowBlur = function() { this.inputFocusedOnWindowBlur = document.activeElement === this.inputElement; }; + + /** + * Evaluates an attribute expression against the parent scope. + * @param {String} attr Name of the attribute to be evaluated. + */ + DatePickerCtrl.prototype.evalAttr = function(attr) { + if (this.$attrs[attr]) { + this.$scope.$parent.$eval(this.$attrs[attr]); + } + }; })(); diff --git a/src/components/datepicker/js/datepickerDirective.spec.js b/src/components/datepicker/js/datepickerDirective.spec.js index 5957259fcd..ab6551dfec 100644 --- a/src/components/datepicker/js/datepickerDirective.spec.js +++ b/src/components/datepicker/js/datepickerDirective.spec.js @@ -17,6 +17,8 @@ describe('md-datepicker', function() { 'md-date-filter="dateFilter"' + 'ng-model="myDate" ' + 'ng-change="dateChangedHandler()" ' + + 'ng-focus="focusHandler()" ' + + 'ng-blur="blurHandler()" ' + 'ng-required="isRequired" ' + 'ng-disabled="isDisabled">' + ''; @@ -709,4 +711,56 @@ describe('md-datepicker', function() { pageScope.$digest(); } }); + + describe('ngFocus support', function() { + beforeEach(function() { + pageScope.focusHandler = jasmine.createSpy('ng-focus handler'); + }); + + it('should trigger the ngFocus handler when the input is focused', function() { + controller.ngInputElement.triggerHandler('focus'); + expect(pageScope.focusHandler).toHaveBeenCalled(); + }); + + it('should trigger the ngFocus handler when the calendar is opened', function() { + controller.openCalendarPane({}); + expect(pageScope.focusHandler).toHaveBeenCalled(); + }); + + it('should only trigger once when mdOpenOnFocus is set', function() { + createDatepickerInstance(''); + + controller.ngInputElement.triggerHandler('focus'); + expect(pageScope.focusHandler).toHaveBeenCalledTimes(1); + }); + }); + + describe('ngBlur support', function() { + beforeEach(function() { + pageScope.blurHandler = jasmine.createSpy('ng-blur handler'); + }); + + it('should trigger the ngBlur handler when the input is blurred', function() { + controller.ngInputElement.triggerHandler('blur'); + expect(pageScope.blurHandler).toHaveBeenCalled(); + }); + + it('should trigger the ngBlur handler when the calendar is closed', function() { + controller.openCalendarPane({ + target: controller.ngInputElement + }); + controller.closeCalendarPane(); + expect(pageScope.blurHandler).toHaveBeenCalled(); + }); + + it('should only trigger once when mdOpenOnFocus is set', function() { + createDatepickerInstance(''); + + controller.ngInputElement.triggerHandler('focus'); + controller.closeCalendarPane(); + expect(pageScope.blurHandler).toHaveBeenCalledTimes(1); + }); + }); });