Listen to resize events in <visualize> (#16048)

* Add enable method to ResizeChecker

* Listen in visualize for resize events

* Listen on resize event

* Fix rendering timing issue
This commit is contained in:
Tim Roes 2018-01-16 19:47:35 +01:00 committed by GitHub
parent bc3f36095f
commit 2116eecb8a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 91 additions and 17 deletions

View file

@ -41,7 +41,7 @@ describe('Resize Checker', () => {
return listener;
};
return { EventEmitter, createEl, createChecker, createListener };
return { EventEmitter, createEl, createChecker, createListener, ResizeChecker };
};
}));
@ -82,6 +82,53 @@ describe('Resize Checker', () => {
});
});
describe('enable/disabled state', () => {
it('should not trigger events while disabled', async () => {
const { createEl, createListener, ResizeChecker } = setup();
const el = createEl();
const checker = new ResizeChecker(el, { disabled: true });
const listener = createListener();
checker.on('resize', listener);
expect(listener.notCalled).to.be(true);
$(el).height(100);
await delay(1000);
expect(listener.notCalled).to.be(true);
});
it('should trigger resize events after calling enable', async () => {
const { createEl, createListener, ResizeChecker } = setup();
const el = createEl();
const checker = new ResizeChecker(el, { disabled: true });
const listener = createListener();
checker.on('resize', listener);
expect(listener.notCalled).to.be(true);
checker.enable();
$(el).height(100);
await listener.firstCallPromise;
expect(listener.calledOnce).to.be(true);
});
it('should not trigger the first time after enable when the size does not change', async () => {
const { createEl, createListener, ResizeChecker } = setup();
const el = createEl();
const checker = new ResizeChecker(el, { disabled: true });
const listener = createListener();
checker.on('resize', listener);
expect(listener.notCalled).to.be(true);
$(el).height(250);
checker.enable();
$(el).height(250);
await delay(1000);
expect(listener.notCalled).to.be(true);
});
});
describe('#modifySizeWithoutTriggeringResize()', () => {
it(`does not emit "resize" events caused by the block`, async () => {
const { createChecker, createEl, createListener } = setup();

View file

@ -30,16 +30,11 @@ export function ResizeCheckerProvider(Private) {
* same reason, but for the editors.
*/
return class ResizeChecker extends EventEmitter {
constructor(el) {
constructor(el, args = {}) {
super();
this._el = validateElArg(el);
// the width and height of the element that we expect to see
// on the next resize notification. If it matches the size at
// the time of the notifications then it we will be ignored.
this._expectedSize = getSize(this._el);
this._observer = new ResizeObserver(() => {
if (this._expectedSize) {
const sameSize = isEqual(getSize(this._el), this._expectedSize);
@ -54,6 +49,21 @@ export function ResizeCheckerProvider(Private) {
this.emit('resize');
});
// Only enable the checker immediately if args.disabled wasn't set to true
if (!args.disabled) {
this.enable();
}
}
enable() {
if (this._destroyed) {
// Don't allow enabling an already destroyed resize checker
return;
}
// the width and height of the element that we expect to see
// on the next resize notification. If it matches the size at
// the time of starting observing then it we will be ignored.
this._expectedSize = getSize(this._el);
this._observer.observe(this._el);
}

View file

@ -34,7 +34,6 @@ uiModules
},
template: visualizationTemplate,
link: function ($scope, $el) {
const resizeChecker = new ResizeChecker($el);
//todo: lets make this a simple function call.
const getVisEl = jQueryGetter('.visualize-chart');
const getVisContainer = jQueryGetter('.vis-container');
@ -116,7 +115,6 @@ uiModules
});
$scope.$on('$destroy', () => {
resizeChecker.destroy();
visualization.destroy();
renderSubscription.unsubscribe();
});
@ -130,16 +128,18 @@ uiModules
$scope.$watchGroup(['visData', 'vis.params'], onChangeListener);
const resizeChecker = new ResizeChecker($el);
resizeChecker.on('resize', () => {
$scope.$emit('render');
});
$scope.uiState.on('change', onChangeListener);
$scope.$on('$destroy', () => {
resizeChecker.destroy();
$scope.uiState.off('change', onChangeListener);
});
}
resizeChecker.on('resize', () => {
$scope.$emit('render');
});
function jQueryGetter(selector) {
return function () {
const $sel = $el.find(selector);

View file

@ -10,6 +10,8 @@ import './visualization';
import './visualization_editor';
import { FilterBarQueryFilterProvider } from 'ui/filter_bar/query_filter';
import { ResizeCheckerProvider } from 'ui/resize_checker';
import {
isTermSizeZeroError,
@ -17,11 +19,12 @@ import {
uiModules
.get('kibana/directive', ['ngSanitize'])
.directive('visualize', function (Notifier, Private, timefilter, getAppState, Promise) {
.directive('visualize', function ($timeout, Notifier, Private, timefilter, getAppState, Promise) {
const notify = new Notifier({ location: 'Visualize' });
const requestHandlers = Private(VisRequestHandlersRegistryProvider);
const responseHandlers = Private(VisResponseHandlersRegistryProvider);
const queryFilter = Private(FilterBarQueryFilterProvider);
const ResizeChecker = Private(ResizeCheckerProvider);
function getHandler(from, name) {
if (typeof name === 'function') return name;
@ -36,14 +39,22 @@ uiModules
savedObj: '=?',
appState: '=?',
uiState: '=?',
savedId: '=?',
timeRange: '=?',
},
template: visualizeTemplate,
link: async function ($scope) {
link: async function ($scope, $el) {
let destroyed = false;
if (!$scope.savedObj) throw(`saved object was not provided to <visualize> directive`);
if (!$scope.appState) $scope.appState = getAppState();
const resizeChecker = new ResizeChecker($el, { disabled: true });
$timeout(() => {
// We give the visualize one digest cycle time to actually render before
// we start tracking its size. If we don't do that, we cause a double
// initial rendering in editor mode.
resizeChecker.enable();
});
$scope.vis = $scope.savedObj.vis;
// Set the passed in uiState to the vis object. uiState reference should never be changed
@ -79,7 +90,10 @@ uiModules
const responseHandler = getHandler(responseHandlers, $scope.vis.type.responseHandler);
$scope.fetch = _.debounce(function () {
if (!$scope.vis.initialized || !$scope.savedObj) return;
// If destroyed == true the scope has already been destroyed, while this method
// was still waiting for its debounce, in this case we don't want to start
// fetching new data and rendering.
if (!$scope.vis.initialized || !$scope.savedObj || destroyed) return;
// searchSource is only there for courier request handler
requestHandler($scope.vis, $scope.appState, $scope.uiState, queryFilter, $scope.savedObj.searchSource)
.then(requestHandlerResponse => {
@ -166,14 +180,17 @@ uiModules
// checking if anything changed, that actually require a new fetch or return
// cached data otherwise.
$scope.uiState.on('change', $scope.fetch);
resizeChecker.on('resize', $scope.fetch);
// visualize needs to know about timeFilter
$scope.$listen(timefilter, 'fetch', $scope.fetch);
$scope.$on('$destroy', () => {
destroyed = true;
$scope.vis.removeListener('update', handleVisUpdate);
queryFilter.off('update', handleQueryUpdate);
$scope.uiState.off('change', $scope.fetch);
resizeChecker.destroy();
});
$scope.$watch('vis.initialized', $scope.fetch);