From 86bfec1919be26d5424657967469a623ad39c7df Mon Sep 17 00:00:00 2001 From: "david.rus" Date: Mon, 29 Jun 2015 21:25:34 +0200 Subject: [PATCH] feat(typeahead): popup position - Position the dropdown appropriately when appended to the body and window is resized or scrolled Closes #3874 --- src/typeahead/test/typeahead.spec.js | 24 ++++++++++++ src/typeahead/typeahead.js | 52 +++++++++++++++++++++++-- template/typeahead/typeahead-popup.html | 2 +- 3 files changed, 73 insertions(+), 5 deletions(-) diff --git a/src/typeahead/test/typeahead.spec.js b/src/typeahead/test/typeahead.spec.js index a86dcedd7e..7d44456968 100644 --- a/src/typeahead/test/typeahead.spec.js +++ b/src/typeahead/test/typeahead.spec.js @@ -752,6 +752,30 @@ describe('typeahead tests', function () { changeInputValueTo(element, 'ba'); expect(findDropDown($document.find('body')).length).toEqual(0); }); + + it('should have right position after scroll', function() { + var element = prepareInputEl('
'); + var dropdown = findDropDown($document.find('body')); + var body = angular.element(document.body); + + // Set body height to allow scrolling + body.css({height:'10000px'}); + + // Scroll top + window.scroll(0, 1000); + + // Set input value to show dropdown + changeInputValueTo(element, 'ba'); + + // Init position of dropdown must be 1000px + expect(dropdown.css('top') ).toEqual('1000px'); + + // After scroll, must have new position + window.scroll(0, 500); + body.triggerHandler('scroll'); + $timeout.flush(); + expect(dropdown.css('top') ).toEqual('500px'); + }); }); describe('focus first', function () { diff --git a/src/typeahead/typeahead.js b/src/typeahead/typeahead.js index 10b7778cf7..c40f7ae75d 100644 --- a/src/typeahead/typeahead.js +++ b/src/typeahead/typeahead.js @@ -29,10 +29,11 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position', 'ui.bootstrap }; }]) - .directive('typeahead', ['$compile', '$parse', '$q', '$timeout', '$document', '$rootScope', '$position', 'typeaheadParser', - function ($compile, $parse, $q, $timeout, $document, $rootScope, $position, typeaheadParser) { + .directive('typeahead', ['$compile', '$parse', '$q', '$timeout', '$document', '$window', '$rootScope', '$position', 'typeaheadParser', + function ($compile, $parse, $q, $timeout, $document, $window, $rootScope, $position, typeaheadParser) { var HOT_KEYS = [9, 13, 27, 38, 40]; + var eventDebounceTime = 200; return { require:'ngModel', @@ -96,6 +97,7 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position', 'ui.bootstrap matches: 'matches', active: 'activeIdx', select: 'select(activeIdx)', + 'move-in-progress': 'moveInProgress', query: 'query', position: 'position' }); @@ -153,8 +155,7 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position', 'ui.bootstrap //position pop-up with matches - we need to re-calculate its position each time we are opening a window //with matches as a pop-up might be absolute-positioned and position of an input might have changed on a page //due to other elements being rendered - scope.position = appendToBody ? $position.offset(element) : $position.position(element); - scope.position.top = scope.position.top + element.prop('offsetHeight'); + recalculatePosition(); element.attr('aria-expanded', true); } else { @@ -170,6 +171,48 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position', 'ui.bootstrap }); }; + // bind events only if appendToBody params exist - performance feature + if (appendToBody) { + angular.element($window).bind('resize', fireRecalculating); + $document.find('body').bind('scroll', fireRecalculating); + } + + // Declare the timeout promise var outside the function scope so that stacked calls can be cancelled later + var timeoutEventPromise; + + // Default progress type + scope.moveInProgress = false; + + function fireRecalculating() { + if(!scope.moveInProgress){ + scope.moveInProgress = true; + scope.$digest(); + } + + // Cancel previous timeout + if (timeoutEventPromise) { + $timeout.cancel(timeoutEventPromise); + } + + // Debounced executing recalculate after events fired + timeoutEventPromise = $timeout(function () { + // if popup is visible + if (scope.matches.length) { + recalculatePosition(); + } + + scope.moveInProgress = false; + scope.$digest(); + }, eventDebounceTime); + } + + // recalculate actual position and set new values to scope + // after digest loop is popup in right position + function recalculatePosition() { + scope.position = appendToBody ? $position.offset(element) : $position.position(element); + scope.position.top += element.prop('offsetHeight'); + } + resetMatches(); //we need to propagate user's query so we can higlight matches @@ -358,6 +401,7 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position', 'ui.bootstrap query:'=', active:'=', position:'&', + moveInProgress:'=', select:'&' }, replace:true, diff --git a/template/typeahead/typeahead-popup.html b/template/typeahead/typeahead-popup.html index 9760c154b5..43693ac35e 100644 --- a/template/typeahead/typeahead-popup.html +++ b/template/typeahead/typeahead-popup.html @@ -1,4 +1,4 @@ -