Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/2236-render-certain-element-and-scroll-to-it #8839

Merged
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