Skip to content

Commit

Permalink
fix(resizer): resizer not always triggered in SF and show broken UI
Browse files Browse the repository at this point in the history
- in Salesforce we need to constantly check that the grid is resized properly and the previous code was doing that but we were stopping sometime too early and in some rare occasion it was later showing a grid with just the 1st column being rendered (see print screen) and this simply requires another resize call so we'll increase the timeout (1 hour) for that check and add a 2nd condition check to cover all use cases
  • Loading branch information
ghiscoding committed Aug 10, 2021
1 parent f1cbd52 commit 89fc62e
Show file tree
Hide file tree
Showing 5 changed files with 59 additions and 10 deletions.
2 changes: 1 addition & 1 deletion packages/common/src/interfaces/slickGrid.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ export interface SlickGrid {
getPreHeaderPanelRight(): HTMLElement;

/** Get rendered range */
getRenderedRange(viewportTop: number, viewportLeft: number): { top: number; bottom: number; leftPx: number; rightPx: number; };
getRenderedRange(viewportTop?: number, viewportLeft?: number): { top: number; bottom: number; leftPx: number; rightPx: number; };

/** Get scrollbar dimensions */
getScrollbarDimensions(): { height: number; width: number; };
Expand Down
34 changes: 34 additions & 0 deletions packages/common/src/services/__tests__/resizer.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ const mockDataView = {
init: jest.fn(),
destroy: jest.fn(),
getItemMetadata: jest.fn(),
getItemCount: jest.fn(),
getItems: jest.fn(),
};

Expand All @@ -40,6 +41,7 @@ const gridStub = {
getColumnIndex: jest.fn(),
getColumns: jest.fn(),
getOptions: jest.fn(),
getRenderedRange: jest.fn(),
getViewports: jest.fn(),
getData: () => mockDataView,
getUID: () => GRID_UID,
Expand Down Expand Up @@ -636,6 +638,38 @@ describe('Resizer Service', () => {
}, 15);
});

it('should try to resize grid when its UI is deemed broken by the 2nd condition check of "getRenderedRange"', (done) => {
const resizeSpy = jest.spyOn(service, 'resizeGrid').mockReturnValue(Promise.resolve({ height: 150, width: 350 }));
Object.defineProperty(document.querySelector(`.${GRID_UID}`), 'offsetParent', { writable: true, configurable: true, value: 55 });
jest.spyOn(mockDataView, 'getItemCount').mockReturnValue(99);
jest.spyOn(gridStub, 'getRenderedRange').mockReturnValue({ top: 0, bottom: 0, leftPx: 0, rightPx: 0 });

mockGridOptions.autoFixResizeTimeout = 10;
mockGridOptions.autoFixResizeRequiredGoodCount = 5;
mockGridOptions.autoFixResizeWhenBrokenStyleDetected = true;
service.intervalRetryDelay = 1;

const divHeaderElm = divContainer.querySelector('.slick-header') as HTMLDivElement;
const divViewportElm = divContainer.querySelector('.slick-viewport') as HTMLDivElement;
jest.spyOn(divContainer, 'getBoundingClientRect').mockReturnValue({ top: 10, left: 20 } as unknown as DOMRect);
jest.spyOn(divHeaderElm, 'getBoundingClientRect').mockReturnValue({ top: 5, left: 25 } as unknown as DOMRect);
jest.spyOn(divViewportElm, 'getBoundingClientRect').mockReturnValue({ top: 98, left: 25 } as unknown as DOMRect);
divHeaderElm.style.top = '5px';
divHeaderElm.style.left = '25px';
divContainer.style.top = '10px';
divContainer.style.left = '20px';
service.init(gridStub, divContainer);

setTimeout(() => {
expect(divContainer.outerHTML).toBeTruthy();
expect(resizeSpy).toHaveBeenCalled();
expect(resizeSpy).toHaveBeenNthCalledWith(2);
expect(resizeSpy).toHaveBeenNthCalledWith(3);
done();
service.requestStopOfAutoFixResizeGrid();
}, 20);
});

it('should try to resize grid when its UI is deemed broken but expect an error shown in the console when "resizeGrid" throws an error', (done) => {
const consoleSpy = jest.spyOn(global.console, 'log').mockReturnValue();
const promise = new Promise((_resolve, reject) => setTimeout(() => reject('some error'), 0));
Expand Down
31 changes: 23 additions & 8 deletions packages/common/src/services/resizer.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -586,12 +586,15 @@ export class ResizerService {
const autoFixResizeTimeout = this.gridOptions?.autoFixResizeTimeout ?? (5 * 60 * 60); // interval is 200ms, so 4x is 1sec, so (4 * 60 * 60 = 60min)
const autoFixResizeRequiredGoodCount = this.gridOptions?.autoFixResizeRequiredGoodCount ?? 5;

const headerElm = document.querySelector<HTMLDivElement>(`.${this.gridUid} .slick-header`);
const viewportElm = document.querySelector<HTMLDivElement>(`.${this.gridUid} .slick-viewport`);
const headerElm = this._gridParentContainerElm.querySelector<HTMLDivElement>(`.${this.gridUid} .slick-header`);
const viewportElm = this._gridParentContainerElm.querySelector<HTMLDivElement>(`.${this.gridUid} .slick-viewport`);
let intervalExecutionCounter = 0;
let resizeGoodCount = 0;

if (headerElm && viewportElm && this.gridOptions.autoFixResizeWhenBrokenStyleDetected) {
const dataLn = this.dataView.getItemCount();
const columns = this._grid.getColumns() || [];

this._intervalId = setInterval(async () => {
const headerTitleRowHeight = 44; // this one is set by SASS/CSS so let's hard code it
const headerPos = getHtmlElementOffset(headerElm);
Expand All @@ -613,26 +616,38 @@ export class ResizerService {
const containerElmOffset = getHtmlElementOffset(this._gridParentContainerElm);
let isResizeRequired = (headerPos?.top === 0 || ((headerOffsetTop - viewportOffsetTop) > 2) || (containerElmOffset?.left === 0 && containerElmOffset?.top === 0)) ? true : false;

// another condition for a required resize is when the grid is hidden (not in current tab) then its "rightPx" rendered range will be 0px
// if that's the case then we know the grid is still hidden and we need to resize it whenever it becomes visible (when its "rightPx" becomes greater than 0 then it's visible)
const renderedRangeRightPx = this._grid.getRenderedRange?.()?.rightPx ?? 0;
if (!isResizeRequired && dataLn > 0 && renderedRangeRightPx === 0 && columns.length > 1) {
isResizeRequired = true;
}

// user could choose to manually stop the looped of auto resize fix
if (this._isStopResizeIntervalRequested) {
isResizeRequired = false;
intervalExecutionCounter = autoFixResizeTimeout;
}

// visible grid (shown to the user and not hidden in another Tab will have an offsetParent defined)
const isGridVisible = !!(document.querySelector<HTMLDivElement>(`.${this.gridUid}`)?.offsetParent ?? false);
let isGridVisible = !!(document.querySelector<HTMLDivElement>(`.${this.gridUid}`)?.offsetParent ?? false);

if (isGridVisible && (isResizeRequired || resizeGoodCount < autoFixResizeRequiredGoodCount) && (containerElmOffset?.left > 0 || containerElmOffset?.top > 0)) {
if (isGridVisible && (isResizeRequired || containerElmOffset?.left === 0 || containerElmOffset?.top === 0)) {
await this.resizeGrid();

// make sure the grid is still visible after doing the resize and if so we consider it a good resize (it might not be visible if user quickly switch to another Tab)
const isGridStillVisible = !!(document.querySelector<HTMLDivElement>(`.${this.gridUid}`)?.offsetParent ?? false);
if (isGridStillVisible) {
// make sure the grid is still visible after doing the resize
isGridVisible = !!(document.querySelector<HTMLDivElement>(`.${this.gridUid}`)?.offsetParent ?? false);
if (isGridVisible) {
isResizeRequired = false;
resizeGoodCount++;
}
}

// make sure the grid is still visible after optionally doing a resize
// if it visible then we can consider it a good resize (it might not be visible if user quickly switch to another Tab)
if (isGridVisible) {
resizeGoodCount++;
}

if (isGridVisible && !isResizeRequired && (resizeGoodCount >= autoFixResizeRequiredGoodCount || intervalExecutionCounter++ >= autoFixResizeTimeout)) {
clearInterval(this._intervalId); // stop the interval if we don't need resize or if we passed let say 70min
}
Expand Down
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export const SalesforceGlobalGridOptions = {
autoEdit: true, // true single click (false for double-click)
autoCommitEdit: true,
autoFixResizeTimeout: 5 * 60 * 60, // interval is 200ms, so 4x is 1sec, so (5 * 60 * 60 = 60min)
autoFixResizeRequiredGoodCount: 5,
autoFixResizeRequiredGoodCount: 5 * 60 * 60, // make it the same as the interval timeout, this is equivalent to say don't stop until the timeout is over
autoFixResizeWhenBrokenStyleDetected: true,
cellValueCouldBeUndefined: true,
eventNamingStyle: EventNamingStyle.lowerCaseWithoutOnPrefix,
Expand Down

0 comments on commit 89fc62e

Please sign in to comment.