From b72efed56f68e9e5dbed934df7f47e0b5c94242c Mon Sep 17 00:00:00 2001 From: RobJacobs Date: Wed, 13 May 2015 14:18:39 -0400 Subject: [PATCH] refactor(datepicker): implement ng-if on datepicker-popup Implementing ng-if on the datepicker-popup directive for performance. Closes #1915 Closes #3666 --- src/datepicker/datepicker.js | 61 ++++++++++------- src/datepicker/test/datepicker.spec.js | 94 ++++++++++++++------------ template/datepicker/popup.html | 2 +- 3 files changed, 87 insertions(+), 70 deletions(-) diff --git a/src/datepicker/datepicker.js b/src/datepicker/datepicker.js index a98b48dc7e..9b7d48d53e 100644 --- a/src/datepicker/datepicker.js +++ b/src/datepicker/datepicker.js @@ -18,7 +18,7 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst shortcutPropagation: false }) -.controller('DatepickerController', ['$scope', '$attrs', '$parse', '$interpolate', '$timeout', '$log', 'dateFilter', 'datepickerConfig', function($scope, $attrs, $parse, $interpolate, $timeout, $log, dateFilter, datepickerConfig) { +.controller('DatepickerController', ['$scope', '$attrs', '$parse', '$interpolate', '$log', 'dateFilter', 'datepickerConfig', function($scope, $attrs, $parse, $interpolate, $log, dateFilter, datepickerConfig) { var self = this, ngModelCtrl = { $setViewValue: angular.noop }; // nullModelCtrl; @@ -161,9 +161,7 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst $scope.keys = { 13:'enter', 32:'space', 33:'pageup', 34:'pagedown', 35:'end', 36:'home', 37:'left', 38:'up', 39:'right', 40:'down' }; var focusElement = function() { - $timeout(function() { - self.element[0].focus(); - }, 0 , false); + self.element[0].focus(); }; // Listen for focus requests from popup directive @@ -460,8 +458,8 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst showButtonBar: true }) -.directive('datepickerPopup', ['$compile', '$parse', '$document', '$position', 'dateFilter', 'dateParser', 'datepickerPopupConfig', -function ($compile, $parse, $document, $position, dateFilter, dateParser, datepickerPopupConfig) { +.directive('datepickerPopup', ['$compile', '$parse', '$document', '$position', 'dateFilter', 'dateParser', 'datepickerPopupConfig', '$timeout', +function ($compile, $parse, $document, $position, dateFilter, dateParser, datepickerPopupConfig, $timeout) { return { restrict: 'EA', require: 'ngModel', @@ -517,7 +515,7 @@ function ($compile, $parse, $document, $position, dateFilter, dateParser, datepi var popupEl = angular.element('
'); popupEl.attr({ 'ng-model': 'date', - 'ng-change': 'dateSelection()' + 'ng-change': 'dateSelection(date)' }); function cameltoDash( string ){ @@ -661,30 +659,41 @@ function ($compile, $parse, $document, $position, dateFilter, dateParser, datepi } }; - var keydown = function(evt, noApply) { - scope.keydown(evt); + var inputKeydownBind = function(evt) { + if (evt.which === 27 && scope.isOpen) { + evt.preventDefault(); + evt.stopPropagation(); + scope.$apply(function() { + scope.isOpen = false; + }); + element[0].focus(); + } else if (evt.which === 40 && !scope.isOpen) { + evt.preventDefault(); + evt.stopPropagation(); + scope.$apply(function() { + scope.isOpen = true; + }); + } }; - element.bind('keydown', keydown); + element.bind('keydown', inputKeydownBind); scope.keydown = function(evt) { if (evt.which === 27) { - evt.preventDefault(); - if (scope.isOpen) { - evt.stopPropagation(); - } - scope.close(); - } else if (evt.which === 40 && !scope.isOpen) { - scope.isOpen = true; + scope.isOpen = false; + element[0].focus(); } }; scope.$watch('isOpen', function(value) { if (value) { - scope.$broadcast('datepicker.focus'); scope.position = appendToBody ? $position.offset(element) : $position.position(element); scope.position.top = scope.position.top + element.prop('offsetHeight'); $document.bind('click', documentClickBind); + + $timeout(function() { + scope.$broadcast('datepicker.focus'); + }, 0, false); } else { $document.unbind('click', documentClickBind); } @@ -719,8 +728,14 @@ function ($compile, $parse, $document, $position, dateFilter, dateParser, datepi } scope.$on('$destroy', function() { + if (scope.isOpen === true) { + scope.$apply(function() { + scope.isOpen = false; + }); + } + $popup.remove(); - element.unbind('keydown', keydown); + element.unbind('keydown', inputKeydownBind); $document.unbind('click', documentClickBind); }); } @@ -732,12 +747,6 @@ function ($compile, $parse, $document, $position, dateFilter, dateParser, datepi restrict:'EA', replace: true, transclude: true, - templateUrl: 'template/datepicker/popup.html', - link:function (scope, element, attrs) { - element.bind('click', function(event) { - event.preventDefault(); - event.stopPropagation(); - }); - } + templateUrl: 'template/datepicker/popup.html' }; }); diff --git a/src/datepicker/test/datepicker.spec.js b/src/datepicker/test/datepicker.spec.js index 09aa37e26b..3d60c259eb 100644 --- a/src/datepicker/test/datepicker.spec.js +++ b/src/datepicker/test/datepicker.spec.js @@ -1185,7 +1185,7 @@ describe('datepicker directive', function () { })); it('does not to display datepicker initially', function() { - expect(dropdownEl).toBeHidden(); + expect(dropdownEl.length).toBe(0); }); it('to display the correct value in input', function() { @@ -1194,18 +1194,20 @@ describe('datepicker directive', function () { }); describe('initially opened', function () { + var wrapElement; + beforeEach(inject(function(_$document_, _$sniffer_) { $document = _$document_; $sniffer = _$sniffer_; $rootScope.isopen = true; $rootScope.date = new Date('September 30, 2010 15:30:00'); - var wrapElement = $compile('
')($rootScope); + wrapElement = $compile('
')($rootScope); $rootScope.$digest(); assignElements(wrapElement); })); it('datepicker is displayed', function() { - expect(dropdownEl).not.toBeHidden(); + expect(dropdownEl.length).toBe(1); }); it('renders the calendar correctly', function() { @@ -1240,10 +1242,11 @@ describe('datepicker directive', function () { }); it('closes the dropdown when a day is clicked', function() { - expect(dropdownEl.css('display')).not.toBe('none'); + expect(dropdownEl.length).toBe(1); clickOption(17); - expect(dropdownEl.css('display')).toBe('none'); + assignElements(wrapElement); + expect(dropdownEl.length).toBe(0); }); it('updates the model & calendar when input value changes', function() { @@ -1265,10 +1268,11 @@ describe('datepicker directive', function () { }); it('closes when click outside of calendar', function() { - expect(dropdownEl).not.toBeHidden(); + expect(dropdownEl.length).toBe(1); $document.find('body').click(); - expect(dropdownEl.css('display')).toBe('none'); + assignElements(wrapElement); + expect(dropdownEl.length).toBe(0); }); it('sets `ng-invalid` for invalid input', function() { @@ -1303,41 +1307,36 @@ describe('datepicker directive', function () { }); it('returns to the input when ESC key is pressed in the popup and closes', function() { - expect(dropdownEl).not.toBeHidden(); + expect(dropdownEl.length).toBe(1); dropdownEl.find('button').eq(0).focus(); expect(document.activeElement.tagName).toBe('BUTTON'); triggerKeyDown(dropdownEl, 'esc'); - expect(dropdownEl).toBeHidden(); + assignElements(wrapElement); + expect(dropdownEl.length).toBe(0); expect(document.activeElement.tagName).toBe('INPUT'); }); it('returns to the input when ESC key is pressed in the input and closes', function() { - expect(dropdownEl).not.toBeHidden(); + expect(dropdownEl.length).toBe(1); dropdownEl.find('button').eq(0).focus(); expect(document.activeElement.tagName).toBe('BUTTON'); triggerKeyDown(inputEl, 'esc'); $rootScope.$digest(); - expect(dropdownEl).toBeHidden(); + assignElements(wrapElement); + expect(dropdownEl.length).toBe(0); expect(document.activeElement.tagName).toBe('INPUT'); }); it('stops the ESC key from propagating if the dropdown is open, but not when closed', function() { - expect(dropdownEl).not.toBeHidden(); - - dropdownEl.find('button').eq(0).focus(); - expect(document.activeElement.tagName).toBe('BUTTON'); - var documentKey = -1; var getKey = function(evt) { documentKey = evt.which; }; $document.bind('keydown', getKey); triggerKeyDown(inputEl, 'esc'); - $rootScope.$digest(); - expect(dropdownEl).toBeHidden(); expect(documentKey).toBe(-1); triggerKeyDown(inputEl, 'esc'); @@ -1357,7 +1356,7 @@ describe('datepicker directive', function () { $rootScope.date = new Date('September 30, 2010 15:30:00'); var wrapElement = $compile('
')($rootScope); + 'datepicker-popup is-open="isopen">
')($rootScope); $rootScope.$digest(); assignElements(wrapElement); })); @@ -1420,7 +1419,7 @@ describe('datepicker directive', function () { it('works as date', function() { setupInputWithType('date'); - expect(dropdownEl).toBeHidden(); + expect(dropdownEl.length).toBe(1); expect(inputEl.val()).toBe('2010-09-30'); changeInputValueTo(inputEl, '1980-03-05'); @@ -1482,7 +1481,7 @@ describe('datepicker directive', function () { function setupInputWithType(type) { var wrapElement = $compile('
')($rootScope); + type + '" ng-model="date" datepicker-popup is-open="isopen">
')($rootScope); $rootScope.$digest(); assignElements(wrapElement); } @@ -1594,32 +1593,36 @@ describe('datepicker directive', function () { }); describe('toggles programatically by `open` attribute', function () { + var wrapElement; + beforeEach(inject(function() { $rootScope.open = true; - var wrapElement = $compile('
')($rootScope); + wrapElement = $compile('
')($rootScope); $rootScope.$digest(); assignElements(wrapElement); })); it('to display initially', function() { - expect(dropdownEl.css('display')).not.toBe('none'); + expect(dropdownEl.length).toBe(1); }); it('to close / open from scope variable', function() { - expect(dropdownEl.css('display')).not.toBe('none'); + expect(dropdownEl.length).toBe(1); $rootScope.open = false; $rootScope.$digest(); - expect(dropdownEl.css('display')).toBe('none'); + assignElements(wrapElement); + expect(dropdownEl.length).toBe(0); $rootScope.open = true; $rootScope.$digest(); - expect(dropdownEl.css('display')).not.toBe('none'); + assignElements(wrapElement); + expect(dropdownEl.length).toBe(1); }); }); describe('custom format', function () { beforeEach(inject(function() { - var wrapElement = $compile('
')($rootScope); + var wrapElement = $compile('
')($rootScope); $rootScope.$digest(); assignElements(wrapElement); })); @@ -1644,7 +1647,7 @@ describe('datepicker directive', function () { describe('dynamic custom format', function () { beforeEach(inject(function() { $rootScope.format = 'dd-MMMM-yyyy'; - var wrapElement = $compile('
')($rootScope); + var wrapElement = $compile('
')($rootScope); $rootScope.$digest(); assignElements(wrapElement); })); @@ -1686,16 +1689,18 @@ describe('datepicker directive', function () { }); describe('`close-on-date-selection` attribute', function () { + var wrapElement; beforeEach(inject(function() { $rootScope.close = false; - var wrapElement = $compile('
')($rootScope); + wrapElement = $compile('
')($rootScope); $rootScope.$digest(); assignElements(wrapElement); })); it('does not close the dropdown when a day is clicked', function() { clickOption(17); - expect(dropdownEl.css('display')).not.toBe('none'); + assignElements(wrapElement); + expect(dropdownEl.length).toBe(1); }); }); @@ -1708,16 +1713,18 @@ describe('datepicker directive', function () { } describe('', function () { + var wrapElement; + beforeEach(inject(function() { $rootScope.isopen = true; - var wrapElement = $compile('
')($rootScope); + wrapElement = $compile('
')($rootScope); $rootScope.$digest(); assignElements(wrapElement); assignButtonBar(); })); it('should exist', function() { - expect(dropdownEl).not.toBeHidden(); + expect(dropdownEl.length).toBe(1); expect(dropdownEl.find('li').length).toBe(2); }); @@ -1763,7 +1770,8 @@ describe('datepicker directive', function () { it('should have a button to close calendar', function() { buttons.eq(2).click(); - expect(dropdownEl).toBeHidden(); + assignElements(wrapElement); + expect(dropdownEl.length).toBe(0); }); }); @@ -1771,7 +1779,7 @@ describe('datepicker directive', function () { it('should change text from attributes', function() { $rootScope.clearText = 'Null it!'; $rootScope.close = 'Close'; - var wrapElement = $compile('
')($rootScope); + var wrapElement = $compile('
')($rootScope); $rootScope.$digest(); assignElements(wrapElement); assignButtonBar(); @@ -1783,14 +1791,14 @@ describe('datepicker directive', function () { it('should remove bar', function() { $rootScope.showBar = false; - var wrapElement = $compile('
')($rootScope); + var wrapElement = $compile('
')($rootScope); $rootScope.$digest(); assignElements(wrapElement); expect(dropdownEl.find('li').length).toBe(1); }); it('should hide weeks column on popup', function() { - var wrapElement = $compile('
')($rootScope); + var wrapElement = $compile('
')($rootScope); $rootScope.$digest(); assignElements(wrapElement); @@ -1802,7 +1810,7 @@ describe('datepicker directive', function () { }); it('should show weeks column on popup', function() { - var wrapElement = $compile('
')($rootScope); + var wrapElement = $compile('
')($rootScope); $rootScope.$digest(); assignElements(wrapElement); @@ -1817,7 +1825,7 @@ describe('datepicker directive', function () { describe('`ng-change`', function() { beforeEach(inject(function() { $rootScope.changeHandler = jasmine.createSpy('changeHandler'); - var wrapElement = $compile('
')($rootScope); + var wrapElement = $compile('
')($rootScope); $rootScope.$digest(); assignElements(wrapElement); assignButtonBar(); @@ -1869,7 +1877,7 @@ describe('datepicker directive', function () { beforeEach(inject(function() { $rootScope.changeHandler = jasmine.createSpy('changeHandler'); $rootScope.date = new Date('09/16/2010'); - var wrapElement = $compile('
')($rootScope); + var wrapElement = $compile('
')($rootScope); $rootScope.$digest(); assignElements(wrapElement); })); @@ -1903,7 +1911,7 @@ describe('datepicker directive', function () { var $body = $document.find('body'), bodyLength = $body.children().length, elm = angular.element( - '
' + '
' ); $compile(elm)($rootScope); $rootScope.$digest(); @@ -1916,7 +1924,7 @@ describe('datepicker directive', function () { bodyLength = $body.children().length, isolatedScope = $rootScope.$new(), elm = angular.element( - '' + '' ); $compile(elm)(isolatedScope); isolatedScope.$digest(); @@ -1932,7 +1940,7 @@ describe('datepicker directive', function () { angular.extend(originalConfig, datepickerConfig); datepickerConfig.showWeeks = false; - var wrapElement = $compile('
')($rootScope); + var wrapElement = $compile('
')($rootScope); $rootScope.$digest(); assignElements(wrapElement); })); @@ -1954,7 +1962,7 @@ describe('datepicker directive', function () { beforeEach(inject(function() { $rootScope.date = new Date('August 11, 2013'); $rootScope.mode = 'month'; - var wrapElement = $compile('
')($rootScope); + var wrapElement = $compile('
')($rootScope); $rootScope.$digest(); assignElements(wrapElement); })); diff --git a/template/datepicker/popup.html b/template/datepicker/popup.html index 483bbe1e7f..13b29a155f 100644 --- a/template/datepicker/popup.html +++ b/template/datepicker/popup.html @@ -1,4 +1,4 @@ -