33 * @name material.components.tooltip
44 */
55angular
6- . module ( 'material.components.tooltip' , [ 'material.core' ] )
7- . directive ( 'mdTooltip' , MdTooltipDirective ) ;
6+ . module ( 'material.components.tooltip' , [ 'material.core' ] )
7+ . directive ( 'mdTooltip' , MdTooltipDirective )
8+ . service ( '$$mdTooltipRegistry' , MdTooltipRegistry ) ;
89
910/**
1011 * @ngdoc directive
@@ -33,8 +34,8 @@ angular
3334 * @param {boolean= } md-autohide If present or provided with a boolean value, the tooltip will hide on mouse leave, regardless of focus
3435 * @param {string= } md-direction Which direction would you like the tooltip to go? Supports left, right, top, and bottom. Defaults to bottom.
3536 */
36- function MdTooltipDirective ( $timeout , $window , $$rAF , $document , $mdUtil , $mdTheming , $rootElement ,
37- $animate , $q , $interpolate ) {
37+ function MdTooltipDirective ( $timeout , $window , $$rAF , $document , $mdUtil , $mdTheming , $animate ,
38+ $interpolate , $$mdTooltipRegistry ) {
3839
3940 var ENTER_EVENTS = 'focus touchstart mouseenter' ;
4041 var LEAVE_EVENTS = 'blur touchcancel mouseleave' ;
@@ -143,7 +144,7 @@ function MdTooltipDirective($timeout, $window, $$rAF, $document, $mdUtil, $mdThe
143144 // - In these cases the scope might not have been destroyed, which is why we
144145 // destroy it manually. An example of this can be having `md-visible="false"` and
145146 // adding tooltips while they're invisible. If `md-visible` becomes true, at some
146- // point, you'd usually get a lot of inputs .
147+ // point, you'd usually get a lot of tooltips .
147148 // - We use `.one`, not `.on`, because this only needs to fire once. If we were
148149 // using `.on`, it would get thrown into an infinite loop.
149150 // - This kicks off the scope's `$destroy` event which finishes the cleanup.
@@ -202,21 +203,21 @@ function MdTooltipDirective($timeout, $window, $$rAF, $document, $mdUtil, $mdThe
202203 var windowBlurHandler = function ( ) {
203204 elementFocusedOnWindowBlur = document . activeElement === parent [ 0 ] ;
204205 } ;
206+
205207 var elementFocusedOnWindowBlur = false ;
206208
207209 function windowScrollHandler ( ) {
208210 setVisible ( false ) ;
209211 }
210212
211- angular . element ( $window )
212- . on ( 'blur' , windowBlurHandler )
213- . on ( 'resize' , debouncedOnResize ) ;
213+ $$mdTooltipRegistry . register ( 'scroll' , windowScrollHandler , true ) ;
214+ $$mdTooltipRegistry . register ( 'blur' , windowBlurHandler ) ;
215+ $$mdTooltipRegistry . register ( 'resize' , debouncedOnResize ) ;
214216
215- document . addEventListener ( 'scroll' , windowScrollHandler , true ) ;
216217 scope . $on ( '$destroy' , function ( ) {
217- angular . element ( $window )
218- . off ( 'blur' , windowBlurHandler )
219- . off ( 'resize' , debouncedOnResize ) ;
218+ $$mdTooltipRegistry . deregister ( 'scroll' , windowScrollHandler , true ) ;
219+ $$mdTooltipRegistry . deregister ( 'blur' , windowBlurHandler ) ;
220+ $$mdTooltipRegistry . deregister ( 'resize' , debouncedOnResize ) ;
220221
221222 parent
222223 . off ( ENTER_EVENTS , enterHandler )
@@ -225,7 +226,6 @@ function MdTooltipDirective($timeout, $window, $$rAF, $document, $mdUtil, $mdThe
225226
226227 // Trigger the handler in case any the tooltip was still visible.
227228 leaveHandler ( ) ;
228- document . removeEventListener ( 'scroll' , windowScrollHandler , true ) ;
229229 attributeObserver && attributeObserver . disconnect ( ) ;
230230 } ) ;
231231
@@ -248,6 +248,7 @@ function MdTooltipDirective($timeout, $window, $$rAF, $document, $mdUtil, $mdThe
248248 }
249249 }
250250 } ;
251+
251252 var leaveHandler = function ( ) {
252253 var autohide = scope . hasOwnProperty ( 'autohide' ) ? scope . autohide : attr . hasOwnProperty ( 'mdAutohide' ) ;
253254
@@ -266,6 +267,7 @@ function MdTooltipDirective($timeout, $window, $$rAF, $document, $mdUtil, $mdThe
266267 }
267268 mouseActive = false ;
268269 } ;
270+
269271 var mousedownHandler = function ( ) {
270272 mouseActive = true ;
271273 } ;
@@ -386,3 +388,78 @@ function MdTooltipDirective($timeout, $window, $$rAF, $document, $mdUtil, $mdThe
386388 }
387389
388390}
391+
392+ /**
393+ * Service that is used to reduce the amount of listeners that are being
394+ * registered on the `window` by the tooltip component. Works by collecting
395+ * the individual event handlers and dispatching them from a global handler.
396+ *
397+ * @ngInject
398+ */
399+ function MdTooltipRegistry ( ) {
400+ var listeners = { } ;
401+ var ngWindow = angular . element ( window ) ;
402+
403+ return {
404+ register : register ,
405+ deregister : deregister
406+ } ;
407+
408+ /**
409+ * Global event handler that dispatches the registered
410+ * handlers in the service.
411+ * @param {Event } event Event object passed in by the browser.
412+ */
413+ function globalEventHandler ( event ) {
414+ if ( listeners [ event . type ] ) {
415+ listeners [ event . type ] . forEach ( function ( currentHandler ) {
416+ currentHandler . call ( this , event ) ;
417+ } , this ) ;
418+ }
419+ }
420+
421+ /**
422+ * Registers a new handler with the service.
423+ * @param {String } type Type of event to be registered.
424+ * @param {Function } handler Event handler
425+ * @param {Boolean } useCapture Whether to use event capturing.
426+ */
427+ function register ( type , handler , useCapture ) {
428+ var array = listeners [ type ] = listeners [ type ] || [ ] ;
429+
430+ if ( ! array . length ) {
431+ if ( useCapture ) {
432+ window . addEventListener ( type , globalEventHandler , true ) ;
433+ } else {
434+ ngWindow . on ( type , globalEventHandler ) ;
435+ }
436+ }
437+
438+ if ( array . indexOf ( handler ) === - 1 ) {
439+ array . push ( handler ) ;
440+ }
441+ }
442+
443+ /**
444+ * Removes an event handler from the service.
445+ * @param {String } type Type of event handler.
446+ * @param {Function } handler The event handler itself.
447+ * @param {Boolean } useCapture Whether the event handler used event capturing.
448+ */
449+ function deregister ( type , handler , useCapture ) {
450+ var array = listeners [ type ] ;
451+ var index = array ? array . indexOf ( handler ) : - 1 ;
452+
453+ if ( index > - 1 ) {
454+ array . splice ( index , 1 ) ;
455+
456+ if ( array . length === 0 ) {
457+ if ( useCapture ) {
458+ window . removeEventListener ( type , globalEventHandler , true ) ;
459+ } else {
460+ ngWindow . off ( type , globalEventHandler ) ;
461+ }
462+ }
463+ }
464+ }
465+ }
0 commit comments