Skip to content

Commit

Permalink
Merge pull request #8839 from surveyjs/feature/2236-render-certain-el…
Browse files Browse the repository at this point in the history
…ement-and-scroll-to-it

Feature/2236-render-certain-element-and-scroll-to-it
  • Loading branch information
andrewtelnov authored Sep 28, 2024
2 parents a405807 + 88ec6ec commit 652fea4
Show file tree
Hide file tree
Showing 12 changed files with 712 additions and 146 deletions.
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
<ng-template #template>
<div class="sv-skeleton-element" [id]="element.id"></div>
<div class="sv-skeleton-element" [id]="element.id" [style.height]="element.skeletonHeight"></div>
</ng-template>
51 changes: 51 additions & 0 deletions packages/survey-core/src/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1171,6 +1171,57 @@ export class Base {
}
}

export class RenderingCompletedAwaiter {
constructor(private _elements: Array<Base>, private _renderedHandler: () => void, waitingTimeout = 100) {
this._elements.forEach(element => {
if (element.onElementRerendered) {
element.onElementRerendered.add(this._elementRenderedHandler);
this._elementsToRenderCount++;
}
});
if (this._elementsToRenderCount > 0) {
this._elementsToRenderTimer = setTimeout(() => {
if (this._elementsToRenderCount > 0) {
this.visibleElementsRendered();
}
}, waitingTimeout);
} else {
this.visibleElementsRendered();
}
}
private _elementsToRenderCount = 0;
private _elementsToRenderTimer: any = undefined;
private _elementRenderedHandler = (s: Base, o: any) => {
s.onElementRerendered?.remove(this._elementRenderedHandler);
this._elementsToRenderCount--;
if (this._elementsToRenderCount <= 0) {
this.visibleElementsRendered();
}
}
private stopWaitingForElementsRendering() {
if (this._elementsToRenderTimer) {
clearTimeout(this._elementsToRenderTimer);
this._elementsToRenderTimer = undefined;
}
this._elements.forEach(element => {
element.onElementRerendered?.remove(this._elementRenderedHandler);
});
this._elementsToRenderCount = 0;
}
private visibleElementsRendered(): void {
const renderedHandler = this._renderedHandler;
this.dispose();
if (typeof renderedHandler == "function") {
renderedHandler();
}
}
public dispose(): void {
this.stopWaitingForElementsRendering();
this._elements = undefined;
this._renderedHandler = undefined;
}
}

export class ArrayChanges<T = any> {
constructor(
public index: number,
Expand Down
104 changes: 78 additions & 26 deletions packages/survey-core/src/panel.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { property, propertyArray, Serializer } from "./jsonobject";
import { HashTable, Helpers } from "./helpers";
import { ArrayChanges, Base } from "./base";
import { ArrayChanges, Base, RenderingCompletedAwaiter } from "./base";
import {
ISurveyImpl,
IPage,
Expand Down Expand Up @@ -41,6 +41,9 @@ export class QuestionRowModel extends Base {
}
protected _scrollableParent: any = undefined;
protected _updateVisibility: any = undefined;
private get allowRendering(): boolean {
return !this.panel || !this.panel.survey || !(this.panel.survey as any)["isLazyRenderingSuspended"];
}
public startLazyRendering(rowContainerDiv: HTMLElement, findScrollableContainer = findScrollableParent): void {
if (!DomDocumentHelper.isAvailable()) return;
this._scrollableParent = findScrollableContainer(rowContainerDiv);
Expand All @@ -52,6 +55,9 @@ export class QuestionRowModel extends Base {
this.isNeedRender = !hasScroll;
if (hasScroll) {
this._updateVisibility = () => {
if (!this.allowRendering) {
return;
}
var isRowContainerDivVisible = isElementVisible(rowContainerDiv, 50);
if (!this.isNeedRender && isRowContainerDivVisible) {
this.isNeedRender = true;
Expand Down Expand Up @@ -188,6 +194,10 @@ export class QuestionRowModel extends Base {
if (this.elements[i].isVisible) {
visElements.push(this.elements[i]);
}
if (this.elements[i].isPanel || this.elements[i].getType() === "paneldynamic") {
this.setIsLazyRendering(false);
this.stopLazyRendering();
}
}
this.visibleElements = visElements;
return;
Expand Down Expand Up @@ -1323,11 +1333,22 @@ export class PanelModelBase extends SurveyElement<Question>
this.onVisibleChanged();
}
}
protected canRenderFirstRows(): boolean {
return this.isPage;
}
private isLazyRenderInRow(rowIndex: number): boolean {
if (!this.survey || !this.survey.isLazyRendering) return false;
return (
rowIndex >= this.survey.lazyRenderingFirstBatchSize ||
!this.canRenderFirstRows()
);
}
public createRowAndSetLazy(index: number): QuestionRowModel {
const row = this.createRow();
row.setIsLazyRendering(this.isLazyRenderInRow(index));
return row;
}
// TODO V2: make all createRow API private (at least protected) after removing DragDropPanelHelperV1
public createRow(): QuestionRowModel {
return new QuestionRowModel(this);
}
Expand Down Expand Up @@ -1535,16 +1556,6 @@ export class PanelModelBase extends SurveyElement<Question>
}
return result;
}
private isLazyRenderInRow(rowIndex: number): boolean {
if (!this.survey || !this.survey.isLazyRendering) return false;
return (
rowIndex >= this.survey.lazyRenderingFirstBatchSize ||
!this.canRenderFirstRows()
);
}
protected canRenderFirstRows(): boolean {
return this.isPage;
}
public getDragDropInfo(): any {
const page: PanelModelBase = <any>this.getPage(this.parent);
return !!page ? page.getDragDropInfo() : undefined;
Expand Down Expand Up @@ -1573,25 +1584,66 @@ export class PanelModelBase extends SurveyElement<Question>
}
}
}
public disableLazyRenderingBeforeElement(el?: IElement): void {
const row = el ? this.findRowByElement(el) : undefined;
const index = el ? this.rows.indexOf(row) : this.rows.length - 1;
for (let i = index; i >= 0; i--) {
const currentRow = this.rows[i];
if (currentRow.isNeedRender) {
break;
} else {
currentRow.isNeedRender = true;
currentRow.stopLazyRendering();
public getAllRows(): Array<QuestionRowModel> {
const allRows: Array<QuestionRowModel> = [];
this.rows.forEach(row => {
const nestedRows: Array<QuestionRowModel> = [];
row.elements.forEach(element => {
if (element.isPanel) {
nestedRows.push(...(<any>element as PanelModelBase).getAllRows());
} else if (element.getType() == "paneldynamic") {
if (this.isDesignMode) {
nestedRows.push(...(element as any).template.getAllRows());
} else {
(element as any).panels.forEach((panel: PanelModelBase) => nestedRows.push(...panel.getAllRows()));
}
}
});
allRows.push(row);
allRows.push(...nestedRows);
});
return allRows;
}
private findRowAndIndexByElement(el: IElement, rows?: Array<QuestionRowModel>): { row: QuestionRowModel, index: number } {
if (!el) {
return { row: undefined, index: this.rows.length - 1 };
}
rows = rows || this.rows;
for (var i = 0; i < rows.length; i++) {
if (rows[i].elements.indexOf(el) > -1) return { row: rows[i], index: i };
}
return { row: null, index: -1 };
}
private forceRenderRow(row: QuestionRowModel): void {
if (!!row && !row.isNeedRender) {
row.isNeedRender = true;
row.stopLazyRendering();
}
}
public forceRenderElement(el: IElement, elementsRendered = () => { }, gap = 0): void {
const allRows = this.getAllRows();
const { row, index } = this.findRowAndIndexByElement(el, allRows);
if (index >= 0 && index < allRows.length) {
const rowsToRender = [];
rowsToRender.push(row);
for (let i = index - 1; i >= index - gap && i >= 0; i--) {
rowsToRender.push(allRows[i]);
}
this.forceRenderRows(rowsToRender, elementsRendered);
}
}
public forceRenderRows(rows: Array<QuestionRowModel>, elementsRendered = () => { }): void {
const rowRenderedHandler = (rowsCount => () => {
rowsCount--;
if (rowsCount <= 0) {
elementsRendered();
}
})(rows.length);
rows.forEach(row => new RenderingCompletedAwaiter(row.visibleElements as any, rowRenderedHandler));
rows.forEach(row => this.forceRenderRow(row));
}
public findRowByElement(el: IElement): QuestionRowModel {
var rows = this.rows;
for (var i = 0; i < rows.length; i++) {
if (rows[i].elements.indexOf(el) > -1) return rows[i];
}
return null;
return this.findRowAndIndexByElement(el).row;
}
elementWidthChanged(el: IElement) {
if (this.isLoadingFromJson) return;
Expand Down
48 changes: 34 additions & 14 deletions packages/survey-core/src/survey-element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -188,31 +188,43 @@ export class SurveyElement<E = any> extends SurveyElementCore implements ISurvey

public readOnlyChangedCallback: () => void;

public static ScrollElementToTop(elementId: string, scrollIfVisible?: boolean, scrollIntoViewOptions?: ScrollIntoViewOptions): boolean {
public static ScrollElementToTop(elementId: string, scrollIfVisible?: boolean, scrollIntoViewOptions?: ScrollIntoViewOptions, doneCallback?: () => void): boolean {
const { root } = settings.environment;
if (!elementId || typeof root === "undefined") return false;
const el = root.getElementById(elementId);
return SurveyElement.ScrollElementToViewCore(el, false, scrollIfVisible, scrollIntoViewOptions);
return SurveyElement.ScrollElementToViewCore(el, false, scrollIfVisible, scrollIntoViewOptions, doneCallback);
}
public static ScrollElementToViewCore(el: HTMLElement, checkLeft: boolean, scrollIfVisible?: boolean, scrollIntoViewOptions?: ScrollIntoViewOptions): boolean {
public static ScrollElementToViewCore(el: HTMLElement, checkLeft: boolean, scrollIfVisible?: boolean, scrollIntoViewOptions?: ScrollIntoViewOptions, doneCallback?: () => void): boolean {
if (!el || !el.scrollIntoView) return false;
const elTop: number = scrollIfVisible ? -1 : el.getBoundingClientRect().top;
let needScroll = elTop < 0;
let elLeft: number = -1;
if(!needScroll && checkLeft) {
if (!needScroll && checkLeft) {
elLeft = el.getBoundingClientRect().left;
needScroll = elLeft < 0;
}
if(!needScroll && DomWindowHelper.isAvailable()) {
if (!needScroll && DomWindowHelper.isAvailable()) {
const height = DomWindowHelper.getInnerHeight();
needScroll = height > 0 && height < elTop;
if(!needScroll && checkLeft) {
if (!needScroll && checkLeft) {
const width = DomWindowHelper.getInnerWidth();
needScroll = width > 0 && width < elLeft;
}
}
if (needScroll) {
el.scrollIntoView(scrollIntoViewOptions);
if (typeof doneCallback === "function") {
let currPageXOffset = window.pageXOffset;
let currPageYOffset = window.pageYOffset;
var scrollDone = setInterval(function () {
if (currPageXOffset == window.pageXOffset && currPageYOffset == window.pageYOffset) {
clearInterval(scrollDone);
doneCallback();
}
currPageXOffset = window.pageXOffset;
currPageYOffset = window.pageYOffset;
}, 25);
}
}
return needScroll;
}
Expand Down Expand Up @@ -528,6 +540,13 @@ export class SurveyElement<E = any> extends SurveyElementCore implements ISurvey
this.surveyChangedCallback();
}
}
public get skeletonHeight(): string {
let skeletonHeight: string = undefined;
if (!!this.survey && (this.survey as any).skeletonHeight) {
skeletonHeight = (this.survey as any).skeletonHeight + "px";
}
return skeletonHeight;
}
public isContentElement: boolean = false;
public isEditableTemplateElement: boolean = false;
public isInteractiveDesignElement: boolean = true;
Expand Down Expand Up @@ -695,7 +714,7 @@ export class SurveyElement<E = any> extends SurveyElementCore implements ISurvey
}
public updateCustomWidgets(): void { }

public onSurveyLoad(): void {}
public onSurveyLoad(): void { }
private wasRenderedValue: boolean;
public get wasRendered(): boolean { return !!this.wasRenderedValue; }
public onFirstRendering(): void {
Expand Down Expand Up @@ -1090,7 +1109,7 @@ export class SurveyElement<E = any> extends SurveyElementCore implements ISurvey
@property() private _renderedIsExpanded: boolean = true;
private _isAnimatingCollapseExpand: boolean = false;
private set isAnimatingCollapseExpand(val: boolean) {
if(val !== this._isAnimatingCollapseExpand) {
if (val !== this._isAnimatingCollapseExpand) {
this._isAnimatingCollapseExpand = val;
this.updateElementCss(false);
}
Expand Down Expand Up @@ -1124,16 +1143,17 @@ export class SurveyElement<E = any> extends SurveyElementCore implements ISurvey
},
getLeaveOptions: () => {
const cssClasses = this.isPanel ? this.cssClasses.panel : this.cssClasses;
return { cssClass: cssClasses.contentLeave,
return {
cssClass: cssClasses.contentLeave,
onBeforeRunAnimation: beforeRunAnimation,
onAfterRunAnimation: afterRunAnimation
};
},
getAnimatedElement: () => {
const cssClasses = this.isPanel ? this.cssClasses.panel : this.cssClasses;
if(cssClasses.content) {
if (cssClasses.content) {
const selector = classesToSelector(cssClasses.content);
if(selector) {
if (selector) {
return this.getWrapperElement()?.querySelector(`:scope ${selector}`);
}
}
Expand All @@ -1149,8 +1169,8 @@ export class SurveyElement<E = any> extends SurveyElementCore implements ISurvey

private animationCollapsed = new AnimationBoolean(this.getExpandCollapseAnimationOptions(), (val) => {
this._renderedIsExpanded = val;
if(this.animationAllowed) {
if(val) {
if (this.animationAllowed) {
if (val) {
this.isAnimatingCollapseExpand = true;
} else {
this.updateElementCss(false);
Expand All @@ -1160,7 +1180,7 @@ export class SurveyElement<E = any> extends SurveyElementCore implements ISurvey
public set renderedIsExpanded(val: boolean) {
const oldValue = this._renderedIsExpanded;
this.animationCollapsed.sync(val);
if(!this.isExpandCollapseAnimationEnabled && !oldValue && this.renderedIsExpanded) {
if (!this.isExpandCollapseAnimationEnabled && !oldValue && this.renderedIsExpanded) {
this.onElementExpanded(false);
}
}
Expand Down
Loading

0 comments on commit 652fea4

Please sign in to comment.