Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 31 additions & 3 deletions src/embed/app.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1720,6 +1720,36 @@ describe('App embed tests', () => {
addEventListenerSpy.mockRestore();
});

test('should listen to scroll and resize changes from scrollable iframe ancestors', async () => {
const scrollContainer = getRootEl();
scrollContainer.style.overflow = 'auto';

const scrollContainerAddEventListenerSpy = jest.spyOn(scrollContainer, 'addEventListener');
const resizeObserveSpy = jest.fn();
const resizeDisconnectSpy = jest.fn();
(window as any).ResizeObserver = jest.fn().mockImplementation(() => ({
observe: resizeObserveSpy,
disconnect: resizeDisconnectSpy,
}));

const appEmbed = new AppEmbed(getRootEl(), {
...defaultViewConfig,
fullHeight: true,
lazyLoadingForFullHeight: true,
enableScrollableContainerLazyLoading: true,
} as AppViewConfig);

await appEmbed.render();

await executeAfterWait(() => {
expect(scrollContainerAddEventListenerSpy).toHaveBeenCalledWith('scroll', expect.any(Function));
expect(resizeObserveSpy).toHaveBeenCalledWith(scrollContainer);
}, 100);

appEmbed.destroy();
expect(resizeDisconnectSpy).toHaveBeenCalled();
});

test('should remove window event listeners on destroy when fullHeight and lazyLoadingForFullHeight are enabled', async () => {
const removeEventListenerSpy = jest.spyOn(window, 'removeEventListener');

Expand All @@ -1734,7 +1764,7 @@ describe('App embed tests', () => {
appEmbed.destroy();

expect(removeEventListenerSpy).toHaveBeenCalledWith('resize', expect.any(Function));
expect(removeEventListenerSpy).toHaveBeenCalledWith('scroll', expect.any(Function));
expect(removeEventListenerSpy).toHaveBeenCalledWith('scroll', expect.any(Function), true);

removeEventListenerSpy.mockRestore();
});
Expand Down Expand Up @@ -1965,5 +1995,3 @@ describe('AppEmbed visualOverrides tests', () => {
await testVisualOverridesInEmbed(appEmbed, visualOverrides);
});
});


56 changes: 52 additions & 4 deletions src/embed/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
*/

import { logger } from '../utils/logger';
import { calculateVisibleElementData, getQueryParamString, isUndefined, isValidCssMargin, setParamIfDefined } from '../utils';
import { calculateVisibleElementData, getEffectiveClippingAncestors, getQueryParamString, getScrollableAncestors, isUndefined, isValidCssMargin, setParamIfDefined } from '../utils';
import {
Param,
DOMSelector,
Expand Down Expand Up @@ -614,7 +614,7 @@
* @version SDK: 1.46.0 | ThoughtSpot: 26.3.0.cl
* @example
* ```js
* // Replace <EmbedComponent> with embed component name. For example, AppEmbed or LiveboardEmbed

Check warning on line 617 in src/embed/app.ts

View workflow job for this annotation

GitHub Actions / build

Comments may not exceed 90 characters
* const embed = new <EmbedComponent>('#tsEmbed', {
* ... // other embed view config
* isLiveboardXLSXCSVDownloadEnabled: true,
Expand Down Expand Up @@ -656,6 +656,17 @@
* ```
*/
lazyLoadingForFullHeight?: boolean;
/**
* This flag is used to enable container-aware full height lazy loading.
*
* Use this when the embed is rendered inside a scrollable or clipping
* container instead of relying on the browser window as the only viewport.
*
* @type {boolean}
* @default false
* @hidden
*/
enableScrollableContainerLazyLoading?: boolean;

/**
* The margin to be used for lazy loading.
Expand Down Expand Up @@ -826,6 +837,10 @@

private defaultHeight = 500;

private lazyLoadScrollContainers: HTMLElement[] = [];

private lazyLoadResizeObserver: ResizeObserver | undefined;

constructor(domSelector: DOMSelector, viewConfig: AppViewConfig) {
viewConfig.embedComponentType = 'AppEmbed';
super(domSelector, viewConfig);
Expand Down Expand Up @@ -1130,7 +1145,10 @@
}

private sendFullHeightLazyLoadData = () => {
const data = calculateVisibleElementData(this.iFrame);
const data = calculateVisibleElementData(
this.iFrame,
this.viewConfig.enableScrollableContainerLazyLoading,
);
// this should be fired only if the lazyLoadingForFullHeight and fullHeight are true
if(this.viewConfig.lazyLoadingForFullHeight && this.viewConfig.fullHeight){
this.trigger(HostEvent.VisibleEmbedCoordinates, data);
Expand All @@ -1145,7 +1163,10 @@
*/
private requestVisibleEmbedCoordinatesHandler = (data: MessagePayload, responder: any) => {
logger.info('Sending RequestVisibleEmbedCoordinates', data);
const visibleCoordinatesData = calculateVisibleElementData(this.iFrame);
const visibleCoordinatesData = calculateVisibleElementData(
this.iFrame,
this.viewConfig.enableScrollableContainerLazyLoading,
);
responder({ type: EmbedEvent.RequestVisibleEmbedCoordinates, data: visibleCoordinatesData });
}

Expand Down Expand Up @@ -1200,7 +1221,7 @@
return;
}
this.setIFrameHeight(frameHeight || this.defaultHeight);
};

Check warning on line 1224 in src/embed/app.ts

View workflow job for this annotation

GitHub Actions / build

Comments may not exceed 80 characters

/**
* Gets the ThoughtSpot route of the page for a particular page ID.
Expand Down Expand Up @@ -1294,17 +1315,44 @@
}

private registerLazyLoadEvents() {
if (!this.iFrame) {
return;
}
if (this.viewConfig.fullHeight && this.viewConfig.lazyLoadingForFullHeight) {
Comment thread
ruchI9897 marked this conversation as resolved.
this.unregisterLazyLoadEvents();
// TODO: Use passive: true, install modernizr to check for passive
window.addEventListener('resize', this.sendFullHeightLazyLoadData);
window.addEventListener('scroll', this.sendFullHeightLazyLoadData, true);
if (!this.viewConfig.enableScrollableContainerLazyLoading) {
return;
}
this.lazyLoadScrollContainers = getScrollableAncestors(this.iFrame);
this.lazyLoadScrollContainers.forEach((scrollContainer) => {
scrollContainer.addEventListener('scroll', this.sendFullHeightLazyLoadData);
});
if (typeof ResizeObserver !== 'undefined') {
const resizeTargets = new Set([
this.iFrame.parentElement,
...getEffectiveClippingAncestors(this.iFrame),
].filter(Boolean) as HTMLElement[]);
this.lazyLoadResizeObserver = new ResizeObserver(this.sendFullHeightLazyLoadData);
resizeTargets.forEach((resizeTarget) => {
this.lazyLoadResizeObserver.observe(resizeTarget);
});
}
}
}

private unregisterLazyLoadEvents() {
if (this.viewConfig.fullHeight && this.viewConfig.lazyLoadingForFullHeight) {
window.removeEventListener('resize', this.sendFullHeightLazyLoadData);
window.removeEventListener('scroll', this.sendFullHeightLazyLoadData);
window.removeEventListener('scroll', this.sendFullHeightLazyLoadData, true);
this.lazyLoadResizeObserver?.disconnect();
this.lazyLoadResizeObserver = undefined;
this.lazyLoadScrollContainers.forEach((scrollContainer) => {
scrollContainer.removeEventListener('scroll', this.sendFullHeightLazyLoadData);
});
this.lazyLoadScrollContainers = [];
}
}

Expand Down
33 changes: 32 additions & 1 deletion src/embed/liveboard.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1835,6 +1835,37 @@ describe('Liveboard/viz embed tests', () => {
addEventListenerSpy.mockRestore();
});

test('should listen to scroll and resize changes from scrollable iframe ancestors', async () => {
const scrollContainer = getRootEl();
scrollContainer.style.overflow = 'auto';

const scrollContainerAddEventListenerSpy = jest.spyOn(scrollContainer, 'addEventListener');
const resizeObserveSpy = jest.fn();
const resizeDisconnectSpy = jest.fn();
(window as any).ResizeObserver = jest.fn().mockImplementation(() => ({
observe: resizeObserveSpy,
disconnect: resizeDisconnectSpy,
}));

const liveboardEmbed = new LiveboardEmbed(getRootEl(), {
...defaultViewConfig,
liveboardId,
fullHeight: true,
lazyLoadingForFullHeight: true,
enableScrollableContainerLazyLoading: true,
} as LiveboardViewConfig);

await liveboardEmbed.render();

await executeAfterWait(() => {
expect(scrollContainerAddEventListenerSpy).toHaveBeenCalledWith('scroll', expect.any(Function));
expect(resizeObserveSpy).toHaveBeenCalledWith(scrollContainer);
}, 100);

liveboardEmbed.destroy();
expect(resizeDisconnectSpy).toHaveBeenCalled();
});

test('should remove window event listeners on destroy when fullHeight and lazyLoadingForFullHeight are enabled', async () => {
const removeEventListenerSpy = jest.spyOn(window, 'removeEventListener');

Expand All @@ -1850,7 +1881,7 @@ describe('Liveboard/viz embed tests', () => {
liveboardEmbed.destroy();

expect(removeEventListenerSpy).toHaveBeenCalledWith('resize', expect.anything());
expect(removeEventListenerSpy).toHaveBeenCalledWith('scroll', expect.anything());
expect(removeEventListenerSpy).toHaveBeenCalledWith('scroll', expect.anything(), true);

removeEventListenerSpy.mockRestore();
});
Expand Down
55 changes: 51 additions & 4 deletions src/embed/liveboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import {
EmbedErrorCodes,
ContextType,
} from '../types';
import { calculateVisibleElementData, getQueryParamString, isUndefined, isValidCssMargin, setParamIfDefined } from '../utils';
import { calculateVisibleElementData, getEffectiveClippingAncestors, getQueryParamString, getScrollableAncestors, isUndefined, isValidCssMargin, setParamIfDefined } from '../utils';
import { getAuthPromise } from './base';
import { TsEmbed, V1Embed } from './ts-embed';
import { addPreviewStylesIfNotPresent } from '../utils/global-styles';
Expand Down Expand Up @@ -453,6 +453,16 @@ export interface LiveboardViewConfig extends BaseViewConfig, LiveboardOtherViewC
* ```
*/
lazyLoadingForFullHeight?: boolean;
/**
* This flag is used to enable container-aware full height lazy loading.
*
* Use this when the embed is rendered inside a scrollable or clipping
* container instead of relying on the browser window as the only viewport.
*
* @type {boolean}
* @default false
*/
enableScrollableContainerLazyLoading?: boolean;
/**
* The margin to be used for lazy loading.
*
Expand Down Expand Up @@ -569,6 +579,10 @@ export class LiveboardEmbed extends V1Embed {

private defaultHeight = 500;

private lazyLoadScrollContainers: HTMLElement[] = [];

private lazyLoadResizeObserver: ResizeObserver | undefined;


constructor(domSelector: DOMSelector, viewConfig: LiveboardViewConfig) {
viewConfig.embedComponentType = 'LiveboardEmbed';
Expand Down Expand Up @@ -825,7 +839,10 @@ export class LiveboardEmbed extends V1Embed {
}

private sendFullHeightLazyLoadData = () => {
const data = calculateVisibleElementData(this.iFrame);
const data = calculateVisibleElementData(
this.iFrame,
this.viewConfig.enableScrollableContainerLazyLoading,
);
// this should be fired only if the lazyLoadingForFullHeight and fullHeight are true
if(this.viewConfig.lazyLoadingForFullHeight && this.viewConfig.fullHeight){
this.trigger(HostEvent.VisibleEmbedCoordinates, data);
Expand All @@ -840,7 +857,10 @@ export class LiveboardEmbed extends V1Embed {
*/
private requestVisibleEmbedCoordinatesHandler = (data: MessagePayload, responder: any) => {
logger.info('Sending RequestVisibleEmbedCoordinates', data);
const visibleCoordinatesData = calculateVisibleElementData(this.iFrame);
const visibleCoordinatesData = calculateVisibleElementData(
this.iFrame,
this.viewConfig.enableScrollableContainerLazyLoading,
);
responder({ type: EmbedEvent.RequestVisibleEmbedCoordinates, data: visibleCoordinatesData });
}

Expand Down Expand Up @@ -1023,17 +1043,44 @@ export class LiveboardEmbed extends V1Embed {
}

private registerLazyLoadEvents() {
if(!this.iFrame) {
return;
}
if (this.viewConfig.fullHeight && this.viewConfig.lazyLoadingForFullHeight) {
Comment thread
ruchI9897 marked this conversation as resolved.
Comment thread
ruchI9897 marked this conversation as resolved.
this.unregisterLazyLoadEvents();
// TODO: Use passive: true, install modernizr to check for passive
window.addEventListener('resize', this.sendFullHeightLazyLoadData);
window.addEventListener('scroll', this.sendFullHeightLazyLoadData, true);
if (!this.viewConfig.enableScrollableContainerLazyLoading) {
return;
}
this.lazyLoadScrollContainers = getScrollableAncestors(this.iFrame);
this.lazyLoadScrollContainers.forEach((scrollContainer) => {
scrollContainer.addEventListener('scroll', this.sendFullHeightLazyLoadData);
});
if (typeof ResizeObserver !== 'undefined') {
const resizeTargets = new Set([
this.iFrame.parentElement,
...getEffectiveClippingAncestors(this.iFrame),
].filter(Boolean) as HTMLElement[]);
this.lazyLoadResizeObserver = new ResizeObserver(this.sendFullHeightLazyLoadData);
resizeTargets.forEach((resizeTarget) => {
this.lazyLoadResizeObserver.observe(resizeTarget);
});
}
}
}

private unregisterLazyLoadEvents() {
if (this.viewConfig.fullHeight && this.viewConfig.lazyLoadingForFullHeight) {
window.removeEventListener('resize', this.sendFullHeightLazyLoadData);
window.removeEventListener('scroll', this.sendFullHeightLazyLoadData);
window.removeEventListener('scroll', this.sendFullHeightLazyLoadData, true);
this.lazyLoadResizeObserver?.disconnect();
this.lazyLoadResizeObserver = undefined;
this.lazyLoadScrollContainers.forEach((scrollContainer) => {
scrollContainer.removeEventListener('scroll', this.sendFullHeightLazyLoadData);
});
this.lazyLoadScrollContainers = [];
}
}

Expand Down
Loading
Loading