UNPKG

kibana-123

Version:

Kibana is an open source (Apache Licensed), browser based analytics and search dashboard for Elasticsearch. Kibana is a snap to setup and start using. Kibana strives to be easy to get started with, while also being flexible and powerful, just like Elastic

134 lines (112 loc) 3.9 kB
import $ from 'jquery'; import _ from 'lodash'; import uiModules from 'ui/modules'; const SCROLLER_HEIGHT = 20; /** * This directive adds a fixed horizontal scrollbar to the bottom of the window that proxies its scroll events * to the target element's real scrollbar. This is useful when the target element's horizontal scrollbar * might be waaaay down the page, like the doc table on Discover. */ uiModules .get('kibana') .directive('fixedScroll', function (debounce) { return { restrict: 'A', link: function ($scope, $el) { let $window = $(window); let $scroller = $('<div class="fixed-scroll-scroller">').height(SCROLLER_HEIGHT); /** * Remove the listeners bound in listen() * @type {function} */ let unlisten = _.noop; /** * Listen for scroll events on the $scroller and the $el, sets unlisten() * * unlisten must be called before calling or listen() will throw an Error * * Since the browser emits "scroll" events after setting scrollLeft * the listeners also prevent tug-of-war * * @throws {Error} If unlisten was not called first * @return {undefined} */ function listen() { if (unlisten !== _.noop) { throw new Error('fixedScroll listeners were not cleaned up properly before re-listening!'); } let blockTo; function bind($from, $to) { function handler() { if (blockTo === $to) return (blockTo = null); $to.scrollLeft((blockTo = $from).scrollLeft()); } $from.on('scroll', handler); return function () { $from.off('scroll', handler); }; } unlisten = _.flow( bind($el, $scroller), bind($scroller, $el), function () { unlisten = _.noop; } ); } /** * Revert DOM changes and event listeners * @return {undefined} */ function cleanUp() { unlisten(); $scroller.detach(); $el.css('padding-bottom', 0); } /** * Modify the DOM and attach event listeners based on need. * Is called many times to re-setup, must be idempotent * @return {undefined} */ function setup() { cleanUp(); const containerWidth = $el.width(); const contentWidth = $el.prop('scrollWidth'); const containerHorizOverflow = contentWidth - containerWidth; const elTop = $el.offset().top - $window.scrollTop(); const elBottom = elTop + $el.height(); const windowVertOverflow = elBottom - $window.height(); const requireScroller = containerHorizOverflow > 0 && windowVertOverflow > 0; if (!requireScroller) return; // push the content away from the scroller $el.css('padding-bottom', SCROLLER_HEIGHT); // fill the scroller with a dummy element that mimics the content $scroller .width(containerWidth) .html($('<div>').css({ width: contentWidth, height: SCROLLER_HEIGHT })) .insertAfter($el); // listen for scroll events listen(); } let width; let scrollWidth; function checkWidth() { const newScrollWidth = $el.prop('scrollWidth'); const newWidth = $el.width(); if (scrollWidth !== newScrollWidth || width !== newWidth) { $scope.$apply(setup); scrollWidth = newScrollWidth; width = newWidth; } } const debouncedCheckWidth = debounce(checkWidth, 100, { invokeApply: false, }); $scope.$watch(debouncedCheckWidth); // cleanup when the scope is destroyed $scope.$on('$destroy', function () { cleanUp(); debouncedCheckWidth.cancel(); $scroller = $window = null; }); } }; });